diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm index 49228f79cb750..6c6d4c0239ab9 100644 --- a/_maps/map_files/BoxStation/BoxStation.dmm +++ b/_maps/map_files/BoxStation/BoxStation.dmm @@ -52712,21 +52712,6 @@ /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden/layer4, /turf/open/floor/carpet/green, /area/chapel/main) -"rMF" = ( -/obj/machinery/airalarm/directional/west{ - pixel_x = -22 - }, -/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{ - dir = 4 - }, -/obj/machinery/camera/directional/west, -/obj/structure/table/wood/fancy, -/obj/item/soulstone/anybody/chaplain, -/obj/item/organ/heart, -/obj/item/reagent_containers/cup/glass/bottle/holywater, -/obj/item/book/granter/spell/smoke/lesser, -/turf/open/floor/iron/dark, -/area/chapel/office) "rMK" = ( /obj/structure/cable/yellow{ icon_state = "1-2" @@ -57982,6 +57967,21 @@ /obj/machinery/power/apc/auto_name/directional/north, /turf/open/floor/wood, /area/crew_quarters/theatre) +"tMt" = ( +/obj/machinery/airalarm/directional/west{ + pixel_x = -22 + }, +/obj/machinery/atmospherics/components/unary/vent_pump/on/layer2{ + dir = 4 + }, +/obj/machinery/camera/directional/west, +/obj/structure/table/wood/fancy, +/obj/item/soulstone/anybody/chaplain, +/obj/item/organ/heart, +/obj/item/reagent_containers/cup/glass/bottle/holywater, +/obj/item/book/granter/action/spell/smoke/lesser, +/turf/open/floor/iron/dark, +/area/chapel/office) "tMw" = ( /obj/effect/turf_decal/tile/red/half/contrasted{ dir = 4 @@ -113430,7 +113430,7 @@ aBF aFw gWQ aJJ -rMF +tMt aMW aFw aFu diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 98c3ab7602e18..4fb63c6fa2666 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -43736,7 +43736,7 @@ /obj/item/organ/heart, /obj/item/reagent_containers/cup/glass/bottle/holywater, /obj/item/soulstone/anybody/chaplain, -/obj/item/book/granter/spell/smoke/lesser, +/obj/item/book/granter/action/spell/smoke/lesser, /turf/open/floor/carpet/grimy, /area/chapel/office) "hVw" = ( diff --git a/_maps/map_files/EchoStation/EchoStation.dmm b/_maps/map_files/EchoStation/EchoStation.dmm index 7d918b94e284b..b70ae14f01256 100644 --- a/_maps/map_files/EchoStation/EchoStation.dmm +++ b/_maps/map_files/EchoStation/EchoStation.dmm @@ -15252,10 +15252,6 @@ /turf/open/floor/iron/dark, /area/bridge/meeting_room) "htj" = ( -/obj/item/book/granter/spell/smoke/lesser{ - pixel_x = -4; - pixel_y = 5 - }, /obj/item/reagent_containers/cup/glass/bottle/holywater{ pixel_x = 8; pixel_y = 4 @@ -15273,6 +15269,7 @@ pixel_x = -5; pixel_y = -1 }, +/obj/item/book/granter/action/spell/smoke/lesser, /turf/open/floor/wood, /area/chapel/office) "htm" = ( diff --git a/_maps/map_files/FlandStation/FlandStation.dmm b/_maps/map_files/FlandStation/FlandStation.dmm index b288d12fe801a..95548551cc292 100644 --- a/_maps/map_files/FlandStation/FlandStation.dmm +++ b/_maps/map_files/FlandStation/FlandStation.dmm @@ -84104,7 +84104,7 @@ pixel_y = 7 }, /obj/item/reagent_containers/cup/glass/bottle/holywater, -/obj/item/book/granter/spell/smoke/lesser, +/obj/item/book/granter/action/spell/smoke/lesser, /turf/open/floor/cult, /area/chapel/office) "vsi" = ( diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index 0a8099d9d1dbf..6f3524af8abc1 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -6900,18 +6900,6 @@ }, /turf/open/floor/iron/dark, /area/chapel/office) -"bta" = ( -/obj/structure/table/wood/fancy, -/obj/machinery/light/small{ - dir = 4 - }, -/obj/item/clothing/suit/hooded/chaplain_hoodie, -/obj/item/reagent_containers/cup/glass/bottle/holywater, -/obj/item/soulstone/anybody/chaplain, -/obj/item/organ/heart, -/obj/item/book/granter/spell/smoke/lesser, -/turf/open/floor/iron/dark, -/area/chapel/office) "btc" = ( /obj/effect/turf_decal/tile/blue/half/contrasted, /obj/effect/turf_decal/tile/blue{ @@ -51957,16 +51945,24 @@ broken = 1 }, /area/engine/engineering) -"oOg" = ( -/obj/effect/turf_decal/tile/red/half/contrasted{ - dir = 8 +"oNw" = ( +/obj/structure/table/wood/fancy, +/obj/machinery/light/small{ + dir = 4 }, -/obj/effect/turf_decal/tile/neutral, +/obj/item/clothing/suit/hooded/chaplain_hoodie, +/obj/item/reagent_containers/cup/glass/bottle/holywater, +/obj/item/soulstone/anybody/chaplain, +/obj/item/organ/heart, +/obj/item/book/granter/action/spell/smoke/lesser, +/turf/open/floor/iron/dark, +/area/chapel/office) +"oNy" = ( /obj/structure/disposalpipe/segment{ dir = 5 }, -/turf/open/floor/iron, -/area/security/brig/aft) +/turf/open/space/basic, +/area/space) "oOp" = ( /obj/effect/decal/cleanable/dirt, /obj/machinery/door/airlock/maintenance{ @@ -98105,7 +98101,7 @@ tcA eJn uLN aEC -oOg +oNy bQH nKN fVc @@ -106272,7 +106268,7 @@ tcB alf unx aWG -bta +oNw btC eAC uow diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 206881b00d477..bff606f4a3424 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -43967,7 +43967,7 @@ }, /obj/item/organ/heart, /obj/item/soulstone/anybody/chaplain, -/obj/item/book/granter/spell/smoke/lesser, +/obj/item/book/granter/action/spell/smoke/lesser, /turf/open/floor/cult, /area/chapel/office) "lGm" = ( diff --git a/_maps/map_files/RadStation/RadStation.dmm b/_maps/map_files/RadStation/RadStation.dmm index f5bcfefc491dc..065feb262e813 100644 --- a/_maps/map_files/RadStation/RadStation.dmm +++ b/_maps/map_files/RadStation/RadStation.dmm @@ -33169,10 +33169,10 @@ dir = 8 }, /obj/structure/table/wood/fancy/purple, -/obj/item/book/granter/spell/smoke/lesser, /obj/item/reagent_containers/cup/glass/bottle/holywater, /obj/item/soulstone/anybody/chaplain, /obj/item/organ/heart, +/obj/item/book/granter/action/spell/smoke/lesser, /turf/open/floor/carpet/grimy, /area/chapel/office) "kDQ" = ( diff --git a/beestation.dme b/beestation.dme index c42e283f1e289..c2977c2fd3130 100644 --- a/beestation.dme +++ b/beestation.dme @@ -206,14 +206,18 @@ #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals.dm" +#include "code\__DEFINES\dcs\signals\signals_action.dm" #include "code\__DEFINES\dcs\signals\signals_area.dm" #include "code\__DEFINES\dcs\signals\signals_global.dm" +#include "code\__DEFINES\dcs\signals\signals_heretic.dm" #include "code\__DEFINES\dcs\signals\signals_lighting.dm" #include "code\__DEFINES\dcs\signals\signals_movable.dm" #include "code\__DEFINES\dcs\signals\signals_spatial_grid.dm" +#include "code\__DEFINES\dcs\signals\signals_spell.dm" #include "code\__DEFINES\dcs\signals\signals_turf.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_attack.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_mouse.dm" #include "code\__DEFINES\dcs\signals\signals_datum\signals_circuit.dm" #include "code\__DEFINES\dcs\signals\signals_datum\signals_datum.dm" #include "code\__DEFINES\dcs\signals\signals_datum\signals_moveloop.dm" @@ -224,6 +228,7 @@ #include "code\__DEFINES\dcs\signals\signals_mob\signals_holoparasite.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_human.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_living.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mind.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mobattack.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_silicon.dm" @@ -297,6 +302,7 @@ #include "code\__HELPERS\randoms.dm" #include "code\__HELPERS\roundend.dm" #include "code\__HELPERS\sanitize_values.dm" +#include "code\__HELPERS\screen_objs.dm" #include "code\__HELPERS\shell.dm" #include "code\__HELPERS\spatial_info.dm" #include "code\__HELPERS\stack_trace.dm" @@ -365,7 +371,6 @@ #include "code\_onclick\pai.dm" #include "code\_onclick\silicon.dm" #include "code\_onclick\telekinesis.dm" -#include "code\_onclick\hud\_defines.dm" #include "code\_onclick\hud\action_button.dm" #include "code\_onclick\hud\ai.dm" #include "code\_onclick\hud\alert.dm" @@ -533,7 +538,6 @@ #include "code\controllers\subsystem\processing\singulo.dm" #include "code\controllers\subsystem\processing\station.dm" #include "code\controllers\subsystem\processing\wet_floors.dm" -#include "code\datums\action.dm" #include "code\datums\ai_laws.dm" #include "code\datums\alarm.dm" #include "code\datums\beam.dm" @@ -561,7 +565,6 @@ #include "code\datums\mind.dm" #include "code\datums\movement_detector.dm" #include "code\datums\mutable_appearance.dm" -#include "code\datums\mutations.dm" #include "code\datums\numbered_display.dm" #include "code\datums\outfit.dm" #include "code\datums\profiling.dm" @@ -586,8 +589,25 @@ #include "code\datums\achievements\boss_scores.dm" #include "code\datums\achievements\misc_achievements.dm" #include "code\datums\achievements\misc_scores.dm" -#include "code\datums\actions\beam_rifle.dm" -#include "code\datums\actions\ninja.dm" +#include "code\datums\actions\action.dm" +#include "code\datums\actions\innate_action.dm" +#include "code\datums\actions\item_action.dm" +#include "code\datums\actions\items\adjust.dm" +#include "code\datums\actions\items\beam_rifle.dm" +#include "code\datums\actions\items\boot_dash.dm" +#include "code\datums\actions\items\clothing.dm" +#include "code\datums\actions\items\cult_dagger.dm" +#include "code\datums\actions\items\hands_free.dm" +#include "code\datums\actions\items\ninja.dm" +#include "code\datums\actions\items\organ_action.dm" +#include "code\datums\actions\items\portaseeder.dm" +#include "code\datums\actions\items\set_internals.dm" +#include "code\datums\actions\items\stealth_box.dm" +#include "code\datums\actions\items\summon_stickmen.dm" +#include "code\datums\actions\items\toggles.dm" +#include "code\datums\actions\items\vortex_recall.dm" +#include "code\datums\actions\mobs\language_menu.dm" +#include "code\datums\actions\mobs\small_sprite.dm" #include "code\datums\ai\_ai_behavior.dm" #include "code\datums\ai\_ai_controller.dm" #include "code\datums\ai\_ai_planning_subtree.dm" @@ -686,6 +706,7 @@ #include "code\datums\components\manual_blinking.dm" #include "code\datums\components\manual_breathing.dm" #include "code\datums\components\material_container.dm" +#include "code\datums\components\mind_linker.dm" #include "code\datums\components\mirv.dm" #include "code\datums\components\mood.dm" #include "code\datums\components\moved_relay.dm" @@ -696,6 +717,7 @@ #include "code\datums\components\paintable.dm" #include "code\datums\components\payment.dm" #include "code\datums\components\pellet_cloud.dm" +#include "code\datums\components\phylactery.dm" #include "code\datums\components\rad_insulation.dm" #include "code\datums\components\radio_jamming.dm" #include "code\datums\components\radioactive.dm" @@ -965,20 +987,31 @@ #include "code\datums\mood_events\generic_positive_events.dm" #include "code\datums\mood_events\mood_event.dm" #include "code\datums\mood_events\needs_events.dm" +#include "code\datums\mutations\__mutations.dm" #include "code\datums\mutations\_combined.dm" -#include "code\datums\mutations\actions.dm" +#include "code\datums\mutations\acidooze.dm" #include "code\datums\mutations\antenna.dm" +#include "code\datums\mutations\autonomy.dm" #include "code\datums\mutations\body.dm" #include "code\datums\mutations\chameleon.dm" #include "code\datums\mutations\cluwne.dm" #include "code\datums\mutations\cold.dm" +#include "code\datums\mutations\drone.dm" +#include "code\datums\mutations\fire_breath.dm" #include "code\datums\mutations\hulk.dm" +#include "code\datums\mutations\olfaction.dm" +#include "code\datums\mutations\overload.dm" #include "code\datums\mutations\radioactive.dm" #include "code\datums\mutations\sight.dm" #include "code\datums\mutations\space_adaptation.dm" #include "code\datums\mutations\speech.dm" +#include "code\datums\mutations\spores.dm" #include "code\datums\mutations\telekinesis.dm" +#include "code\datums\mutations\telepathy.dm" +#include "code\datums\mutations\tongue_spike.dm" #include "code\datums\mutations\touch.dm" +#include "code\datums\mutations\void_magnet.dm" +#include "code\datums\mutations\webbing.dm" #include "code\datums\proximity_monitor\field.dm" #include "code\datums\proximity_monitor\proximity_monitor.dm" #include "code\datums\proximity_monitor\fields\gravity.dm" @@ -1290,6 +1323,7 @@ #include "code\game\objects\effects\mines.dm" #include "code\game\objects\effects\misc.dm" #include "code\game\objects\effects\overlays.dm" +#include "code\game\objects\effects\phased_mob.dm" #include "code\game\objects\effects\portals.dm" #include "code\game\objects\effects\spiders.dm" #include "code\game\objects\effects\step_triggers.dm" @@ -1385,7 +1419,6 @@ #include "code\game\objects\items\fireaxe.dm" #include "code\game\objects\items\flamethrower.dm" #include "code\game\objects\items\gift.dm" -#include "code\game\objects\items\granters.dm" #include "code\game\objects\items\handcuffs.dm" #include "code\game\objects\items\his_grace.dm" #include "code\game\objects\items\holosign_creator.dm" @@ -1512,6 +1545,28 @@ #include "code\game\objects\items\food\sushi.dm" #include "code\game\objects\items\food\sweets.dm" #include "code\game\objects\items\food\vegetables.dm" +#include "code\game\objects\items\granters\_granters.dm" +#include "code\game\objects\items\granters\oragami.dm" +#include "code\game\objects\items\granters\crafting\_crafting_granter.dm" +#include "code\game\objects\items\granters\crafting\desserts.dm" +#include "code\game\objects\items\granters\magic\_spell_granter.dm" +#include "code\game\objects\items\granters\magic\barnyard.dm" +#include "code\game\objects\items\granters\magic\blind.dm" +#include "code\game\objects\items\granters\magic\charge.dm" +#include "code\game\objects\items\granters\magic\fireball.dm" +#include "code\game\objects\items\granters\magic\forcewall.dm" +#include "code\game\objects\items\granters\magic\knock.dm" +#include "code\game\objects\items\granters\magic\mime.dm" +#include "code\game\objects\items\granters\magic\mindswap.dm" +#include "code\game\objects\items\granters\magic\sacredflame.dm" +#include "code\game\objects\items\granters\magic\smoke.dm" +#include "code\game\objects\items\granters\magic\summon_item.dm" +#include "code\game\objects\items\granters\martial_arts\_martial_arts.dm" +#include "code\game\objects\items\granters\martial_arts\cqc.dm" +#include "code\game\objects\items\granters\martial_arts\karate.dm" +#include "code\game\objects\items\granters\martial_arts\plasma_fist.dm" +#include "code\game\objects\items\granters\martial_arts\sleeping_carp.dm" +#include "code\game\objects\items\granters\martial_arts\tribal_claw.dm" #include "code\game\objects\items\grenades\_grenade.dm" #include "code\game\objects\items\grenades\antigravity.dm" #include "code\game\objects\items\grenades\chem_grenade.dm" @@ -2020,14 +2075,11 @@ #include "code\modules\antagonists\clock_cult\scriptures\abstraction_crystal.dm" #include "code\modules\antagonists\clock_cult\scriptures\clockwork_armaments.dm" #include "code\modules\antagonists\clock_cult\scriptures\dimensional_breach.dm" -#include "code\modules\antagonists\clock_cult\scriptures\hateful_manacles.dm" #include "code\modules\antagonists\clock_cult\scriptures\integration_cog.dm" #include "code\modules\antagonists\clock_cult\scriptures\interdiction_lens.dm" -#include "code\modules\antagonists\clock_cult\scriptures\kindle.dm" #include "code\modules\antagonists\clock_cult\scriptures\ocular_warden.dm" #include "code\modules\antagonists\clock_cult\scriptures\prosperity_prism.dm" #include "code\modules\antagonists\clock_cult\scriptures\replicant.dm" -#include "code\modules\antagonists\clock_cult\scriptures\sentinels_compromise.dm" #include "code\modules\antagonists\clock_cult\scriptures\sigil_of_submission.dm" #include "code\modules\antagonists\clock_cult\scriptures\sigil_of_vitality.dm" #include "code\modules\antagonists\clock_cult\scriptures\sigil_transmission.dm" @@ -2037,7 +2089,10 @@ #include "code\modules\antagonists\clock_cult\scriptures\summon_replica_fabricator.dm" #include "code\modules\antagonists\clock_cult\scriptures\summon_spear.dm" #include "code\modules\antagonists\clock_cult\scriptures\summon_tinkerers_cache.dm" -#include "code\modules\antagonists\clock_cult\scriptures\vanguard.dm" +#include "code\modules\antagonists\clock_cult\scriptures\slab_empowerments\hateful_manacles.dm" +#include "code\modules\antagonists\clock_cult\scriptures\slab_empowerments\kindle.dm" +#include "code\modules\antagonists\clock_cult\scriptures\slab_empowerments\sentinels_compromise.dm" +#include "code\modules\antagonists\clock_cult\scriptures\slab_empowerments\vanguard.dm" #include "code\modules\antagonists\clock_cult\structure\clockwork_camera.dm" #include "code\modules\antagonists\clock_cult\structure\clockwork_overlay.dm" #include "code\modules\antagonists\clock_cult\structure\clockwork_sigil.dm" @@ -2108,7 +2163,6 @@ #include "code\modules\antagonists\heretic\magic\eldritch_shapeshift.dm" #include "code\modules\antagonists\heretic\magic\eldritch_telepathy.dm" #include "code\modules\antagonists\heretic\magic\flesh_ascension.dm" -#include "code\modules\antagonists\heretic\magic\madness_touch.dm" #include "code\modules\antagonists\heretic\magic\manse_link.dm" #include "code\modules\antagonists\heretic\magic\mansus_grasp.dm" #include "code\modules\antagonists\heretic\magic\nightwatcher_rebirth.dm" @@ -2183,7 +2237,14 @@ #include "code\modules\antagonists\wizard\wizard.dm" #include "code\modules\antagonists\wizard\equipment\artefact.dm" #include "code\modules\antagonists\wizard\equipment\soulstone.dm" -#include "code\modules\antagonists\wizard\equipment\spellbook.dm" +#include "code\modules\antagonists\wizard\equipment\wizard_spellbook.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook_entries\_entry.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook_entries\assistance.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook_entries\challenges.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook_entries\defensive.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook_entries\mobility.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook_entries\offensive.dm" +#include "code\modules\antagonists\wizard\equipment\spellbook_entries\summons.dm" #include "code\modules\antagonists\xeno\xeno.dm" #include "code\modules\aquarium\aquarium.dm" #include "code\modules\aquarium\aquarium_behaviour.dm" @@ -3012,7 +3073,6 @@ #include "code\modules\mob\dead\observer\orbit.dm" #include "code\modules\mob\dead\observer\say.dm" #include "code\modules\mob\living\blood.dm" -#include "code\modules\mob\living\bloodcrawl.dm" #include "code\modules\mob\living\damage_procs.dm" #include "code\modules\mob\living\death.dm" #include "code\modules\mob\living\emote.dm" @@ -3952,50 +4012,81 @@ #include "code\modules\shuttle\super_cruise\shuttle_components\plasma_refiner.dm" #include "code\modules\shuttle\super_cruise\shuttle_components\shuttle_console.dm" #include "code\modules\shuttle\super_cruise\shuttle_components\shuttle_docking.dm" -#include "code\modules\spells\__DEFINES\spell.dm" -#include "code\modules\spells\spell_types\aimed.dm" -#include "code\modules\spells\spell_types\area_teleport.dm" -#include "code\modules\spells\spell_types\barnyard.dm" -#include "code\modules\spells\spell_types\blind.dm" -#include "code\modules\spells\spell_types\bloodcrawl.dm" -#include "code\modules\spells\spell_types\charge.dm" -#include "code\modules\spells\spell_types\cluwnecurse.dm" -#include "code\modules\spells\spell_types\cone_spells.dm" -#include "code\modules\spells\spell_types\conjure.dm" -#include "code\modules\spells\spell_types\construct_spells.dm" -#include "code\modules\spells\spell_types\curse.dm" -#include "code\modules\spells\spell_types\emplosion.dm" -#include "code\modules\spells\spell_types\ethereal_jaunt.dm" -#include "code\modules\spells\spell_types\explosion.dm" -#include "code\modules\spells\spell_types\forcewall.dm" -#include "code\modules\spells\spell_types\genetic.dm" -#include "code\modules\spells\spell_types\godhand.dm" -#include "code\modules\spells\spell_types\infinite_guns.dm" -#include "code\modules\spells\spell_types\knock.dm" -#include "code\modules\spells\spell_types\lesserlichdom.dm" -#include "code\modules\spells\spell_types\lichdom.dm" -#include "code\modules\spells\spell_types\lightning.dm" -#include "code\modules\spells\spell_types\mime.dm" -#include "code\modules\spells\spell_types\mind_transfer.dm" -#include "code\modules\spells\spell_types\personality_commune.dm" -#include "code\modules\spells\spell_types\pointed.dm" -#include "code\modules\spells\spell_types\projectile.dm" -#include "code\modules\spells\spell_types\rightandwrong.dm" -#include "code\modules\spells\spell_types\rod_form.dm" -#include "code\modules\spells\spell_types\santa.dm" -#include "code\modules\spells\spell_types\shadow_walk.dm" -#include "code\modules\spells\spell_types\shapeshift.dm" -#include "code\modules\spells\spell_types\soultap.dm" -#include "code\modules\spells\spell_types\spacetime_distortion.dm" -#include "code\modules\spells\spell_types\summonitem.dm" -#include "code\modules\spells\spell_types\taeclowndo.dm" -#include "code\modules\spells\spell_types\telepathy.dm" -#include "code\modules\spells\spell_types\the_traps.dm" -#include "code\modules\spells\spell_types\touch_attacks.dm" -#include "code\modules\spells\spell_types\trigger.dm" -#include "code\modules\spells\spell_types\turf_teleport.dm" -#include "code\modules\spells\spell_types\voice_of_god.dm" -#include "code\modules\spells\spell_types\wizard.dm" +#include "code\modules\spells\spell.dm" +#include "code\modules\spells\spell_types\aoe_spell\_aoe_spell.dm" +#include "code\modules\spells\spell_types\aoe_spell\area_conversion.dm" +#include "code\modules\spells\spell_types\aoe_spell\knock.dm" +#include "code\modules\spells\spell_types\aoe_spell\magic_missile.dm" +#include "code\modules\spells\spell_types\aoe_spell\repulse.dm" +#include "code\modules\spells\spell_types\aoe_spell\sacred_flame.dm" +#include "code\modules\spells\spell_types\bee_spells\cluwnecurse.dm" +#include "code\modules\spells\spell_types\bee_spells\curse.dm" +#include "code\modules\spells\spell_types\bee_spells\lesserlichdom.dm" +#include "code\modules\spells\spell_types\bee_spells\rightandwrong.dm" +#include "code\modules\spells\spell_types\bee_spells\taeclowndo.dm" +#include "code\modules\spells\spell_types\cone\_cone.dm" +#include "code\modules\spells\spell_types\conjure\_conjure.dm" +#include "code\modules\spells\spell_types\conjure\bees.dm" +#include "code\modules\spells\spell_types\conjure\carp.dm" +#include "code\modules\spells\spell_types\conjure\constructs.dm" +#include "code\modules\spells\spell_types\conjure\creatures.dm" +#include "code\modules\spells\spell_types\conjure\cult_turfs.dm" +#include "code\modules\spells\spell_types\conjure\ed_swarm.dm" +#include "code\modules\spells\spell_types\conjure\invisible_chair.dm" +#include "code\modules\spells\spell_types\conjure\invisible_wall.dm" +#include "code\modules\spells\spell_types\conjure\link_words.dm" +#include "code\modules\spells\spell_types\conjure\presents.dm" +#include "code\modules\spells\spell_types\conjure\soulstone.dm" +#include "code\modules\spells\spell_types\conjure\the_traps.dm" +#include "code\modules\spells\spell_types\conjure_item\_conjure_item.dm" +#include "code\modules\spells\spell_types\conjure_item\infinite_guns.dm" +#include "code\modules\spells\spell_types\conjure_item\invisible_box.dm" +#include "code\modules\spells\spell_types\conjure_item\lightning_packet.dm" +#include "code\modules\spells\spell_types\conjure_item\snowball.dm" +#include "code\modules\spells\spell_types\jaunt\_jaunt.dm" +#include "code\modules\spells\spell_types\jaunt\bloodcrawl.dm" +#include "code\modules\spells\spell_types\jaunt\ethereal_jaunt.dm" +#include "code\modules\spells\spell_types\jaunt\shadow_walk.dm" +#include "code\modules\spells\spell_types\list_targets\telepathy.dm" +#include "code\modules\spells\spell_types\pointed\_pointed.dm" +#include "code\modules\spells\spell_types\pointed\abyssal_gaze.dm" +#include "code\modules\spells\spell_types\pointed\barnyard.dm" +#include "code\modules\spells\spell_types\pointed\blind.dm" +#include "code\modules\spells\spell_types\pointed\dominate.dm" +#include "code\modules\spells\spell_types\pointed\finger_guns.dm" +#include "code\modules\spells\spell_types\pointed\fireball.dm" +#include "code\modules\spells\spell_types\pointed\lightning_bolt.dm" +#include "code\modules\spells\spell_types\pointed\mind_transfer.dm" +#include "code\modules\spells\spell_types\pointed\spell_cards.dm" +#include "code\modules\spells\spell_types\projectile\_basic_projectile.dm" +#include "code\modules\spells\spell_types\projectile\juggernaut.dm" +#include "code\modules\spells\spell_types\self\basic_heal.dm" +#include "code\modules\spells\spell_types\self\charge.dm" +#include "code\modules\spells\spell_types\self\disable_tech.dm" +#include "code\modules\spells\spell_types\self\forcewall.dm" +#include "code\modules\spells\spell_types\self\lichdom.dm" +#include "code\modules\spells\spell_types\self\lightning.dm" +#include "code\modules\spells\spell_types\self\mime_vow.dm" +#include "code\modules\spells\spell_types\self\mutate.dm" +#include "code\modules\spells\spell_types\self\night_vision.dm" +#include "code\modules\spells\spell_types\self\personality_commune.dm" +#include "code\modules\spells\spell_types\self\rod_form.dm" +#include "code\modules\spells\spell_types\self\smoke.dm" +#include "code\modules\spells\spell_types\self\soultap.dm" +#include "code\modules\spells\spell_types\self\spacetime_distortion.dm" +#include "code\modules\spells\spell_types\self\stop_time.dm" +#include "code\modules\spells\spell_types\self\summon_item.dm" +#include "code\modules\spells\spell_types\self\voice_of_god.dm" +#include "code\modules\spells\spell_types\shapeshift\_shapeshift.dm" +#include "code\modules\spells\spell_types\shapeshift\dragon.dm" +#include "code\modules\spells\spell_types\shapeshift\polar_bear.dm" +#include "code\modules\spells\spell_types\shapeshift\shapechange.dm" +#include "code\modules\spells\spell_types\teleport\_teleport.dm" +#include "code\modules\spells\spell_types\teleport\blink.dm" +#include "code\modules\spells\spell_types\teleport\teleport.dm" +#include "code\modules\spells\spell_types\touch\_touch.dm" +#include "code\modules\spells\spell_types\touch\flesh_to_stone.dm" +#include "code\modules\spells\spell_types\touch\smite.dm" #include "code\modules\station_goals\bluespace_tap.dm" #include "code\modules\station_goals\bsa.dm" #include "code\modules\station_goals\custom_shuttle.dm" diff --git a/code/__DEFINES/actions.dm b/code/__DEFINES/actions.dm index b89528a9baec9..dbe8a5a42383e 100644 --- a/code/__DEFINES/actions.dm +++ b/code/__DEFINES/actions.dm @@ -1,8 +1,44 @@ -// Checks to see if the mob is able to use their hands, or if they are blocked by cuffs or stuns + +///Action button checks if hands are unusable #define AB_CHECK_HANDS_BLOCKED (1<<0) -// Checks to see if the mob is incapacitated by stuns or paralysis effects -#define AB_CHECK_INCAPACITATED (1<<1) -// Checks to see if the mob is standing +///Action button checks if user is immobile +#define AB_CHECK_IMMOBILE (1<<1) +///Action button checks if user is resting #define AB_CHECK_LYING (1<<2) -// Checks to see if the mob in concious +///Action button checks if user is conscious #define AB_CHECK_CONSCIOUS (1<<3) +///Action button checks if user is incapacitated +#define AB_CHECK_INCAPACITATED (1<<4) +///Action button checks if user is jaunting +#define AB_CHECK_PHASED (1<<5) +/// Action button works when unconcious, but not when dead +#define AB_CHECK_DEAD (1<<6) + +//Bitfield is in /_DEFINES/_globablvars/bitfields.dm for reasons + +///Action triggered to ignore any availability checks +#define TRIGGER_FORCE_AVAILABLE (1<<1) + +/// The status shown in the stat panel. +/// Can be stuff like "ready", "on cooldown", "active", "charges", "charge cost", etc. +#define STAT_STATUS "Status" + +#define ACTION_BUTTON_DEFAULT_BACKGROUND "_use_ui_default_background" + +#define UPDATE_BUTTON_NAME (1<<0) +#define UPDATE_BUTTON_ICON (1<<1) +#define UPDATE_BUTTON_BACKGROUND (1<<2) +#define UPDATE_BUTTON_OVERLAY (1<<3) +#define UPDATE_BUTTON_STATUS (1<<4) + +/// Takes in a typepath of a `/datum/action` and adds it to `src`. +/// Only useful if you want to add the action and never desire to reference it again ever. +#define GRANT_ACTION(typepath) do {\ + var/datum/action/_ability = new typepath(src);\ + _ability.Grant(src);\ +} while (FALSE) + +#define GRANT_ACTION_MOB(typepath, mob) do {\ + var/datum/action/_ability = new typepath(mob);\ + _ability.Grant(mob);\ +} while (FALSE) diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 46c5b43e3f1ea..403acb4501a2d 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -108,6 +108,12 @@ #define CONSTRUCT_WRAITH "Wraith" #define CONSTRUCT_ARTIFICER "Artificer" +/// Used in logging spells for roundend results +#define LOG_SPELL_TYPE "type" +#define LOG_SPELL_AMOUNT "amount" + + + /// How much does it cost to reroll strains? #define BLOB_REROLL_COST 40 @@ -139,6 +145,8 @@ WIZARD_LOADOUT_SOULTAP, \ ) +/// Checks if the given mob is a wizard +#define IS_WIZARD(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/wizard)) ///Checks if given mob is a hive host #define IS_HIVEHOST(mob) (mob.mind?.has_antag_datum(/datum/antagonist/hivemind)) ///Checks if given mob is an awakened vessel diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index 3fe2d6671f74c..f71056b20f82b 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -55,6 +55,7 @@ #define COLOR_LIME "#32CD32" #define COLOR_GREEN "#008000" #define COLOR_DARK_MODERATE_LIME_GREEN "#44964A" +#define COLOR_VERY_DARK_LIME_GREEN "#003300" #define COLOR_CYAN "#00FFFF" #define COLOR_DARK_CYAN "#00A2FF" diff --git a/code/__DEFINES/dcs/signals/signals_action.dm b/code/__DEFINES/dcs/signals/signals_action.dm new file mode 100644 index 0000000000000..994855cff58f9 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_action.dm @@ -0,0 +1,33 @@ +// Action signals + +///from base of datum/action/proc/Trigger(): (datum/action) +#define COMSIG_ACTION_TRIGGER "action_trigger" + // Return to block the trigger from occuring + #define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0) +/// From /datum/action/Grant(): (mob/grant_to) +#define COMSIG_ACTION_GRANTED "action_grant" +/// From /datum/action/Remove(): (mob/removed_from) +#define COMSIG_ACTION_REMOVED "action_removed" +/// From /datum/action/apply_button_overlay() +#define COMSIG_ACTION_OVERLAY_APPLY "action_overlay_applied" +// Cooldown action signals + +/// From base of /datum/action/proc/pre_activate(), sent to the action owner: (datum/action/cooldown/activated) +#define COMSIG_MOB_ABILITY_STARTED "mob_ability_base_started" + /// Return to block the ability from starting / activating + #define COMPONENT_BLOCK_ABILITY_START (1<<0) +/// From base of /datum/action/proc/pre_activate(), sent to the action owner: (datum/action/cooldown/finished) +#define COMSIG_MOB_ABILITY_FINISHED "mob_ability_base_finished" + +// Specific cooldown action signals + +/// From base of /datum/action/mob_cooldown/blood_warp/proc/blood_warp(): () +#define COMSIG_BLOOD_WARP "mob_ability_blood_warp" +/// From base of /datum/action/mob_cooldown/charge/proc/do_charge(): () +#define COMSIG_STARTED_CHARGE "mob_ability_charge_started" +/// From base of /datum/action/mob_cooldown/charge/proc/do_charge(): () +#define COMSIG_FINISHED_CHARGE "mob_ability_charge_finished" +/// From base of /datum/action/mob_cooldown/lava_swoop/proc/swoop_attack(): () +#define COMSIG_SWOOP_INVULNERABILITY_STARTED "mob_swoop_invulnerability_started" +/// From base of /datum/action/mob_cooldown/lava_swoop/proc/swoop_attack(): () +#define COMSIG_LAVA_ARENA_FAILED "mob_lava_arena_failed" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom.dm index dd0bed49487cb..2bac19505ecbf 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom.dm @@ -14,6 +14,8 @@ //Positions for overrides list #define EXAMINE_POSITION_ARTICLE (1<<0) #define EXAMINE_POSITION_BEFORE (1<<1) +///from base of atom/examine(): (/mob, list/examine_text) +#define COMSIG_ATOM_EXAMINE "atom_examine" //End positions #define COMPONENT_EXNAME_CHANGED (1<<0) @@ -146,7 +148,7 @@ ///////////////// #define COMSIG_CLICK "atom_click" //! from base of atom/Click(): (location, control, params, mob/user) #define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob) - #define COMPONENT_ALLOW_EXAMINATE 1 //Allows the user to examinate regardless of client.eye. + #define COMPONENT_ALLOW_EXAMINATE 1 //Allows the user to examinate regardless of client.eye. #define COMSIG_CLICK_CTRL "ctrl_click" //! from base of atom/CtrlClickOn(): (/mob) #define COMSIG_CLICK_ALT "alt_click" //! from base of atom/AltClick(): (/mob) #define COMPONENT_INTERCEPT_ALT 1 diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm new file mode 100644 index 0000000000000..c687d759a04ce --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm @@ -0,0 +1,9 @@ +// mouse signals. Format: +// When the signal is called: (signal arguments) +// All signals send the source datum of the signal as the first argument + +///from base of client/Click(): (atom/target, atom/location, control, params, mob/user) +#define COMSIG_CLIENT_CLICK "atom_client_click" + #define COMPONENT_CANCEL_CLICK_ALT (1<<0) +///from base of mob/MouseWheelOn(): (/atom, delta_x, delta_y, params) +#define COMSIG_MOUSE_SCROLL_ON "mousescroll_on" diff --git a/code/__DEFINES/dcs/signals/signals_datum/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum/signals_datum.dm index e76747e22846f..32b30fef588ff 100644 --- a/code/__DEFINES/dcs/signals/signals_datum/signals_datum.dm +++ b/code/__DEFINES/dcs/signals/signals_datum/signals_datum.dm @@ -74,9 +74,6 @@ #define COMPONENT_TWOHANDED_BLOCK_WIELD 1 #define COMSIG_TWOHANDED_UNWIELD "twohanded_unwield" //from base of datum/component/two_handed/proc/unwield(mob/living/carbon/user): (/mob/user) -// /datum/action signals -#define COMSIG_ACTION_TRIGGER "action_trigger" //! from base of datum/action/proc/Trigger(): (datum/action) - #define COMPONENT_ACTION_BLOCK_TRIGGER 1 // /datum/mind signals #define COMSIG_MIND_TRANSFER_TO "mind_transfer_to" // (mob/old, mob/new) diff --git a/code/__DEFINES/dcs/signals/signals_heretic.dm b/code/__DEFINES/dcs/signals/signals_heretic.dm new file mode 100644 index 0000000000000..f5c59a015eb39 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_heretic.dm @@ -0,0 +1,16 @@ +/// Heretic signals +/// From /obj/item/clothing/mask/madness_mask/process : (amount) +#define COMSIG_HERETIC_MASK_ACT "void_mask_act" + +/// From /obj/item/melee/touch_attack/mansus_fist/on_mob_hit : (mob/living/source, mob/living/target) +#define COMSIG_HERETIC_MANSUS_GRASP_ATTACK "mansus_grasp_attack" +/// Default behavior is to use the hand, so return this to blocks the mansus fist from being consumed after use. + #define COMPONENT_BLOCK_HAND_USE (1<<0) + +/// Default behavior is to continue attack chain and do nothing else, so return this to use up the hand after use. + #define COMPONENT_USE_HAND (1<<0) + +/// From /obj/item/melee/sickly_blade/afterattack (with proximity) : (mob/living/source, mob/living/target) +#define COMSIG_HERETIC_BLADE_ATTACK "blade_attack" +/// From /obj/item/melee/sickly_blade/afterattack (without proximity) : (mob/living/source, mob/living/target) +#define COMSIG_HERETIC_RANGED_BLADE_ATTACK "ranged_blade_attack" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm index 768d0f0ee04ed..ecbe4f0747c5d 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm @@ -23,6 +23,7 @@ //! from base of mob/living/carbon/set_species(): (new_race) #define COMSIG_CARBON_SPECIESCHANGE "mob_carbon_specieschange" + ///from base of /obj/item/bodypart/proc/try_attach_limb(): (new_limb, special) #define COMSIG_CARBON_ATTACH_LIMB "carbon_attach_limb" #define COMPONENT_NO_ATTACH (1<<0) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_human.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_human.dm index 9c57150679f92..2350c9e7258ef 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_human.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_human.dm @@ -6,17 +6,9 @@ #define COMSIG_HUMAN_DISARM_HIT "human_disarm_hit" //! Hit by successful disarm attack (mob/living/carbon/human/attacker,zone_targeted) #define COMSIG_HUMAN_ATTACKED "carbon_attacked" //hit by something that checks shields. -//Heretics stuff -#define COMSIG_HERETIC_MASK_ACT "void_mask_act" -/// From /obj/item/melee/touch_attack/mansus_fist/on_mob_hit : (mob/living/source, mob/living/target) -#define COMSIG_HERETIC_MANSUS_GRASP_ATTACK "mansus_grasp_attack" - /// Default behavior is to use a charge, so return this to blocks the mansus fist from being consumed after use. - #define COMPONENT_BLOCK_CHARGE_USE (1<<0) +///From mob/living/carbon/human/suicide() +#define COMSIG_HUMAN_SUICIDE_ACT "human_suicide_act" -/// From /obj/item/melee/sickly_blade/afterattack (with proximity) : (mob/living/source, mob/living/target) -#define COMSIG_HERETIC_BLADE_ATTACK "blade_attack" -/// From /obj/item/melee/sickly_blade/afterattack (without proximity) : (mob/living/source, mob/living/target) -#define COMSIG_HERETIC_RANGED_BLADE_ATTACK "ranged_blade_attack" ///called from /obj/effect/proc_holder/spell/cast_check (src) #define COMSIG_MOB_PRE_CAST_SPELL "mob_cast_spell" /// Return to cancel the cast from beginning. diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm index 0f6068756a8e6..71b4352494233 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm @@ -69,3 +69,25 @@ // basic mob signals /// Called on /basic when updating its speed, from base of /mob/living/basic/update_basic_mob_varspeed(): () #define POST_BASIC_MOB_UPDATE_VARSPEED "post_basic_mob_update_varspeed" + +/// from /datum/status_effect/incapacitating/stamcrit/on_apply() +#define COMSIG_LIVING_ENTER_STAMCRIT "living_enter_stamcrit" +///from /obj/structure/door/crush(): (mob/living/crushed, /obj/machinery/door/crushing_door) +#define COMSIG_LIVING_DOORCRUSHED "living_doorcrush" +///sent when items with siemen coeff. of 0 block a shock: (power_source, source, siemens_coeff, dist_check) +#define COMSIG_LIVING_SHOCK_PREVENTED "living_shock_prevented" + /// Block the electrocute_act() proc from proceeding + #define COMPONENT_LIVING_BLOCK_SHOCK (1<<0) +///sent by stuff like stunbatons and tasers: () +///from base of mob/living/set_body_position() +#define COMSIG_LIVING_SET_BODY_POSITION "living_set_body_position" +/// Sent to a mob being injected with a syringe when the do_after initiates +#define COMSIG_LIVING_TRY_SYRINGE_INJECT "living_try_syringe_inject" +/// Sent to a mob being withdrawn from with a syringe when the do_after initiates +#define COMSIG_LIVING_TRY_SYRINGE_WITHDRAW "living_try_syringe_withdraw" +///from base of mob/living/set_usable_legs() +#define COMSIG_LIVING_LIMBLESS_SLOWDOWN "living_limbless_slowdown" +/// Block the Life() proc from proceeding... this should really only be done in some really wacky situations. +#define COMPONENT_LIVING_CANCEL_LIFE_PROCESSING (1<<0) +///From living/set_resting(): (new_resting, silent, instant) +#define COMSIG_LIVING_RESTING "living_resting" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mind.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mind.dm new file mode 100644 index 0000000000000..e9a62a26102cf --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mind.dm @@ -0,0 +1,11 @@ +///from mind/transfer_to. Sent after the mind has been transferred: (mob/previous_body) +#define COMSIG_MIND_TRANSFERRED "mind_transferred" + +/// Called on the mind when an antagonist is being gained, after the antagonist list has updated (datum/antagonist/antagonist) +#define COMSIG_ANTAGONIST_GAINED "antagonist_gained" + +/// Called on the mind when an antagonist is being removed, after the antagonist list has updated (datum/antagonist/antagonist) +#define COMSIG_ANTAGONIST_REMOVED "antagonist_removed" + +/// Called on the mob when losing an antagonist datum (datum/antagonist/antagonist) +#define COMSIG_MOB_ANTAGONIST_REMOVED "mob_antagonist_removed" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm index ed7d0bebf176e..ca3e66013582f 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob.dm @@ -31,10 +31,16 @@ /// Should we stop the current living movement attempt #define COMSIG_MOB_CLIENT_BLOCK_PRE_LIVING_MOVE COMPONENT_MOVABLE_BLOCK_PRE_MOVE #define COMSIG_MOB_ALLOWED "mob_allowed" //! from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj -#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" //! from base of mob/anti_magic_check(): (mob/user, magic, holy, major, self, protection_sources) - #define COMPONENT_BLOCK_MAGIC 1 + +///from base of mob/can_cast_magic(): (mob/user, magic_flags, charge_cost) +#define COMSIG_MOB_RESTRICT_MAGIC "mob_cast_magic" +///from base of mob/can_block_magic(): (mob/user, casted_magic_flags, charge_cost) +#define COMSIG_MOB_RECEIVE_MAGIC "mob_receive_magic" + #define COMPONENT_MAGIC_BLOCKED (1<<0) #define COMSIG_MOB_RECEIVE_ARTIFACT "mob_receive_artifact" // #define COMPONENT_BLOCK_ARTIFACT 1 + + #define COMSIG_MOB_HUD_CREATED "mob_hud_created" //! from base of mob/create_mob_hud(): () #define COMSIG_MOB_ATTACK_HAND_TURF "mob_attack_hand_turf" //! from base of turf/attack_hand #define COMSIG_MOB_HAND_ATTACKED "mob_hand_attacked" //! from base of @@ -69,6 +75,8 @@ ///Called after a client connects to a mob and all UI elements have been setup #define COMSIG_MOB_CLIENT_LOGIN "comsig_mob_client_login" #define COMSIG_MOB_MOUSE_SCROLL_ON "comsig_mob_mouse_scroll_on" //! from base of /mob/MouseWheelOn(): (atom/A, delta_x, delta_y, params) +//from base of client/MouseUp(): (/client, object, location, control, params) +#define COMSIG_CLIENT_MOUSEDRAG "client_mousedrag" /// Called before a mob fires a gun (mob/source, obj/item/gun, atom/target, aimed) #define COMSIG_MOB_BEFORE_FIRE_GUN "before_fire_gun" diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_item.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_item.dm index 7feed06591d9a..36de08624592a 100644 --- a/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_item.dm +++ b/code/__DEFINES/dcs/signals/signals_obj/signals_item/signals_item.dm @@ -24,9 +24,6 @@ #define COMPONENT_ACTION_HANDLED (1<<0) #define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" //! from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone) -#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" //! return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user) -#define COMSIG_ITEM_MARK_RETRIEVAL "item_mark_retrieval" //! called before marking an object for retrieval, checked in /obj/effect/proc_holder/spell/targeted/summonitem/cast() : (mob/user) - #define COMPONENT_BLOCK_MARK_RETRIEVAL 1 #define COMSIG_ITEM_HIT_REACT "item_hit_react" //! from base of obj/item/hit_reaction(): (mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", damage = 0, attack_type = MELEE_ATTACK) #define COMPONENT_HIT_REACTION_BLOCK (1<<0) #define COMSIG_ITEM_SHARPEN_ACT "sharpen_act" //! from base of item/sharpener/attackby(): (amount, max) diff --git a/code/__DEFINES/dcs/signals/signals_obj/signals_object.dm b/code/__DEFINES/dcs/signals/signals_obj/signals_object.dm index 357d32dc19ae0..8fa89964c2331 100644 --- a/code/__DEFINES/dcs/signals/signals_obj/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_obj/signals_object.dm @@ -21,5 +21,8 @@ ///Called when a payment component changes value #define COMSIG_OBJ_ATTEMPT_CHARGE_CHANGE "obj_attempt_simple_charge_change" +/// called once a mindshield is implanted: (mob/user) +#define COMSIG_MINDSHIELD_IMPLANTED "mindshield_implanted" + ///from /obj/item/assembly/proc/pulsed(mob/pulser) #define COMSIG_ASSEMBLY_PULSED "assembly_pulsed" diff --git a/code/__DEFINES/dcs/signals/signals_spell.dm b/code/__DEFINES/dcs/signals/signals_spell.dm new file mode 100644 index 0000000000000..4e25a4642c715 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_spell.dm @@ -0,0 +1,93 @@ +// Signals sent to or by spells + +// Generic spell signals + + +/// Sent from /datum/action/spell/pre_cast() to the caster: (datum/action/cooldown/spell/spell, mob/user, atom/target) +#define COMSIG_MOB_PRE_SPELL_CAST "mob_spell_pre_cast" +/// Sent from /datum/action/spell/pre_cast() to the spell: (mob/user, atom/target) +#define COMSIG_SPELL_PRE_CAST "spell_pre_cast" + /// Return to prevent the spell cast from continuing. + #define SPELL_CANCEL_CAST (1 << 0) + /// Return from before cast signals to prevent the spell from giving off sound or invocation. + #define SPELL_NO_FEEDBACK (1 << 1) + /// Return from before cast signals to prevent the spell from going on cooldown before aftercast. + #define SPELL_NO_IMMEDIATE_COOLDOWN (1 << 2) + +/// Sent from /datum/action/spell/set_click_ability() to the caster: (datum/action/cooldown/spell/spell) +#define COMSIG_MOB_SPELL_ACTIVATED "mob_spell_active" + /// Same as spell_cancel_cast, as they're able to be used interchangeably + #define SPELL_CANCEL_ACTIVATION SPELL_CANCEL_CAST + +/// Sent from /datum/action/spell/cast() to the caster: (datum/action/cooldown/spell/spell, mob/user, atom/target) +#define COMSIG_MOB_CAST_SPELL "mob_cast_spell" +/// Sent from /datum/action/spell/cast() to the spell: (mob/user, atom/target) +#define COMSIG_SPELL_CAST "spell_cast" +// Sent from /datum/action/spell/post_cast() to the caster: (datum/action/cooldown/spell/spell, mob/user, atom/target) +#define COMSIG_MOB_POST_SPELL_CAST "mob_after_spell_cast" +/// Sent from /datum/action/spell/post_cast() to the spell: (mob/user, atom/target) +#define COMSIG_SPELL_POST_CAST "spell_after_cast" +/// Sent from /datum/action/spell/reset_spell_cooldown() to the spell: () +#define COMSIG_SPELL_CAST_RESET "spell_cast_reset" + +// Spell type signals + +// Pointed projectiles +/// Sent from /datum/action/spell/pointed/projectile/on_cast_hit: (atom/hit, atom/firer, obj/projectile/source) +#define COMSIG_SPELL_PROJECTILE_HIT "spell_projectile_hit" + +// AOE spells +/// Sent from /datum/action/spell/aoe/cast: (list/atoms_affected, atom/caster) +#define COMSIG_SPELL_AOE_ON_CAST "spell_aoe_cast" + +// Cone spells +/// Sent from /datum/action/spell/cone/cast: (list/atoms_affected, atom/caster) +#define COMSIG_SPELL_CONE_ON_CAST "spell_cone_cast" +/// Sent from /datum/action/spell/cone/do_cone_effects: (list/atoms_affected, atom/caster, level) +#define COMSIG_SPELL_CONE_ON_LAYER_EFFECT "spell_cone_cast_effect" + +// Touch spells +/// Sent from /datum/action/spell/touch/do_hand_hit: (atom/hit, mob/living/carbon/caster, obj/item/melee/touch_attack/hand) +#define COMSIG_SPELL_TOUCH_HAND_HIT "spell_touch_hand_cast" + +// Jaunt Spells +/// Sent from datum/action/spell/jaunt/enter_jaunt, to the mob jaunting: (obj/effect/dummy/phased_mob/jaunt, datum/action/spell/spell) +#define COMSIG_MOB_ENTER_JAUNT "spell_mob_enter_jaunt" +/// Sent from datum/action/spell/jaunt/exit_jaunt, after the mob exited jaunt: (datum/action/cooldown/spell/spell) +#define COMSIG_MOB_AFTER_EXIT_JAUNT "spell_mob_after_exit_jaunt" + +/// Sent from/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt, +/// to any unconscious / critical mobs being dragged when the jaunter enters blood: +/// (datum/action/cooldown/spell/jaunt/bloodcrawl/crawl, mob/living/jaunter, obj/effect/decal/cleanable/blood) +#define COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED "living_pre_consumed_by_bloodcrawl" +/// Sent from/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/consume_victim, +/// to the victim being consumed by the slaughter demon. +/// (datum/action/cooldown/spell/jaunt/bloodcrawl/crawl, mob/living/jaunter) +#define COMSIG_LIVING_BLOOD_CRAWL_CONSUMED "living_consumed_by_bloodcrawl" + /// Return at any point to stop the bloodcrawl "consume" process from continuing. + #define COMPONENT_STOP_CONSUMPTION (1 << 0) + +// Signals for specific spells + +// Lichdom +/// Sent from /datum/action/spell/lichdom/cast(), to the item being imbued: (datum/action/cooldown/spell/spell, mob/user) +#define COMSIG_ITEM_IMBUE_SOUL "item_imbue_soul" + /// Return to stop the cast and prevent the soul imbue + #define COMPONENT_BLOCK_IMBUE (1 << 0) + +/// Sent from /datum/action/spell/aoe/knock/cast(), to every nearby turf (for connect loc): (datum/action/cooldown/spell/aoe/knock/spell, mob/living/caster) +#define COMSIG_ATOM_MAGICALLY_UNLOCKED "atom_magic_unlock" + +// Instant Summons +/// Sent from /datum/action/spell/summonitem/cast(), to the item being marked for recall: (datum/action/cooldown/spell/spell, mob/user) +#define COMSIG_ITEM_MARK_RETRIEVAL "item_mark_retrieval" + /// Return to stop the cast and prevent the item from being marked + #define COMPONENT_BLOCK_MARK_RETRIEVAL (1 << 0) + +// Charge +/// Sent from /datum/action/spell/charge/cast(), to the item in hand being charged: (datum/action/cooldown/spell/spell, mob/user) +#define COMSIG_ITEM_MAGICALLY_CHARGED "item_magic_charged" + /// Return if an item was successfuly recharged + #define COMPONENT_ITEM_CHARGED (1 << 0) + /// Return if the item had a negative side effect occur while recharging + #define COMPONENT_ITEM_BURNT_OUT (1 << 1) diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 820be445b4754..dbac8a87007ed 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -115,7 +115,6 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define REMOTE_APC (1<<11) /// This area is prevented from having gravity (ie. space, nearstation, or outside solars) #define NO_GRAVITY (1<<12) - /* These defines are used specifically with the atom/pass_flags bitmask the atom/checkpass() proc uses them (tables will call movable atom checkpass(PASSTABLE) for example) diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm index 925253207a2b0..a354d0db13655 100644 --- a/code/__DEFINES/hud.dm +++ b/code/__DEFINES/hud.dm @@ -18,4 +18,240 @@ /// Used for HUD objects #define APPEARANCE_UI (RESET_COLOR|RESET_TRANSFORM|NO_CLIENT_COLOR|PIXEL_SCALE) -#define ACTION_BUTTON_DEFAULT_BACKGROUND "default" +/* + These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var. + The short version: + Everything is encoded as strings because apparently that's how Byond rolls. + "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid. + "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid. + Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy. + In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective + screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your + UI to scale with screen size. + The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15". + Therefore, the top right corner (except during admin shenanigans) is at "15,15" +*/ + +/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5) + var/x_off = i % 2 ? 0 : -1 + var/y_off = round((i-1) / 2) + return"CENTER+[x_off]:16,SOUTH+[y_off]:5" + +/proc/ui_equip_position(mob/M) + var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5) + return "CENTER:-16,SOUTH+[y_off+1]:5" + +/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5) + var/x_off = which == 1 ? -1 : 0 + var/y_off = round((M.held_items.len-1) / 2) + return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5" + +//Lower left, persistent menu +#define ui_inventory "WEST:6,SOUTH:5" + +//Middle left indicators +#define ui_lingchemdisplay "WEST,CENTER-1:15" +#define ui_lingstingdisplay "WEST:6,CENTER-3:11" + +//Lower center, persistent menu +#define ui_sstore1 "CENTER-5:10,SOUTH:5" +#define ui_id "CENTER-4:12,SOUTH:5" +#define ui_belt "CENTER-3:14,SOUTH:5" +#define ui_back "CENTER-2:14,SOUTH:5" +#define ui_storage1 "CENTER+1:18,SOUTH:5" +#define ui_storage2 "CENTER+2:20,SOUTH:5" +#define ui_combo "CENTER+4:24,SOUTH+1:7" //combo meter for martial arts + +//Lower right, persistent menu +#define ui_combat_toggle "EAST-3:24,SOUTH:5" +#define ui_skill_menu "EAST-4:22,SOUTH:5" + +//Upper left (action buttons) +#define ui_action_palette "WEST+0:23,NORTH-1:5" +#define ui_action_palette_offset(north_offset) ("WEST+0:23,NORTH-[1+north_offset]:5") + +#define ui_palette_scroll "WEST+1:8,NORTH-6:28" +#define ui_palette_scroll_offset(north_offset) ("WEST+1:8,NORTH-[6+north_offset]:28") + +//Pop-up inventory +#define ui_shoes "WEST+1:8,SOUTH:5" +#define ui_iclothing "WEST:6,SOUTH+1:7" +#define ui_oclothing "WEST+1:8,SOUTH+1:7" +#define ui_gloves "WEST+2:10,SOUTH+1:7" +#define ui_glasses "WEST:6,SOUTH+3:11" +#define ui_mask "WEST+1:8,SOUTH+2:9" +#define ui_ears "WEST+2:10,SOUTH+2:9" +#define ui_neck "WEST:6,SOUTH+2:9" +#define ui_head "WEST+1:8,SOUTH+3:11" + +//Generic living +#define ui_living_pull "EAST-1:28,CENTER-3:15" +#define ui_living_healthdoll "EAST-1:28,CENTER-1:15" + +//Cyborgs +#define ui_borg_lamp "CENTER-3:16, SOUTH:5" +#define ui_borg_tablet "CENTER-4:16, SOUTH:5" +#define ui_inv1 "CENTER-2:16,SOUTH:5" +#define ui_inv2 "CENTER-1 :16,SOUTH:5" +#define ui_inv3 "CENTER :16,SOUTH:5" +#define ui_borg_module "CENTER+1:16,SOUTH:5" +#define ui_borg_store "CENTER+2:16,SOUTH:5" +#define ui_borg_camera "CENTER+3:21,SOUTH:5" +#define ui_borg_alerts "CENTER+4:21,SOUTH:5" +#define ui_borg_language_menu "CENTER+4:19,SOUTH+1:6" + +//AI +#define ui_ai_core "SOUTH:6,WEST" +#define ui_ai_camera_list "SOUTH:6,WEST+1" +#define ui_ai_track_with_camera "SOUTH:6,WEST+2" +#define ui_ai_camera_light "SOUTH:6,WEST+3" +#define ui_ai_crew_monitor "SOUTH:6,WEST+4" +#define ui_ai_crew_manifest "SOUTH:6,WEST+5" +#define ui_ai_alerts "SOUTH:6,WEST+6" +#define ui_ai_announcement "SOUTH:6,WEST+7" +#define ui_ai_shuttle "SOUTH:6,WEST+8" +#define ui_ai_state_laws "SOUTH:6,WEST+9" +#define ui_ai_pda_send "SOUTH:6,WEST+10" +#define ui_ai_pda_log "SOUTH:6,WEST+11" +#define ui_ai_take_picture "SOUTH:6,WEST+12" +#define ui_ai_view_images "SOUTH:6,WEST+13" +#define ui_ai_sensor "SOUTH:6,WEST+14" +#define ui_ai_multicam "SOUTH+1:6,WEST+13" +#define ui_ai_add_multicam "SOUTH+1:6,WEST+14" +#define ui_ai_language_menu "SOUTH+1:8,WEST+11:30" + +//pAI +#define ui_pai_software "SOUTH:6,WEST" +#define ui_pai_shell "SOUTH:6,WEST+1" +#define ui_pai_chassis "SOUTH:6,WEST+2" +#define ui_pai_rest "SOUTH:6,WEST+3" +#define ui_pai_light "SOUTH:6,WEST+4" +#define ui_pai_newscaster "SOUTH:6,WEST+5" +#define ui_pai_host_monitor "SOUTH:6,WEST+6" +#define ui_pai_crew_manifest "SOUTH:6,WEST+7" +#define ui_pai_state_laws "SOUTH:6,WEST+8" +#define ui_pai_pda_send "SOUTH:6,WEST+9" +#define ui_pai_pda_log "SOUTH:6,WEST+10" +#define ui_pai_internal_gps "SOUTH:6,WEST+11" +#define ui_pai_take_picture "SOUTH:6,WEST+12" +#define ui_pai_view_images "SOUTH:6,WEST+13" +#define ui_pai_radio "SOUTH:6,WEST+14" +#define ui_pai_language_menu "SOUTH+1:8,WEST+13:31" + +//Ghosts + +#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24" +#define ui_ghost_orbit "SOUTH:6,CENTER-2:24" +#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24" +#define ui_ghost_teleport "SOUTH:6,CENTER:24" +#define ui_ghost_spawners_menu "SOUTH:6,CENTER+1:24" +#define ui_ghost_pai "SOUTH: 6, CENTER+2:24" +#define ui_ghost_language_menu "SOUTH:21, CENTER+3:7" + +//Blobbernauts +#define ui_blobbernaut_overmind_health "EAST-1:28,CENTER+0:19" + +//Families +#define ui_wanted_lvl "NORTH,11" + + +#define ui_devilsouldisplay "WEST:6,CENTER-1:15" //Feel free to delete this later, devils aren't real + //borgs +#define ui_borg_crew_manifest "CENTER+5:21,SOUTH:5" //borgs + +#define ui_monkey_body "CENTER-6:12,SOUTH:5" //monkey +#define ui_monkey_head "CENTER-5:14,SOUTH:5" //monkey +#define ui_monkey_mask "CENTER-4:15,SOUTH:5" //monkey +#define ui_monkey_neck "CENTER-3:16,SOUTH:5" //monkey +#define ui_monkey_back "CENTER-2:17,SOUTH:5" //monkey + +//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien +#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien +#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien + +#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones +#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones +#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones +#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones + +//Lower right, persistent menu +#define ui_drop_throw "EAST-1:28,SOUTH+1:7" +#define ui_above_movement "EAST-2:26,SOUTH+1:7" +#define ui_above_intent "EAST-3:24, SOUTH+1:7" +#define ui_movi "EAST-2:26,SOUTH:5" +#define ui_acti "EAST-3:24,SOUTH:5" +#define ui_zonesel "EAST-1:28,SOUTH:5" +#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) +#define ui_crafting "EAST-4:22,SOUTH:5" +#define ui_building "EAST-4:22,SOUTH:21" +#define ui_language_menu "EAST-4:6,SOUTH:21" + +#define ui_borg_pull "EAST-2:26,SOUTH+1:7" +#define ui_borg_radio "EAST-1:28,SOUTH+1:7" +#define ui_borg_intents "EAST-2:26,SOUTH:5" + + +//Upper-middle right (alerts) +#define ui_alert1 "EAST-1:28,CENTER+5:27" +#define ui_alert2 "EAST-1:28,CENTER+4:25" +#define ui_alert3 "EAST-1:28,CENTER+3:23" +#define ui_alert4 "EAST-1:28,CENTER+2:21" +#define ui_alert5 "EAST-1:28,CENTER+1:19" + + +//Middle right (status indicators) +#define ui_healthdoll "EAST-1:28,CENTER-2:13" +#define ui_health "EAST-1:28,CENTER-1:15" +#define ui_internal "EAST-1:28,CENTER+1:17" +#define ui_mood "EAST-1:28,CENTER:17" +#define ui_spacesuit "EAST-1:28,CENTER-4:10" +#define ui_stamina "EAST-1:28,CENTER-3:10" + +//borgs +#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator. + +//aliens +#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator. +#define ui_alienplasmadisplay "EAST,CENTER-2:15" +#define ui_alien_queen_finder "EAST,CENTER-3:15" + +//constructs +#define ui_construct_pull "EAST,CENTER-2:15" +#define ui_construct_health "EAST,CENTER:15" //same as borgs and humans + +//slimes +#define ui_slime_health "EAST,CENTER:15" //same as borgs, constructs and humans + +#define ui_ai_mod_int "SOUTH:6,WEST+10" +#define ui_ai_move_up "SOUTH:6,WEST+14" +#define ui_ai_move_down "SOUTH:6,WEST+15" + +#define ui_pai_mod_int "SOUTH:6,WEST+12" + +//Team finder + +#define ui_team_finder "CENTER,CENTER" + +// Holoparasites +#define ui_holopara_l_hand "CENTER:8,SOUTH+1:4" +#define ui_holopara_r_hand "CENTER+1:8,SOUTH+1:4" +#define ui_holopara_pull "CENTER:24,SOUTH:20" +#define ui_holopara_pull_dex "CENTER-1:9,SOUTH+1:2" +#define ui_holopara_swap_l "CENTER:8,SOUTH+2:4" +#define ui_holopara_swap_r "CENTER+1:8,SOUTH+2:4" +#define ui_holopara_button(pos) "CENTER[pos >= 0 ? "+" : ""][pos]:8,SOUTH:5" +#define ui_holopara_hand(pos) "CENTER[pos >= 0 ? "+" : ""][pos]:8,SOUTH+1:4" + + + + +// Defines relating to action button positions + +/// Whatever the base action datum thinks is best +#define SCRN_OBJ_DEFAULT "default" +/// Floating somewhere on the hud, not in any predefined place +#define SCRN_OBJ_FLOATING "floating" +/// In the list of buttons stored at the top of the screen +#define SCRN_OBJ_IN_LIST "list" +/// In the collapseable palette +#define SCRN_OBJ_IN_PALETTE "palette" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index beb339be799f5..2260615ecb69f 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -183,6 +183,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define ismimite(A) (istype(A, /mob/living/simple_animal/hostile/mimite)) +#define isspider(A) (istype(A, /mob/living/simple_animal/hostile/poison/giant_spider)) + //Misc mobs #define isobserver(A) (istype(A, /mob/dead/observer)) diff --git a/code/__DEFINES/magic.dm b/code/__DEFINES/magic.dm index 9708cf2cb5add..414788fce3f8d 100644 --- a/code/__DEFINES/magic.dm +++ b/code/__DEFINES/magic.dm @@ -3,6 +3,31 @@ ///Spawns random wands and spellbooks near players and gives some players antag objectives #define SUMMON_MAGIC "magic" +// Magic schools +/// Unset / default / "not actually magic" school. +#define SCHOOL_UNSET "unset" +// GOOD SCHOOLS (allowed by honorbound gods, some of these you can get on station) +/// Holy school (chaplain magic) +#define SCHOOL_HOLY "holy" +/// Mime... school? Mime magic. It counts +#define SCHOOL_MIME "mime" +/// Restoration school, which is mostly healing stuff +#define SCHOOL_RESTORATION "restoration" +// NEUTRAL SPELLS (punished by honorbound gods if you get caught using it) +/// Evocation school, usually involves killing or destroy stuff, usually out of thin air +#define SCHOOL_EVOCATION "evocation" +/// School of transforming stuff into other stuff +#define SCHOOL_TRANSMUTATION "transmutation" +/// School of transolcation, usually movement spells +#define SCHOOL_TRANSLOCATION "translocation" +/// Conjuration spells summon items / mobs / etc somehow +#define SCHOOL_CONJURATION "conjuration" +// EVIL SPELLS (instant smite + banishment) +/// Necromancy spells, usually involves soul / evil / bad stuff +#define SCHOOL_NECROMANCY "necromancy" +/// Other forbidden magics, such as heretic spells +#define SCHOOL_FORBIDDEN "forbidden" + // magical invocation types ///Allows being able to cast the spell without saying anything. #define INVOCATION_NONE "none" @@ -13,3 +38,61 @@ ///Forces the wizard to whisper (and be able to) to cast the spell. #define INVOCATION_WHISPER "whisper" + +// Bitflags for spell requirements +/// Whether the spell requires wizard clothes to cast. +#define SPELL_REQUIRES_WIZARD_GARB (1 << 0) +/// Whether the spell can only be cast by humans (mob type, not species). +/// SPELL_REQUIRES_WIZARD_GARB comes with this flag implied, as carbons and below can't wear clothes. +#define SPELL_REQUIRES_HUMAN (1 << 1) +/// Whether the spell can be cast by mobs who are brains / mmis. +/// When applying, bear in mind most spells will not function for brains out of the box. +#define SPELL_CASTABLE_AS_BRAIN (1 << 2) +/// Whether the spell can be cast while phased, such as blood crawling, ethereal jaunting or using rod form. +#define SPELL_CASTABLE_WHILE_PHASED (1 << 3) +/// Whether the spell can be cast while the user has antimagic on them that corresponds to the spell's own antimagic flags. +#define SPELL_REQUIRES_NO_ANTIMAGIC (1 << 4) +/// Whether the spell can be cast on the centcom z level. +#define SPELL_REQUIRES_OFF_CENTCOM (1 << 5) +/// Whether the spell must be cast by someone with a mind datum. +#define SPELL_REQUIRES_MIND (1 << 6) +/// Whether the spell requires the caster have a mime vow (mindless mobs will succeed this check regardless). +#define SPELL_REQUIRES_MIME_VOW (1 << 7) +/// Whether the spell can be cast, even if the caster is unable to speak the invocation +/// (effectively making the invocation flavor, instead of required). +#define SPELL_CASTABLE_WITHOUT_INVOCATION (1 << 8) + +DEFINE_BITFIELD(spell_requirements, list( + "SPELL_CASTABLE_AS_BRAIN" = SPELL_CASTABLE_AS_BRAIN, + "SPELL_CASTABLE_WHILE_PHASED" = SPELL_CASTABLE_WHILE_PHASED, + "SPELL_CASTABLE_WITHOUT_INVOCATION" = SPELL_CASTABLE_WITHOUT_INVOCATION, + "SPELL_REQUIRES_HUMAN" = SPELL_REQUIRES_HUMAN, + "SPELL_REQUIRES_MIME_VOW" = SPELL_REQUIRES_MIME_VOW, + "SPELL_REQUIRES_MIND" = SPELL_REQUIRES_MIND, + "SPELL_REQUIRES_NO_ANTIMAGIC" = SPELL_REQUIRES_NO_ANTIMAGIC, + "SPELL_REQUIRES_OFF_CENTCOM" = SPELL_REQUIRES_OFF_CENTCOM, + "SPELL_REQUIRES_WIZARD_GARB" = SPELL_REQUIRES_WIZARD_GARB, +)) + +// Bitflags for teleport spells +/// Whether the teleport spell skips over space turfs +#define TELEPORT_SPELL_SKIP_SPACE (1 << 0) +/// Whether the teleport spell skips over dense turfs +#define TELEPORT_SPELL_SKIP_DENSE (1 << 1) +/// Whether the teleport spell skips over blocked turfs +#define TELEPORT_SPELL_SKIP_BLOCKED (1 << 2) + +// Bitflags for magic resistance types +/// Default magic resistance that blocks normal magic (wizard, spells, magical staff projectiles) +#define MAGIC_RESISTANCE (1<<0) +/// Tinfoil hat magic resistance that blocks mental magic (telepathy / mind links, mind curses, abductors) +#define MAGIC_RESISTANCE_MIND (1<<1) +/// Holy magic resistance that blocks unholy magic (revenant, cult, vampire, voice of god) +#define MAGIC_RESISTANCE_HOLY (1<<2) + +DEFINE_BITFIELD(antimagic_flags, list( + "MAGIC_RESISTANCE" = MAGIC_RESISTANCE, + "MAGIC_RESISTANCE_HOLY" = MAGIC_RESISTANCE_HOLY, + "MAGIC_RESISTANCE_MIND" = MAGIC_RESISTANCE_MIND, +)) + diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm index 74c18eecfb30d..864279dab7365 100644 --- a/code/__DEFINES/maps.dm +++ b/code/__DEFINES/maps.dm @@ -95,6 +95,10 @@ require only minor tweaks. #define DL_TRAITS "traits" #define DECLARE_LEVEL(NAME, TRAITS) list(DL_NAME = NAME, DL_TRAITS = TRAITS) +/// boolean - does this z prevent phasing +#define ZTRAIT_NOPHASE "No Phase" + + /// must correspond to _basemap.dm for things to work correctly #define DEFAULT_MAP_TRAITS list(\ DECLARE_LEVEL("CentCom", ZTRAITS_CENTCOM),\ diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 0e70f97a99c10..928a7bfbb3567 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -456,6 +456,7 @@ GLOBAL_LIST_INIT(available_random_trauma_list, list( ///Whether or not to gib when the squashed mob is moved over #define SQUASHED_SHOULD_BE_GIBBED (1<<0) + /* * Defines for "AI emotions", allowing the AI to expression emotions * with status displays via emotes. diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm index 7fd596f20e403..c5450ed1ee4eb 100644 --- a/code/__DEFINES/movement.dm +++ b/code/__DEFINES/movement.dm @@ -65,6 +65,8 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0) #define TELEPORT_CHANNEL_WORMHOLE "wormhole" /// Magic teleportation, does whatever it wants (unless there's antimagic) #define TELEPORT_CHANNEL_MAGIC "magic" +/// Magic teleportation cast by the user +#define TELEPORT_CHANNEL_MAGIC_SELF "magic_self" /// Cult teleportation, does whatever it wants (unless there's holiness) #define TELEPORT_CHANNEL_CULT "cult" /// Teleportation with only a sender, but not disrupted by the BOH diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index 32fbfe849398e..0898f088c4319 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -51,6 +51,8 @@ #define SCAN_BOOZEPOWER (1<<12) //! Allows helmets and glasses to scan reagents. #define MASKEXTENDRANGE (1<<13) //! For masks, allows you to breathe from internals on adjecent tiles #define NOTCONSUMABLE (1<<14) //! Moths cannot eat clothing with that flag +/// Usable as casting clothes by wizards (matters for suits, glasses and headwear) +#define CASTING_CLOTHES (1<<15) /// Headgear/helmet allows internals #define HEADINTERNALS (1<<18) diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 1b1783108971e..39a6620be8b70 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -53,6 +53,7 @@ #define ROLE_PYRO_SLIME "Pyroclastic Anomaly Slime" #define ROLE_MONKEY_HELMET "Sentient Monkey" #define ROLE_PRISONER "Prisoner" +#define ROLE_WIZARD_APPRENTICE "apprentice" /// Roles that are antagonists, roundstart or not, and have passes to do.. antagonistry GLOBAL_LIST_INIT(antagonist_bannable_roles, list( diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 725630f412cfb..a08bb92d75cb3 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -60,6 +60,15 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_SLEEPIMMUNE "sleep_immunity" #define TRAIT_PUSHIMMUNE "push_immunity" #define TRAIT_SHOCKIMMUNE "shock_immunity" +#define TRAIT_HOLY "holy" +#define TRAIT_ANTIMAGIC "antimagic" //Unharmable +#define TRAIT_RECENTLY_BLOCKED_MAGIC "recently_blocked_magic" /// This mob recently blocked magic with some form of antimagic +#define TRAIT_ANTIMAGIC_NO_SELFBLOCK "anti_magic_no_selfblock" /// This allows a person who has antimagic to cast spells without getting blocked +/// Are we immune to specifically tesla / SM shocks? +#define TRAIT_TESLA_SHOCKIMMUNE "tesla_shock_immunity" +#define TRAIT_SNOWSTORM_IMMUNE "snowstorm_immune" +/// Can weave webs into cloth +#define TRAIT_WEB_WEAVER "web_weaver" #define TRAIT_STABLEHEART "stable_heart" #define TRAIT_STABLELIVER "stable_liver" #define TRAIT_NOVOMIT "no_vomit" @@ -171,6 +180,18 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NO_BLEEDING "no_bleed" // The user can acquire the bleeding status effect, but will no lose blood #define TRAIT_BLOOD_COOLANT "blood_coolant" // Replaces blood with coolant, meaning we overheat instead of losing air +/// This mob has no soul +#define TRAIT_NO_SOUL "no_soul" +/// Immune to being afflicted by time stop (spell) +#define TRAIT_TIME_STOP_IMMUNE "time_stop_immune" +/// Whether a spider's consumed this mob +#define TRAIT_SPIDER_CONSUMED "spider_consumed" +/// Whether we're sneaking, from the alien sneak ability. +/// Maybe worth generalizing into a general "is sneaky" / "is stealth" trait in the future. +#define TRAIT_ALIEN_SNEAK "sneaking_alien" +/// This mob is phased out of reality from magic, either a jaunt or rod form +#define TRAIT_MAGICALLY_PHASED "magically_phased" + // You can stare into the abyss, but it does not stare back. // You're immune to the hallucination effect of the supermatter, either // through force of will, or equipment. @@ -232,6 +253,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_AI_BAGATTACK "bagattack" // This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met +/// Climbable trait, given and taken by the climbable element when added or removed. Exists to be easily checked via HAS_TRAIT(). +#define TRAIT_CLIMBABLE "trait_climbable" + /// Allows heretics to cast their spells. #define TRAIT_ALLOW_HERETIC_CASTING "allow_heretic_casting" /// Designates a heart as a living heart for a heretic. @@ -285,7 +309,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Turf trait for when a turf is transparent #define TURF_Z_TRANSPARENT_TRAIT "turf_z_transparent" - ///Traits given by station traits #define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments" #define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation" @@ -314,7 +337,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// For unit testing, all do_afters set on this mob complete instantly and do not sleep #define INSTANT_DO_AFTER "instant_do_after" - +/// This mob heals from cult pylons. +#define TRAIT_HEALS_FROM_CULT_PYLONS "heals_from_cult_pylons" /// This means the user is currently holding/wearing a "tactical camouflage" item (like a potted plant). #define TRAIT_TACTICALLY_CAMOUFLAGED "tactically_camouflaged" diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index 9cd1e59e7144d..95af5052ef586 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -124,7 +124,7 @@ #define EXPERIMENTAL_SURGERY_TRAIT "experimental_surgery" #define NINJA_KIDNAPPED_TRAIT "ninja_kidnapped" #define TABLE_TRAIT "table_trait" - +#define LICH_TRAIT "lich_trait" /// Trait given to an atom/movable when they orbit something. #define ORBITING_TRAIT "orbiting" diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm index d4606518d19b7..f1124ac78aa3d 100644 --- a/code/__DEFINES/turfs.dm +++ b/code/__DEFINES/turfs.dm @@ -71,3 +71,4 @@ * Use instead of `A.loc.loc`. */ #define get_area(A) (isarea(A) ? A : get_step(A, 0)?.loc) + diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index c4b0afa9ec011..f4651ca50e1dd 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -220,6 +220,11 @@ WRITE_LOG(GLOB.href_exploit_attempt_log, "HREF: [key_name(user)] has potentially attempted an href exploit.[data]") message_admins("[key_name_admin(user)] has potentially attempted an href exploit.[data]") +/// Logging for wizard powers learned +/proc/log_spellbook(text) + WRITE_LOG(world.log, text) + + /* Log to both DD and the logfile. */ /proc/log_world(text) #ifdef USE_CUSTOM_ERROR_HANDLER @@ -367,3 +372,4 @@ return "([AREACOORD(T)])" else if(A.loc) return "(UNKNOWN (?, ?, ?))" + diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 96281807c7e66..3a4b4f14907a3 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -628,18 +628,18 @@ name = "Show roundend report" button_icon_state = "round_end" -/datum/action/report/Trigger() +/datum/action/report/on_activate() if(owner && GLOB.common_report && SSticker.current_state == GAME_STATE_FINISHED) SSticker.show_roundend_report(owner.client, FALSE) -/datum/action/report/IsAvailable() +/datum/action/report/is_available() return 1 /datum/action/report/Topic(href,href_list) if(usr != owner) return if(href_list["report"]) - Trigger() + trigger() return ///Returns a custom title for the roundend credit/report diff --git a/code/__HELPERS/screen_objs.dm b/code/__HELPERS/screen_objs.dm new file mode 100644 index 0000000000000..cb8520225ab8c --- /dev/null +++ b/code/__HELPERS/screen_objs.dm @@ -0,0 +1,115 @@ +/// Takes a screen loc string in the format +/// "+-left-offset:+-pixel,+-bottom-offset:+-pixel" +/// Where the :pixel is optional, and returns +/// A list in the format (x_offset, y_offset) +/// We require context to get info out of screen locs that contain relative info, so NORTH, SOUTH, etc +/proc/screen_loc_to_offset(screen_loc, view) + if(!screen_loc) + return list(64, 64) + var/list/view_size = view_to_pixels(view) + var/x = 0 + var/y = 0 + // Time to parse for directional relative offsets + if(findtext(screen_loc, "EAST")) // If you're starting from the east, we start from the east too + x += view_size[1] + if(findtext(screen_loc, "WEST")) // HHHHHHHHHHHHHHHHHHHHHH WEST is technically a 1 tile offset from the start. Shoot me please + x += world.icon_size + if(findtext(screen_loc, "NORTH")) + y += view_size[2] + if(findtext(screen_loc, "SOUTH")) + y += world.icon_size + + var/list/x_and_y = splittext(screen_loc, ",") + + var/list/x_pack = splittext(x_and_y[1], ":") + var/list/y_pack = splittext(x_and_y[2], ":") + + var/x_coord = x_pack[1] + var/y_coord = y_pack[1] + + if (findtext(x_coord, "CENTER")) + x += view_size[1] / 2 + + if (findtext(y_coord, "CENTER")) + y += view_size[2] / 2 + + x_coord = text2num(cut_relative_direction(x_coord)) + y_coord = text2num(cut_relative_direction(y_coord)) + + x += x_coord * world.icon_size + y += y_coord * world.icon_size + + if(length(x_pack) > 1) + x += text2num(x_pack[2]) + if(length(y_pack) > 1) + y += text2num(y_pack[2]) + return list(x, y) + +/// Takes a list in the form (x_offset, y_offset) +/// And converts it to a screen loc string +/// Accepts an optional view string/size to force the screen_loc around, so it can't go out of scope +/proc/offset_to_screen_loc(x_offset, y_offset, view = null) + if(view) + var/list/view_bounds = view_to_pixels(view) + x_offset = clamp(x_offset, world.icon_size, view_bounds[1]) + y_offset = clamp(y_offset, world.icon_size, view_bounds[2]) + + // Round with no argument is floor, so we get the non pixel offset here + var/x = round(x_offset / world.icon_size) + var/pixel_x = x_offset % world.icon_size + var/y = round(y_offset / world.icon_size) + var/pixel_y = y_offset % world.icon_size + + var/list/generated_loc = list() + generated_loc += "[x]" + if(pixel_x) + generated_loc += ":[pixel_x]" + generated_loc += ",[y]" + if(pixel_y) + generated_loc += ":[pixel_y]" + return jointext(generated_loc, "") + +/** + * Returns a valid location to place a screen object without overflowing the viewport + * + * * target: The target location as a purely number based screen_loc string "+-left-offset:+-pixel,+-bottom-offset:+-pixel" + * * target_offset: The amount we want to offset the target location by. We explictly don't care about direction here, we will try all 4 + * * view: The view variable of the client we're doing this for. We use this to get the size of the screen + * + * Returns a screen loc representing the valid location +**/ +/proc/get_valid_screen_location(target_loc, target_offset, view) + var/list/offsets = screen_loc_to_offset(target_loc) + var/base_x = offsets[1] + var/base_y = offsets[2] + + var/list/view_size = view_to_pixels(view) + + // Bias to the right, down, left, and then finally up + if(base_x + target_offset < view_size[1]) + return offset_to_screen_loc(base_x + target_offset, base_y, view) + if(base_y - target_offset > world.icon_size) + return offset_to_screen_loc(base_x, base_y - target_offset, view) + if(base_x - target_offset > world.icon_size) + return offset_to_screen_loc(base_x - target_offset, base_y, view) + if(base_y + target_offset < view_size[2]) + return offset_to_screen_loc(base_x, base_y + target_offset, view) + stack_trace("You passed in a scren location {[target_loc]} and offset {[target_offset]} that can't be fit in the viewport Width {[view_size[1]]}, Height {[view_size[2]]}. what did you do lad") + return null // The fuck did you do lad + +/// Takes a screen_loc string and cut out any directions like NORTH or SOUTH +/proc/cut_relative_direction(fragment) + var/static/regex/regex = regex(@"([A-Z])\w+", "g") + return regex.Replace(fragment, "") + +/// Returns a screen_loc format for a tiling screen objects from start and end positions. Start should be bottom left corner, and end top right corner. +/proc/spanning_screen_loc(start_px, start_py, end_px, end_py) + var/starting_tile_x = round(start_px / 32) + start_px -= starting_tile_x * 32 + var/starting_tile_y = round(start_py/ 32) + start_py -= starting_tile_y * 32 + var/ending_tile_x = round(end_px / 32) + end_px -= ending_tile_x * 32 + var/ending_tile_y = round(end_py / 32) + end_py -= ending_tile_y * 32 + return "[starting_tile_x]:[start_px],[starting_tile_y]:[start_py] to [ending_tile_x]:[end_px],[ending_tile_y]:[end_py]" diff --git a/code/__HELPERS/view.dm b/code/__HELPERS/view.dm index c2cb59a42b7a2..4f7aa4f92f551 100644 --- a/code/__HELPERS/view.dm +++ b/code/__HELPERS/view.dm @@ -36,3 +36,13 @@ viewY += zoom_amt //God, I hate that we have to round this. return "[round(viewX)]x[round(viewY)]" + + +/// Takes a string or num view, and converts it to pixel width/height in a list(pixel_width, pixel_height) +/proc/view_to_pixels(view) + if(!view) + return list(0, 0) + var/list/view_info = getviewsize(view) + view_info[1] *= world.icon_size + view_info[2] *= world.icon_size + return view_info diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 43a1497a6ec07..7adcf351fe2b3 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -216,6 +216,7 @@ DEFINE_BITFIELD(clothing_flags, list( "SCAN_REAGENTS" = SCAN_REAGENTS, "SCAN_BOOZEPOWER" = SCAN_BOOZEPOWER, "HEADINTERNALS" = HEADINTERNALS, + "CASTING_CLOTHES" = CASTING_CLOTHES, )) DEFINE_BITFIELD(tesla_flags, list( @@ -303,6 +304,15 @@ DEFINE_BITFIELD(mecha_flags, list( "HAS_LIGHTS" = HAS_LIGHTS, )) +DEFINE_BITFIELD(check_flags, list(\ + "CHECK IF HANDS BLOCKED" = AB_CHECK_HANDS_BLOCKED, \ + "CHECK IF IMMOBILIZED" = AB_CHECK_IMMOBILE, \ + "CHECK IF LYING DOWN" = AB_CHECK_LYING, \ + "CHECK IF CONSCIOUS" = AB_CHECK_CONSCIOUS, \ + "CHECK IF INCAPACITATED" = AB_CHECK_INCAPACITATED, \ + "CHECK IF TEMPORARILY INCORPOREAL" = AB_CHECK_PHASED, +)) + DEFINE_BITFIELD(emote_flags, list( "EMOTE_AUDIBLE" = EMOTE_AUDIBLE, "EMOTE_VISIBLE" = EMOTE_VISIBLE, diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 08080ba01b0f3..632543305f796 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -4,6 +4,12 @@ quirks have it's own panel so we don't need them here. */ GLOBAL_LIST_INIT(traits_by_type, list( + /atom = list( + "TRAIT_CLIMBABLE" = TRAIT_CLIMBABLE, + ), + /atom/movable = list( + "TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE, + ), /mob = list( "TRAIT_KNOCKEDOUT" = TRAIT_KNOCKEDOUT, "TRAIT_IMMOBILIZED" = TRAIT_IMMOBILIZED, @@ -35,6 +41,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_DISFIGURED" = TRAIT_DISFIGURED, "TRAIT_XENO_HOST" = TRAIT_XENO_HOST, "TRAIT_STUNIMMUNE" = TRAIT_STUNIMMUNE, + "TRAIT_RECENTLY_BLOCKED_MAGIC" = TRAIT_RECENTLY_BLOCKED_MAGIC, + "TRAIT_ANTIMAGIC_NO_SELFBLOCK" = TRAIT_ANTIMAGIC_NO_SELFBLOCK, "TRAIT_STUNRESISTANCE" = TRAIT_STUNRESISTANCE, "TRAIT_CONFUSEIMMUNE" = TRAIT_CONFUSEIMMUNE, "TRAIT_SLEEPIMMUNE" = TRAIT_SLEEPIMMUNE, @@ -54,9 +62,128 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_NODISMEMBER" = TRAIT_NODISMEMBER, "TRAIT_NOFIRE" = TRAIT_NOFIRE, "TRAIT_NOGUNS" = TRAIT_NOGUNS, + "TRAIT_MAGICALLY_PHASED" = TRAIT_MAGICALLY_PHASED, + "TRAIT_NO_SOUL" = TRAIT_NO_SOUL, + "TRAIT_SPIDER_CONSUMED" = TRAIT_SPIDER_CONSUMED, + "TRAIT_TESLA_SHOCKIMMUNE" = TRAIT_TESLA_SHOCKIMMUNE, + "TRAIT_ALIEN_SNEAK" = TRAIT_ALIEN_SNEAK, + "TRAIT_GENERIC" = TRAIT_GENERIC, + "GENERIC_ITEM_TRAIT" = GENERIC_ITEM_TRAIT, + "UNCONSCIOUS_TRAIT" = UNCONSCIOUS_TRAIT, + "EYE_DAMAGE" = EYE_DAMAGE, + "GENETIC_MUTATION" = GENETIC_MUTATION, + "OBESITY" = OBESITY, + "MAGIC_TRAIT" = MAGIC_TRAIT, + "TRAUMA_TRAIT" = TRAUMA_TRAIT, + "DISEASE_TRAIT" = DISEASE_TRAIT, + "SPECIES_TRAIT" = SPECIES_TRAIT, + "ORGAN_TRAIT" = ORGAN_TRAIT, + "ROUNDSTART_TRAIT" = ROUNDSTART_TRAIT, + "JOB_TRAIT" = JOB_TRAIT, + "CYBORG_ITEM_TRAIT" = CYBORG_ITEM_TRAIT, + "ADMIN_TRAIT" = ADMIN_TRAIT, + "CHANGELING_TRAIT" = CHANGELING_TRAIT, + "CULT_TRAIT" = CULT_TRAIT, + "CURSED_ITEM_TRAIT" = CURSED_ITEM_TRAIT, + "ABSTRACT_ITEM_TRAIT" = ABSTRACT_ITEM_TRAIT, + "STATUS_EFFECT_TRAIT" = STATUS_EFFECT_TRAIT, + "CLOTHING_TRAIT" = CLOTHING_TRAIT, + "CLOTHING_FEET_TRAIT" = CLOTHING_FEET_TRAIT, + "VEHICLE_TRAIT" = VEHICLE_TRAIT, + "INNATE_TRAIT" = INNATE_TRAIT, + "CRIT_HEALTH_TRAIT" = CRIT_HEALTH_TRAIT, + "OXYLOSS_TRAIT" = OXYLOSS_TRAIT, + "BUCKLED_TRAIT" = BUCKLED_TRAIT, + "CHOKEHOLD_TRAIT" = CHOKEHOLD_TRAIT, + "RESTING_TRAIT" = RESTING_TRAIT, + "STAT_TRAIT" = STAT_TRAIT, + "SUIT_TRAIT" = SUIT_TRAIT, + "LYING_DOWN_TRAIT" = LYING_DOWN_TRAIT, + "POWER_LACK_TRAIT" = POWER_LACK_TRAIT, + "GLASSES_TRAIT" = GLASSES_TRAIT, + "CURSE_TRAIT" = CURSE_TRAIT, + "STATION_TRAIT" = STATION_TRAIT, + "TRAIT_RUSTY" = TRAIT_RUSTY, + "ACTION_TRAIT" = ACTION_TRAIT, + "TURF_TRAIT" = TURF_TRAIT, + "LICH_TRAIT" = LICH_TRAIT, + "CLONING_POD_TRAIT" = CLONING_POD_TRAIT, + "STATUE_MUTE" = STATUE_MUTE, + "CHANGELING_DRAIN" = CHANGELING_DRAIN, + "MAGIC_BLIND" = MAGIC_BLIND, + "HIGHLANDER" = HIGHLANDER, + "TRAIT_HULK" = TRAIT_HULK, + "STASIS_MUTE" = STASIS_MUTE, + "GENETICS_SPELL" = GENETICS_SPELL, + "EYES_COVERED" = EYES_COVERED, + "CULT_EYES" = CULT_EYES, + "TRAIT_SANTA" = TRAIT_SANTA, + "SCRYING_ORB" = SCRYING_ORB, + "ABDUCTOR_ANTAGONIST" = ABDUCTOR_ANTAGONIST, + "NUKEOP_TRAIT" = NUKEOP_TRAIT, + "DEATHSQUAD_TRAIT" = DEATHSQUAD_TRAIT, + "MEGAFAUNA_TRAIT" = MEGAFAUNA_TRAIT, + "CLOWN_NUKE_TRAIT" = CLOWN_NUKE_TRAIT, + "STICKY_MOUSTACHE_TRAIT" = STICKY_MOUSTACHE_TRAIT, + "CHAINSAW_FRENZY_TRAIT" = CHAINSAW_FRENZY_TRAIT, + "CHRONO_GUN_TRAIT" = CHRONO_GUN_TRAIT, + "REVERSE_BEAR_TRAP_TRAIT" = REVERSE_BEAR_TRAP_TRAIT, + "CURSED_MASK_TRAIT" = CURSED_MASK_TRAIT, + "HIS_GRACE_TRAIT" = HIS_GRACE_TRAIT, + "HAND_REPLACEMENT_TRAIT" = HAND_REPLACEMENT_TRAIT, + "HOT_POTATO_TRAIT" = HOT_POTATO_TRAIT, + "SABRE_SUICIDE_TRAIT" = SABRE_SUICIDE_TRAIT, + "ABDUCTOR_VEST_TRAIT" = ABDUCTOR_VEST_TRAIT, + "CAPTURE_THE_FLAG_TRAIT" = CAPTURE_THE_FLAG_TRAIT, + "EYE_OF_GOD_TRAIT" = EYE_OF_GOD_TRAIT, + "SHAMEBRERO_TRAIT" = SHAMEBRERO_TRAIT, + "JAUNT_TRAIT" = JAUNT_TRAIT, + "CHRONOSUIT_TRAIT" = CHRONOSUIT_TRAIT, + "LOCKED_HELMET_TRAIT" = LOCKED_HELMET_TRAIT, + "NINJA_SUIT_TRAIT" = NINJA_SUIT_TRAIT, + "ANTI_DROP_IMPLANT_TRAIT" = ANTI_DROP_IMPLANT_TRAIT, + "HIVEMIND_TRAIT" = HIVEMIND_TRAIT, + "VR_ZONE_TRAIT" = VR_ZONE_TRAIT, + "GLUED_ITEM_TRAIT" = GLUED_ITEM_TRAIT, + "LEGION_CORE_TRAIT" = LEGION_CORE_TRAIT, + "MIRROR_TRAIT" = MIRROR_TRAIT, + "CRAYON_TRAIT" = CRAYON_TRAIT, + "HOLYWATER_TRAIT" = HOLYWATER_TRAIT, + "VANGUARD_TRAIT" = VANGUARD_TRAIT, + "STARGAZER_TRAIT" = STARGAZER_TRAIT, + "HOLOPARASITE_CLOAK_TRAIT" = HOLOPARASITE_CLOAK_TRAIT, + "HOLOPARASITE_SCOUT_TRAIT" = HOLOPARASITE_SCOUT_TRAIT, + "HOLOPARASITE_STAT_TRAIT" = HOLOPARASITE_STAT_TRAIT, + "PARRY_TRAIT" = PARRY_TRAIT, + "LIGHTPINK_TRAIT" = LIGHTPINK_TRAIT, + "BATTLE_ROYALE_TRAIT" = BATTLE_ROYALE_TRAIT, + "MADE_UNCLONEABLE" = MADE_UNCLONEABLE, + "TRAIT_JAWS_OF_LIFE" = TRAIT_JAWS_OF_LIFE, + "STICKY_NODROP" = STICKY_NODROP, + "BUSY_FLOORBOT_TRAIT" = BUSY_FLOORBOT_TRAIT, + "PULLED_WHILE_SOFTCRIT_TRAIT" = PULLED_WHILE_SOFTCRIT_TRAIT, + "LOCKED_BORG_TRAIT" = LOCKED_BORG_TRAIT, + "LACKING_LOCOMOTION_APPENDAGES_TRAIT" = LACKING_LOCOMOTION_APPENDAGES_TRAIT, + "LACKING_MANIPULATION_APPENDAGES_TRAIT" = LACKING_MANIPULATION_APPENDAGES_TRAIT, + "HANDCUFFED_TRAIT" = HANDCUFFED_TRAIT, + "WARPWHISTLE_TRAIT" = WARPWHISTLE_TRAIT, + "SOULSTONE_TRAIT" = SOULSTONE_TRAIT, + "SLIME_COLD" = SLIME_COLD, + "BOT_TIPPED_OVER" = BOT_TIPPED_OVER, + "PAI_FOLDED" = PAI_FOLDED, + "BRAIN_UNAIDED" = BRAIN_UNAIDED, + "TRAIT_PRESERVE_UI_WITHOUT_CLIENT" = TRAIT_PRESERVE_UI_WITHOUT_CLIENT, + "EXPERIMENTAL_SURGERY_TRAIT" = EXPERIMENTAL_SURGERY_TRAIT, + "NINJA_KIDNAPPED_TRAIT" = NINJA_KIDNAPPED_TRAIT, + "TABLE_TRAIT" = TABLE_TRAIT, + "TRAIT_HEALS_FROM_CULT_PYLONS" = TRAIT_HEALS_FROM_CULT_PYLONS, + "TRAIT_TIME_STOP_IMMUNE" = TRAIT_TIME_STOP_IMMUNE, + "TRAIT_WEB_WEAVER" = TRAIT_WEB_WEAVER, "TRAIT_NOHUNGER" = TRAIT_NOHUNGER, "TRAIT_NOMETABOLISM" = TRAIT_NOMETABOLISM, "TRAIT_POWERHUNGRY" = TRAIT_POWERHUNGRY, + "TRAIT_HOLY" = TRAIT_HOLY, + "TRAIT_ANTIMAGIC" = TRAIT_ANTIMAGIC, "TRAIT_NOCLONELOSS" = TRAIT_NOCLONELOSS, "TRAIT_TOXIMMUNE" = TRAIT_TOXIMMUNE, "TRAIT_EASYDISMEMBER" = TRAIT_EASYDISMEMBER, diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm index 724d640bb8373..3cc84f1ff43f7 100644 --- a/code/_onclick/drag_drop.dm +++ b/code/_onclick/drag_drop.dm @@ -86,18 +86,27 @@ if (LAZYACCESS(modifiers, MIDDLE_CLICK)) if (src_object && src_location != over_location) middragtime = world.time - middragatom = src_object + middle_drag_atom_ref = WEAKREF(src_object) else middragtime = 0 - middragatom = null + middle_drag_atom_ref = null + mouseParams = params + mouse_location_ref = WEAKREF(over_location) + mouse_object_ref = WEAKREF(over_object) + if(selected_target[1] && over_object?.IsAutoclickable()) + selected_target[1] = over_object + selected_target[2] = params if(active_mousedown_item) active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob) + SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDRAG, src_object, over_object, src_location, over_location, src_control, over_control, params) + return ..() /obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob) return -/client/MouseDrop(src_object, over_object, src_location, over_location, src_control, over_control, params) - if (middragatom == src_object) +/client/MouseDrop(atom/src_object, atom/over_object, atom/src_location, atom/over_location, src_control, over_control, params) + if (IS_WEAKREF_OF(src_object, middle_drag_atom_ref)) middragtime = 0 - middragatom = null + middle_drag_atom_ref = null ..() + diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm deleted file mode 100644 index c74cd00a5ad27..0000000000000 --- a/code/_onclick/hud/_defines.dm +++ /dev/null @@ -1,200 +0,0 @@ -/* - These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var. - - The short version: - - Everything is encoded as strings because apparently that's how Byond rolls. - - "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid. - "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid. - Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy. - - In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective - screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your - UI to scale with screen size. - - The size of the user's screen is defined by client.view (indirectly by world.view), in our case "17x15". - Therefore, the top right corner (except during admin shenanigans) is at "17,15" -*/ - -//Lower left, persistent menu -#define ui_inventory "WEST:6,SOUTH:5" - -//Middle left indicators -#define ui_lingchemdisplay "WEST,CENTER-1:15" -#define ui_lingstingdisplay "WEST:6,CENTER-3:11" - -#define ui_devilsouldisplay "WEST:6,CENTER-1:15" - -//Lower center, persistent menu -#define ui_sstore1 "CENTER-5:10,SOUTH:5" -#define ui_id "CENTER-4:12,SOUTH:5" -#define ui_belt "CENTER-3:14,SOUTH:5" -#define ui_back "CENTER-2:14,SOUTH:5" - -/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5) - var/x_off = -(!(i % 2)) - var/y_off = round((i-1) / 2) - return"CENTER+[x_off]:16,SOUTH+[y_off]:5" - -/proc/ui_equip_position(mob/M) - var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5) - return "CENTER:-16,SOUTH+[y_off+1]:5" - -/proc/ui_swaphand_position(mob/M, which = 1) //values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5) - var/x_off = which == 1 ? -1 : 0 - var/y_off = round((M.held_items.len-1) / 2) - return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5" - -#define ui_storage1 "CENTER+1:18,SOUTH:5" -#define ui_storage2 "CENTER+2:20,SOUTH:5" - -#define ui_borg_lamp "CENTER-3:16, SOUTH:5" //borgs -#define ui_borg_tablet "CENTER-4:16, SOUTH:5" //borgs -#define ui_inv1 "CENTER-2:16,SOUTH:5" //borgs -#define ui_inv2 "CENTER-1 :16,SOUTH:5" //borgs -#define ui_inv3 "CENTER :16,SOUTH:5" //borgs -#define ui_borg_module "CENTER+1:16,SOUTH:5" //borgs -#define ui_borg_store "CENTER+2:16,SOUTH:5" //borgs -#define ui_borg_camera "CENTER+3:21,SOUTH:5" //borgs -#define ui_borg_alerts "CENTER+4:21,SOUTH:5" //borgs -#define ui_borg_crew_manifest "CENTER+5:21,SOUTH:5" //borgs -#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5" //borgs - -#define ui_monkey_body "CENTER-6:12,SOUTH:5" //monkey -#define ui_monkey_head "CENTER-5:14,SOUTH:5" //monkey -#define ui_monkey_mask "CENTER-4:15,SOUTH:5" //monkey -#define ui_monkey_neck "CENTER-3:16,SOUTH:5" //monkey -#define ui_monkey_back "CENTER-2:17,SOUTH:5" //monkey - -//#define ui_alien_storage_l "CENTER-2:14,SOUTH:5"//alien -#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"//alien -#define ui_alien_language_menu "EAST-3:26,SOUTH:5" //alien - -#define ui_drone_drop "CENTER+1:18,SOUTH:5" //maintenance drones -#define ui_drone_pull "CENTER+2:2,SOUTH:5" //maintenance drones -#define ui_drone_storage "CENTER-2:14,SOUTH:5" //maintenance drones -#define ui_drone_head "CENTER-3:14,SOUTH:5" //maintenance drones - -//Lower right, persistent menu -#define ui_drop_throw "EAST-1:28,SOUTH+1:7" -#define ui_above_movement "EAST-2:26,SOUTH+1:7" -#define ui_above_intent "EAST-3:24, SOUTH+1:7" -#define ui_movi "EAST-2:26,SOUTH:5" -#define ui_acti "EAST-3:24,SOUTH:5" -#define ui_zonesel "EAST-1:28,SOUTH:5" -#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12) -#define ui_crafting "EAST-4:22,SOUTH:5" -#define ui_building "EAST-4:22,SOUTH:21" -#define ui_language_menu "EAST-4:6,SOUTH:21" - -#define ui_borg_pull "EAST-2:26,SOUTH+1:7" -#define ui_borg_radio "EAST-1:28,SOUTH+1:7" -#define ui_borg_intents "EAST-2:26,SOUTH:5" - - -//Upper-middle right (alerts) -#define ui_alert1 "EAST-1:28,CENTER+5:27" -#define ui_alert2 "EAST-1:28,CENTER+4:25" -#define ui_alert3 "EAST-1:28,CENTER+3:23" -#define ui_alert4 "EAST-1:28,CENTER+2:21" -#define ui_alert5 "EAST-1:28,CENTER+1:19" - - -//Middle right (status indicators) -#define ui_healthdoll "EAST-1:28,CENTER-2:13" -#define ui_health "EAST-1:28,CENTER-1:15" -#define ui_internal "EAST-1:28,CENTER+1:17" -#define ui_mood "EAST-1:28,CENTER:17" -#define ui_spacesuit "EAST-1:28,CENTER-4:10" -#define ui_stamina "EAST-1:28,CENTER-3:10" - -//borgs -#define ui_borg_health "EAST-1:28,CENTER-1:15" //borgs have the health display where humans have the pressure damage indicator. - -//aliens -#define ui_alien_health "EAST,CENTER-1:15" //aliens have the health display where humans have the pressure damage indicator. -#define ui_alienplasmadisplay "EAST,CENTER-2:15" -#define ui_alien_queen_finder "EAST,CENTER-3:15" - -//constructs -#define ui_construct_pull "EAST,CENTER-2:15" -#define ui_construct_health "EAST,CENTER:15" //same as borgs and humans - -//slimes -#define ui_slime_health "EAST,CENTER:15" //same as borgs, constructs and humans - -// AI - -#define ui_ai_core "SOUTH:6,WEST" -#define ui_ai_camera_list "SOUTH:6,WEST+1" -#define ui_ai_track_with_camera "SOUTH:6,WEST+2" -#define ui_ai_camera_light "SOUTH:6,WEST+3" -#define ui_ai_crew_monitor "SOUTH:6,WEST+4" -#define ui_ai_crew_manifest "SOUTH:6,WEST+5" -#define ui_ai_alerts "SOUTH:6,WEST+6" -#define ui_ai_announcement "SOUTH:6,WEST+7" -#define ui_ai_shuttle "SOUTH:6,WEST+8" -#define ui_ai_state_laws "SOUTH:6,WEST+9" -#define ui_ai_mod_int "SOUTH:6,WEST+10" -#define ui_ai_take_picture "SOUTH:6,WEST+11" -#define ui_ai_view_images "SOUTH:6,WEST+12" -#define ui_ai_sensor "SOUTH:6,WEST+13" -#define ui_ai_multicam "SOUTH:6,WEST+12" -#define ui_ai_add_multicam "SOUTH:6,WEST+13" -#define ui_ai_move_up "SOUTH:6,WEST+14" -#define ui_ai_move_down "SOUTH:6,WEST+15" -#define ui_ai_language_menu "CENTER+7:32,SOUTH+1:5" - -// pAI - -#define ui_pai_software "SOUTH:6,WEST" -#define ui_pai_shell "SOUTH:6,WEST+1" -#define ui_pai_chassis "SOUTH:6,WEST+2" -#define ui_pai_rest "SOUTH:6,WEST+3" -#define ui_pai_light "SOUTH:6,WEST+4" -#define ui_pai_newscaster "SOUTH:6,WEST+5" -#define ui_pai_host_monitor "SOUTH:6,WEST+6" -#define ui_pai_crew_manifest "SOUTH:6,WEST+7" -#define ui_pai_state_laws "SOUTH:6,WEST+8" -#define ui_pai_internal_gps "SOUTH:6,WEST+9" -#define ui_pai_take_picture "SOUTH:6,WEST+10" -#define ui_pai_view_images "SOUTH:6,WEST+11" -#define ui_pai_mod_int "SOUTH:6,WEST+12" - -//Pop-up inventory -#define ui_shoes "WEST+1:8,SOUTH:5" - -#define ui_iclothing "WEST:6,SOUTH+1:7" -#define ui_oclothing "WEST+1:8,SOUTH+1:7" -#define ui_gloves "WEST+2:10,SOUTH+1:7" - -#define ui_glasses "WEST:6,SOUTH+3:11" -#define ui_mask "WEST+1:8,SOUTH+2:9" -#define ui_ears "WEST+2:10,SOUTH+2:9" -#define ui_neck "WEST:6,SOUTH+2:9" -#define ui_head "WEST+1:8,SOUTH+3:11" - -//Ghosts - -#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24" -#define ui_ghost_orbit "SOUTH:6,CENTER-2:24" -#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24" -#define ui_ghost_teleport "SOUTH:6,CENTER:24" -#define ui_ghost_spawners_menu "SOUTH:6,CENTER+1:24" -#define ui_ghost_pai "SOUTH: 6, CENTER+2:24" -#define ui_ghost_language_menu "SOUTH:21, CENTER+3:7" - -//Team finder - -#define ui_team_finder "CENTER,CENTER" - -// Holoparasites -#define ui_holopara_l_hand "CENTER:8,SOUTH+1:4" -#define ui_holopara_r_hand "CENTER+1:8,SOUTH+1:4" -#define ui_holopara_pull "CENTER:24,SOUTH:20" -#define ui_holopara_pull_dex "CENTER-1:9,SOUTH+1:2" -#define ui_holopara_swap_l "CENTER:8,SOUTH+2:4" -#define ui_holopara_swap_r "CENTER+1:8,SOUTH+2:4" -#define ui_holopara_button(pos) "CENTER[pos >= 0 ? "+" : ""][pos]:8,SOUTH:5" -#define ui_holopara_hand(pos) "CENTER[pos >= 0 ? "+" : ""][pos]:8,SOUTH+1:4" diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm index 6c4f40becf29c..a152881497912 100644 --- a/code/_onclick/hud/action_button.dm +++ b/code/_onclick/hud/action_button.dm @@ -1,174 +1,149 @@ + /atom/movable/screen/movable/action_button var/datum/action/linked_action + var/datum/hud/our_hud var/actiontooltipstyle = "" screen_loc = null var/button_icon_state var/appearance_cache + /// Where we are currently placed on the hud. SCRN_OBJ_DEFAULT asks the linked action what it thinks + var/location = SCRN_OBJ_DEFAULT + /// A unique bitflag, combined with the name of our linked action this lets us persistently remember any user changes to our position var/id - var/ordered = TRUE //If the button gets placed into the default bar. + /// A weakref of the last thing we hovered over + /// God I hate how dragging works + var/datum/weakref/last_hovored_ref /atom/movable/screen/movable/action_button/Destroy() - . = ..() + if(our_hud) + var/mob/viewer = our_hud.mymob + our_hud.hide_action(src) + viewer?.client?.screen -= src + linked_action.viewers -= our_hud + viewer.update_action_buttons() + our_hud = null + linked_action = null + return ..() /atom/movable/screen/movable/action_button/proc/can_use(mob/user) - if (linked_action) - return linked_action.owner == user + if(linked_action) + if(linked_action.viewers[user.hud_used]) + return TRUE + return FALSE else if (isobserver(user)) var/mob/dead/observer/O = user return !O.observetarget else return TRUE -/atom/movable/screen/movable/action_button/MouseDrop(over_object) - if(!can_use(usr)) - return - if((istype(over_object, /atom/movable/screen/movable/action_button) && !istype(over_object, /atom/movable/screen/movable/action_button/hide_toggle))) - if(locked) - to_chat(usr, "Action button \"[name]\" is locked, unlock it first.") - return - var/atom/movable/screen/movable/action_button/B = over_object - var/list/actions = usr.actions - actions.Swap(actions.Find(src.linked_action), actions.Find(B.linked_action)) - moved = FALSE - ordered = TRUE - B.moved = FALSE - B.ordered = TRUE - usr.update_action_buttons() - else - return ..() - /atom/movable/screen/movable/action_button/Click(location,control,params) if (!can_use(usr)) return FALSE var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, SHIFT_CLICK)) - if(locked) - to_chat(usr, "Action button \"[name]\" is locked, unlock it first.") - return TRUE - moved = 0 - usr.update_action_buttons() //redraw buttons that are no longer considered "moved" - return TRUE - if(LAZYACCESS(modifiers, CTRL_CLICK)) - locked = !locked - to_chat(usr, "Action button \"[name]\" [locked ? "" : "un"]locked.") - if(id && usr.client) //try to (un)remember position - usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = locked ? moved : null + var/datum/hud/our_hud = usr.hud_used + our_hud.position_action(src, SCRN_OBJ_DEFAULT) return TRUE if(usr.next_click > world.time) return usr.next_click = world.time + 1 - linked_action.Trigger() + + linked_action.trigger() SEND_SOUND(usr, 'sound/effects/pop.ogg') transform = turn(matrix() * 0.9, pick(-8, 8)) alpha = 200 - animate(src, transform = matrix(), time=4, alpha=255) + animate(src, transform = matrix(), time=0.4 SECONDS, alpha=255) return TRUE -//Hide/Show Action Buttons ... Button -/atom/movable/screen/movable/action_button/hide_toggle - name = "Hide Buttons" - desc = "Shift-click any button to reset its position, and Control-click it to lock it in place. Alt-click this button to reset all buttons to their default positions." - icon = 'icons/hud/actions/action_generic.dmi' - icon_state = "bg_default" - var/hidden = FALSE - var/hide_icon = 'icons/hud/actions/action_generic.dmi' - var/hide_state = "hide" - var/show_state = "show" - var/mutable_appearance/hide_appearance - var/mutable_appearance/show_appearance - -/atom/movable/screen/movable/action_button/hide_toggle/Initialize(mapload) - . = ..() - var/static/list/icon_cache = list() - - var/cache_key = "[hide_icon][hide_state]" - hide_appearance = icon_cache[cache_key] - if(!hide_appearance) - hide_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, hide_state) - - cache_key = "[hide_icon][show_state]" - show_appearance = icon_cache[cache_key] - if(!show_appearance) - show_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, show_state) - -/atom/movable/screen/movable/action_button/hide_toggle/Click(location,control,params) - if (!can_use(usr)) +// Entered and Exited won't fire while you're dragging something, because you're still "holding" it +// Very much byond logic, but I want nice behavior, so we fake it with drag +/atom/movable/screen/movable/action_button/MouseDrag(atom/over_object, src_location, over_location, src_control, over_control, params) + if(!can_use(usr)) return + if(IS_WEAKREF_OF(over_object, last_hovored_ref)) + return + var/atom/old_object + if(last_hovored_ref) + old_object = last_hovored_ref?.resolve() + else // If there's no current ref, we assume it was us. We also treat this as our "first go" location + old_object = src + var/datum/hud/our_hud = usr.hud_used + our_hud?.generate_landings(src) - var/list/modifiers = params2list(params) - if(LAZYACCESS(modifiers, SHIFT_CLICK)) - if(locked) - to_chat(usr, "Action button \"[name]\" is locked, unlock it first.") - return TRUE - moved = FALSE - usr.update_action_buttons(TRUE) - return TRUE - if(LAZYACCESS(modifiers, CTRL_CLICK)) - locked = !locked - to_chat(usr, "Action button \"[name]\" [locked ? "" : "un"]locked.") - if(id && usr.client) //try to (un)remember position - usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = locked ? moved : null - return TRUE - if(LAZYACCESS(modifiers, ALT_CLICK)) - var/buttons_locked = usr.client.prefs.read_player_preference(/datum/preference/toggle/buttons_locked) - for(var/V in usr.actions) - var/datum/action/A = V - var/atom/movable/screen/movable/action_button/B = A.button - B.moved = FALSE - if(B.id && usr.client) - usr.client.prefs.action_buttons_screen_locs["[B.name]_[B.id]"] = null - B.locked = buttons_locked - locked = buttons_locked - moved = FALSE - if(id && usr.client) - usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = null - usr.update_action_buttons(TRUE) - to_chat(usr, "Action button positions have been reset.") - return TRUE - usr.hud_used.action_buttons_hidden = !usr.hud_used.action_buttons_hidden + if(old_object) + old_object.MouseExited(over_location, over_control, params) - hidden = usr.hud_used.action_buttons_hidden - if(hidden) - name = "Show Buttons" - else - name = "Hide Buttons" - update_appearance() - usr.update_action_buttons() + last_hovored_ref = WEAKREF(over_object) + over_object.MouseEntered(over_location, over_control, params) -/atom/movable/screen/movable/action_button/hide_toggle/AltClick(mob/user) - for(var/V in user.actions) - var/datum/action/A = V - var/atom/movable/screen/movable/action_button/B = A.button - B.moved = FALSE - if(moved) - moved = FALSE - user.update_action_buttons(TRUE) - to_chat(user, "Action button positions have been reset.") +/atom/movable/screen/movable/action_button/MouseEntered(location, control, params) + . = ..() + if(!QDELETED(src)) + openToolTip(usr, src, params, title = name, content = desc, theme = actiontooltipstyle) +/atom/movable/screen/movable/action_button/MouseExited(location, control, params) + closeToolTip(usr) + return ..() -/atom/movable/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(datum/hud/owner_hud) - var/settings = owner_hud.get_action_buttons_icons() - icon = settings["bg_icon"] - icon_state = settings["bg_state"] - hide_icon = settings["toggle_icon"] - hide_state = settings["toggle_hide"] - show_state = settings["toggle_show"] - update_appearance() +/atom/movable/screen/movable/action_button/MouseDrop(over_object) + last_hovored_ref = null + if(!can_use(usr)) + return + var/datum/hud/our_hud = usr.hud_used + if(over_object == src) + our_hud.hide_landings() + return + if(istype(over_object, /atom/movable/screen/action_landing)) + var/atom/movable/screen/action_landing/reserve = over_object + reserve.hit_by(src) + our_hud.hide_landings() + save_position() + return -/atom/movable/screen/movable/action_button/hide_toggle/update_overlays() + our_hud.hide_landings() + if(istype(over_object, /atom/movable/screen/button_palette) || istype(over_object, /atom/movable/screen/palette_scroll)) + our_hud.position_action(src, SCRN_OBJ_IN_PALETTE) + save_position() + return + if(istype(over_object, /atom/movable/screen/movable/action_button)) + var/atom/movable/screen/movable/action_button/button = over_object + our_hud.position_action_relative(src, button) + save_position() + return . = ..() - . += hidden ? show_appearance : hide_appearance - -/atom/movable/screen/movable/action_button/MouseEntered(location,control,params) - if(!QDELETED(src)) - openToolTip(usr,src,params,title = name,content = desc,theme = actiontooltipstyle) + our_hud.position_action(src, screen_loc) + save_position() +/atom/movable/screen/movable/action_button/proc/save_position() + var/mob/user = our_hud.mymob + if(!user?.client) + return + var/position_info = "" + switch(location) + if(SCRN_OBJ_FLOATING) + position_info = screen_loc + if(SCRN_OBJ_IN_LIST) + position_info = SCRN_OBJ_IN_LIST + if(SCRN_OBJ_IN_PALETTE) + position_info = SCRN_OBJ_IN_PALETTE + + user.client.prefs.action_buttons_screen_locs["[name]_[id]"] = position_info + +/atom/movable/screen/movable/action_button/proc/load_position() + var/mob/user = our_hud.mymob + if(!user) + return + var/position_info = user.client?.prefs?.action_buttons_screen_locs["[name]_[id]"] || SCRN_OBJ_DEFAULT + user.hud_used.position_action(src, position_info) -/atom/movable/screen/movable/action_button/MouseExited() - closeToolTip(usr) +/atom/movable/screen/movable/action_button/proc/dump_save() + var/mob/user = our_hud.mymob + if(!user?.client) + return + user.client.prefs.action_buttons_screen_locs -= "[name]_[id]" /datum/hud/proc/get_action_buttons_icons() . = list() @@ -185,7 +160,7 @@ /mob/proc/update_action_buttons_icon(status_only = FALSE) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon(status_only) + A.update_buttons(status_only) //This is the proc used to update all the action buttons. /mob/proc/update_action_buttons(reload_screen) @@ -195,60 +170,241 @@ if(hud_used.hud_shown != HUD_STYLE_STANDARD) return - var/button_number = 0 + for(var/datum/action/action as anything in actions) + var/atom/movable/screen/movable/action_button/button = action.viewers[hud_used] + action.update_buttons() + if(reload_screen) + client.screen += button - if(hud_used.action_buttons_hidden) - for(var/datum/action/A in actions) - A.button.screen_loc = null - if(reload_screen) - client.screen += A.button - else - for(var/datum/action/A in actions) - A.UpdateButtonIcon() - var/atom/movable/screen/movable/action_button/B = A.button - if(B.ordered) - button_number++ - if(B.moved) - B.screen_loc = B.moved - else - B.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number) - if(reload_screen) - client.screen += B - - if(!button_number) - hud_used.hide_actions_toggle.screen_loc = null - return - - if(!hud_used.hide_actions_toggle.moved) - hud_used.hide_actions_toggle.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number+1) - else - hud_used.hide_actions_toggle.screen_loc = hud_used.hide_actions_toggle.moved if(reload_screen) - client.screen += hud_used.hide_actions_toggle + hud_used.update_our_owner() + // This holds the logic for the palette buttons + hud_used.palette_actions.refresh_actions() + +/atom/movable/screen/button_palette + desc = "Drag buttons to move them
Shift-click any button to reset it
Alt-click this to reset all buttons" + icon = 'icons/hud/64x16_actions.dmi' + icon_state = "screen_gen_palette" + screen_loc = ui_action_palette + var/datum/hud/our_hud + var/expanded = FALSE + /// Id of any currently running timers that set our color matrix + var/color_timer_id + +/atom/movable/screen/button_palette/Destroy() + if(our_hud) + our_hud.mymob?.client?.screen -= src + our_hud.toggle_palette = null + our_hud = null + return ..() + +/atom/movable/screen/button_palette/Initialize(mapload) + . = ..() + update_appearance() +/atom/movable/screen/button_palette/proc/set_hud(datum/hud/our_hud) + src.our_hud = our_hud + refresh_owner() +/atom/movable/screen/button_palette/update_name(updates) + . = ..() + if(expanded) + name = "Hide Buttons" + else + name = "Show Buttons" + +/atom/movable/screen/button_palette/proc/refresh_owner() + var/mob/viewer = our_hud.mymob + if(viewer.client) + viewer.client.screen |= src + + var/list/settings = our_hud.get_action_buttons_icons() + var/ui_icon = "[settings["bg_icon"]]" + var/list/ui_segments = splittext(ui_icon, ".") + var/list/ui_paths = splittext(ui_segments[1], "/") + var/ui_name = ui_paths[length(ui_paths)] + icon_state = "[ui_name]_palette" + +/atom/movable/screen/button_palette/MouseEntered(location, control, params) + . = ..() + if(QDELETED(src)) + return + show_tooltip(params) + +/atom/movable/screen/button_palette/MouseExited() + closeToolTip(usr) + return ..() + +/atom/movable/screen/button_palette/proc/show_tooltip(params) + openToolTip(usr, src, params, title = name, content = desc) + +GLOBAL_LIST_INIT(palette_added_matrix, list(0.4,0.5,0.2,0, 0,1.4,0,0, 0,0.4,0.6,0, 0,0,0,1, 0,0,0,0)) +GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,0, 0,0,0,1, 0,0,0,0)) + +/atom/movable/screen/button_palette/proc/play_item_added() + color_for_now(GLOB.palette_added_matrix) + +/atom/movable/screen/button_palette/proc/play_item_removed() + color_for_now(GLOB.palette_removed_matrix) + +/atom/movable/screen/button_palette/proc/color_for_now(list/color) + if(color_timer_id) + return + add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY) //We unfortunately cannot animate matrix colors. Curse you lummy it would be ~~non~~trivial to interpolate between the two valuessssssssss + color_timer_id = addtimer(CALLBACK(src, PROC_REF(remove_color), color), 2 SECONDS) + +/atom/movable/screen/button_palette/proc/remove_color(list/to_remove) + color_timer_id = null + remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, to_remove) + +/atom/movable/screen/button_palette/proc/can_use(mob/user) + if (isobserver(user)) + var/mob/dead/observer/O = user + return !O.observetarget + return TRUE + +/atom/movable/screen/button_palette/Click(location, control, params) + if(!can_use(usr)) + return + + var/list/modifiers = params2list(params) + + if(LAZYACCESS(modifiers, ALT_CLICK)) + for(var/datum/action/action as anything in usr.actions) // Reset action positions to default + for(var/datum/hud/hud as anything in action.viewers) + var/atom/movable/screen/movable/action_button/button = action.viewers[hud] + hud.position_action(button, SCRN_OBJ_DEFAULT) + to_chat(usr, "Action button positions have been reset.") + return TRUE + + set_expanded(!expanded) + +/atom/movable/screen/button_palette/proc/clicked_while_open(datum/source, atom/target, atom/location, control, params, mob/user) + if(istype(target, /atom/movable/screen/movable/action_button) || istype(target, /atom/movable/screen/palette_scroll) || target == src) // If you're clicking on an action button, or us, you can live + return + set_expanded(FALSE) + if(source) + UnregisterSignal(source, COMSIG_CLIENT_CLICK) + +/atom/movable/screen/button_palette/proc/set_expanded(new_expanded) + var/datum/action_group/our_group = our_hud.palette_actions + if(!length(our_group.actions)) //Looks dumb, trust me lad + new_expanded = FALSE + if(expanded == new_expanded) + return + + expanded = new_expanded + our_group.refresh_actions() + update_appearance() + + if(!usr.client) + return -#define AB_MAX_COLUMNS 10 + if(expanded) + RegisterSignal(usr.client, COMSIG_CLIENT_CLICK, PROC_REF(clicked_while_open)) + else + UnregisterSignal(usr.client, COMSIG_CLIENT_CLICK) -/datum/hud/proc/ButtonNumberToScreenCoords(number) // TODO : Make this zero-indexed for readabilty - var/row = round((number - 1)/AB_MAX_COLUMNS) - var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1 + closeToolTip(usr) //Our tooltips are now invalid, can't seem to update them in one frame, so here, just close them - var/coord_col = "+[col-1]" - var/coord_col_offset = 4 + 2 * col +/atom/movable/screen/palette_scroll + icon = 'icons/hud/screen_gen.dmi' + screen_loc = ui_palette_scroll + /// How should we move the palette's actions? + /// Positive scrolls down the list, negative scrolls back + var/scroll_direction = 0 + var/datum/hud/our_hud - var/coord_row = "[row ? -row : "+0"]" +/atom/movable/screen/palette_scroll/proc/can_use(mob/user) + if (isobserver(user)) + var/mob/dead/observer/O = user + return !O.observetarget + return TRUE - return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-6" +/atom/movable/screen/palette_scroll/proc/set_hud(datum/hud/our_hud) + src.our_hud = our_hud + refresh_owner() -/datum/hud/proc/SetButtonCoords(atom/movable/screen/button,number) - var/row = round((number-1)/AB_MAX_COLUMNS) - var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1 - var/x_offset = 32*(col-1) + 4 + 2*col - var/y_offset = -32*(row+1) + 26 +/atom/movable/screen/palette_scroll/proc/refresh_owner() + var/mob/viewer = our_hud.mymob + if(viewer.client) + viewer.client.screen |= src + var/list/settings = our_hud.get_action_buttons_icons() + icon = settings["bg_icon"] - var/matrix/M = matrix() - M.Translate(x_offset,y_offset) - button.transform = M +/atom/movable/screen/palette_scroll/Click(location, control, params) + if(!can_use(usr)) + return + our_hud.palette_actions.scroll(scroll_direction) + +/atom/movable/screen/palette_scroll/MouseEntered(location, control, params) + . = ..() + if(QDELETED(src)) + return + openToolTip(usr, src, params, title = name, content = desc) + +/atom/movable/screen/palette_scroll/MouseExited() + closeToolTip(usr) + return ..() + +/atom/movable/screen/palette_scroll/down + name = "Scroll Down" + desc = "Click on this to scroll the actions above down" + icon_state = "scroll_down" + scroll_direction = 1 + +/atom/movable/screen/palette_scroll/down/Destroy() + if(our_hud) + our_hud.mymob?.client?.screen -= src + our_hud.palette_down = null + our_hud = null + return ..() + +/atom/movable/screen/palette_scroll/up + name = "Scroll Up" + desc = "Click on this to scroll the actions above up" + icon_state = "scroll_up" + scroll_direction = -1 + +/atom/movable/screen/palette_scroll/up/Destroy() + if(our_hud) + our_hud.mymob?.client?.screen -= src + our_hud.palette_up = null + our_hud = null + return ..() + +/// Exists so you have a place to put your buttons when you move them around +/atom/movable/screen/action_landing + name = "Button Space" + desc = "Drag and drop a button into this spot
to add it to the group" + icon = 'icons/hud/screen_gen.dmi' + icon_state = "reserved" + // We want our whole 32x32 space to be clickable, so dropping's forgiving + mouse_opacity = MOUSE_OPACITY_OPAQUE + var/datum/action_group/owner + +/atom/movable/screen/action_landing/Destroy() + if(owner) + owner.landing = null + owner?.owner?.mymob?.client?.screen -= src + owner.refresh_actions() + owner = null + return ..() + +/atom/movable/screen/action_landing/proc/set_owner(datum/action_group/owner) + src.owner = owner + refresh_owner() + +/atom/movable/screen/action_landing/proc/refresh_owner() + var/datum/hud/our_hud = owner.owner + var/mob/viewer = our_hud.mymob + if(viewer.client) + viewer.client.screen |= src + + var/list/settings = our_hud.get_action_buttons_icons() + icon = settings["bg_icon"] -#undef AB_MAX_COLUMNS +/// Reacts to having a button dropped on it +/atom/movable/screen/action_landing/proc/hit_by(atom/movable/screen/movable/action_button/button) + var/datum/hud/our_hud = owner.owner + our_hud.position_action(button, owner.location) diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index 80a0c4896153b..09fd87aa87f7b 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -58,8 +58,13 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/list/atom/movable/plane_master_controller/plane_master_controllers = list() var/list/team_finder_arrows = list() - var/atom/movable/screen/movable/action_button/hide_toggle/hide_actions_toggle - var/action_buttons_hidden = FALSE + var/atom/movable/screen/button_palette/toggle_palette + var/atom/movable/screen/palette_scroll/down/palette_down + var/atom/movable/screen/palette_scroll/up/palette_up + + var/datum/action_group/palette/palette_actions + var/datum/action_group/listed/listed_actions + var/list/floating_actions var/atom/movable/screen/healths var/atom/movable/screen/stamina @@ -77,10 +82,12 @@ GLOBAL_LIST_INIT(available_ui_styles, list( // will fall back to the default if any of these are null ui_style = ui_style2icon(owner.client?.prefs?.read_player_preference(/datum/preference/choiced/ui_style)) - hide_actions_toggle = new - hide_actions_toggle.InitialiseIcon(src) - if(mymob.client) - hide_actions_toggle.locked = mymob.client.prefs.read_player_preference(/datum/preference/toggle/buttons_locked) + toggle_palette = new() + toggle_palette.set_hud(src) + palette_down = new() + palette_down.set_hud(src) + palette_up = new() + palette_up.set_hud(src) hand_slots = list() @@ -97,7 +104,12 @@ GLOBAL_LIST_INIT(available_ui_styles, list( if(mymob?.hud_used == src) mymob.hud_used = null - QDEL_NULL(hide_actions_toggle) + QDEL_NULL(toggle_palette) + QDEL_NULL(palette_down) + QDEL_NULL(palette_up) + QDEL_NULL(palette_actions) + QDEL_NULL(listed_actions) + QDEL_LIST(floating_actions) QDEL_NULL(module_store_icon) QDEL_LIST(static_inventory) QDEL_LIST(team_finder_arrows) @@ -135,10 +147,14 @@ GLOBAL_LIST_INIT(available_ui_styles, list( /mob/proc/create_mob_hud() if(!client || hud_used) return - hud_used = new hud_type(src) + set_hud_used(new hud_type(src)) update_sight() SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED) +/mob/proc/set_hud_used(datum/hud/new_hud) + hud_used = new_hud + new_hud.build_action_groups() + //Version denotes which style should be displayed. blank or 0 means "next version" /datum/hud/proc/show_hud(version = 0, mob/viewmob) if(!ismob(mymob)) @@ -146,7 +162,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/mob/screenmob = viewmob || mymob if(!screenmob.client) return FALSE - + // This code is the absolute fucking worst, I want it to go die in a fire + // Seriously, why if(screenmob.client.prefs?.character_preview_view) // Changing HUDs clears the screen, we need to reregister then. screenmob.client.prefs.character_preview_view.unregister_from_client(screenmob.client) @@ -173,8 +190,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( if(team_finder_arrows.len) screenmob.client.screen += team_finder_arrows - screenmob.client.screen += hide_actions_toggle - + screenmob.client.screen += toggle_palette if(action_intent && !custom_hud_locs) action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position @@ -271,7 +287,6 @@ GLOBAL_LIST_INIT(available_ui_styles, list( ui_style = new_ui_style build_hand_slots() - hide_actions_toggle.InitialiseIcon(src) //Triggered when F12 is pressed (Unless someone changed something in the DMF) /mob/verb/button_pressed_F12() @@ -319,3 +334,306 @@ GLOBAL_LIST_INIT(available_ui_styles, list( /datum/hud/proc/update_locked_slots() return + +/datum/hud/proc/position_action(atom/movable/screen/movable/action_button/button, position) + if(button.location != SCRN_OBJ_DEFAULT) + hide_action(button) + switch(position) + if(SCRN_OBJ_DEFAULT) // Reset to the default + button.dump_save() // Nuke any existing saves + position_action(button, button.linked_action.default_button_position) + return + if(SCRN_OBJ_IN_LIST) + listed_actions.insert_action(button) + if(SCRN_OBJ_IN_PALETTE) + palette_actions.insert_action(button) + else // If we don't have it as a define, this is a screen_loc, and we should be floating + floating_actions += button + button.screen_loc = position + position = SCRN_OBJ_FLOATING + + button.location = position + +/datum/hud/proc/position_action_relative(atom/movable/screen/movable/action_button/button, atom/movable/screen/movable/action_button/relative_to) + if(button.location != SCRN_OBJ_DEFAULT) + hide_action(button) + switch(relative_to.location) + if(SCRN_OBJ_IN_LIST) + listed_actions.insert_action(button, listed_actions.index_of(relative_to)) + if(SCRN_OBJ_IN_PALETTE) + palette_actions.insert_action(button, palette_actions.index_of(relative_to)) + if(SCRN_OBJ_FLOATING) // If we don't have it as a define, this is a screen_loc, and we should be floating + floating_actions += button + var/client/our_client = mymob.client + if(!our_client) + position_action(button, button.linked_action.default_button_position) + return + button.screen_loc = get_valid_screen_location(relative_to.screen_loc, world.icon_size, our_client.view_size.getView()) // Asks for a location adjacent to our button that won't overflow the map + + button.location = relative_to.location + +/// Removes the passed in action from its current position on the screen +/datum/hud/proc/hide_action(atom/movable/screen/movable/action_button/button) + switch(button.location) + if(SCRN_OBJ_DEFAULT) // Invalid + CRASH("We just tried to hide an action buttion that somehow has the default position as its location, you done fucked up") + if(SCRN_OBJ_FLOATING) + floating_actions -= button + if(SCRN_OBJ_IN_LIST) + listed_actions.remove_action(button) + if(SCRN_OBJ_IN_PALETTE) + palette_actions.remove_action(button) + button.screen_loc = null + +/// Generates visual landings for all groups that the button is not a memeber of +/datum/hud/proc/generate_landings(atom/movable/screen/movable/action_button/button) + listed_actions.generate_landing() + palette_actions.generate_landing() + +/// Clears all currently visible landings +/datum/hud/proc/hide_landings() + listed_actions.clear_landing() + palette_actions.clear_landing() + +// Updates any existing "owned" visuals, ensures they continue to be visible +/datum/hud/proc/update_our_owner() + toggle_palette.refresh_owner() + palette_down.refresh_owner() + palette_up.refresh_owner() + listed_actions.update_landing() + palette_actions.update_landing() + +/// Ensures all of our buttons are properly within the bounds of our client's view, moves them if they're not +/datum/hud/proc/view_audit_buttons() + var/our_view = mymob?.client?.view + if(!our_view) + return + listed_actions.check_against_view() + palette_actions.check_against_view() + for(var/atom/movable/screen/movable/action_button/floating_button as anything in floating_actions) + var/list/current_offsets = screen_loc_to_offset(floating_button.screen_loc) + // We set the view arg here, so the output will be properly hemm'd in by our new view + floating_button.screen_loc = offset_to_screen_loc(current_offsets[1], current_offsets[2], view = our_view) + +/// Generates and fills new action groups with our mob's current actions +/datum/hud/proc/build_action_groups() + listed_actions = new(src) + palette_actions = new(src) + floating_actions = list() + for(var/datum/action/action as anything in mymob.actions) + var/atom/movable/screen/movable/action_button/button = action.viewers[src] + if(!button) + action.show_to(mymob) + button = action.viewers[src] + position_action(button, button.location) + +/datum/action_group + /// The hud we're owned by + var/datum/hud/owner + /// The actions we're managing + var/list/atom/movable/screen/movable/action_button/actions + /// The initial vertical offset of our action buttons + var/north_offset = 0 + /// The pixel vertical offset of our action buttons + var/pixel_north_offset = 0 + /// Max amount of buttons we can have per row + /// Indexes at 1 + var/column_max = 0 + /// How far "ahead" of the first row we start. Lets us "scroll" our rows + /// Indexes at 1 + var/row_offset = 0 + /// How many rows of actions we can have at max before we just stop hiding + /// Indexes at 1 + var/max_rows = INFINITY + /// The screen location we go by + var/location + /// Our landing screen object + var/atom/movable/screen/action_landing/landing + +/datum/action_group/New(datum/hud/owner) + ..() + actions = list() + src.owner = owner + +/datum/action_group/Destroy() + owner = null + QDEL_NULL(landing) + QDEL_LIST(actions) + return ..() + +/datum/action_group/proc/insert_action(atom/movable/screen/action, index) + if(action in actions) + if(actions[index] == action) + return + actions -= action // Don't dupe, come on + if(!index) + index = length(actions) + 1 + index = min(length(actions) + 1, index) + actions.Insert(index, action) + refresh_actions() + +/datum/action_group/proc/remove_action(atom/movable/screen/action) + actions -= action + refresh_actions() + +/datum/action_group/proc/refresh_actions() + + // We don't use size() here because landings are not canon + var/total_rows = ROUND_UP(length(actions) / column_max) + total_rows -= max_rows // Lets get the amount of rows we're off from our max + row_offset = clamp(row_offset, 0, total_rows) // You're not allowed to offset so far that we have a row of blank space + + var/button_number = 0 + for(var/atom/movable/screen/button as anything in actions) + var/postion = ButtonNumberToScreenCoords(button_number ) + button.screen_loc = postion + button_number++ + + if(landing) + var/postion = ButtonNumberToScreenCoords(button_number, landing = TRUE) // Need a good way to count buttons off screen, but allow this to display in the right place if it's being placed with no concern for dropdown + landing.screen_loc = postion + button_number++ + +/// Accepts a number represeting our position in the group, indexes at 0 to make the math nicer +/datum/action_group/proc/ButtonNumberToScreenCoords(number, landing = FALSE) + var/row = round(number / column_max) + row -= row_offset // If you're less then 0, you don't get to render, this lets us "scroll" rows ya feel? + if(row < 0) + return null + + // Could use >= here, but I think it's worth noting that the two start at different places, since row is based on number here + if(row > max_rows - 1) + if(!landing) // If you're not a landing, go away please. thx + return null + // We always want to render landings, even if their action button can't be displayed. + // So we set a row equal to the max amount of rows + 1. Willing to overrun that max slightly to properly display the landing spot + row = max_rows // Remembering that max_rows indexes at 1, and row indexes at 0 + + // We're going to need to set our column to match the first item in the last row, so let's set number properly now + number = row * column_max + + var/visual_row = row + north_offset + var/coord_row = visual_row ? "-[visual_row]" : "+0" + + var/visual_column = number % column_max + var/coord_col = "+[visual_column]" + var/coord_col_offset = 4 + 2 * (visual_column + 1) + return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-[pixel_north_offset]" + +/datum/action_group/proc/check_against_view() + var/owner_view = owner?.mymob?.client?.view + if(!owner_view) + return + // Unlikey as it is, we may have been changed. Want to start from our target position and fail down + column_max = initial(column_max) + // Convert our viewer's view var into a workable offset + var/list/view_size = view_to_pixels(owner_view) + + // We're primarially concerned about width here, if someone makes us 1x2000 I wish them a swift and watery death + var/furthest_screen_loc = ButtonNumberToScreenCoords(column_max - 1) + var/list/offsets = screen_loc_to_offset(furthest_screen_loc, owner_view) + if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2]) // We're all good + return + + for(column_max in column_max - 1 to 1 step -1) // Yes I could do this by unwrapping ButtonNumberToScreenCoords, but I don't feel like it + var/tested_screen_loc = ButtonNumberToScreenCoords(column_max) + offsets = screen_loc_to_offset(tested_screen_loc, owner_view) + // We've found a valid max length, pack it in + if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2]) + break + // Use our newly resized column max + refresh_actions() + +/// Returns the amount of objects we're storing at the moment +/datum/action_group/proc/size() + var/amount = length(actions) + if(landing) + amount += 1 + return amount + +/datum/action_group/proc/index_of(atom/movable/screen/get_location) + return actions.Find(get_location) + +/// Generates a landing object that can be dropped on to join this group +/datum/action_group/proc/generate_landing() + if(landing) + return + landing = new() + landing.set_owner(src) + refresh_actions() + +/// Clears any landing objects we may currently have +/datum/action_group/proc/clear_landing() + QDEL_NULL(landing) + +/datum/action_group/proc/update_landing() + if(!landing) + return + landing.refresh_owner() + +/datum/action_group/proc/scroll(amount) + row_offset += amount + refresh_actions() + +/datum/action_group/palette + north_offset = 2 + column_max = 3 + max_rows = 3 + location = SCRN_OBJ_IN_PALETTE + +/datum/action_group/palette/insert_action(atom/movable/screen/action, index) + . = ..() + var/atom/movable/screen/button_palette/palette = owner.toggle_palette + palette.play_item_added() + +/datum/action_group/palette/remove_action(atom/movable/screen/action) + . = ..() + var/atom/movable/screen/button_palette/palette = owner.toggle_palette + palette.play_item_removed() + if(!length(actions)) + palette.set_expanded(FALSE) + +/datum/action_group/palette/refresh_actions() + var/atom/movable/screen/button_palette/palette = owner.toggle_palette + var/atom/movable/screen/palette_scroll/scroll_down = owner.palette_down + var/atom/movable/screen/palette_scroll/scroll_up = owner.palette_up + + var/actions_above = round((owner.listed_actions.size() - 1) / owner.listed_actions.column_max) + north_offset = initial(north_offset) + actions_above + + palette.screen_loc = ui_action_palette_offset(actions_above) + var/action_count = length(owner?.mymob?.actions) + var/our_row_count = round((length(actions) - 1) / column_max) + if(!action_count) + palette.screen_loc = null + + if(palette.expanded && action_count && our_row_count >= max_rows) + scroll_down.screen_loc = ui_palette_scroll_offset(actions_above) + scroll_up.screen_loc = ui_palette_scroll_offset(actions_above) + else + scroll_down.screen_loc = null + scroll_up.screen_loc = null + + return ..() + +/datum/action_group/palette/ButtonNumberToScreenCoords(number, landing) + var/atom/movable/screen/button_palette/palette = owner.toggle_palette + if(palette.expanded) + return ..() + + if(!landing) + return null + + // We only render the landing in this case, so we force it to be the second item displayed (Second rather then first since it looks nicer) + // Remember the number var indexes at 0 + return ..(1 + (row_offset * column_max), landing) + + +/datum/action_group/listed + pixel_north_offset = 6 + column_max = 10 + location = SCRN_OBJ_IN_LIST + +/datum/action_group/listed/refresh_actions() + . = ..() + owner.palette_actions.refresh_actions() // We effect them, so we gotta refresh em diff --git a/code/_onclick/hud/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm index d277c6f113170..9e36657d23796 100644 --- a/code/_onclick/hud/movable_screen_objects.dm +++ b/code/_onclick/hud/movable_screen_objects.dm @@ -9,6 +9,7 @@ //Not tied to the grid, places it's center where the cursor is /atom/movable/screen/movable + mouse_drag_pointer = 'icons/effects/mouse_pointers/screen_drag.dmi' var/snap2grid = FALSE var/moved = FALSE var/locked = FALSE @@ -21,53 +22,43 @@ /atom/movable/screen/movable/snap snap2grid = TRUE - /atom/movable/screen/movable/MouseDrop(over_object, src_location, over_location, src_control, over_control, params) - if(locked) //no! I am locked! begone! + var/position = mouse_params_to_position(params) + if(!position) return + screen_loc = position + +/// Takes mouse parmas as input, returns a string representing the appropriate mouse position +/atom/movable/screen/movable/proc/mouse_params_to_position(params) var/list/modifiers = params2list(params) //No screen-loc information? abort. if(!LAZYACCESS(modifiers, SCREEN_LOC)) return - - //Split screen-loc up into X+Pixel_X and Y+Pixel_Y - var/list/screen_loc_params = splittext(LAZYACCESS(modifiers, SCREEN_LOC), ",") - - //Split X+Pixel_X up into list(X, Pixel_X) - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - - //Split Y+Pixel_Y up into list(Y, Pixel_Y) - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - + var/client/our_client = usr.client + var/list/offset = screen_loc_to_offset(LAZYACCESS(modifiers, SCREEN_LOC)) if(snap2grid) //Discard Pixel Values - screen_loc = "[screen_loc_X[1]],[screen_loc_Y[1]]" - + offset[1] = FLOOR(offset[1], world.icon_size) // drops any pixel offset + offset[2] = FLOOR(offset[2], world.icon_size) // drops any pixel offset else //Normalise Pixel Values (So the object drops at the center of the mouse, not 16 pixels off) - var/pix_X = text2num(screen_loc_X[2]) + x_off - var/pix_Y = text2num(screen_loc_Y[2]) + y_off - screen_loc = "[screen_loc_X[1]]:[pix_X],[screen_loc_Y[1]]:[pix_Y]" - - moved = screen_loc + offset[1] += x_off + offset[2] += y_off + return offset_to_screen_loc(offset[1], offset[2], our_client?.view) //Debug procs /client/proc/test_movable_UI() set category = "Debug" set name = "Spawn Movable UI Object" - var/atom/movable/screen/movable/M = new() M.name = "Movable UI Object" M.icon_state = "block" M.maptext = MAPTEXT("Movable") M.maptext_width = 64 - - var/screen_l = capped_input(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Movable UI Object") + var/screen_l = input(usr,"Where on the screen? (Formatted as 'X,Y' e.g: '1,1' for bottom left)","Spawn Movable UI Object") as text|null if(!screen_l) return - M.screen_loc = screen_l - screen += M diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index aabadeb99dea9..f9eab4739af3a 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -369,6 +369,9 @@ GLOBAL_LIST_EMPTY(radial_menus) /// If provided, will display an info button that will put this text in your chat var/info + /// If provided, this will be the name the radial slice hud button. This has priority over everything else. + var/name + /datum/radial_menu_choice/Destroy(force, ...) . = ..() QDEL_NULL(image) diff --git a/code/controllers/subsystem/augury.dm b/code/controllers/subsystem/augury.dm index e51e511177208..8b8c5aa7065d0 100644 --- a/code/controllers/subsystem/augury.dm +++ b/code/controllers/subsystem/augury.dm @@ -33,12 +33,12 @@ SUBSYSTEM_DEF(augury) if(length(doombringers)) for(var/mob/dead/observer/O in GLOB.player_list) if(!(O in observers_given_action)) - var/datum/action/innate/augury/A = new + var/datum/action/augury/A = new A.Grant(O) observers_given_action += O else for(var/mob/dead/observer/O as() in observers_given_action) - for(var/datum/action/innate/augury/A in O.actions) + for(var/datum/action/augury/A in O.actions) qdel(A) O.actions -= A observers_given_action -= O @@ -50,31 +50,30 @@ SUBSYSTEM_DEF(augury) if(biggest_doom && (!W.orbiting || W.orbiting.parent != biggest_doom)) W.check_orbitable(biggest_doom) -/datum/action/innate/augury +/datum/action/augury name = "Auto Follow Debris" icon_icon = 'icons/obj/meteor.dmi' button_icon_state = "flaming" background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND + toggleable = TRUE -/datum/action/innate/augury/Destroy() +/datum/action/augury/Destroy() if(owner) SSaugury.watchers -= owner return ..() -/datum/action/innate/augury/Activate() +/datum/action/augury/on_activate(at) SSaugury.watchers += owner to_chat(owner, "You are now auto-following debris.") - active = TRUE - UpdateButtonIcon() + update_buttons() -/datum/action/innate/augury/Deactivate() +/datum/action/augury/on_deactivate(mob/user, atom/target) SSaugury.watchers -= owner to_chat(owner, "You are no longer auto-following debris.") - active = FALSE - UpdateButtonIcon() + update_buttons() -/datum/action/innate/augury/UpdateButtonIcon(status_only = FALSE, force) - ..() +/datum/action/augury/update_button(atom/movable/screen/movable/action_button/button, status_only = FALSE, force) + . = ..() if(active) button.icon_state = "template_active" else diff --git a/code/controllers/subsystem/explosion.dm b/code/controllers/subsystem/explosion.dm index 9cf586c7f3fe7..d2da392323efa 100644 --- a/code/controllers/subsystem/explosion.dm +++ b/code/controllers/subsystem/explosion.dm @@ -314,7 +314,7 @@ SUBSYSTEM_DEF(explosions) //flash mobs if(flash_range) for(var/mob/living/L in viewers(flash_range, epicenter)) - if(L.anti_magic_check(magic, holy)) + if(L.can_block_magic((magic ? MAGIC_RESISTANCE : 0) | (holy ? MAGIC_RESISTANCE_HOLY : 0), 0)) continue L.flash_act() @@ -363,7 +363,7 @@ SUBSYSTEM_DEF(explosions) //Ignore magic protected things. if(ismob(A)) var/mob/M = A - if(M.anti_magic_check(magic, holy, TRUE, TRUE)) + if(M.can_block_magic((magic ? MAGIC_RESISTANCE : 0) | (holy ? MAGIC_RESISTANCE_HOLY : 0))) continue if (length(A.contents) && !(A.flags_1 & PREVENT_CONTENTS_EXPLOSION_1)) //The atom/contents_explosion() proc returns null if the contents ex_acting has been handled by the atom, and TRUE if it hasn't. items += A.GetAllContents(ignore_flag_1 = PREVENT_CONTENTS_EXPLOSION_1) @@ -383,7 +383,7 @@ SUBSYSTEM_DEF(explosions) if(magic || holy) var/divine_protection = FALSE for(var/mob/living/L in T.contents) - if(L.anti_magic_check(magic, holy, TRUE)) + if(L.can_block_magic((magic ? MAGIC_RESISTANCE : 0) | (holy ? MAGIC_RESISTANCE_HOLY : 0))) divine_protection = TRUE break if(divine_protection) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index ff2d55e3fd4b1..fbe6b549cf17f 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -357,13 +357,13 @@ SUBSYSTEM_DEF(vote) name = "Vote!" button_icon_state = "vote" -/datum/action/vote/Trigger() +/datum/action/vote/on_activate() if(owner) owner.vote() remove_from_client() Remove(owner) -/datum/action/vote/IsAvailable() +/datum/action/vote/is_available() return 1 /datum/action/vote/proc/remove_from_client() diff --git a/code/datums/action.dm b/code/datums/action.dm deleted file mode 100644 index 589658362f8d5..0000000000000 --- a/code/datums/action.dm +++ /dev/null @@ -1,807 +0,0 @@ -/datum/action - var/name = "Generic Action" - var/desc = null - var/obj/target = null - var/check_flags = NONE - var/processing = FALSE - var/atom/movable/screen/movable/action_button/button = null - var/buttontooltipstyle = "" - var/transparent_when_unavailable = TRUE - - var/button_icon = 'icons/hud/actions/backgrounds.dmi' //This is the file for the BACKGROUND icon - var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND //And this is the state for the background icon - - var/icon_icon = 'icons/hud/actions/action_generic.dmi' //This is the file for the ACTION icon - var/button_icon_state = "default" //And this is the state for the action icon - var/mob/owner - - var/has_cooldown_timer = FALSE - -/datum/action/New(Target) - link_to(Target) - button = new - button.linked_action = src - button.name = name - button.actiontooltipstyle = buttontooltipstyle - if(desc) - button.desc = desc - -/datum/action/proc/link_to(Target) - target = Target - RegisterSignal(Target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(OnUpdatedIcon)) - -/datum/action/Destroy() - if(owner) - Remove(owner) - target = null - QDEL_NULL(button) - return ..() - -/datum/action/proc/Grant(mob/M) - if(M) - if(owner) - if(owner == M) - return - Remove(owner) - owner = M - RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(owner_deleted)) - - //button id generation - var/counter = 0 - var/bitfield = 0 - for(var/datum/action/A in M.actions) - if(A.name == name && A.button.id) - counter += 1 - bitfield |= A.button.id - bitfield = ~bitfield - var/bitflag = 1 - for(var/i in 1 to (counter + 1)) - if(bitfield & bitflag) - button.id = bitflag - break - bitflag *= 2 - - M.actions += src - if(M.client) - M.client.screen += button - button.locked = M.client.prefs.read_player_preference(/datum/preference/toggle/buttons_locked) || button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE //even if it's not defaultly locked we should remember we locked it before - button.moved = button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE - var/obj/effect/proc_holder/spell/spell_proc_holder = button.linked_action.target - if(istype(spell_proc_holder) && spell_proc_holder.text_overlay) - M.client.images += spell_proc_holder.text_overlay - M.update_action_buttons() - else - Remove(owner) - -/datum/action/proc/owner_deleted(datum/source) - SIGNAL_HANDLER - - Remove(owner) - -/datum/action/proc/Remove(mob/M) - if(M) - if(M.client) - M.client.screen -= button - M.actions -= src - M.update_action_buttons() - if(owner) - UnregisterSignal(owner, COMSIG_PARENT_QDELETING) - owner = null - button.moved = FALSE //so the button appears in its normal position when given to another owner. - button.locked = FALSE - button.id = null - -/datum/action/proc/Trigger() - if(!IsAvailable()) - return FALSE - if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER) - return FALSE - return TRUE - -/datum/action/proc/IsAvailable() - if(!owner) - return FALSE - if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED)) - return FALSE - if((check_flags & AB_CHECK_INCAPACITATED) && HAS_TRAIT(owner, TRAIT_INCAPACITATED)) - return FALSE - if((check_flags & AB_CHECK_LYING) && isliving(owner)) - var/mob/living/action_user = owner - if(action_user.body_position == LYING_DOWN) - return FALSE - if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS) - return FALSE - return TRUE - -/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE) - if(!button) - return FALSE - - if(!status_only) - button.name = name - button.desc = desc - if(owner?.hud_used && background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND) - var/list/settings = owner.hud_used.get_action_buttons_icons() - if(button.icon != settings["bg_icon"]) - button.icon = settings["bg_icon"] - if(button.icon_state != settings["bg_state"]) - button.icon_state = settings["bg_state"] - else - if(button.icon != button_icon) - button.icon = button_icon - if(button.icon_state != background_icon_state) - button.icon_state = background_icon_state - ApplyIcon(button, force) - if(!IsAvailable()) - button.color = has_cooldown_timer ? rgb(219, 219, 219, 255) : transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0) - else - button.color = rgb(255,255,255,255) - return TRUE - -/datum/action/proc/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force = FALSE) - if(icon_icon && button_icon_state || force) - current_button.cut_overlays() - current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state)) - current_button.button_icon_state = button_icon_state - -/datum/action/proc/OnUpdatedIcon() - SIGNAL_HANDLER - UpdateButtonIcon() - -//Presets for item actions -/datum/action/item_action - check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS - button_icon_state = null - // If you want to override the normal icon being the item - // then change this to an icon state - -/datum/action/item_action/New(Target) - ..() - var/obj/item/I = target - LAZYINITLIST(I.actions) - I.actions += src - -/datum/action/item_action/Destroy() - var/obj/item/I = target - I?.actions -= src - UNSETEMPTY(I.actions) - return ..() - -/datum/action/item_action/Trigger() - . = ..() - if(!..()) - return FALSE - if(target) - var/obj/item/I = target - I.ui_action_click(owner, src) - return TRUE - -/datum/action/item_action/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force) - if(button_icon && button_icon_state) - // If set, use the custom icon that we set instead - // of the item appearance - ..() - else if((target && current_button.appearance_cache != target.appearance) || force) //replace with /ref comparison if this is not valid. - var/obj/item/I = target - var/old_layer = I.layer - var/old_plane = I.plane - I.layer = FLOAT_LAYER //AAAH - I.plane = FLOAT_PLANE //^ what that guy said - current_button.cut_overlays() - current_button.add_overlay(I) - I.layer = old_layer - I.plane = old_plane - current_button.appearance_cache = I.appearance - -/datum/action/item_action/toggle_light - name = "Toggle Light" - -/datum/action/item_action/toggle_light/Trigger() - if(istype(target, /obj/item/modular_computer)) - var/obj/item/modular_computer/mc = target - mc.toggle_flashlight() - return - ..() - -/datum/action/item_action/toggle_hood - name = "Toggle Hood" - -/datum/action/item_action/toggle_firemode - name = "Toggle Firemode" - -/datum/action/item_action/rcl_col - name = "Change Cable Color" - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "rcl_rainbow" - -/datum/action/item_action/rcl_gui - name = "Toggle Fast Wiring Gui" - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "rcl_gui" - -/datum/action/item_action/startchainsaw - name = "Pull The Starting Cord" - -/datum/action/item_action/toggle_computer_light - name = "Toggle Flashlight" - -/datum/action/item_action/toggle_gunlight - name = "Toggle Gunlight" - -/datum/action/item_action/toggle_mode - name = "Toggle Mode" - -/datum/action/item_action/toggle_barrier_spread - name = "Toggle Barrier Spread" - -/datum/action/item_action/equip_unequip_TED_Gun - name = "Equip/Unequip TED Gun" - -/datum/action/item_action/toggle_paddles - name = "Toggle Paddles" - -/datum/action/item_action/set_internals - name = "Set Internals" - -/datum/action/item_action/set_internals/UpdateButtonIcon(status_only = FALSE, force) - if(..()) //button available - if(iscarbon(owner)) - var/mob/living/carbon/C = owner - if(target == C.internal) - button.icon_state = "template_active" - -/datum/action/item_action/pick_color - name = "Choose A Color" - -/datum/action/item_action/toggle_mister - name = "Toggle Mister" - -/datum/action/item_action/activate_injector - name = "Activate Injector" - -/datum/action/item_action/toggle_helmet_light - name = "Toggle Helmet Light" - -/datum/action/item_action/toggle_welding_screen - name = "Toggle Welding Screen" - -/datum/action/item_action/toggle_welding_screen/Trigger() - var/obj/item/clothing/head/utility/hardhat/welding/H = target - if(istype(H)) - H.toggle_welding_screen(owner) - -/datum/action/item_action/toggle_welding_screen/plasmaman - name = "Toggle Welding Screen" - -/datum/action/item_action/toggle_welding_screen/plasmaman/Trigger() - var/obj/item/clothing/head/helmet/space/plasmaman/H = target - if(istype(H)) - H.toggle_welding_screen(owner) - -/datum/action/item_action/toggle_headphones - name = "Open Music Menu" - desc = "UNTZ UNTZ UNTZ" - -/datum/action/item_action/toggle_headphones/Trigger() - var/obj/item/clothing/ears/headphones/H = target - if(istype(H)) - H.interact(owner) - -/datum/action/item_action/toggle_spacesuit - name = "Toggle Suit Thermal Regulator" - icon_icon = 'icons/hud/actions/actions_spacesuit.dmi' - button_icon_state = "thermal_off" - -/datum/action/item_action/toggle_spacesuit/New(Target) - . = ..() - RegisterSignal(target, COMSIG_SUIT_SPACE_TOGGLE, PROC_REF(toggle)) - -/datum/action/item_action/toggle_spacesuit/Destroy() - UnregisterSignal(target, COMSIG_SUIT_SPACE_TOGGLE) - return ..() - -/datum/action/item_action/toggle_spacesuit/Trigger() - var/obj/item/clothing/suit/space/suit = target - if(!istype(suit)) - return - suit.toggle_spacesuit() - -/// Toggle the action icon for the space suit thermal regulator -/datum/action/item_action/toggle_spacesuit/proc/toggle(obj/item/clothing/suit/space/suit) - button_icon_state = "thermal_[suit.thermal_on ? "on" : "off"]" - UpdateButtonIcon() - -/datum/action/item_action/toggle_unfriendly_fire - name = "Toggle Friendly Fire \[ON\]" - desc = "Toggles if the club's blasts cause friendly fire." - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "vortex_ff_on" - -/datum/action/item_action/toggle_unfriendly_fire/Trigger() - if(..()) - UpdateButtonIcon() - -/datum/action/item_action/toggle_unfriendly_fire/UpdateButtonIcon(status_only = FALSE, force) - if(istype(target, /obj/item/hierophant_club)) - var/obj/item/hierophant_club/H = target - if(H.friendly_fire_check) - button_icon_state = "vortex_ff_off" - name = "Toggle Friendly Fire \[OFF\]" - else - button_icon_state = "vortex_ff_on" - name = "Toggle Friendly Fire \[ON\]" - ..() - -/datum/action/item_action/vortex_recall - name = "Vortex Recall" - desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it." - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "vortex_recall" - -/datum/action/item_action/vortex_recall/IsAvailable() - if(istype(target, /obj/item/hierophant_club)) - var/obj/item/hierophant_club/H = target - if(H.teleporting) - return 0 - return ..() - -/datum/action/item_action/clock/hierophant - name = "Hierophant Network" - desc = "Lets you discreetly talk with all other servants. Nearby listeners can hear you whispering, so make sure to do this privately." - button_icon_state = "hierophant_slab" - -/datum/action/item_action/clock/quickbind - name = "Quickbind" - desc = "If you're seeing this, file a bug report." - var/scripture_index = 0 //the index of the scripture we're associated with - -/datum/action/item_action/toggle_helmet_flashlight - name = "Toggle Helmet Flashlight" - -/datum/action/item_action/toggle_helmet_mode - name = "Toggle Helmet Mode" - -/datum/action/item_action/toggle_beacon - name = "Toggle Hardsuit Locator Beacon" - icon_icon = 'icons/hud/actions/action_generic.dmi' - button_icon_state = "toggle-transmission" - -/datum/action/item_action/toggle_beacon_hud - name = "Toggle Hardsuit Locator HUD" - icon_icon = 'icons/hud/actions/action_generic.dmi' - button_icon_state = "toggle-hud" - -/datum/action/item_action/toggle_beacon_hud/explorer - button_icon_state = "toggle-hud-explo" - -/datum/action/item_action/toggle_beacon_frequency - name = "Toggle Hardsuit Locator Frequency" - icon_icon = 'icons/hud/actions/action_generic.dmi' - button_icon_state = "change-code" - -/datum/action/item_action/crew_monitor - name = "Interface With Crew Monitor" - -/datum/action/item_action/toggle - -/datum/action/item_action/toggle/New(Target) - ..() - name = "Toggle [target.name]" - button.name = name - -/datum/action/item_action/halt - name = "HALT!" - -/datum/action/item_action/toggle_voice_box - name = "Toggle Voice Box" - -/datum/action/item_action/change - name = "Change" - -/datum/action/item_action/nano_picket_sign - name = "Retext Nano Picket Sign" - -/datum/action/item_action/nano_picket_sign/Trigger() - if(!istype(target, /obj/item/picket_sign)) - return - var/obj/item/picket_sign/sign = target - sign.retext(owner) - -/datum/action/item_action/adjust - -/datum/action/item_action/adjust/New(Target) - ..() - name = "Adjust [target.name]" - button.name = name - -/datum/action/item_action/switch_hud - name = "Switch HUD" - -/datum/action/item_action/toggle_human_head - name = "Toggle Human Head" - -/datum/action/item_action/toggle_helmet - name = "Toggle Helmet" - -/datum/action/item_action/toggle_seclight - name = "Toggle Seclight" - -/datum/action/item_action/toggle_jetpack - name = "Toggle Jetpack" - -/datum/action/item_action/jetpack_stabilization - name = "Toggle Jetpack Stabilization" - -/datum/action/item_action/jetpack_stabilization/IsAvailable() - var/obj/item/tank/jetpack/J = target - if(!istype(J) || !J.on) - return 0 - return ..() - -/datum/action/item_action/hands_free - check_flags = AB_CHECK_CONSCIOUS - -/datum/action/item_action/hands_free/activate - name = "Activate" - -/datum/action/item_action/hands_free/shift_nerves - name = "Shift Nerves" - -/datum/action/item_action/explosive_implant - check_flags = NONE - name = "Activate Explosive Implant" - -/datum/action/item_action/toggle_research_scanner - name = "Toggle Research Scanner" - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "scan_mode" - var/active = FALSE - -/datum/action/item_action/toggle_research_scanner/Trigger() - if(IsAvailable()) - active = !active - if(active) - owner.research_scanner++ - else - owner.research_scanner-- - to_chat(owner, "[target] research scanner has been [active ? "activated" : "deactivated"].") - return 1 - -/datum/action/item_action/toggle_research_scanner/Remove(mob/M) - if(owner && active) - owner.research_scanner-- - active = FALSE - ..() - -/datum/action/item_action/instrument - name = "Use Instrument" - desc = "Use the instrument specified" - -/datum/action/item_action/instrument/Trigger() - if(istype(target, /obj/item/instrument)) - var/obj/item/instrument/I = target - I.interact(usr) - return - return ..() - -/datum/action/item_action/activate_remote_view - name = "Activate Remote View" - desc = "Activates the Remote View of your spy sunglasses." - -/datum/action/item_action/organ_action - check_flags = AB_CHECK_CONSCIOUS - -/datum/action/item_action/organ_action/IsAvailable() - var/obj/item/organ/I = target - if(!I.owner) - return 0 - return ..() - -/datum/action/item_action/organ_action/toggle/New(Target) - ..() - name = "Toggle [target.name]" - button.name = name - -/datum/action/item_action/organ_action/use/New(Target) - ..() - name = "Use [target.name]" - button.name = name - -/datum/action/item_action/cult_dagger - name = "Draw Blood Rune" - desc = "Use the ritual dagger to create a powerful blood rune" - icon_icon = 'icons/hud/actions/actions_cult.dmi' - button_icon_state = "draw" - buttontooltipstyle = "cult" - background_icon_state = "bg_demon" - -/datum/action/item_action/cult_dagger/Grant(mob/M) - if(!IS_CULTIST(M)) - Remove(owner) - return - . = ..() - button.screen_loc = "6:157,4:-2" - button.moved = "6:157,4:-2" - -/datum/action/item_action/cult_dagger/Trigger() - for(var/obj/item/melee/cultblade/dagger/held_item in owner.held_items) // In case we were already holding a dagger - held_item.attack_self(owner) - return - var/obj/item/target_item = target - if(owner.can_equip(target_item, ITEM_SLOT_HANDS)) - owner.temporarilyRemoveItemFromInventory(target_item) - owner.put_in_hands(target_item) - target_item.attack_self(owner) - return - if(!isliving(owner)) - to_chat(owner, "You lack the necessary living force for this action.") - return - var/mob/living/living_owner = owner - if (living_owner.usable_hands <= 0) - to_chat(living_owner, "You dont have any usable hands!") - else - to_chat(living_owner, "Your hands are full!") - - -///MGS BOX! -/datum/action/item_action/agent_box - name = "Deploy Box" - desc = "Find inner peace, here, in the box." - check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS - background_icon_state = "bg_agent" - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "deploy_box" - ///The type of closet this action spawns. - var/boxtype = /obj/structure/closet/cardboard/agent - COOLDOWN_DECLARE(box_cooldown) - -///Handles opening and closing the box. -/datum/action/item_action/agent_box/Trigger() - . = ..() - if(!.) - return FALSE - if(istype(owner.loc, /obj/structure/closet/cardboard/agent)) - var/obj/structure/closet/cardboard/agent/box = owner.loc - owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) - box.open() - return - //Box closing from here on out. - if(!isturf(owner.loc)) //Don't let the player use this to escape mechs/welded closets. - to_chat(owner, "You need more space to activate this implant.") - return - if(!COOLDOWN_FINISHED(src, box_cooldown)) - return - COOLDOWN_START(src, box_cooldown, 10 SECONDS) - var/box = new boxtype(owner.drop_location()) - owner.forceMove(box) - owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) - -/datum/action/item_action/portaseeder_dissolve - name = "Activate Seed Extractor" - -/datum/action/item_action/portaseeder_dissolve/Trigger() - var/obj/item/storage/bag/plants/portaseeder/H = target - H.dissolve_contents() - -//Preset for spells -/datum/action/spell_action - check_flags = NONE - background_icon_state = "bg_spell" - -/datum/action/spell_action/New(Target) - ..() - var/obj/effect/proc_holder/S = target - S.action = src - name = S.name - desc = S.desc - icon_icon = S.action_icon - button_icon_state = S.action_icon_state - background_icon_state = S.action_background_icon_state - button.name = name - -/datum/action/spell_action/Destroy() - var/obj/effect/proc_holder/S = target - S?.action = null - return ..() - -/datum/action/spell_action/Trigger() - if(!..()) - return FALSE - if(target) - var/obj/effect/proc_holder/S = target - S.Click() - return TRUE - -/datum/action/spell_action/IsAvailable() - if(!target) - return FALSE - return TRUE - -/datum/action/spell_action/spell - -/datum/action/spell_action/spell/IsAvailable() - if(!target) - return FALSE - var/obj/effect/proc_holder/spell/S = target - if(owner) - return S.can_cast(owner) - return FALSE - -/datum/action/spell_action/alien - -/datum/action/spell_action/alien/IsAvailable() - if(!target) - return FALSE - var/obj/effect/proc_holder/alien/ab = target - if(owner) - return ab.cost_check(ab.check_turf,owner,1) - return FALSE - - - -//Preset for general and toggled actions -/datum/action/innate - check_flags = NONE - var/active = 0 - -/datum/action/innate/Trigger() - if(!..()) - return 0 - if(!active) - Activate() - else - Deactivate() - return 1 - -/datum/action/innate/proc/Activate() - return - -/datum/action/innate/proc/Deactivate() - return - -//Preset for an action with a cooldown - -/datum/action/cooldown - check_flags = NONE - transparent_when_unavailable = FALSE - var/cooldown_time = 0 - var/next_use_time = 0 - -/datum/action/cooldown/New() - ..() - button.maptext = "" - button.maptext_x = 8 - button.maptext_y = 0 - button.maptext_width = 24 - button.maptext_height = 12 - -/datum/action/cooldown/IsAvailable() - return next_use_time <= world.time - -/datum/action/cooldown/proc/StartCooldown() - next_use_time = world.time + cooldown_time - button.maptext = MAPTEXT("[round(cooldown_time/10, 0.1)]") - UpdateButtonIcon() - START_PROCESSING(SSfastprocess, src) - -/datum/action/cooldown/process() - if(!owner) - button.maptext = "" - return PROCESS_KILL - var/timeleft = max(next_use_time - world.time, 0) - if(timeleft == 0) - button.maptext = "" - UpdateButtonIcon() - return PROCESS_KILL - else - button.maptext = MAPTEXT("[round(timeleft/10, 0.1)]") - -/datum/action/cooldown/Grant(mob/M) - ..() - if(owner) - UpdateButtonIcon() - if(next_use_time > world.time) - START_PROCESSING(SSfastprocess, src) - - -//Stickmemes -/datum/action/item_action/stickmen - name = "Summon Stick Minions" - desc = "Allows you to summon faithful stickmen allies to aide you in battle." - icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' - button_icon_state = "art_summon" - -//surf_ss13 -/datum/action/item_action/bhop - name = "Activate Jump Boots" - desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps." - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "jetboot" - -/datum/action/item_action/wheelys - name = "Toggle Wheely-Heel's Wheels" - desc = "Pops out or in your wheely-heel's wheels." - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "wheelys" - -/datum/action/item_action/kindleKicks - name = "Activate Kindle Kicks" - desc = "Kick you feet together, activating the lights in your Kindle Kicks." - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "kindleKicks" - -//Small sprites -/datum/action/small_sprite - name = "Toggle Giant Sprite" - desc = "Others will always see you as giant." - icon_icon = 'icons/hud/actions/actions_xeno.dmi' - button_icon_state = "smallqueen" - background_icon_state = "bg_alien" - var/small = FALSE - var/small_icon - var/small_icon_state - -/datum/action/small_sprite/queen - small_icon = 'icons/mob/alien.dmi' - small_icon_state = "alienq" - -/datum/action/small_sprite/megafauna - icon_icon = 'icons/hud/actions/actions_xeno.dmi' - small_icon = 'icons/mob/lavaland/lavaland_monsters.dmi' - -/datum/action/small_sprite/megafauna/drake - small_icon_state = "ash_whelp" - -/datum/action/small_sprite/megafauna/colossus - small_icon_state = "Basilisk" - -/datum/action/small_sprite/megafauna/bubblegum - small_icon_state = "goliath2" - -/datum/action/small_sprite/megafauna/legion - small_icon_state = "dwarf_legion" - -/datum/action/small_sprite/space_dragon - small_icon = 'icons/mob/carp.dmi' - small_icon_state = "carp" - icon_icon = 'icons/mob/carp.dmi' - button_icon_state = "carp" - -/datum/action/small_sprite/space_dragon/Trigger() - ..() - if(small) // parent call already reversed this. Effectively !small - owner.cut_overlays() // remove the overlays. can't be done with signals, unfortunately - else if(istype(owner, /mob/living/simple_animal/hostile/space_dragon)) - var/mob/living/simple_animal/hostile/space_dragon/D = owner - D.update_dragon_overlay() // restore overlays - -/datum/action/small_sprite/Trigger() - ..() - if(!small) - var/image/I = image(icon = small_icon, icon_state = small_icon_state, loc = owner) - I.override = TRUE - I.pixel_x -= owner.pixel_x - I.pixel_y -= owner.pixel_y - owner.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic, "smallsprite", I, AA_TARGET_SEE_APPEARANCE | AA_MATCH_TARGET_OVERLAYS) - small = TRUE - else - owner.remove_alt_appearance("smallsprite") - small = FALSE - -/datum/action/item_action/storage_gather_mode - name = "Switch gathering mode" - desc = "Switches the gathering mode of a storage object." - icon_icon = 'icons/hud/actions/actions_items.dmi' - button_icon_state = "storage_gather_switch" - -/datum/action/item_action/storage_gather_mode/ApplyIcon(atom/movable/screen/movable/action_button/current_button) - . = ..() - var/old_layer = target.layer - var/old_plane = target.plane - target.layer = FLOAT_LAYER //AAAH - target.plane = FLOAT_PLANE //^ what that guy said - current_button.cut_overlays() - current_button.add_overlay(target) - target.layer = old_layer - target.plane = old_plane - current_button.appearance_cache = target.appearance diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm new file mode 100644 index 0000000000000..2b29f6bd11812 --- /dev/null +++ b/code/datums/actions/action.dm @@ -0,0 +1,506 @@ +/** + * # Action system + * + * A simple base for an modular behavior attached to atom or datum. + */ +/datum/action + /// The name of the action + var/name = "Generic Action" + /// The description of what the action does + var/desc + /// The datum the action is attached to. If the master datum is deleted, the action is as well. + /// Set in New() via the proc link_to(). + /// DO NOT ASSIGN TO THIS VARIABLE, ASSIGN IT ON /NEW() + VAR_PRIVATE/datum/master + /// Where any buttons we create should be by default. Accepts screen_loc and location defines + var/default_button_position = SCRN_OBJ_IN_LIST + /// This is who currently owns the action, and most often, this is who is using the action if it is triggered + /// This can be the same as "target" but is not ALWAYS the same - this is set and unset with Grant() and Remove() + var/mob/owner + // ===================================== + // Action Behaviour + // ===================================== + /// Flags that will determine of the owner / user of the action can... use the action + var/check_flags = NONE + /// Setting for intercepting clicks before activating the ability + var/requires_target = FALSE + /// If TRUE, we will unset after using our click intercept. Requires requires_target + /// If false, then cooldown and action conclussion needs to be handled manually + var/unset_after_click = TRUE + /// The cooldown added onto the user's next click. Requires requires_target + var/click_cd_override = CLICK_CD_CLICK_ABILITY + /// If toggleable, deactivate will be called when the action button is pressed after + /// being activated. + var/toggleable = FALSE + // ===================================== + // Action Appearance + // ===================================== + /// The style the button's tooltips appear to be + var/buttontooltipstyle = "" + /// Whether the button becomes transparent when it can't be used or just reddened + var/transparent_when_unavailable = TRUE + /// This is the file for the BACKGROUND icon of the button + var/button_icon = 'icons/hud/actions/backgrounds.dmi' + /// This is the icon state state for the BACKGROUND icon of the button + var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND + /// This is the file for the icon that appears OVER the button background + var/icon_icon = 'icons/hud/actions.dmi' + /// This is the icon state for the icon that appears OVER the button background + var/button_icon_state = "default" + ///List of all mobs that are viewing our action button -> A unique movable for them to view. + var/list/viewers = list() + /// What icon to replace our mouse cursor with when active. Optional, Requires requires_target + var/ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' + /// Whether or not you want the cooldown for the ability to display in text form + var/show_cooldown = TRUE + // ===================================== + // Cooldown + // ===================================== + /// The default cooldown applied when start_cooldown() is called + /// Actions are responsible for setting their own cooldown. + var/cooldown_time = 0 + /// Shares cooldowns with other cooldown abilities of the same value, not active if null + /// This allows a single thing with cooldowns to have multiple actions which share the same cooldown + var/cooldown_group + // ===================================== + // Internal + // ===================================== + /// The actual next time this ability can be used + VAR_PRIVATE/next_use_time = 0 + /// If the ability is currently active or not + VAR_PRIVATE/active = FALSE + /// If we require a target and are a toggleable button, we track a reference to the + /// object that we are targetting. + VAR_PRIVATE/datum/selected_target = null + /// Overlay currently applied to this action + VAR_PRIVATE/mutable_appearance/timer_overlay + /// Timer icon file + VAR_PROTECTED/timer_icon = 'icons/effects/cooldown.dmi' + /// Icon state for the timer icon + VAR_PROTECTED/timer_icon_state_active = "second" + +/datum/action/New(master) + if (master) + link_to(master) + +/// Links the passed target to our action, registering any relevant signals +/datum/action/proc/link_to(master) + src.master = master + RegisterSignal(master, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE) + + if(isatom(master)) + RegisterSignal(master, COMSIG_ATOM_UPDATED_ICON, PROC_REF(update_icon_on_signal)) + + if(istype(master, /datum/mind)) + RegisterSignal(master, COMSIG_MIND_TRANSFERRED, PROC_REF(on_master_mind_swapped)) + +/datum/action/Destroy() + if(owner) + Remove(owner) + master = null + if (selected_target) + UnregisterSignal(selected_target, COMSIG_PARENT_QDELETING) + selected_target = null + QDEL_LIST_ASSOC_VAL(viewers) // Qdel the buttons in the viewers list **NOT THE HUDS** + return ..() + +/// Signal proc that clears any references based on the owner or target deleting +/// If the owner's deleted, we will simply remove from them, but if the target's deleted, we will self-delete +/datum/action/proc/clear_ref(datum/ref) + SIGNAL_HANDLER + if(ref == owner) + Remove(owner) + if(ref == master) + qdel(src) + if (ref == selected_target) + deactivate(owner) + +/// Grants the action to the passed mob, making it the owner +/datum/action/proc/Grant(mob/grant_to) + if(!grant_to) + Remove(owner) + return + if(owner) + if(owner == grant_to) + return + Remove(owner) + SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, grant_to) + owner = grant_to + RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE) + + // Register some signals based on our check_flags + // so that our button icon updates when relevant + if(check_flags & (AB_CHECK_CONSCIOUS | AB_CHECK_DEAD)) + RegisterSignal(owner, COMSIG_MOB_STATCHANGE, PROC_REF(update_icon_on_signal)) + if(check_flags & AB_CHECK_IMMOBILE) + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), PROC_REF(update_icon_on_signal)) + if(check_flags & AB_CHECK_HANDS_BLOCKED) + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), PROC_REF(update_icon_on_signal)) + if(check_flags & AB_CHECK_LYING) + RegisterSignal(owner, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(update_icon_on_signal)) + + give_action(grant_to) + // Start cooldown timer if we gained it mid-cooldown + if(!owner) + return + update_buttons() + if(next_use_time > world.time) + START_PROCESSING(SSfastprocess, src) + +/// Remove the passed mob from being owner of our action +/datum/action/proc/Remove(mob/remove_from) + SHOULD_CALL_PARENT(TRUE) + + for(var/datum/hud/hud in viewers) + if(!hud.mymob) + continue + hide_from(hud.mymob) + LAZYREMOVE(remove_from.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared + viewers = list() + + if(owner) + SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner) + UnregisterSignal(owner, COMSIG_PARENT_QDELETING) + + // Clean up our check_flag signals + UnregisterSignal(owner, list( + COMSIG_LIVING_SET_BODY_POSITION, + COMSIG_MOB_STATCHANGE, + SIGNAL_ADDTRAIT(TRAIT_HANDS_BLOCKED), + SIGNAL_ADDTRAIT(TRAIT_IMMOBILIZED), + )) + + if(master == owner) + RegisterSignal(master, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref)) + owner = null + + if (remove_from.click_intercept == src) + unset_click_ability(remove_from) + +/// Actually triggers the effects of the action. +/// Called when the on-screen button is clicked, for example. +/// If you want to implement an action, override: +/// - on_activate to do the effect +/// - is_available for things that need checks (only if you handle button icon updates, otherwise put the check in pre_activation) +/datum/action/proc/trigger() + SHOULD_NOT_OVERRIDE(TRUE) + if(!is_available()) + return FALSE + if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER) + return FALSE + if(!owner) + return FALSE + + var/mob/user = usr || owner + + // If we were active and we clicked again, disable the action + if (active && toggleable) + deactivate(user) + return TRUE + + // If our cooldown action is a requires_target action: + // The actual action is activated on whatever the user clicks on - + // the target is what the action is being used on + // In trigger, we handle setting the click intercept + if(requires_target) + var/datum/action/already_set = user.click_intercept + if(already_set == src) + // if we clicked ourself and we're already set, unset and return + return unset_click_ability(user, refund_cooldown = TRUE) + + else if(istype(already_set)) + // if we have an active set already, unset it before we set our's + already_set.unset_click_ability(user, refund_cooldown = TRUE) + + return set_click_ability(user) + + // If our cooldown action is not a requires_target action: + // We can just continue on and use the action + // the target is the user of the action (often, the owner) + return pre_activate(user, master) + +/// Adds the ability for signals to intercept the ability +/datum/action/proc/pre_activate(mob/user, atom/target) + if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) + return + // If we successfully activated and are a toggle action, become active + if (toggleable) + active = TRUE + if (target) + selected_target = target + RegisterSignal(selected_target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref)) + . = on_activate(user, target) + // There is a possibility our action (or owner) is qdeleted in on_activate(). + if(!QDELETED(src) && !QDELETED(owner)) + SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_FINISHED, src) + +/// Override to implement behaviour +/// If this action is not a targetted spell, target will be the master +/// If this action is a toggleable action, must return true to signify successful activation +/datum/action/proc/on_activate(mob/user, atom/target) + return + +/// Deactivates the action. Can be called internally if an action +/// does something to a target until deactivated. +/datum/action/proc/deactivate(mob/user) + SHOULD_NOT_OVERRIDE(TRUE) + if (!active) + return + active = FALSE + on_deactivate(user, selected_target) + if (selected_target) + UnregisterSignal(selected_target, COMSIG_PARENT_QDELETING) + selected_target = null + +/// Called when the action is deactivated. +/// Called under the following conditions: +/// - toggleable is set to true +/// - the action is currently active +/datum/action/proc/on_deactivate(mob/user, atom/target) + return + +/// Intercepts client owner clicks to activate the ability +/// This proc is called via reflection, do not change the name if you do +/// not know what that means. +/datum/action/proc/InterceptClickOn(mob/living/caller, params, atom/target) + if(!is_available()) + unset_click_ability(caller, refund_cooldown = FALSE) + return FALSE + if(!target) + return FALSE + // The actual action begins here + if(!pre_activate(caller, target)) + return FALSE + + // And if we reach here, the action was complete successfully + if(unset_after_click) + start_cooldown() + unset_click_ability(caller, refund_cooldown = FALSE) + caller.next_click = world.time + click_cd_override + + return TRUE + +/// Whether our action is currently available to use or not +/datum/action/proc/is_available() + if(!owner) + return FALSE + if (next_use_time && world.time < next_use_time) + return FALSE + if((check_flags & AB_CHECK_HANDS_BLOCKED) && HAS_TRAIT(owner, TRAIT_HANDS_BLOCKED)) + return FALSE + if((check_flags & AB_CHECK_IMMOBILE) && HAS_TRAIT(owner, TRAIT_IMMOBILIZED)) + return FALSE + if((check_flags & AB_CHECK_LYING) && isliving(owner)) + var/mob/living/action_user = owner + if(action_user.body_position == LYING_DOWN) + return FALSE + if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS) + return FALSE + if ((check_flags & AB_CHECK_DEAD) && owner.stat == DEAD) + return FALSE + return TRUE + +/datum/action/proc/update_buttons(status_only, force) + for(var/datum/hud/hud in viewers) + var/atom/movable/screen/movable/button = viewers[hud] + update_button(button, status_only, force) + +/datum/action/proc/update_button(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE) + if(!button) + return + if(!status_only) + button.name = name + button.desc = desc + if(owner?.hud_used && background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND) + var/list/settings = owner.hud_used.get_action_buttons_icons() + if(button.icon != settings["bg_icon"]) + button.icon = settings["bg_icon"] + if(button.icon_state != settings["bg_state"]) + button.icon_state = settings["bg_state"] + else + if(button.icon != button_icon) + button.icon = button_icon + if(button.icon_state != background_icon_state) + button.icon_state = background_icon_state + + apply_icon(button, force) + + if (next_use_time >= world.time) + update_cooldown_icon(button) + else if(timer_overlay) + button.cut_overlay(timer_overlay) + QDEL_NULL(timer_overlay) + + var/available = is_available() + if(available) + button.color = rgb(255,255,255,255) + else + button.color = next_use_time ? rgb(219, 219, 219, 128) : (transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)) + return available + +/// Applies our button icon over top the background icon of the action +/datum/action/proc/apply_icon(atom/movable/screen/movable/action_button/current_button, force = FALSE) + if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force)) + current_button.cut_overlays(TRUE) + current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state)) + current_button.button_icon_state = button_icon_state + +/datum/action/proc/update_cooldown_icon(atom/movable/screen/movable/action_button/button, force = FALSE) + if(!button) + return + if (!timer_overlay) + timer_overlay = mutable_appearance(timer_icon, timer_icon_state_active) + timer_overlay.alpha = 180 + timer_overlay.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA + timer_overlay.maptext_width = 64 + timer_overlay.maptext_height = 64 + timer_overlay.maptext_x = -8 + timer_overlay.maptext_y = -6 + var/new_maptext = "
[FLOOR((next_use_time - world.time)/10, 1)]
" + if (new_maptext != timer_overlay.maptext || force) + button.cut_overlay(timer_overlay) + timer_overlay.maptext = new_maptext + button.add_overlay(timer_overlay) + +/// Gives our action to the passed viewer. +/// Puts our action in their actions list and shows them the button. +/datum/action/proc/give_action(mob/viewer) + var/datum/hud/our_hud = viewer.hud_used + if(viewers[our_hud]) // Already have a copy of us? go away + return + + LAZYOR(viewer.actions, src) // Move this in + show_to(viewer) + +/// Adds our action button to the screen of the passed viewer. +/datum/action/proc/show_to(mob/viewer) + var/datum/hud/our_hud = viewer.hud_used + if(!our_hud || viewers[our_hud]) // There's no point in this if you have no hud in the first place + return + + var/atom/movable/screen/movable/action_button/button = create_button() + set_id(button, viewer) + + button.our_hud = our_hud + viewers[our_hud] = button + if(viewer.client) + viewer.client.screen += button + + button.load_position(viewer) + viewer.update_action_buttons() + +/// Removes our action from the passed viewer. +/datum/action/proc/hide_from(mob/viewer) + var/datum/hud/our_hud = viewer.hud_used + var/atom/movable/screen/movable/action_button/button = viewers[our_hud] + LAZYREMOVE(viewer.actions, src) + if(button) + qdel(button) + +/// Creates an action button movable for the passed mob, and returns it. +/datum/action/proc/create_button() + var/atom/movable/screen/movable/action_button/button = new() + button.linked_action = src + button.name = name + button.actiontooltipstyle = buttontooltipstyle + if(desc) + button.desc = desc + return button + +/datum/action/proc/set_id(atom/movable/screen/movable/action_button/our_button, mob/owner) + //button id generation + var/bitfield = 0 + for(var/datum/action/action in owner.actions) + if(action == src) // This could be us, which is dumb + continue + var/atom/movable/screen/movable/action_button/button = action.viewers[owner.hud_used] + if(action.name == name && button.id) + bitfield |= button.id + + bitfield = ~bitfield // Flip our possible ids, so we can check if we've found a unique one + for(var/i in 0 to 23) // We get 24 possible bitflags in dm + var/bitflag = 1 << i // Shift us over one + if(bitfield & bitflag) + our_button.id = bitflag + return + +/// A general use signal proc that reacts to an event and updates our button icon in accordance +/datum/action/proc/update_icon_on_signal(datum/source) + SIGNAL_HANDLER + + update_buttons() + +/// Signal proc for COMSIG_MIND_TRANSFERRED - for minds, transfers our action to our new mob on mind transfer +/datum/action/proc/on_master_mind_swapped(datum/mind/source, mob/old_current) + SIGNAL_HANDLER + + // Grant() calls Remove() from the existing owner so we're covered on that + Grant(source.current) + +/// Starts a cooldown time to be shared with similar abilities +/// Will use default cooldown time if an override is not specified +/datum/action/proc/start_cooldown(override_cooldown_time) + // "Shared cooldowns" covers actions which are not the same type, + // but have the same cooldown group and are on the same mob + if(cooldown_group) + for(var/datum/action/shared_ability in owner.actions - src) + if(cooldown_group != shared_ability.cooldown_group) + continue + shared_ability.start_cooldown_self(override_cooldown_time) + + start_cooldown_self(override_cooldown_time) + +/// Starts a cooldown time for this ability only +/// Will use default cooldown time if an override is not specified +/datum/action/proc/start_cooldown_self(override_cooldown_time) + if(isnum(override_cooldown_time)) + next_use_time = world.time + override_cooldown_time + else + next_use_time = world.time + cooldown_time + update_buttons() + // Needs to update once per second + START_PROCESSING(SSprocessing, src) + +/// Actions process to handle their cooldown timer +/datum/action/process() + if(!owner || (next_use_time - world.time) <= 0) + update_buttons() + STOP_PROCESSING(SSfastprocess, src) + return + + update_buttons() + +/** + * Set our action as the click override on the passed mob. + */ +/datum/action/proc/set_click_ability(mob/on_who) + SHOULD_CALL_PARENT(TRUE) + + on_who.click_intercept = src + if(ranged_mousepointer) + on_who.client?.mouse_override_icon = ranged_mousepointer + on_who.update_mouse_pointer() + update_buttons() + return TRUE + +/** + * Unset our action as the click override of the passed mob. + * + * if refund_cooldown is TRUE, we are being unset by the user clicking the action off + * if refund_cooldown is FALSE, we are being forcefully unset, likely by someone actually using the action + */ +/datum/action/proc/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + SHOULD_CALL_PARENT(TRUE) + + on_who.click_intercept = null + if(ranged_mousepointer) + on_who.client?.mouse_override_icon = initial(on_who.client?.mouse_override_icon) + on_who.update_mouse_pointer() + update_buttons() + return TRUE + +/datum/action/proc/reduce_cooldown(amount) + next_use_time -= amount + +/datum/action/proc/is_active() + return active diff --git a/code/datums/actions/innate_action.dm b/code/datums/actions/innate_action.dm new file mode 100644 index 0000000000000..5e7dae86de5ce --- /dev/null +++ b/code/datums/actions/innate_action.dm @@ -0,0 +1,25 @@ +/// For actions that are innate and not directly tied to a master datum +/datum/action/innate + /// If we're a click action, the text shown on enable + var/enable_text + /// If we're a click action, the text shown on disable + var/disable_text + +/datum/action/innate/set_click_ability(mob/on_who) + . = ..() + if (enable_text && requires_target) + to_chat(on_who, enable_text) + +/datum/action/innate/unset_click_ability(mob/on_who, refund_cooldown) + . = ..() + if (disable_text && requires_target) + to_chat(on_who, disable_text) + +/datum/action/innate/on_activate(mob/user, atom/target) + if (enable_text && !requires_target) + to_chat(user, enable_text) + +/datum/action/innate/on_deactivate(mob/user, atom/target) + if (disable_text && !requires_target) + to_chat(user, disable_text) + diff --git a/code/datums/actions/item_action.dm b/code/datums/actions/item_action.dm new file mode 100644 index 0000000000000..61c967e64d2b9 --- /dev/null +++ b/code/datums/actions/item_action.dm @@ -0,0 +1,30 @@ +//Presets for item actions +/datum/action/item_action + name = "Item Action" + check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_CONSCIOUS + button_icon_state = null + // If you want to override the normal icon being the item + // then change this to an icon state + +/datum/action/item_action/on_activate(mob/user, atom/target) + if(target) + var/obj/item/I = target + I.ui_action_click(owner, src) + return TRUE + +/datum/action/item_action/apply_icon(atom/movable/screen/movable/action_button/current_button, force) + var/obj/item/item_target = master + if(button_icon && button_icon_state) + // If set, use the custom icon that we set instead + // of the item appearence + ..() + else if((master && current_button.appearance_cache != item_target.appearance) || force) //replace with /ref comparison if this is not valid. + var/old_layer = item_target.layer + var/old_plane = item_target.plane + item_target.layer = FLOAT_LAYER //AAAH + item_target.plane = FLOAT_PLANE //^ what that guy said + current_button.cut_overlays() + current_button.add_overlay(item_target) + item_target.layer = old_layer + item_target.plane = old_plane + current_button.appearance_cache = item_target.appearance diff --git a/code/datums/actions/items/adjust.dm b/code/datums/actions/items/adjust.dm new file mode 100644 index 0000000000000..c4e55b3fc242c --- /dev/null +++ b/code/datums/actions/items/adjust.dm @@ -0,0 +1,7 @@ +/datum/action/item_action/adjust + name = "Adjust Item" + +/datum/action/item_action/adjust/New(master) + ..() + var/obj/item/item_target = master + name = "Adjust [item_target.name]" diff --git a/code/datums/actions/beam_rifle.dm b/code/datums/actions/items/beam_rifle.dm similarity index 100% rename from code/datums/actions/beam_rifle.dm rename to code/datums/actions/items/beam_rifle.dm diff --git a/code/datums/actions/items/boot_dash.dm b/code/datums/actions/items/boot_dash.dm new file mode 100644 index 0000000000000..b86f529986a72 --- /dev/null +++ b/code/datums/actions/items/boot_dash.dm @@ -0,0 +1,10 @@ +//surf_ss13 +/datum/action/item_action/bhop + name = "Activate Jump Boots" + desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "jetboot" + +/datum/action/item_action/bhop/brocket + name = "Activate Rocket Boots" + desc = "Activates the boot's rocket propulsion system, allowing the user to hurl themselves great distances." diff --git a/code/datums/actions/items/clothing.dm b/code/datums/actions/items/clothing.dm new file mode 100644 index 0000000000000..d1629e6968b33 --- /dev/null +++ b/code/datums/actions/items/clothing.dm @@ -0,0 +1,67 @@ +/datum/action/item_action/toggle_helmet_flashlight + name = "Toggle Helmet Flashlight" + +/datum/action/item_action/toggle_helmet_mode + name = "Toggle Helmet Mode" + +/datum/action/item_action/toggle_beacon + name = "Toggle Hardsuit Locator Beacon" + icon_icon = 'icons/hud/actions/action_generic.dmi' + button_icon_state = "toggle-transmission" + +/datum/action/item_action/toggle_beacon_hud + name = "Toggle Hardsuit Locator HUD" + icon_icon = 'icons/hud/actions/action_generic.dmi' + button_icon_state = "toggle-hud" + +/datum/action/item_action/toggle_beacon_hud/explorer + button_icon_state = "toggle-hud-explo" + +/datum/action/item_action/toggle_beacon_frequency + name = "Toggle Hardsuit Locator Frequency" + icon_icon = 'icons/hud/actions/action_generic.dmi' + button_icon_state = "change-code" + +/datum/action/item_action/toggle_research_scanner + name = "Toggle Research Scanner" + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "scan_mode" + toggleable = TRUE + +/datum/action/item_action/toggle_research_scanner/on_activate(mob/user, atom/target) + owner.research_scanner++ + to_chat(owner, "[target] research scanner has been activated.") + return TRUE + +/datum/action/item_action/toggle_research_scanner/on_deactivate(mob/user, atom/target) + owner.research_scanner-- + to_chat(owner, "[target] research scanner has been deactivated.") + return TRUE + +/datum/action/item_action/toggle_research_scanner/Remove(mob/M) + if(owner && active) + owner.research_scanner-- + active = FALSE + ..() + +//surf_ss13 +/datum/action/item_action/bhop + name = "Activate Jump Boots" + desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "jetboot" + +/datum/action/item_action/kindleKicks + name = "Activate Kindle Kicks" + desc = "Kick you feet together, activating the lights in your Kindle Kicks." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "kindleKicks" + +/datum/action/item_action/toggle_headphones + name = "Open Music Menu" + desc = "UNTZ UNTZ UNTZ" + +/datum/action/item_action/toggle_headphones/on_activate(mob/user, atom/target) + var/obj/item/clothing/ears/headphones/H = target + if(istype(H)) + H.interact(owner) diff --git a/code/datums/actions/items/cult_dagger.dm b/code/datums/actions/items/cult_dagger.dm new file mode 100644 index 0000000000000..f03b4fe5a4ff4 --- /dev/null +++ b/code/datums/actions/items/cult_dagger.dm @@ -0,0 +1,37 @@ +/datum/action/item_action/cult_dagger + name = "Draw Blood Rune" + desc = "Use the ritual dagger to create a powerful blood rune" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "draw" + buttontooltipstyle = "cult" + background_icon_state = "bg_demon" + default_button_position = "6:157,4:-2" + +/datum/action/item_action/cult_dagger/Grant(mob/grant_to) + if(!IS_CULTIST(grant_to)) + Remove(owner) + return + + return ..() + +/datum/action/item_action/cult_dagger/on_activate(mob/user, atom/target) + for(var/obj/item/held_item as anything in owner.held_items) // In case we were already holding a dagger + if(istype(held_item, /obj/item/melee/cultblade/dagger)) + held_item.attack_self(owner) + return + var/obj/item/target_item = target + if(owner.can_equip(target_item, ITEM_SLOT_HANDS)) + owner.temporarilyRemoveItemFromInventory(target_item) + owner.put_in_hands(target_item) + target_item.attack_self(owner) + return + + if(!isliving(owner)) + to_chat(owner, ("You lack the necessary living force for this action.")) + return + + var/mob/living/living_owner = owner + if (living_owner.usable_hands <= 0) + to_chat(living_owner, ("You don't have any usable hands!")) + else + to_chat(living_owner, ("Your hands are full!")) diff --git a/code/datums/actions/items/hands_free.dm b/code/datums/actions/items/hands_free.dm new file mode 100644 index 0000000000000..24fddb52942dc --- /dev/null +++ b/code/datums/actions/items/hands_free.dm @@ -0,0 +1,8 @@ +/datum/action/item_action/hands_free + check_flags = AB_CHECK_CONSCIOUS + +/datum/action/item_action/hands_free/activate + name = "Activate" + +/datum/action/item_action/hands_free/shift_nerves + name = "Shift Nerves" diff --git a/code/datums/actions/ninja.dm b/code/datums/actions/items/ninja.dm similarity index 100% rename from code/datums/actions/ninja.dm rename to code/datums/actions/items/ninja.dm diff --git a/code/datums/actions/items/organ_action.dm b/code/datums/actions/items/organ_action.dm new file mode 100644 index 0000000000000..d3fd504cf1d9d --- /dev/null +++ b/code/datums/actions/items/organ_action.dm @@ -0,0 +1,25 @@ +/datum/action/item_action/organ_action + name = "Organ Action" + check_flags = AB_CHECK_CONSCIOUS + +/datum/action/item_action/organ_action/is_available() + var/obj/item/organ/attached_organ = master + if(!attached_organ.owner) + return FALSE + return ..() + +/datum/action/item_action/organ_action/toggle + name = "Toggle Organ" + +/datum/action/item_action/organ_action/toggle/New(Target) + ..() + var/obj/item/organ/organ_target = master + name = "Toggle [organ_target.name]" + +/datum/action/item_action/organ_action/use + name = "Use Organ" + +/datum/action/item_action/organ_action/use/New(Target) + ..() + var/obj/item/organ/organ_target = master + name = "Use [organ_target.name]" diff --git a/code/datums/actions/items/portaseeder.dm b/code/datums/actions/items/portaseeder.dm new file mode 100644 index 0000000000000..ee8d61b032389 --- /dev/null +++ b/code/datums/actions/items/portaseeder.dm @@ -0,0 +1,6 @@ +/datum/action/item_action/portaseeder_dissolve + name = "Activate Seed Extractor" + +/datum/action/item_action/portaseeder_dissolve/on_activate(mob/user, atom/target) + var/obj/item/storage/bag/plants/portaseeder/H = target + H.dissolve_contents() diff --git a/code/datums/actions/items/set_internals.dm b/code/datums/actions/items/set_internals.dm new file mode 100644 index 0000000000000..f4610e43e8ca8 --- /dev/null +++ b/code/datums/actions/items/set_internals.dm @@ -0,0 +1,12 @@ +/datum/action/item_action/set_internals + name = "Set Internals" + +/datum/action/item_action/set_internals/update_button(atom/movable/screen/movable/action_button/button, status_only = FALSE, force) + . = ..() + if(!. || !button) // no button available + return + if(!iscarbon(owner)) + return + var/mob/living/carbon/carbon_owner = owner + if(master == carbon_owner.internal) + button.icon_state = "template_active" diff --git a/code/datums/actions/items/stealth_box.dm b/code/datums/actions/items/stealth_box.dm new file mode 100644 index 0000000000000..5abf4e8b79ed8 --- /dev/null +++ b/code/datums/actions/items/stealth_box.dm @@ -0,0 +1,52 @@ +///MGS BOX! +/datum/action/item_action/agent_box + name = "Deploy Box" + desc = "Find inner peace, here, in the box." + check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS + background_icon_state = "bg_agent" + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "deploy_box" + ///The type of closet this action spawns. + var/boxtype = /obj/structure/closet/cardboard/agent + COOLDOWN_DECLARE(box_cooldown) + +///Handles opening and closing the box. +/datum/action/item_action/agent_box/on_activate(mob/user, atom/target) + if(istype(owner.loc, /obj/structure/closet/cardboard/agent)) + var/obj/structure/closet/cardboard/agent/box = owner.loc + if(box.open()) + owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) + return + //Box closing from here on out. + if(!isturf(owner.loc)) //Don't let the player use this to escape mechs/welded closets. + to_chat(owner, ("You need more space to activate this implant!")) + return + if(!COOLDOWN_FINISHED(src, box_cooldown)) + return + COOLDOWN_START(src, box_cooldown, 10 SECONDS) + var/box = new boxtype(owner.drop_location()) + owner.forceMove(box) + owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) + +/datum/action/item_action/agent_box/Grant(mob/grant_to) + . = ..() + if(owner) + RegisterSignal(owner, COMSIG_HUMAN_SUICIDE_ACT, PROC_REF(suicide_act)) + +/datum/action/item_action/agent_box/Remove(mob/M) + if(owner) + UnregisterSignal(owner, COMSIG_HUMAN_SUICIDE_ACT) + return ..() + +/datum/action/item_action/agent_box/proc/suicide_act(datum/source) + SIGNAL_HANDLER + + if(!istype(owner.loc, /obj/structure/closet/cardboard/agent)) + return + + var/obj/structure/closet/cardboard/agent/box = owner.loc + owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) + box.open() + owner.visible_message("[owner] falls out of [box]! It looks like [owner.p_they()] committed suicide!") + owner.throw_at(get_turf(owner)) + return OXYLOSS diff --git a/code/datums/actions/items/summon_stickmen.dm b/code/datums/actions/items/summon_stickmen.dm new file mode 100644 index 0000000000000..15dfb2ba995f5 --- /dev/null +++ b/code/datums/actions/items/summon_stickmen.dm @@ -0,0 +1,6 @@ +//Stickmemes +/datum/action/item_action/stickmen + name = "Summon Stick Minions" + desc = "Allows you to summon faithful stickmen allies to aide you in battle." + icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' + button_icon_state = "art_summon" diff --git a/code/datums/actions/items/toggles.dm b/code/datums/actions/items/toggles.dm new file mode 100644 index 0000000000000..504cc6757aac5 --- /dev/null +++ b/code/datums/actions/items/toggles.dm @@ -0,0 +1,116 @@ +/datum/action/item_action/toggle + +/datum/action/item_action/toggle/New(master) + ..() + var/obj/item/item_target = master + name = "Toggle [item_target.name]" + +/datum/action/item_action/toggle_light + name = "Toggle Light" + +/datum/action/item_action/toggle_computer_light + name = "Toggle Flashlight" + +/datum/action/item_action/toggle_hood + name = "Toggle Hood" + +/datum/action/item_action/toggle_firemode + name = "Toggle Firemode" + +/datum/action/item_action/toggle_gunlight + name = "Toggle Gunlight" + +/datum/action/item_action/toggle_mode + name = "Toggle Mode" + +/datum/action/item_action/toggle_barrier_spread + name = "Toggle Barrier Spread" + +/datum/action/item_action/toggle_paddles + name = "Toggle Paddles" + +/datum/action/item_action/toggle_mister + name = "Toggle Mister" + +/datum/action/item_action/toggle_helmet_light + name = "Toggle Helmet Light" + +/datum/action/item_action/toggle_welding_screen + name = "Toggle Welding Screen" + +/datum/action/item_action/toggle_spacesuit + name = "Toggle Suit Thermal Regulator" + icon_icon = 'icons/hud/actions/actions_spacesuit.dmi' + button_icon_state = "thermal_off" + +/datum/action/item_action/toggle_spacesuit/update_button(atom/movable/screen/movable/action_button/button, status_only = FALSE, force) + var/obj/item/clothing/suit/space/suit = master + if(istype(suit)) + button_icon_state = "thermal_[suit.thermal_on ? "on" : "off"]" + + return ..() + +/datum/action/item_action/toggle_helmet_flashlight + name = "Toggle Helmet Flashlight" + +/datum/action/item_action/toggle_helmet_mode + name = "Toggle Helmet Mode" + +/datum/action/item_action/toggle_voice_box + name = "Toggle Voice Box" + +/datum/action/item_action/toggle_human_head + name = "Toggle Human Head" + +/datum/action/item_action/toggle_helmet + name = "Toggle Helmet" + +/datum/action/item_action/toggle_seclight + name = "Toggle Seclight" + +/datum/action/item_action/toggle_jetpack + name = "Toggle Jetpack" + +/datum/action/item_action/jetpack_stabilization + name = "Toggle Jetpack Stabilization" + +/datum/action/item_action/jetpack_stabilization/is_available() + var/obj/item/tank/jetpack/linked_jetpack = master + if(!istype(linked_jetpack) || !linked_jetpack.on) + return FALSE + return ..() + +/datum/action/item_action/wheelys + name = "Toggle Wheels" + desc = "Pops out or in your shoes' wheels." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "wheelys" + +/datum/action/item_action/kindle_kicks + name = "Activate Kindle Kicks" + desc = "Kick you feet together, activating the lights in your Kindle Kicks." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "kindleKicks" + +/datum/action/item_action/storage_gather_mode + name = "Switch gathering mode" + desc = "Switches the gathering mode of a storage object." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "storage_gather_switch" + +/datum/action/item_action/storage_gather_mode/apply_icon(atom/movable/screen/movable/action_button/current_button) + . = ..() + var/obj/item/item_target = master + var/old_layer = item_target.layer + var/old_plane = item_target.plane + item_target.layer = FLOAT_LAYER //AAAH + item_target.plane = FLOAT_PLANE //^ what that guy said + current_button.cut_overlays() + current_button.add_overlay(master) + item_target.layer = old_layer + item_target.plane = old_plane + current_button.appearance_cache = item_target.appearance + + +/datum/action/item_action/equip_unequip_TED_Gun + name = "Activate/Deactivate TED Gun" diff --git a/code/datums/actions/items/vortex_recall.dm b/code/datums/actions/items/vortex_recall.dm new file mode 100644 index 0000000000000..1722d8ce2cd9d --- /dev/null +++ b/code/datums/actions/items/vortex_recall.dm @@ -0,0 +1,32 @@ +/datum/action/item_action/vortex_recall + name = "Vortex Recall" + desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.
If the beacon is still attached, will detach it." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "vortex_recall" + +/datum/action/item_action/vortex_recall/is_available() + var/area/current_area = get_area(master) + if(!current_area || current_area.teleport_restriction == TELEPORT_ALLOW_NONE) + return FALSE + if(istype(master, /obj/item/hierophant_club)) + var/obj/item/hierophant_club/teleport_stick = master + if(teleport_stick.teleporting) + return FALSE + return ..() + +/datum/action/item_action/toggle_unfriendly_fire + name = "Toggle Friendly Fire \[ON\]" + desc = "Toggles if the club's blasts cause friendly fire." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "vortex_ff_on" + +/datum/action/item_action/toggle_unfriendly_fire/update_button(atom/movable/screen/movable/action_button/button, status_only, force) + var/obj/item/hierophant_club/teleport_stick = master + if(istype(master, /obj/item/hierophant_club)) + if(teleport_stick.friendly_fire_check == FALSE) + button_icon_state = "vortex_ff_off" + name = "Toggle Friendly Fire \[OFF\]" + else + button_icon_state = "vortex_ff_on" + name = "Toggle Friendly Fire \[ON\]" + return ..() diff --git a/code/datums/actions/mobs/language_menu.dm b/code/datums/actions/mobs/language_menu.dm new file mode 100644 index 0000000000000..bc16f36d9ef7e --- /dev/null +++ b/code/datums/actions/mobs/language_menu.dm @@ -0,0 +1,9 @@ +/datum/action/language_menu + name = "Language Menu" + desc = "Open the language menu to review your languages, their keys, and select your default language." + button_icon_state = "language_menu" + check_flags = NONE + +/datum/action/language_menu/on_activate(mob/user, atom/target) + var/datum/language_holder/owner_holder = owner.get_language_holder() + owner_holder.open_language_menu(usr) diff --git a/code/datums/actions/mobs/small_sprite.dm b/code/datums/actions/mobs/small_sprite.dm new file mode 100644 index 0000000000000..fc0ddaf897cb6 --- /dev/null +++ b/code/datums/actions/mobs/small_sprite.dm @@ -0,0 +1,53 @@ +//Small sprites +/datum/action/small_sprite + name = "Toggle Giant Sprite" + desc = "Others will always see you as giant." + icon_icon = 'icons/hud/actions/actions_xeno.dmi' + button_icon_state = "smallqueen" + background_icon_state = "bg_alien" + var/small = FALSE + var/small_icon + var/small_icon_state + +/datum/action/small_sprite/queen + small_icon = 'icons/mob/alien.dmi' + small_icon_state = "alienq" + +/datum/action/small_sprite/megafauna + icon_icon = 'icons/hud/actions/actions_xeno.dmi' + small_icon = 'icons/mob/lavaland/lavaland_monsters.dmi' + +/datum/action/small_sprite/megafauna/drake + small_icon_state = "ash_whelp" + +/datum/action/small_sprite/megafauna/colossus + small_icon_state = "Basilisk" + +/datum/action/small_sprite/megafauna/bubblegum + small_icon_state = "goliath2" + +/datum/action/small_sprite/megafauna/legion + small_icon_state = "mega_legion" + +/datum/action/small_sprite/mega_arachnid + small_icon = 'icons/mob/jungle/arachnid.dmi' + small_icon_state = "arachnid_mini" + background_icon_state = "bg_demon" + +/datum/action/small_sprite/space_dragon + small_icon = 'icons/mob/carp.dmi' + small_icon_state = "carp" + icon_icon = 'icons/mob/carp.dmi' + button_icon_state = "carp" + +/datum/action/small_sprite/on_activate(mob/user, atom/target) + if(!small) + var/image/I = image(icon = small_icon, icon_state = small_icon_state, loc = owner) + I.override = TRUE + I.pixel_x -= owner.pixel_x + I.pixel_y -= owner.pixel_y + owner.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic, "smallsprite", I, AA_TARGET_SEE_APPEARANCE) + small = TRUE + else + owner.remove_alt_appearance("smallsprite") + small = FALSE diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 94796659f8bc9..0137abbe52398 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -267,7 +267,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/imaginary_friend) background_icon_state = "bg_revenant" button_icon_state = "join" -/datum/action/innate/imaginary_join/Activate() +/datum/action/innate/imaginary_join/on_activate() var/mob/camera/imaginary_friend/I = owner I.recall() @@ -288,9 +288,9 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/imaginary_friend) name = "Hide" desc = "Hide yourself from your owner's sight." button_icon_state = "hide" - UpdateButtonIcon() + update_buttons() -/datum/action/innate/imaginary_hide/Activate() +/datum/action/innate/imaginary_hide/on_activate() var/mob/camera/imaginary_friend/I = owner I.hidden = !I.hidden I.Show() diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm index 005717eec8d8e..898ed9d1e242a 100644 --- a/code/datums/brain_damage/magic.dm +++ b/code/datums/brain_damage/magic.dm @@ -53,7 +53,10 @@ lose_text = "You realize that magic might be real." /datum/brain_trauma/magic/antimagic/on_gain() - owner.AddComponent(/datum/component/anti_magic, TRAUMA_TRAIT, _magic = TRUE, _holy = FALSE) + owner.AddComponent(/datum/component/anti_magic, \ + _source = TRAUMA_TRAIT, \ + antimagic_flags = (MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND), \ + ) ..() /datum/brain_trauma/magic/antimagic/on_lose() diff --git a/code/datums/brain_damage/mrat.dm b/code/datums/brain_damage/mrat.dm index 5fb29ddf11797..d83cb900ed7b0 100644 --- a/code/datums/brain_damage/mrat.dm +++ b/code/datums/brain_damage/mrat.dm @@ -148,7 +148,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/imaginary_friend/mrat) background_icon_state = "bg_revenant" button_icon_state = "ninja_phase" -/datum/action/innate/mrat_costume/Activate() +/datum/action/innate/mrat_costume/on_activate() var/mob/camera/imaginary_friend/mrat/I = owner if(!istype(I)) qdel(src) @@ -161,7 +161,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/imaginary_friend/mrat) background_icon_state = "bg_revenant" button_icon_state = "beam_up" -/datum/action/innate/mrat_leave/Activate() +/datum/action/innate/mrat_leave/on_activate() var/mob/camera/imaginary_friend/friend = owner if(!istype(friend)) qdel(src) @@ -183,7 +183,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/imaginary_friend/mrat) . = ..() friend = null -/datum/action/innate/mrat_kick/Activate() +/datum/action/innate/mrat_kick/on_activate() if(!istype(friend)) qdel(src) if(!istype(friend) || alert(friend, "Are you sure you want to remove your mentor?", "Remove:", "Yes", "No") != "Yes") diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index 79ec38905f1f0..f1accd77cc247 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -23,7 +23,10 @@ speak("neutral", prob(25)) /datum/brain_trauma/special/godwoken/on_gain() - owner.AddComponent(/datum/component/anti_magic, TRAUMA_TRAIT, _magic = FALSE, _holy = TRUE) + owner.AddComponent(/datum/component/anti_magic, \ + _source = TRAUMA_TRAIT, \ + antimagic_flags = (MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND), \ + ) ..() /datum/brain_trauma/special/godwoken/on_lose() diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm index c193fd55f9845..caa3a0fff9e98 100644 --- a/code/datums/brain_damage/split_personality.dm +++ b/code/datums/brain_damage/split_personality.dm @@ -24,12 +24,14 @@ /datum/brain_trauma/severe/split_personality/proc/make_backseats() stranger_backseat = new(owner, src) - var/obj/effect/proc_holder/spell/targeted/personality_commune/stranger_spell = new(src) - stranger_backseat.AddSpell(stranger_spell) + var/datum/action/spell/personality_commune/stranger_spell = new(src) + //stranger_backseat.AddSpell(stranger_spell) + stranger_spell.Grant(stranger_backseat) owner_backseat = new(owner, src) - var/obj/effect/proc_holder/spell/targeted/personality_commune/owner_spell = new(src) - owner_backseat.AddSpell(owner_spell) + var/datum/action/spell/personality_commune/owner_spell = new(src) + owner_spell.Grant(owner_backseat) + //owner_backseat.AddSpell(owner_spell) /datum/brain_trauma/severe/split_personality/proc/get_ghost() set waitfor = FALSE diff --git a/code/datums/components/anti_magic.dm b/code/datums/components/anti_magic.dm index c7179c39a79ba..a28880f7dc0a2 100644 --- a/code/datums/components/anti_magic.dm +++ b/code/datums/components/anti_magic.dm @@ -1,86 +1,173 @@ +/// This provides different types of magic resistance on an object /datum/component/anti_magic dupe_mode = COMPONENT_DUPE_ALLOWED - var/source - var/magic = FALSE - var/holy = FALSE - var/charges = INFINITY - var/blocks_self = TRUE - var/allowed_slots = ~ITEM_SLOT_BACKPACK - var/datum/callback/reaction - var/datum/callback/expire var/static/identifier_current = 0 var/identifier + var/source + + /// A bitflag with the types of magic resistance on the object + var/antimagic_flags + /// The amount of times the object can protect the user from magic + var/charges + /// The inventory slot the object must be located at in order to activate + var/inventory_flags + /// The proc that is triggered when an object has been drained a antimagic charge + var/datum/callback/drain_antimagic + /// The proc that is triggered when the object is depleted of charges + var/datum/callback/expiration + /// Whether we should, on equipping, alert the caster that this item can block any of their spells + /// This changes between true and false on equip and drop, don't set it outright to something + var/alert_caster_on_equip = TRUE + + +/** + * Adds magic resistances to an object + * + * Magic resistance will prevent magic from affecting the user if it has the correct resistance + * against the type of magic being used + * + * args: + * * _source + * * antimagic_flags (optional) A bitflag with the types of magic resistance on the object + * * charges (optional) The amount of times the object can protect the user from magic + * * inventory_flags (optional) The inventory slot the object must be located at in order to activate + * * drain_antimagic (optional) The proc that is triggered when an object has been drained a antimagic charge + * * expiration (optional) The proc that is triggered when the object is depleted of charges + * * + * antimagic bitflags: (see code/__DEFINES/magic.dm) + * * MAGIC_RESISTANCE - Default magic resistance that blocks normal magic (wizard, spells, staffs) + * * MAGIC_RESISTANCE_MIND - Tinfoil hat magic resistance that blocks mental magic (telepathy, abductors, jelly people) + * * MAGIC_RESISTANCE_HOLY - Holy magic resistance that blocks unholy magic (revenant, cult, vampire, voice of god) +**/ + +/datum/component/anti_magic/Initialize( + _source, + antimagic_flags = MAGIC_RESISTANCE, + charges = INFINITY, + inventory_flags = ~ITEM_SLOT_BACKPACK, // items in a backpack won't activate, anywhere else is fine + datum/callback/drain_antimagic, + datum/callback/expiration + ) -/datum/component/anti_magic/Initialize(_source, _magic = FALSE, _holy = FALSE, _charges, _blocks_self = TRUE, datum/callback/_reaction, datum/callback/_expire, _allowed_slots) // Random enough that it will never conflict, and avoids having a static variable identifier = identifier_current++ source = _source - magic = _magic - holy = _holy - if(!isnull(_charges)) - charges = _charges - blocks_self = _blocks_self - reaction = _reaction - expire = _expire - if(!isnull(_allowed_slots)) - allowed_slots = _allowed_slots + src.antimagic_flags = antimagic_flags + src.charges = charges + src.inventory_flags = inventory_flags + src.drain_antimagic = drain_antimagic + src.expiration = expiration if(isitem(parent)) RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) else if(ismob(parent)) - RegisterSignal(parent, COMSIG_MOB_RECEIVE_MAGIC, PROC_REF(protect)) + register_antimagic_signals(parent) var/mob/mob_parent = parent ADD_TRAIT(mob_parent, TRAIT_SEE_ANTIMAGIC, identifier) var/image/forbearance = image('icons/effects/genetics.dmi', mob_parent, "servitude", MOB_OVERLAY_LAYER_ABSOLUTE(mob_parent.layer, MUTATIONS_LAYER)) forbearance.plane = mob_parent.plane mob_parent.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessedAware, "magic_protection_[identifier]", forbearance) mob_parent.update_alt_appearances() + else return COMPONENT_INCOMPATIBLE -/datum/component/anti_magic/proc/on_equip(datum/source, mob/equipper, slot) +/datum/component/anti_magic/proc/register_antimagic_signals(datum/on_what) + RegisterSignal(on_what, COMSIG_MOB_RECEIVE_MAGIC, PROC_REF(block_receiving_magic), override = TRUE) + RegisterSignal(on_what, COMSIG_MOB_RESTRICT_MAGIC, PROC_REF(restrict_casting_magic), override = TRUE) + +/datum/component/anti_magic/proc/unregister_antimagic_signals(datum/on_what) + UnregisterSignal(on_what, list(COMSIG_MOB_RECEIVE_MAGIC, COMSIG_MOB_RESTRICT_MAGIC)) + +/datum/component/anti_magic/Destroy(force, silent) + drain_antimagic = 0 + expiration = 0 + if(ismob(parent)) //If the component is attached to an item, it should go through on_drop instead. + var/mob/user = parent + UnregisterSignal(user, COMSIG_MOB_RECEIVE_MAGIC) + REMOVE_TRAIT(user, TRAIT_SEE_ANTIMAGIC, identifier) + user.remove_alt_appearance("magic_protection_[identifier]") + user.update_alt_appearances() + return ..() + + +/datum/component/anti_magic/proc/on_equip(atom/movable/source, mob/equipper, slot) SIGNAL_HANDLER - if(!(allowed_slots & slot)) //Check that the slot is valid for antimagic - UnregisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC) + if(!(inventory_flags & slot)) //Check that the slot is valid for antimagic + unregister_antimagic_signals(equipper) + equipper.update_action_buttons() REMOVE_TRAIT(equipper, TRAIT_SEE_ANTIMAGIC, identifier) equipper.remove_alt_appearance("magic_protection_[identifier]") equipper.update_alt_appearances() return - RegisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC, PROC_REF(protect), TRUE) - // Gain a protection aura - ADD_TRAIT(equipper, TRAIT_SEE_ANTIMAGIC, identifier) - var/image/forbearance = image('icons/effects/genetics.dmi', equipper, "servitude", MOB_OVERLAY_LAYER_ABSOLUTE(equipper.layer, MUTATIONS_LAYER)) - forbearance.plane = equipper.plane - equipper.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessedAware, "magic_protection_[identifier]", forbearance) - equipper.update_alt_appearances() - -/datum/component/anti_magic/proc/on_drop(datum/source, mob/user) + + register_antimagic_signals(equipper) + equipper.update_action_buttons() + var/mob/mob_parent = equipper + if(!HAS_TRAIT(equipper, TRAIT_SEE_ANTIMAGIC)) + ADD_TRAIT(mob_parent, TRAIT_SEE_ANTIMAGIC, identifier) + var/image/forbearance = image('icons/effects/genetics.dmi', mob_parent, "servitude", MOB_OVERLAY_LAYER_ABSOLUTE(mob_parent.layer, MUTATIONS_LAYER)) + forbearance.plane = mob_parent.plane + mob_parent.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessedAware, "magic_protection_[identifier]", forbearance) + mob_parent.update_alt_appearances() + + if(!alert_caster_on_equip) + return +// Check to see if we have any spells that are blocked due to antimagic + for(var/datum/action/spell/magic_spell in equipper.actions) + if(!(magic_spell.spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC)) + continue + + if(!(antimagic_flags & magic_spell.antimagic_flags)) + continue + + to_chat(equipper, ("[parent] is interfering with your ability to cast magic!")) + alert_caster_on_equip = FALSE + break + +/datum/component/anti_magic/proc/on_drop(atom/movable/source, mob/user) SIGNAL_HANDLER - UnregisterSignal(user, COMSIG_MOB_RECEIVE_MAGIC) + // Reset alert + if(source.loc != user) + alert_caster_on_equip = TRUE + unregister_antimagic_signals(user) + user.update_action_buttons() REMOVE_TRAIT(user, TRAIT_SEE_ANTIMAGIC, identifier) user.remove_alt_appearance("magic_protection_[identifier]") user.update_alt_appearances() -/datum/component/anti_magic/Destroy(force, silent) - if(ismob(parent)) //If the component is attached to an item, it should go through on_drop instead. - var/mob/user = parent - UnregisterSignal(user, COMSIG_MOB_RECEIVE_MAGIC) - REMOVE_TRAIT(user, TRAIT_SEE_ANTIMAGIC, identifier) - user.remove_alt_appearance("magic_protection_[identifier]") - user.update_alt_appearances() - return ..() +/datum/component/anti_magic/proc/block_receiving_magic(mob/living/carbon/source, casted_magic_flags, charge_cost, list/protection_was_used, list/antimagic_sources) + SIGNAL_HANDLER + + // We do not block this type of magic, good day + if(!(casted_magic_flags & antimagic_flags)) + return NONE + + // We have already blocked this spell + if(parent in antimagic_sources) + return NONE + + // Block success! Add this parent to the list of antimagic sources + antimagic_sources += parent + + if((charges != INFINITY) && charge_cost > 0 && drain_antimagic) + drain_antimagic?.Invoke(source, parent) + charges -= charge_cost + if(charges <= 0) + expiration?.Invoke(source, parent) + qdel(src) // no more antimagic + + return COMPONENT_MAGIC_BLOCKED -/datum/component/anti_magic/proc/protect(datum/source, mob/user, _magic, _holy, major, self, list/protection_sources) +/// cannot cast magic with the same type of antimagic present +/datum/component/anti_magic/proc/restrict_casting_magic(mob/user, magic_flags) SIGNAL_HANDLER - if(((_magic && magic) || (_holy && holy)) && (!self || blocks_self)) - protection_sources += parent - reaction?.Invoke(user, major) - if(major) - charges-- - if(charges <= 0) - expire?.Invoke(user) - return COMPONENT_BLOCK_MAGIC + if(magic_flags & antimagic_flags) + if(HAS_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK)) // this trait bypasses magic casting restrictions + return NONE + return COMPONENT_MAGIC_BLOCKED + return NONE diff --git a/code/datums/components/cult_ritual_item.dm b/code/datums/components/cult_ritual_item.dm index 1b3c1651a011a..81eda48da2a8e 100644 --- a/code/datums/components/cult_ritual_item.dm +++ b/code/datums/components/cult_ritual_item.dm @@ -15,8 +15,8 @@ var/list/turfs_that_boost_us /// A list of all shields surrounding us while drawing certain runes (Nar'sie). var/list/obj/structure/emergency_shield/sanguine/shields - /// An item action associated with our parent, to quick-draw runes. - var/datum/action/item_action/linked_action + /// Weakref to an action added to our parent item that allows for quick drawing runes + var/datum/weakref/linked_action_ref /datum/component/cult_ritual_item/Initialize( examine_message, @@ -35,12 +35,13 @@ src.turfs_that_boost_us = list(turfs_that_boost_us) if(ispath(action)) - linked_action = new action(parent) + var/obj/item/item_parent = parent + var/datum/action/added_action = item_parent.add_item_action(action) + linked_action_ref = WEAKREF(added_action) /datum/component/cult_ritual_item/Destroy(force, silent) cleanup_shields() - if(linked_action) - QDEL_NULL(linked_action) + QDEL_NULL(linked_action_ref) return ..() /datum/component/cult_ritual_item/RegisterWithParent() diff --git a/code/datums/components/manual_blinking.dm b/code/datums/components/manual_blinking.dm index 52f9369f5b77a..23a8570c3b7e7 100644 --- a/code/datums/components/manual_blinking.dm +++ b/code/datums/components/manual_blinking.dm @@ -15,10 +15,9 @@ name = "Blink" icon_icon = 'icons/hud/actions/actions_hive.dmi' button_icon_state = "see" //Feel free to replace + check_flags = AB_CHECK_CONSCIOUS -/datum/action/blink/Trigger() - if(owner.stat != CONSCIOUS) - return FALSE +/datum/action/blink/on_activate(mob/user, atom/target) owner.emote("blink") /datum/component/manual_blinking/Initialize() diff --git a/code/datums/components/manual_breathing.dm b/code/datums/components/manual_breathing.dm index 46b9d51053573..8798194bef7ba 100644 --- a/code/datums/components/manual_breathing.dm +++ b/code/datums/components/manual_breathing.dm @@ -15,11 +15,10 @@ name = "Inhale" icon_icon = 'icons/hud/actions/actions_hive.dmi' button_icon_state = "add" //Feel free to replace + check_flags = AB_CHECK_CONSCIOUS var/datum/emote/next_emote = "inhale" -/datum/action/breathe/Trigger() - if(owner.stat != CONSCIOUS) - return FALSE +/datum/action/breathe/on_activate(mob/user, atom/target) owner.emote(next_emote) /datum/action/breathe/proc/update_status(emote) @@ -30,7 +29,7 @@ else name = "Exhale" button_icon_state = "remove" - UpdateButtonIcon() + update_buttons() /datum/component/manual_breathing/Initialize() if(!iscarbon(parent)) diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm new file mode 100644 index 0000000000000..dbfb6bb2b95f4 --- /dev/null +++ b/code/datums/components/mind_linker.dm @@ -0,0 +1,264 @@ +/** + * # Mind Linker + * + * A component that handles linking multiple player's minds + * into one network which allows them to talk directly to one another. + * Like telepathy but for multiple people at once! + */ +/datum/component/mind_linker + /// The name of our network, displayed to all users. + var/network_name = "Mind Link" + /// The color of the network when talking in chat + var/chat_color + /// A list of all signals that will call qdel() on our component if triggered. Optional. + var/list/signals_which_destroy_us + /// A callback invoked after an unlink is done. Optional. + var/datum/callback/post_unlink_callback + /// The icon file given to the speech action handed out. + var/speech_action_icon = 'icons/hud/actions/actions_slime.dmi' + /// The icon state applied to the speech action handed out. + var/speech_action_icon_state = "link_speech" + /// The icon background for the speech action handed out. + var/speech_action_background_icon_state = "bg_alien" + /// The border icon state for the speech action handed out. + var/speech_action_overlay_state = "bg_alien_border" + /// The master's speech action. The owner of the link shouldn't lose this as long as the link remains. + VAR_FINAL/datum/action/innate/linked_speech/master_speech + /// An assoc list of [mob/living]s to [datum/action/innate/linked_speech]s. All the mobs that are linked to our network. + VAR_FINAL/list/mob/living/linked_mobs = list() + +/datum/component/mind_linker/Initialize( + // Customization related settings + network_name = "Mind Link", + chat_color = "#008CA2", + speech_action_icon = 'icons/hud/actions/actions_slime.dmi', + speech_action_icon_state = "link_speech", + speech_action_background_icon_state = "bg_alien", + speech_action_overlay_state = "bg_alien_border", + // Optional + signals_which_destroy_us, + datum/callback/post_unlink_callback, +) + + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + var/mob/living/owner = parent + + src.network_name = network_name + src.chat_color = chat_color + src.speech_action_icon = speech_action_icon + src.speech_action_icon_state = speech_action_icon_state + src.speech_action_background_icon_state = speech_action_background_icon_state + + if(islist(signals_which_destroy_us)) + src.signals_which_destroy_us = signals_which_destroy_us + if(post_unlink_callback) + src.post_unlink_callback = post_unlink_callback + + master_speech = new(src) + master_speech.Grant(owner) + +/datum/component/mind_linker/Destroy(force) + for(var/mob/living/remaining_mob as anything in linked_mobs) + unlink_mob(remaining_mob) + linked_mobs.Cut() + QDEL_NULL(master_speech) + post_unlink_callback = null + return ..() + +/datum/component/mind_linker/RegisterWithParent() + if(signals_which_destroy_us) + RegisterSignals(parent, signals_which_destroy_us, PROC_REF(destroy_link)) + +/datum/component/mind_linker/UnregisterFromParent() + if(signals_which_destroy_us) + UnregisterSignal(parent, signals_which_destroy_us) + +/** + * Attempts to link [to_link] to our network, giving them a speech action. + * + * Returns TRUE if successful, FALSE otherwise + */ +/datum/component/mind_linker/proc/link_mob(mob/living/to_link) + if(QDELETED(to_link) || to_link.stat == DEAD) + return FALSE + if(linked_mobs[to_link]) + return FALSE + + var/mob/living/owner = parent + if(to_link == owner) + return FALSE + + var/datum/action/innate/linked_speech/new_link = new(src) + new_link.Grant(to_link) + + linked_mobs[to_link] = new_link + RegisterSignals(to_link, list(COMSIG_LIVING_DEATH), PROC_REF(sig_unlink_mob)) + + return TRUE + +/datum/component/mind_linker/proc/sig_unlink_mob(mob/living/to_unlink) + SIGNAL_HANDLER + + unlink_mob(to_unlink) + +/** + * Unlinks [to_unlink] from our network, deleting their speech action + * and cleaning up anything involved. + * + * Also invokes post_unlink_callback, if supplied. + */ +/datum/component/mind_linker/proc/unlink_mob(mob/living/to_unlink) + if(!linked_mobs[to_unlink]) + return FALSE + + post_unlink_callback?.Invoke(to_unlink) + + UnregisterSignal(to_unlink, list(COMSIG_LIVING_DEATH)) + + var/datum/action/innate/linked_speech/old_link = linked_mobs[to_unlink] + linked_mobs -= to_unlink + qdel(old_link) + return TRUE + +/** + * Signal proc sent from any signals given to us initialize. + * Destroys our component and unlinks everyone. + */ +/datum/component/mind_linker/proc/destroy_link(datum/source) + SIGNAL_HANDLER + + if(isliving(source)) + var/mob/living/owner = source + to_chat(owner, ("Your [network_name] breaks!")) + + qdel(src) + +/// Subtype of mind linker (I know) which is more active rather than passive, +/// which involves the master linking people manually rather than people being added automatically. +/datum/component/mind_linker/active_linking + /// The message sent to someone when linked up. + var/link_message + /// The message sent to someone when unlinked. + var/unlink_message + /// The master's linking action, which allows them to link people to the network. + VAR_FINAL/datum/action/linker_action + +/datum/component/mind_linker/active_linking/Initialize( + // Customization related settings + network_name = "Mind Link", + chat_color = "#008CA2", + speech_action_icon = 'icons/hud/actions/actions_slime.dmi', + speech_action_icon_state = "link_speech", + speech_action_background_icon_state = "bg_alien", + speech_action_overlay_state = "bg_alien_border", + // Optional + signals_which_destroy_us, + datum/callback/post_unlink_callback, + // Optional for this subtype + link_message, + unlink_message, + // Required for this subtype + linker_action_path, +) + + . = ..() + if(. == COMPONENT_INCOMPATIBLE) + return + + var/mob/living/owner = parent + src.link_message = link_message || "You are now connected to [owner.real_name]'s [network_name]." + src.unlink_message = unlink_message || "You are no longer connected to [owner.real_name]'s [network_name]." + + if(ispath(linker_action_path)) + linker_action = new linker_action_path(src) + linker_action.Grant(owner) + else + stack_trace("[type] was created without a valid linker_action_path. No one will be able to link to it.") + + to_chat(owner, ("You establish a [network_name], allowing you to link minds to communicate telepathically.")) + +/datum/component/mind_linker/active_linking/Destroy() + QDEL_NULL(linker_action) + return ..() + +/datum/component/mind_linker/active_linking/link_mob(mob/living/to_link) + if(HAS_TRAIT(to_link, TRAIT_MINDSHIELD)) + return FALSE + if(to_link.can_block_magic()) + return FALSE + + . = ..() + if(!.) + return + + RegisterSignal(to_link, COMSIG_MINDSHIELD_IMPLANTED, PROC_REF(sig_unlink_mob)) + var/mob/living/owner = parent + to_chat(to_link, ("link_message)")) + to_chat(owner, ("You connect [to_link]'s mind to your [network_name].")) + for(var/mob/living/other_link as anything in linked_mobs) + to_chat(other_link, ("You feel a new presence within [owner.real_name]'s [network_name].")) + +/datum/component/mind_linker/active_linking/unlink_mob(mob/living/to_unlink) + . = ..() + if(!.) + return + + UnregisterSignal(to_unlink, COMSIG_MINDSHIELD_IMPLANTED) + var/mob/living/owner = parent + to_chat(to_unlink, "[unlink_message]") + to_chat(owner, ("You feel someone disconnect from your [network_name].")) + for(var/mob/living/other_link as anything in linked_mobs) + to_chat(other_link, ("You feel a pressence disappear from [owner.real_name]'s [network_name].")) + +// Used in mind linker to talk to everyone in the network. +/datum/action/innate/linked_speech + name = "Mind Link Speech" + desc = "Send a psychic message to everyone connected to your Link." + button_icon_state = "link_speech" + button_icon = 'icons/hud/actions/actions_slime.dmi' + background_icon_state = "bg_alien" + //overlay_icon_state = "bg_alien_border" + +/datum/action/innate/linked_speech/New(Target) + . = ..() + if(!istype(Target, /datum/component/mind_linker)) + stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.") + qdel(src) + return + + var/datum/component/mind_linker/linker = Target + name = "[linker.network_name] Speech" + desc = "Send a psychic message to everyone connected to your [linker.network_name]." + button_icon = linker.speech_action_icon + button_icon_state = linker.speech_action_icon_state + background_icon_state = linker.speech_action_background_icon_state + +/datum/action/innate/linked_speech/is_available(feedback = FALSE) + return ..() && (owner.stat != DEAD) + +/datum/action/innate/linked_speech/on_activate(mob/user, atom/target) + . = ..() + var/datum/component/mind_linker/linker = target + var/mob/living/linker_parent = linker.parent + + var/message = tgui_input_text(owner, "Enter a message to transmit.", "[linker.network_name] Telepathy") + if(!message || QDELETED(src) || QDELETED(owner) || owner.stat == DEAD) + return + + if(QDELETED(linker)) + to_chat(owner, ("The link seems to have been severed.")) + return + + var/formatted_message = "\[[linker_parent.real_name]'s [linker.network_name]\] [owner]: [message]" + log_directed_talk(owner, linker_parent, message, LOG_SAY, "mind link ([linker.network_name])") + + var/list/all_who_can_hear = assoc_to_keys(linker.linked_mobs) + linker_parent + + for(var/mob/living/recipient as anything in all_who_can_hear) + var/avoid_highlighting = (recipient == owner) || (recipient == linker_parent) + to_chat(recipient, formatted_message, type = MESSAGE_TYPE_RADIO, avoid_highlighting = avoid_highlighting) + + for(var/mob/recipient as anything in GLOB.dead_mob_list) + to_chat(recipient, "[FOLLOW_LINK(recipient, owner)] [formatted_message]", type = MESSAGE_TYPE_RADIO) diff --git a/code/datums/components/phylactery.dm b/code/datums/components/phylactery.dm new file mode 100644 index 0000000000000..73fa3b4f5cd3b --- /dev/null +++ b/code/datums/components/phylactery.dm @@ -0,0 +1,219 @@ +/** + * ## Phylactery component + * + * Used for lichtom to turn (almost) any object into a phylactery + * A mob linked to a phylactery will repeatedly revive on death. + */ +/datum/component/phylactery + // Set in initialize. + /// The mind of the lich who is linked to this phylactery. + var/datum/mind/lich_mind + /// The respawn timer of the phylactery. + var/base_respawn_time = 3 MINUTES + /// How much time is added on to the respawn time per revival. + var/time_per_resurrection = 0 + /// How much stun (paralyze) is caused on respawn per revival. + var/stun_per_resurrection = 20 SECONDS + /// The color of the phylactery itself. Applied on creation. + var/phylactery_color = COLOR_VERY_DARK_LIME_GREEN + + // Internal vars. + /// The number of resurrections that have occurred from this phylactery. + var/num_resurrections = 0 + /// A timerid to the current revival timer. + var/revive_timer + +/datum/component/phylactery/Initialize( + datum/mind/lich_mind, + base_respawn_time = 3 MINUTES, + time_per_resurrection = 0 SECONDS, + stun_per_resurrection = 20 SECONDS, + phylactery_color = COLOR_VERY_DARK_LIME_GREEN, +) + if(!isobj(parent)) + return COMPONENT_INCOMPATIBLE + + if(isnull(lich_mind)) + stack_trace("A [type] was created with no target lich mind!") + return COMPONENT_INCOMPATIBLE + + src.lich_mind = lich_mind + src.base_respawn_time = base_respawn_time + src.time_per_resurrection = time_per_resurrection + src.stun_per_resurrection = stun_per_resurrection + src.phylactery_color = phylactery_color + + //RegisterSignal(lich_mind, COMSIG_QDELETING, PROC_REF(on_lich_mind_lost)) GC Code that is not up to date + RegisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH, PROC_REF(check_if_lich_died)) + + var/obj/obj_parent = parent + obj_parent.name = "ensouled [obj_parent.name]" + obj_parent.add_atom_colour(phylactery_color, ADMIN_COLOUR_PRIORITY) + obj_parent.AddComponent(/datum/component/stationloving, FALSE, TRUE) + + //RegisterSignal(obj_parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + + //SSpoints_of_interest.make_point_of_interest(obj_parent) + +/datum/component/phylactery/Destroy() + var/obj/obj_parent = parent + obj_parent.name = initial(obj_parent.name) + obj_parent.remove_atom_colour(ADMIN_COLOUR_PRIORITY, phylactery_color) + // Stationloving items should really never be made a phylactery so I feel safe in doing this + qdel(obj_parent.GetComponent(/datum/component/stationloving)) + + //UnregisterSignal(obj_parent, COMSIG_ATOM_EXAMINE) + UnregisterSignal(SSdcs, COMSIG_GLOB_MOB_DEATH) + // Sweep up any revive signals left on the mind's current + UnregisterSignal(lich_mind.current, COMSIG_LIVING_REVIVE) + + lich_mind = null + return ..() + +/** + * Signal proc for [COMSIG_ATOM_EXAMINE]. + * + * Gives some flavor for the phylactery on examine. + */ +/datum/component/phylactery/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER + + if(IS_WIZARD(user) || isobserver(user)) + if(user.mind == lich_mind) + var/time_to_revive = base_respawn_time + (num_resurrections * time_per_resurrection) + examine_list += ("Your phylactery. The next time you meet an untimely demise, \ + you will revive at this object in [time_to_revive / 10 / 60] minute\s.") + else + examine_list += ("A lich's phylactery. This one belongs to [lich_mind].") + + if(num_resurrections > 0) + examine_list += ("There's [num_resurrections] notches in the side of it.") + + else + examine_list += ("A terrible aura surrounds this item. Its very existence is offensive to life itself...") + +/** + * Signal proc for [COMSIG_QDELETING] registered on the lich's mind. + * + * Minds shouldn't be getting deleted but if for some ungodly reason + * the lich'd mind is deleted our component should go with it, as + * we don't have a reason to exist anymore. + */ +/datum/component/phylactery/proc/on_lich_mind_lost(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/** + * Signal proc for [COMSIG_GLOB_MOB_DEATH]. + * + * If the mob containing our lich's mind is killed, + * we can initiate the revival process. + * + * We use the global mob death signal here, + * instead of registering the normal death signal, + * as it's entirely possible the wizard mindswaps + * or is gibbed or something wacky happens, and + * we need to make sure WHOEVER has our mind is dead + */ +/datum/component/phylactery/proc/check_if_lich_died(datum/source, mob/living/died, gibbed) + SIGNAL_HANDLER + + if(!died.mind) + return + + if(died.mind != lich_mind) + return + + // If we aren't gibbed, we need to check if the lich is + // revived at some point between returning + if(!gibbed) + RegisterSignal(died, COMSIG_LIVING_REVIVE, PROC_REF(stop_timer)) + + // Start revival + var/time_to_revive = base_respawn_time + (num_resurrections * time_per_resurrection) + revive_timer = addtimer(CALLBACK(src, PROC_REF(revive_lich), died), time_to_revive, TIMER_UNIQUE|TIMER_STOPPABLE) + to_chat(died, ("You feel your soul being dragged back to this world... \ + you will revive at your phylactery in [time_to_revive / 10 / 60] minute\s.")) + +/** + * Signal proc for [COMSIG_LIVING_REVIVE]. + * + * If our lich's mob is revived at some point before returning, stop the timer + */ +/datum/component/phylactery/proc/stop_timer(mob/living/source, full_heal_flags) + SIGNAL_HANDLER + + deltimer(revive_timer) + revive_timer = null + + UnregisterSignal(source, COMSIG_LIVING_REVIVE) + +/** + * Actually undergo the process of reviving the lich at the site of the phylactery. + * + * Arguments + * * corpse - optional, the old body of the lich. Can be QDELETED or null. + */ +/datum/component/phylactery/proc/revive_lich(mob/living/corpse) + // If we have a current, and it's not dead, don't yoink their mind + // But if we don't have a current (body destroyed) move on like normal + if(lich_mind.current && lich_mind.current.stat != DEAD) + CRASH("[type] - revive_lich was called when the lich's mind had a current mob that wasn't dead.") + + var/turf/parent_turf = get_turf(parent) + if(!istype(parent_turf)) + CRASH("[type] - revive_lich was called when the phylactery was in an invalid location (nullspace?) (was in: [parent_turf]).") + + revive_timer = null + var/mob/living/carbon/human/lich = new(parent_turf) + ADD_TRAIT(lich, TRAIT_NO_SOUL, LICH_TRAIT) + + var/obj/item/organ/brain/new_lich_brain = lich.getorganslot(ORGAN_SLOT_BRAIN) + if(new_lich_brain) // Prevent MMI cheese + new_lich_brain.organ_flags &= ~ORGAN_VITAL + new_lich_brain.decoy_override = TRUE + + // Give them some duds + lich.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(lich), ITEM_SLOT_FEET) + lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), ITEM_SLOT_ICLOTHING) + lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), ITEM_SLOT_OCLOTHING) + lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), ITEM_SLOT_HEAD) + + // Fix their name + lich.dna.real_name = lich_mind.name + lich.real_name = lich_mind.name + // Slap the lich mind in and get their ghost + lich_mind.transfer_to(lich) + lich_mind.grab_ghost(force = TRUE) + // Make sure they're a spooky skeleton, and their DNA is right + lich.set_species(/datum/species/skeleton) + lich.dna.generate_unique_enzymes() + + to_chat(lich, ("Your bones clatter and shudder as you are pulled back into this world!")) + num_resurrections++ + lich.Paralyze(stun_per_resurrection * num_resurrections) + + if(!QDELETED(corpse)) + UnregisterSignal(corpse, COMSIG_LIVING_REVIVE) + + if(iscarbon(corpse)) + var/mob/living/carbon/carbon_body = corpse + for(var/obj/item/organ/to_drop as anything in carbon_body.internal_organs) + // Skip the brain - it can disappear, we don't need it anymore + if(istype(to_drop, /obj/item/organ/brain)) + continue + + // For the rest, drop all the organs onto the floor (for style) + to_drop.Remove(carbon_body) + to_drop.forceMove(corpse.drop_location()) + + var/turf/body_turf = get_turf(corpse) + var/wheres_wizdo = dir2text(get_dir(body_turf, parent_turf)) + if(wheres_wizdo) + corpse.visible_message(("Suddenly, [corpse.name]'s corpse falls to pieces! You see a strange energy rise from the remains, and speed off towards the [wheres_wizdo]!")) + body_turf.Beam(parent_turf, icon_state = "lichbeam", time = 1 SECONDS * (num_resurrections + 1)) + + corpse.dust(drop_items = TRUE) + + return TRUE diff --git a/code/datums/components/seclight_attachable.dm b/code/datums/components/seclight_attachable.dm index b9708ecbad9ce..77b592e451a2d 100644 --- a/code/datums/components/seclight_attachable.dm +++ b/code/datums/components/seclight_attachable.dm @@ -131,10 +131,8 @@ // Make a new toggle light item action for our parent var/obj/item/item_parent = parent - var/datum/action/item_action/toggle_seclight/toggle_action = new(item_parent) + var/datum/action/item_action/toggle_seclight/toggle_action = item_parent.add_item_action(/datum/action/item_action/toggle_seclight) toggle_action_ref = WEAKREF(toggle_action) - if(attacher && item_parent.loc == attacher) - toggle_action.Grant(attacher) update_light() diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm index 81637fddd0e8c..78eccfdccf18b 100644 --- a/code/datums/components/stationloving.dm +++ b/code/datums/components/stationloving.dm @@ -55,12 +55,12 @@ if(inform_admins) message_admins("[parent] has been moved out of bounds in [ADMIN_VERBOSEJMP(currentturf)]. Moving it to [ADMIN_VERBOSEJMP(targetturf)].") -/datum/component/stationloving/proc/check_soul_imbue() +/datum/component/stationloving/proc/check_soul_imbue(datum/source) SIGNAL_HANDLER return disallow_soul_imbue -/datum/component/stationloving/proc/check_mark_retrieval() +/datum/component/stationloving/proc/check_mark_retrieval(datum/source) SIGNAL_HANDLER return COMPONENT_BLOCK_MARK_RETRIEVAL diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 8cc67f2fbe809..4829c9166690b 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -46,7 +46,7 @@ var/attack_hand_interact = TRUE //interact on attack hand. var/quickdraw = FALSE //altclick interact - var/datum/action/item_action/storage_gather_mode/modeswitch_action + var/datum/weakref/modeswitch_action_ref /// whether or not we should have those cute little animations var/animated = TRUE @@ -154,17 +154,17 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches) return "\n\t[desc.Join("\n\t")]" /datum/component/storage/proc/update_actions() - QDEL_NULL(modeswitch_action) if(!isitem(parent) || !allow_quick_gather) + QDEL_NULL(modeswitch_action_ref) return - var/obj/item/I = parent - modeswitch_action = new(I) + var/datum/action/existing = modeswitch_action_ref?.resolve() + if(!QDELETED(existing)) + return + + var/obj/item/item_parent = parent + var/datum/action/modeswitch_action = item_parent.add_item_action(/datum/action/item_action/storage_gather_mode) RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, PROC_REF(action_trigger)) - if(I.item_flags & PICKED_UP) - var/mob/M = I.loc - if(!istype(M)) - return - modeswitch_action.Grant(M) + modeswitch_action_ref = WEAKREF(modeswitch_action) /datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) if(new_master == src || (!isnull(new_master) && !istype(new_master))) diff --git a/code/datums/dash_weapon.dm b/code/datums/dash_weapon.dm index d699e8b8b037a..2d1295a25a492 100644 --- a/code/datums/dash_weapon.dm +++ b/code/datums/dash_weapon.dm @@ -21,17 +21,17 @@ dashing_item = null return ..() -/datum/action/innate/dash/IsAvailable() +/datum/action/innate/dash/is_available() if(current_charges > 0) return TRUE else return FALSE -/datum/action/innate/dash/Activate() +/datum/action/innate/dash/on_activate() dashing_item.attack_self(owner) //Used to toggle dash behavior in the dashing item /datum/action/innate/dash/proc/Teleport(mob/user, atom/target) - if(!IsAvailable()) + if(!is_available()) return var/turf/T = get_turf(target) if(user in viewers(user.client.view, T)) diff --git a/code/datums/elements/cleaning.dm b/code/datums/elements/cleaning.dm index e4c72604c1d4d..e6f1e20469d4f 100644 --- a/code/datums/elements/cleaning.dm +++ b/code/datums/elements/cleaning.dm @@ -53,8 +53,7 @@ if(isnull(toggle_target)) toggle_target = M -/datum/action/cleaning_toggle/Trigger() - . = ..() +/datum/action/cleaning_toggle/on_activate(mob/user, atom/target) if(!toggle_target) log_runtime("Floor Cleaning Toggle action triggered without a target.") return diff --git a/code/datums/elements/climbable.dm b/code/datums/elements/climbable.dm index 52da91688c929..4cb8f863db441 100644 --- a/code/datums/elements/climbable.dm +++ b/code/datums/elements/climbable.dm @@ -22,9 +22,11 @@ RegisterSignal(target, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine)) RegisterSignal(target, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive)) RegisterSignal(target, COMSIG_ATOM_BUMPED, PROC_REF(try_speedrun)) + ADD_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type)) /datum/element/climbable/Detach(datum/target) UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_EXAMINE, COMSIG_MOUSEDROPPED_ONTO, COMSIG_ATOM_BUMPED)) + REMOVE_TRAIT(target, TRAIT_CLIMBABLE, ELEMENT_TRAIT(type)) return ..() /datum/element/climbable/proc/on_examine(atom/source, mob/user, list/examine_texts) diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 0206af3c09ff9..dc7aa5330b4b5 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -37,9 +37,11 @@ // Checks antimagic if(ismob(teleatom)) var/mob/tele_mob = teleatom - if(channel == TELEPORT_CHANNEL_CULT && tele_mob.anti_magic_check(magic = FALSE, holy = TRUE, self = TRUE)) + if(channel == TELEPORT_CHANNEL_CULT && tele_mob.can_block_magic()) return FALSE - if(channel == TELEPORT_CHANNEL_MAGIC && tele_mob.anti_magic_check(magic = TRUE, holy = FALSE, self = TRUE)) + if(channel == TELEPORT_CHANNEL_MAGIC && tele_mob.can_block_magic()) + return FALSE + if (channel == TELEPORT_CHANNEL_MAGIC_SELF && !tele_mob.can_cast_magic()) return FALSE // Check for NO_TELEPORT restrictions @@ -134,7 +136,7 @@ // If we leave behind a wake, then create that here. // Only leave a wake if we are going to a location that we can actually teleport to. - if (!no_wake && (channel == TELEPORT_CHANNEL_BLUESPACE || channel == TELEPORT_CHANNEL_CULT || channel == TELEPORT_CHANNEL_MAGIC)) + if (!no_wake && (channel == TELEPORT_CHANNEL_BLUESPACE || channel == TELEPORT_CHANNEL_CULT || channel == TELEPORT_CHANNEL_MAGIC || channel == TELEPORT_CHANNEL_MAGIC_SELF)) var/area/cur_area = curturf.loc var/area/dest_area = destturf.loc if(cur_area.teleport_restriction == TELEPORT_ALLOW_ALL && dest_area.teleport_restriction == TELEPORT_ALLOW_ALL && teleport_mode == TELEPORT_ALLOW_ALL) @@ -255,7 +257,7 @@ if(!L) return - if(do_teleport(affected_mob, pick(L), channel = TELEPORT_CHANNEL_MAGIC, no_effects = TRUE)) + if(do_teleport(affected_mob, pick(L), channel = TELEPORT_CHANNEL_MAGIC_SELF, no_effects = TRUE)) affected_mob.say("SCYAR NILA [uppertext(thearea.name)]!", forced = "wizarditis teleport") /obj/effect/temp_visual/teleportation_wake diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index 4ebfb454f337e..9c9da866befd8 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -189,7 +189,7 @@ ..() hcall = HC -/datum/action/innate/end_holocall/Activate() +/datum/action/innate/end_holocall/on_activate() hcall.Disconnect(hcall.calling_holopad) diff --git a/code/datums/martial/krav_maga.dm b/code/datums/martial/krav_maga.dm index d511504d0b9c9..7b71f126bfb43 100644 --- a/code/datums/martial/krav_maga.dm +++ b/code/datums/martial/krav_maga.dm @@ -9,11 +9,9 @@ name = "Neck Chop - Injures the neck, stopping the victim from speaking for a while." icon_icon = 'icons/hud/actions/actions_items.dmi' button_icon_state = "neckchop" + check_flags = AB_CHECK_INCAPACITATED -/datum/action/neck_chop/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't use [name] while you're incapacitated.") - return +/datum/action/neck_chop/on_activate(mob/user, atom/target) var/mob/living/carbon/human/H = owner if (H.mind.martial_art.streak == "neck_chop") owner.visible_message("[owner] assumes a neutral stance.", "Your next attack is cleared.") @@ -26,11 +24,9 @@ name = "Leg Sweep - Trips the victim, knocking them down for a brief moment." icon_icon = 'icons/hud/actions/actions_items.dmi' button_icon_state = "legsweep" + check_flags = AB_CHECK_INCAPACITATED -/datum/action/leg_sweep/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't use [name] while you're incapacitated.") - return +/datum/action/leg_sweep/on_activate(mob/user, atom/target) var/mob/living/carbon/human/H = owner if (H.mind.martial_art.streak == "leg_sweep") owner.visible_message("[owner] assumes a neutral stance.", "Your next attack is cleared.") @@ -43,11 +39,9 @@ name = "Lung Punch - Delivers a strong punch just above the victim's abdomen, constraining the lungs. The victim will be unable to breathe for a short time." icon_icon = 'icons/hud/actions/actions_items.dmi' button_icon_state = "lungpunch" + check_flags = AB_CHECK_INCAPACITATED -/datum/action/lung_punch/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't use [name] while you're incapacitated.") - return +/datum/action/lung_punch/on_activate(mob/user, atom/target) var/mob/living/carbon/human/H = owner if (H.mind.martial_art.streak == "quick_choke") owner.visible_message("[owner] assumes a neutral stance.", "Your next attack is cleared.") diff --git a/code/datums/martial/plasma_fist.dm b/code/datums/martial/plasma_fist.dm index 0e062e9598d07..cf09e2cfdd7c9 100644 --- a/code/datums/martial/plasma_fist.dm +++ b/code/datums/martial/plasma_fist.dm @@ -35,8 +35,9 @@ /datum/martial_art/plasma_fist/proc/Tornado(mob/living/carbon/human/A, mob/living/carbon/human/D) A.say("TORNADO SWEEP!", forced="plasma fist") TornadoAnimate(A) - var/obj/effect/proc_holder/spell/aoe_turf/repulse/R = new(null) - R.cast(RANGE_TURFS(1,A)) + var/datum/action/spell/aoe/repulse/tornado_spell = new(src) + tornado_spell.on_cast(A, null) + qdel(tornado_spell) log_combat(A, D, "tornado sweeped(Plasma Fist)", name) return diff --git a/code/datums/martial/tribal_claw.dm b/code/datums/martial/tribal_claw.dm index 416ed11cb79d1..b5b9d99ddd0b7 100644 --- a/code/datums/martial/tribal_claw.dm +++ b/code/datums/martial/tribal_claw.dm @@ -35,8 +35,8 @@ log_combat(A, D, "tail sweeped(Tribal Claw)", name) D.visible_message("[A] sweeps [D]'s legs with their tail!", \ "[A] sweeps your legs with their tail!") - var/static/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno/R = new - R.cast(RANGE_TURFS(1,A)) + var/datum/action/spell/aoe/repulse/xeno/R = new + R.on_cast(A, null) //Face Scratch, deals 10 brute to head(reduced by armor), blurs the target's vision and gives them the confused effect for a short time. /datum/martial_art/tribal_claw/proc/faceScratch(mob/living/carbon/human/A, mob/living/carbon/human/D) diff --git a/code/datums/martial/wrestling.dm b/code/datums/martial/wrestling.dm index 607618dfce0ae..42f17a4d99104 100644 --- a/code/datums/martial/wrestling.dm +++ b/code/datums/martial/wrestling.dm @@ -43,60 +43,50 @@ /datum/action/slam name = "Slam (Cinch) - Slam a grappled opponent into the floor." - button_icon_state = "wrassle_slam" + //button_icon_state = "wrassle_slam" (There is no such icon yet) + check_flags = AB_CHECK_INCAPACITATED -/datum/action/slam/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't WRESTLE while you're OUT FOR THE COUNT.") - return +/datum/action/slam/on_activate(mob/user, atom/target) owner.visible_message("[owner] prepares to BODY SLAM!", "Your next attack will be a BODY SLAM.") var/mob/living/carbon/human/H = owner H.mind.martial_art.streak = "slam" /datum/action/throw_wrassle name = "Throw (Cinch) - Spin a cinched opponent around and throw them." - button_icon_state = "wrassle_throw" + //button_icon_state = "wrassle_throw" (There is no such icon yet) + check_flags = AB_CHECK_INCAPACITATED -/datum/action/throw_wrassle/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't WRESTLE while you're OUT FOR THE COUNT.") - return +/datum/action/throw_wrassle/on_activate(mob/user, atom/target) owner.visible_message("[owner] prepares to THROW!", "Your next attack will be a THROW.") var/mob/living/carbon/human/H = owner H.mind.martial_art.streak = "throw" /datum/action/kick name = "Kick - A powerful kick, sends people flying away from you. Also useful for escaping from bad situations." - button_icon_state = "wrassle_kick" + //button_icon_state = "wrassle_kick" (There is no such icon yet) + check_flags = AB_CHECK_INCAPACITATED -/datum/action/kick/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't WRESTLE while you're OUT FOR THE COUNT.") - return +/datum/action/kick/on_activate(mob/user, atom/target) owner.visible_message("[owner] prepares to KICK!", "Your next attack will be a KICK.") var/mob/living/carbon/human/H = owner H.mind.martial_art.streak = "kick" /datum/action/strike name = "Strike - Hit a neaby opponent with a quick attack." - button_icon_state = "wrassle_strike" + //button_icon_state = "wrassle_strike" (There is no such icon yet) + check_flags = AB_CHECK_INCAPACITATED -/datum/action/strike/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't WRESTLE while you're OUT FOR THE COUNT.") - return +/datum/action/strike/on_activate(mob/user, atom/target) owner.visible_message("[owner] prepares to STRIKE!", "Your next attack will be a STRIKE.") var/mob/living/carbon/human/H = owner H.mind.martial_art.streak = "strike" /datum/action/drop name = "Drop - Smash down onto an opponent." - button_icon_state = "wrassle_drop" + //button_icon_state = "wrassle_drop" (There is no such icon yet) + check_flags = AB_CHECK_INCAPACITATED -/datum/action/drop/Trigger() - if(owner.incapacitated()) - to_chat(owner, "You can't WRESTLE while you're OUT FOR THE COUNT.") - return +/datum/action/drop/on_activate(mob/user, atom/target) owner.visible_message("[owner] prepares to LEG DROP!", "Your next attack will be a LEG DROP.") var/mob/living/carbon/human/H = owner H.mind.martial_art.streak = "drop" diff --git a/code/datums/mind.dm b/code/datums/mind.dm index d05a0a0b82427..5edc503b36fb5 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -48,8 +48,6 @@ var/datum/job/assigned_role var/special_role var/list/restricted_roles = list() - var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. - var/linglink /// Martial art on this mind var/datum/martial_art/martial_art @@ -131,7 +129,7 @@ else key = new_character.key - if(new_character.mind) //disassociate any mind currently in our new body's mind variable + if(new_character.mind) //disassociate any mind curently in our new body's mind variable new_character.mind.set_current(null) var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list @@ -159,17 +157,16 @@ for(var/a in antag_datums) //Makes sure all antag datums effects are applied in the new body var/datum/antagonist/A = a A.on_body_transfer(old_current, current) - if(iscarbon(new_character)) var/mob/living/carbon/C = new_character C.last_mind = src - transfer_antag_huds(hud_to_transfer) //inherit the antag HUD - transfer_actions(new_character) - transfer_martial_arts(new_character) + transfer_antag_huds(hud_to_transfer) //Inherit the antag HUD + transfer_martial_arts(new_character) //Todo: Port this proc RegisterSignal(new_character, COMSIG_MOB_DEATH, PROC_REF(set_death_time)) if(active || force_key_move) new_character.key = key //now transfer the key to link the client to our new body + SEND_SIGNAL(src, COMSIG_MIND_TRANSFERRED, old_current) SEND_SIGNAL(src, COMSIG_MIND_TRANSFER_TO, old_current, new_character) // Update SSD indicators if(isliving(old_current)) @@ -716,35 +713,9 @@ add_antag_datum(head) special_role = ROLE_REV_HEAD -/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) - // HACK: Preferences menu creates one of every selectable species. - // Some species, like vampires, create spells when they're made. - // The "action" is created when those spells Initialize. - // Preferences menu can create these assets at *any* time, primarily before - // the atoms SS initializes. - // That means "action" won't exist. - if (isnull(S.action)) - return - spell_list += S - S.action.Grant(current) - /datum/mind/proc/owns_soul() return soulOwner == src -//To remove a specific spell from a mind -/datum/mind/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) - if(!spell) - return - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - if(istype(S, spell)) - spell_list -= S - qdel(S) - -/datum/mind/proc/RemoveAllSpells() - for(var/obj/effect/proc_holder/S in spell_list) - RemoveSpell(S) - /datum/mind/proc/transfer_martial_arts(mob/living/new_character) if(!ishuman(new_character)) return @@ -753,28 +724,6 @@ martial_art.remove(new_character) else martial_art.teach(new_character) - -/datum/mind/proc/transfer_actions(mob/living/new_character) - if(current && current.actions) - for(var/datum/action/A in current.actions) - A.Grant(new_character) - transfer_mindbound_actions(new_character) - -/datum/mind/proc/transfer_mindbound_actions(mob/living/new_character) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - S.action.Grant(new_character) - -/datum/mind/proc/disrupt_spells(delay, list/exceptions = New()) - for(var/X in spell_list) - var/obj/effect/proc_holder/spell/S = X - for(var/type in exceptions) - if(istype(S, type)) - continue - S.charge_counter = delay - S.updateButtonIcon() - INVOKE_ASYNC(S, TYPE_PROC_REF(/obj/effect/proc_holder/spell, start_recharge)) - /datum/mind/proc/get_ghost(even_if_they_cant_reenter, ghosts_with_clients) for(var/mob/dead/observer/G in (ghosts_with_clients ? GLOB.player_list : GLOB.dead_mob_list)) if(G.mind == src) diff --git a/code/datums/mutations.dm b/code/datums/mutations/__mutations.dm similarity index 82% rename from code/datums/mutations.dm rename to code/datums/mutations/__mutations.dm index 4de7dbca16153..a00afc5f48a54 100644 --- a/code/datums/mutations.dm +++ b/code/datums/mutations/__mutations.dm @@ -4,10 +4,9 @@ var/locked var/quality var/static/list/visual_indicators = list() - var/obj/effect/proc_holder/spell/power - /// A list of traits to apply to the user whenever this mutation is active. var/list/traits var/layer_used = MUTATIONS_LAYER //which mutation layer to use + var/datum/action/spell/power_path /// The path of action we grant to our user on mutation gain var/list/species_allowed = list() //to restrict mutation to only certain species var/list/mobtypes_allowed = list() //to restrict mutation to only certain mobs var/health_req //minimum health required to acquire the mutation @@ -23,6 +22,10 @@ var/class //Decides player accesibility, sorta var/list/conflicts //any mutations that might conflict. put mutation typepath defines in here. make sure to enter it both ways (so that A conflicts with B, and B with A) var/allow_transfer //Do we transfer upon cloning? + /// Message given to the user upon gaining this mutation + var/text_gain_indication = "" + /// Message given to the user upon losing this mutation + var/text_lose_indication = "" //MUT_NORMAL - A mutation that can be activated and deactived by completing a sequence //MUT_EXTRA - A mutation that is in the mutations tab, and can be given and taken away through though the DNA console. Has a 0 before it's name in the mutation section of the dna console //MUT_OTHER Cannot be interacted with by players through normal means. I.E. wizards mutate @@ -70,6 +73,8 @@ owner = C dna = C.dna dna.mutations += src + if(text_gain_indication) + to_chat(owner, text_gain_indication) if(length(visual_indicators)) var/list/mut_overlay = list(get_visual_indicator()) for (var/mutable_appearance/ma in mut_overlay) @@ -80,7 +85,7 @@ owner.remove_overlay(layer_used) owner.overlays_standing[layer_used] = mut_overlay owner.apply_overlay(layer_used) - grant_spell() //we do checks here so nothing about hulk getting magic + grant_power() //we do checks here so nothing about hulk getting magic if(!modified && can_chromosome == CHROMOSOME_USED) addtimer(CALLBACK(src, PROC_REF(modify), 5)) //gonna want children calling ..() to run first RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) @@ -105,6 +110,8 @@ /datum/mutation/proc/on_losing(mob/living/carbon/owner) if(istype(owner) && (owner.dna.mutations.Remove(src))) + if(text_lose_indication && owner.stat != DEAD) + to_chat(owner, text_lose_indication) if(length(visual_indicators)) var/list/mut_overlay = list() if(owner.overlays_standing[layer_used]) @@ -113,8 +120,10 @@ mut_overlay.Remove(get_visual_indicator()) owner.overlays_standing[layer_used] = mut_overlay owner.apply_overlay(layer_used) - if(power) - owner.RemoveSpell(power) + if(power_path) + // Any powers we made are linked to our mutation datum, + // so deleting ourself will also delete it and remove it + // ...Why don't all mutations delete on loss? Not sure. qdel(src) UnregisterSignal(owner, COMSIG_MOVABLE_MOVED) REMOVE_TRAITS_IN(owner, "[type]") @@ -144,12 +153,21 @@ overlays_standing[CM.layer_used] = mut_overlay apply_overlay(CM.layer_used) -/datum/mutation/proc/modify() //called when a genome is applied so we can properly update some stats without having to remove and reapply the mutation from someone - if(modified || !power || !owner) +/** + * Called when a chromosome is applied so we can properly update some stats + * without having to remove and reapply the mutation from someone + * + * Returns `null` if no modification was done, and + * returns an instance of a power if modification was complete + */ +/datum/mutation/proc/modify() + if(modified || !power_path || !owner) return - power.charge_max *= GET_MUTATION_ENERGY(src) - power.charge_counter *= GET_MUTATION_ENERGY(src) - modified = TRUE + var/datum/action/spell/modified_power = locate(power_path) in owner.actions + if(!modified_power) + CRASH("Genetic mutation [type] called modify(), but could not find a action to modify!") + modified_power.cooldown_time *= GET_MUTATION_ENERGY(src) // Doesn't do anything for mutations with energy_coeff unset + return modified_power /datum/mutation/proc/copy_mutation(datum/mutation/HM) if(!istype(HM)) @@ -178,18 +196,15 @@ else qdel(src) -/datum/mutation/proc/grant_spell() - if(!ispath(power) || !owner) +/datum/mutation/proc/grant_power() + if(!ispath(power_path) || !owner) return FALSE - if(ispath(power, /obj/effect/proc_holder/spell/targeted/touch/mutation)) - power = new power(null, src) - else - power = new power() - power.action_background_icon_state = "bg_tech_blue_on" - power.panel = "Genetic" - owner.AddSpell(power) - return TRUE + var/datum/action/spell/new_power = new power_path(src) + new_power.background_icon_state = "bg_tech_blue_on" + new_power.Grant(owner) + + return new_power // Runs through all the coefficients and uses this to determine which chromosomes the // mutation can take. Stores these as text strings in a list. diff --git a/code/datums/mutations/acidooze.dm b/code/datums/mutations/acidooze.dm new file mode 100644 index 0000000000000..991905e5ee422 --- /dev/null +++ b/code/datums/mutations/acidooze.dm @@ -0,0 +1,54 @@ +/datum/mutation/acidooze + name = "Acidic Hands" + desc = "Allows an Oozeling to metabolize some of their blood into acid, concentrated on their hands." + quality = POSITIVE + locked = TRUE + instability = 30 + power_path = /datum/action/spell/touch/mutation/acidooze + power_coeff = 1 + energy_coeff = 1 + synchronizer_coeff = 1 + species_allowed = list(SPECIES_OOZELING) + +/datum/action/spell/touch/mutation/acidooze + name = "Acidic Hands" + desc = "Concentrate to make some of your blood become acidic." + spell_requirements = null + cooldown_time = 10 SECONDS + button_icon_state = "summons" + hand_path = /obj/item/melee/touch_attack/mutation/acidooze + mindbound = FALSE + +/obj/item/melee/touch_attack/mutation/acidooze + name = "\improper acidic hand" + desc = "Keep away from children, paperwork, and children doing paperwork." + icon = 'icons/effects/blood.dmi' + icon_state = "bloodhand_left" + item_state = "fleshtostone" + var/static/base_acid_volume = 15 + var/static/base_blood_cost = 20 + var/static/icon_left = "bloodhand_left" + var/static/icon_right = "bloodhand_right" + +/obj/item/melee/touch_attack/mutation/acidooze/equipped(mob/user, slot) + . = ..() + //these are intentionally inverted + icon_state = (user.get_held_index_of_item(src) % 2) ? icon_right : icon_left + to_chat(user, "You secrete acid into your hand.") + +/obj/item/melee/touch_attack/mutation/acidooze/afterattack(atom/target, mob/living/carbon/user, proximity) + if(!proximity || !isoozeling(user)) + return + if(!target || user.incapacitated()) + return FALSE + var/acid_volume = base_acid_volume + var/blood_cost = base_blood_cost + if(user.blood_volume < (blood_cost * 2)) + to_chat(user, "You don't have enough blood to do that!") + return FALSE + if(target.acid_act(50, acid_volume)) + user.visible_message("[user] rubs globs of vile stuff all over [target].") + user.blood_volume = max(user.blood_volume - blood_cost, 0) + return ..() + else + to_chat(user, "You cannot dissolve this object.") diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm deleted file mode 100644 index a086dc49f19d3..0000000000000 --- a/code/datums/mutations/actions.dm +++ /dev/null @@ -1,364 +0,0 @@ -/datum/mutation/telepathy - name = "Telepathy" - desc = "A rare mutation that allows the user to telepathically communicate to others." - quality = POSITIVE - difficulty = 12 - power = /obj/effect/proc_holder/spell/targeted/telepathy - instability = 10 - -/datum/mutation/olfaction - name = "Transcendent Olfaction" - desc = "Your sense of smell is comparable to that of a canine." - quality = POSITIVE - difficulty = 12 - power = /obj/effect/proc_holder/spell/targeted/olfaction - instability = 30 - energy_coeff = 1 - -/obj/effect/proc_holder/spell/targeted/olfaction - name = "Remember the Scent" - desc = "Get a scent off of the item you're currently holding to track it. With an empty hand, you'll track the scent you've remembered." - charge_max = 10 SECONDS - clothes_req = FALSE - range = -1 - include_user = TRUE - action_icon_state = "nose" - var/mob/living/carbon/tracking_target - var/list/mob/living/carbon/possible = list() - -/obj/effect/proc_holder/spell/targeted/olfaction/cast(list/targets, mob/living/user = usr) - var/atom/sniffed = user.get_active_held_item() - if(sniffed) - var/old_target = tracking_target - possible = list() - var/list/prints = sniffed.return_fingerprints() - for(var/mob/living/carbon/potential_target in GLOB.carbon_list) - if(prints[rustg_hash_string(RUSTG_HASH_MD5, potential_target.dna.uni_identity)]) - possible |= potential_target - if(!length(possible)) - to_chat(user, "Despite your best efforts, there are no scents to be found on [sniffed]...") - return - tracking_target = tgui_input_list(user, "Choose a scent to remember.", "Scent Tracking", sort_names(possible)) - if(!tracking_target) - if(!old_target) - to_chat(user,"You decide against remembering any scents. Instead, you notice your own nose in your peripheral vision. This goes on to remind you of that one time you started breathing manually and couldn't stop. What an awful day that was.") - return - tracking_target = old_target - on_the_trail(user) - return - to_chat(user,"You pick up the scent of [tracking_target]. The hunt begins.") - on_the_trail(user) - return - - if(!tracking_target) - to_chat(user,"You're not holding anything to smell, and you haven't smelled anything you can track. You smell your palm instead; it's kinda salty.") - return - - on_the_trail(user) - -/obj/effect/proc_holder/spell/targeted/olfaction/proc/on_the_trail(mob/living/user) - if(!tracking_target) - to_chat(user,"You're not tracking a scent, but the game thought you were. Something's gone wrong! Report this as a bug.") - return - if(tracking_target == user) - to_chat(user,"You smell out the trail to yourself. Yep, it's you.") - return - if(usr.get_virtual_z_level() < tracking_target.get_virtual_z_level()) - to_chat(user,"The trail leads... way up above you? Huh. They must be really, really far away.") - return - else if(usr.get_virtual_z_level() > tracking_target.get_virtual_z_level()) - to_chat(user,"The trail leads... way down below you? Huh. They must be really, really far away.") - return - var/direction_text = "[dir2text(get_dir(usr, tracking_target))]" - if(direction_text) - to_chat(user,"You consider [tracking_target]'s scent. The trail leads [direction_text].") - -/datum/mutation/firebreath - name = "Fire Breath" - desc = "An ancient mutation that gives lizards breath of fire." - quality = POSITIVE - difficulty = 12 - locked = TRUE - power = /obj/effect/proc_holder/spell/aimed/firebreath - instability = 30 - energy_coeff = 1 - power_coeff = 1 - species_allowed = list(SPECIES_LIZARD) - -/datum/mutation/firebreath/modify() - ..() - if(power) - var/obj/effect/proc_holder/spell/aimed/firebreath/firebreath = power - firebreath.strength = GET_MUTATION_POWER(src) - -/obj/effect/proc_holder/spell/aimed/firebreath - name = "Fire Breath" - desc = "You can breathe fire at a target." - school = "evocation" - invocation = "" - invocation_type = INVOCATION_NONE - charge_max = 1 MINUTES - clothes_req = FALSE - range = 20 - projectile_type = /obj/projectile/magic/fireball/firebreath - base_icon_state = "fireball" - action_icon_state = "fireball0" - sound = 'sound/magic/demon_dies.ogg' //horrifying lizard noises - active_msg = "You built up heat in your mouth." - deactive_msg = "You swallow the flame." - var/strength = 1 - -/obj/effect/proc_holder/spell/aimed/firebreath/before_cast(list/targets) - . = ..() - var/mob/living/carbon/user = usr - if(!istype(user)) - return - if(user.is_mouth_covered()) - user.adjust_fire_stacks(2) - user.IgniteMob() - to_chat(user, "Something in front of your mouth caught fire!") - return FALSE - -/obj/effect/proc_holder/spell/aimed/firebreath/ready_projectile(obj/projectile/magic/fireball/fireball, atom/target, mob/user, iteration) - if(!istype(fireball)) - return - fireball.exp_light = strength - 1 - fireball.exp_fire += strength - -/obj/projectile/magic/fireball/firebreath - name = "fire breath" - exp_heavy = 0 - exp_light = 0 - exp_flash = 0 - exp_fire = 4 - magic = FALSE - -/datum/mutation/void - name = "Void Magnet" - desc = "A rare genome that attracts odd forces not usually observed." - quality = MINOR_NEGATIVE //upsides and downsides - instability = 30 - power = /obj/effect/proc_holder/spell/self/void - energy_coeff = 1 - synchronizer_coeff = 1 - -/datum/mutation/void/on_life() - if(!isturf(owner.loc)) - return - if(prob((0.5 + ((100 - dna.stability) / 20))) * GET_MUTATION_SYNCHRONIZER(src)) //very rare, but enough to annoy you hopefully. +0.5 probability for every 10 points lost in stability - new /obj/effect/immortality_talisman/void(get_turf(owner), owner) - -/obj/effect/proc_holder/spell/self/void - name = "Convoke Void" //magic the gathering joke here - desc = "A rare genome that attracts odd forces not usually observed. May sometimes pull you in randomly." - school = "evocation" - clothes_req = FALSE - charge_max = 1 MINUTES - invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!" - invocation_type = INVOCATION_SHOUT - action_icon_state = "void_magnet" - -/obj/effect/proc_holder/spell/self/void/can_cast(mob/user = usr) - if(!isturf(user.loc)) - return FALSE - return ..() - -/obj/effect/proc_holder/spell/self/void/cast(mob/user = usr) - . = ..() - new /obj/effect/immortality_talisman/void(get_turf(user), user) - -/datum/mutation/self_amputation - name = "Autotomy" - desc = "Allows a creature to voluntary discard a random appendage." - quality = POSITIVE - instability = 30 - power = /obj/effect/proc_holder/spell/self/self_amputation - energy_coeff = 1 - -/obj/effect/proc_holder/spell/self/self_amputation - name = "Drop a limb" - desc = "Concentrate to make a random limb pop right off your body." - clothes_req = FALSE - human_req = FALSE - charge_max = 10 SECONDS - action_icon_state = "autotomy" - -/obj/effect/proc_holder/spell/self/self_amputation/cast(mob/living/carbon/user = usr) - if(!istype(user) || HAS_TRAIT(user, TRAIT_NODISMEMBER)) - return - var/list/parts = list() - for(var/obj/item/bodypart/part as() in user.bodyparts) - if(part.body_part != HEAD && part.body_part != CHEST && part.dismemberable) - parts += part - if(!length(parts)) - to_chat(user, "You can't shed any more limbs!") - return - var/obj/item/bodypart/yeeted_limb = pick(parts) - yeeted_limb.dismember() - -/datum/mutation/overload - name = "Overload" - desc = "Allows an Ethereal to overload their skin to cause a bright flash." - quality = POSITIVE - locked = TRUE - instability = 30 - power = /obj/effect/proc_holder/spell/self/overload - species_allowed = list(SPECIES_ETHEREAL) - energy_coeff = 1 - power_coeff = 1 - -/datum/mutation/overload/modify() - ..() - if(power) - var/static/max_range = min(getviewsize(world.view)[1], getviewsize(world.view)[2]) - 2 - var/obj/effect/proc_holder/spell/self/overload/overload = power - overload.max_distance = min(max_range, initial(overload.max_distance) * GET_MUTATION_POWER(src)) - -/obj/effect/proc_holder/spell/self/overload - name = "Overload" - desc = "Concentrate to make your skin energize." - clothes_req = FALSE - human_req = FALSE - charge_max = 40 SECONDS - action_icon_state = "blind" - var/max_distance = 4 - -/obj/effect/proc_holder/spell/self/overload/cast(mob/living/carbon/human/user) - if(!isethereal(user)) - return - var/list/mob/targets = oviewers(max_distance, get_turf(user)) - visible_message("[user] emits a blinding light!") - for(var/mob/living/carbon/target in targets) - if(target.flash_act(1)) - target.Paralyze(10 + (5 * max_distance)) - - for(var/mob/living/carbon/C in targets) - if(C.flash_act(1)) - C.Paralyze(10 + (5*max_distance)) - -/datum/mutation/overload/modify() - if(power) - var/obj/effect/proc_holder/spell/self/overload/S = power - S.max_distance = 4 * GET_MUTATION_POWER(src) - -//Psyphoza species mutation -/datum/mutation/spores - name = "Agaricale Pores" //Pores, not spores - desc = "An ancient mutation that gives psyphoza the ability to produce spores." - quality = POSITIVE - difficulty = 12 - locked = TRUE - power = /obj/effect/proc_holder/spell/self/spores - instability = 30 - energy_coeff = 1 - power_coeff = 1 - -/obj/effect/proc_holder/spell/self/spores - name = "Release Spores" - desc = "A rare genome that forces the subject to evict spores from their pores." - school = "evocation" - invocation = "" - clothes_req = FALSE - charge_max = 600 - invocation_type = INVOCATION_NONE - base_icon_state = "smoke" - action_icon_state = "smoke" - -/obj/effect/proc_holder/spell/self/spores/cast(mob/user = usr) - . = ..() - //Setup reagents - var/datum/reagents/holder = new() - //If our user is a carbon, use their blood - var/mob/living/carbon/C = user - if(iscarbon(user) && C.blood_volume > 0) - C.blood_volume = max(0, C.blood_volume-15) - if(C.get_blood_id()) - holder.add_reagent(C.get_blood_id(), min(C.blood_volume, 15)) - else - holder.add_reagent(/datum/reagent/blood, min(C.blood_volume, 15)) - else - holder.add_reagent(/datum/reagent/drug/mushroomhallucinogen, 15) - - var/location = get_turf(user) - var/smoke_radius = round(sqrt(holder.total_volume / 2), 1) - var/datum/effect_system/smoke_spread/chem/S = new - S.attach(location) - playsound(location, 'sound/effects/smoke.ogg', 50, 1, -3) - if(S) - S.set_up(holder, smoke_radius, location, 0) - S.start() - if(holder?.my_atom) - holder.clear_reagents() - -//Diona species mutation -/datum/mutation/drone - name = "Nymph Drone" - desc = "An ancient mutation that gives diona the ability to send out a nymph drone." - quality = POSITIVE - difficulty = 12 - locked = TRUE - power = /obj/effect/proc_holder/spell/self/drone - instability = 30 - energy_coeff = 1 - power_coeff = 1 - species_allowed = list(SPECIES_DIONA) - -/obj/effect/proc_holder/spell/self/drone - name = "Release/Control Drone" - desc = "A rare genome that allows the diona to evict a nymph from their gestalt and gain the ability to control them." - school = "evocation" - invocation = "" - clothes_req = FALSE - charge_max = 60 - invocation_type = INVOCATION_NONE - base_icon_state = "control" - action_icon_state = "control" - var/has_drone = FALSE //If the diona has a drone active or not, for their special mutation. - var/datum/weakref/drone_ref - -/obj/effect/proc_holder/spell/self/drone/cast(list/targets, mob/user = usr) - . = ..() - var/mob/living/carbon/human/C = user - if(!isdiona(C)) - return - CHECK_DNA_AND_SPECIES(C) - var/datum/species/diona/S = C.dna.species - if(has_drone) - var/mob/living/simple_animal/hostile/retaliate/nymph/drone = drone_ref?.resolve() - if(drone.stat == DEAD || QDELETED(drone)) - to_chat(C, "You can't seem to find the psychic link with your nymph.") - has_drone = FALSE - else - to_chat(C, "Switching to nymph...") - SwitchTo(C) - else - if(!do_after(C, 5 SECONDS, C, NONE, TRUE)) - return - has_drone = TRUE - var/mob/living/simple_animal/hostile/retaliate/nymph/nymph = new(C.loc) - nymph.is_drone = TRUE - nymph.drone_parent = C - nymph.switch_ability = new - nymph.switch_ability.Grant(nymph) - drone_ref = WEAKREF(nymph) - S.drone_ref = WEAKREF(nymph) - -/obj/effect/proc_holder/spell/self/drone/proc/SwitchTo(mob/living/carbon/M) - var/mob/living/simple_animal/hostile/retaliate/nymph/drone = drone_ref?.resolve() - if(!drone) - return - if(drone.stat == DEAD || QDELETED(drone)) //sanity check - return - var/datum/mind/C = M.mind - if(M.stat == CONSCIOUS) - M.visible_message("[M] \ - stops moving and starts staring vacantly into space.", - "You stop moving this form...") - else - to_chat(C, "You abandon this nymph...") - C.transfer_to(drone) - drone.mind = C - drone.visible_message("[drone] blinks and looks \ - around.", - "...and move this one instead.") - diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm index 237b1d64e372b..3fbc2301248a0 100644 --- a/code/datums/mutations/antenna.dm +++ b/code/datums/mutations/antenna.dm @@ -38,3 +38,64 @@ /obj/item/implant/radio/antenna/Initialize(mapload) . = ..() radio.name = "internal antenna" + + +/datum/mutation/mindreader + name = "Mind Reader" + desc = "The affected person can look into the recent memories of others." + quality = POSITIVE + text_gain_indication = "You hear distant voices at the corners of your mind." + text_lose_indication = "The distant voices fade." + power_path = /datum/action/spell/pointed/mindread + instability = 40 + difficulty = 8 + locked = TRUE + +/datum/action/spell/pointed/mindread + name = "Mindread" + desc = "Read the target's mind." + button_icon_state = "mindread" + cooldown_time = 5 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + antimagic_flags = MAGIC_RESISTANCE_MIND + mindbound = FALSE + ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi' + +/datum/action/spell/pointed/mindread/is_valid_spell(mob/user, atom/target) + if(!isliving(target)) + return FALSE + var/mob/living/living_cast_on = target + if(!living_cast_on.mind) + to_chat(owner, ("[target] has no mind to read!")) + return FALSE + if(living_cast_on.stat == DEAD) + to_chat(owner, ("[target] is dead!")) + return FALSE + + return TRUE + +/datum/action/spell/pointed/mindread/on_cast(mob/living/user, mob/living/target) + . = ..() + if(target.can_block_magic(MAGIC_RESISTANCE_MIND, 0)) + to_chat(owner, ("As you reach into [target]'s mind, \ + you are stopped by a mental blockage. It seems you've been foiled.")) + return + + if(target == owner) + to_chat(owner, ("You plunge into your mind... Yep, it's your mind.")) + return + + to_chat(owner, ("You plunge into [target]'s mind...")) + if(prob(20)) + // chance to alert the read-ee + to_chat(target, ("You feel something foreign enter your mind.")) + + /* Uhhhh idk what to put here :3 + var/list/recent_speech = list() + var/list/say_log = list() + var/log_source = cast_on.logging + //this whole loop puts the read-ee's say logs into say_log in an easy to access way + for(var/log_type in log_source) + var/nlog_type = text2num(log_type) + if(nlog_type & LOG_SAY) +*/ diff --git a/code/datums/mutations/autonomy.dm b/code/datums/mutations/autonomy.dm new file mode 100644 index 0000000000000..3ca7d5df85223 --- /dev/null +++ b/code/datums/mutations/autonomy.dm @@ -0,0 +1,43 @@ + +/datum/mutation/self_amputation + name = "Autotomy" + desc = "Allows a creature to voluntary discard a random appendage." + quality = POSITIVE + text_gain_indication = ("Your joints feel loose.") + instability = 30 + power_path = /datum/action/spell/self_amputation + + energy_coeff = 1 + synchronizer_coeff = 1 + +/datum/action/spell/self_amputation + name = "Drop a limb" + desc = "Concentrate to make a random limb pop right off your body." + button_icon_state = "autotomy" + mindbound = FALSE + cooldown_time = 10 SECONDS + spell_requirements = NONE + +/datum/action/spell/self_amputation/is_valid_spell(mob/user, atom/target) + return iscarbon(user) + +/datum/action/spell/self_amputation/on_cast(mob/living/carbon/user, atom/target) + . = ..() + if(HAS_TRAIT(user, TRAIT_NODISMEMBER)) + to_chat(user, ("You concentrate really hard, but nothing happens.")) + return + + var/list/parts = list() + for(var/obj/item/bodypart/to_remove as anything in user.bodyparts) + if(to_remove.body_zone == BODY_ZONE_HEAD || to_remove.body_zone == BODY_ZONE_CHEST) + continue + if(!to_remove.dismemberable) + continue + parts += to_remove + + if(!length(parts)) + to_chat(user, ("You can't shed any more limbs!")) + return + + var/obj/item/bodypart/to_remove = pick(parts) + to_remove.dismember() diff --git a/code/datums/mutations/cold.dm b/code/datums/mutations/cold.dm index be012a988cce0..a0220a7998529 100644 --- a/code/datums/mutations/cold.dm +++ b/code/datums/mutations/cold.dm @@ -5,15 +5,18 @@ instability = 10 difficulty = 10 energy_coeff = 1 - power = /obj/effect/proc_holder/spell/targeted/conjure_item/snow + power_path = /datum/action/spell/conjure_item/snow -/obj/effect/proc_holder/spell/targeted/conjure_item/snow +/datum/action/spell/conjure_item/snow name = "Create Snow" desc = "Concentrates cryokinetic forces to create snow, useful for snow-like construction." + button_icon_state = "snow" + + cooldown_time = 5 SECONDS + spell_requirements = NONE + mindbound = FALSE item_type = /obj/item/stack/sheet/snow - charge_max = 5 SECONDS delete_old = FALSE - action_icon_state = "snow" /datum/mutation/wax_saliva name = "Waxy Saliva" @@ -23,15 +26,17 @@ difficulty = 10 energy_coeff = 1 locked = TRUE - power = /obj/effect/proc_holder/spell/targeted/conjure_item/wax + power_path = /datum/action/spell/conjure_item/wax -/obj/effect/proc_holder/spell/targeted/conjure_item/wax +/datum/action/spell/conjure_item/wax name = "Secrete Wax" desc = "Concentrate to spit out some wax, useful for bee-themed construction." item_type = /obj/item/stack/sheet/wax - charge_max = 5 SECONDS + cooldown_time = 5 SECONDS delete_old = FALSE - action_icon_state = "honey" + spell_requirements = NONE + button_icon_state = "honey" + mindbound = FALSE /datum/mutation/cryokinesis name = "Cryokinesis" @@ -41,30 +46,17 @@ difficulty = 12 energy_coeff = 1 power_coeff = 1 - power = /obj/effect/proc_holder/spell/aimed/cryo - -/datum/mutation/cryokinesis/modify() - ..() - if(power) - var/obj/effect/proc_holder/spell/aimed/cryo/cryobeam = power - cryobeam.power = GET_MUTATION_POWER(src) + power_path = /datum/action/spell/pointed/projectile/cryo -/obj/effect/proc_holder/spell/aimed/cryo +/datum/action/spell/pointed/projectile/cryo name = "Cryobeam" desc = "This power fires a frozen bolt at a target." - charge_max = 15 SECONDS - cooldown_min = 15 SECONDS - clothes_req = FALSE - range = 3 - projectile_type = /obj/projectile/temp/cryo + button_icon_state = "icebeam0" + cooldown_time = 15 SECONDS + spell_requirements = NONE + antimagic_flags = NONE + mindbound = FALSE base_icon_state = "icebeam" - action_icon_state = "icebeam" active_msg = "You focus your cryokinesis!" deactive_msg = "You relax." - active = FALSE - var/power = 1 - -/obj/effect/proc_holder/spell/aimed/cryo/ready_projectile(obj/projectile/temp/cryo/cryobeam, atom/target, mob/user, iteration) - if(!istype(cryobeam)) - return - cryobeam.temperature *= power + projectile_type = /obj/projectile/temp/cryo diff --git a/code/datums/mutations/drone.dm b/code/datums/mutations/drone.dm new file mode 100644 index 0000000000000..1414830bedae0 --- /dev/null +++ b/code/datums/mutations/drone.dm @@ -0,0 +1,71 @@ +//Diona species mutation +/datum/mutation/drone + name = "Nymph Drone" + desc = "An ancient mutation that gives diona the ability to send out a nymph drone." + quality = POSITIVE + difficulty = 12 + locked = TRUE + power_path = /datum/action/spell/drone + instability = 30 + energy_coeff = 1 + power_coeff = 1 + species_allowed = list(SPECIES_DIONA) + +/datum/action/spell/drone + name = "Release/Control Drone" + desc = "A rare genome that allows the diona to evict a nymph from their gestalt and gain the ability to control them." + school = "evocation" + invocation = "" + spell_requirements = null + cooldown_time = 60 SECONDS + invocation_type = INVOCATION_NONE + button_icon_state = "control" + mindbound = FALSE + var/has_drone = FALSE //If the diona has a drone active or not, for their special mutation. + var/datum/weakref/drone_ref + +/datum/action/spell/drone/on_cast(mob/user, atom/target) + . = ..() + var/mob/living/carbon/human/C = user + if(!isdiona(C)) + return + CHECK_DNA_AND_SPECIES(C) + var/datum/species/diona/S = C.dna.species + if(has_drone) + var/mob/living/simple_animal/hostile/retaliate/nymph/drone = drone_ref?.resolve() + if(drone.stat == DEAD || QDELETED(drone)) + to_chat(C, "You can't seem to find the psychic link with your nymph.") + has_drone = FALSE + else + to_chat(C, "Switching to nymph...") + SwitchTo(C) + else + if(!do_after(C, 5 SECONDS, C, NONE, TRUE)) + return + has_drone = TRUE + var/mob/living/simple_animal/hostile/retaliate/nymph/nymph = new(C.loc) + nymph.is_drone = TRUE + nymph.drone_parent = C + nymph.switch_ability = new + nymph.switch_ability.Grant(nymph) + drone_ref = WEAKREF(nymph) + S.drone_ref = WEAKREF(nymph) + +/datum/action/spell/drone/proc/SwitchTo(mob/living/carbon/M) + var/mob/living/simple_animal/hostile/retaliate/nymph/drone = drone_ref?.resolve() + if(!drone) + return + if(drone.stat == DEAD || QDELETED(drone)) //sanity check + return + var/datum/mind/C = M.mind + if(M.stat == CONSCIOUS) + M.visible_message("[M] \ + stops moving and starts staring vacantly into space.", + "You stop moving this form...") + else + to_chat(C, "You abandon this nymph...") + C.transfer_to(drone) + drone.mind = C + drone.visible_message("[drone] blinks and looks \ + around.", + "...and move this one instead.") diff --git a/code/datums/mutations/fire_breath.dm b/code/datums/mutations/fire_breath.dm new file mode 100644 index 0000000000000..f67d9b1d1a0b3 --- /dev/null +++ b/code/datums/mutations/fire_breath.dm @@ -0,0 +1,95 @@ +/datum/mutation/firebreath + name = "Fire Breath" + desc = "An ancient mutation that gives lizards breath of fire." + quality = POSITIVE + difficulty = 12 + locked = TRUE + text_gain_indication = "Your throat is burning!" + text_lose_indication = "Your throat is cooling down." + power_path = /datum/action/spell/cone/staggered/fire_breath + instability = 30 + energy_coeff = 1 + power_coeff = 1 + +/datum/mutation/firebreath/modify() + . = ..() + var/datum/action/spell/cone/staggered/fire_breath/to_modify = . + if(!istype(to_modify)) // null or invalid + return + + if(GET_MUTATION_POWER(src) <= 1) // we only care about power from here on + return + + to_modify.cone_levels += 2 // Cone fwooshes further, and... + to_modify.self_throw_range += 1 // the breath throws the user back more + +/datum/action/spell/cone/staggered/fire_breath + name = "Fire Breath" + desc = "You breathe a cone of fire directly in front of you." + button_icon_state = "fireball0" + sound = 'sound/magic/demon_dies.ogg' //horrifying lizard noises + mindbound = FALSE + school = SCHOOL_EVOCATION + cooldown_time = 40 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + antimagic_flags = NONE + + cone_levels = 3 + respect_density = TRUE + /// The range our user is thrown backwards after casting the spell + var/self_throw_range = 1 + +/datum/action/spell/cone/staggered/fire_breath/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + if(!iscarbon(user)) + return + + var/mob/living/carbon/our_lizard = user + if(!our_lizard.is_mouth_covered()) + return + + our_lizard.adjust_fire_stacks(cone_levels) + our_lizard.IgniteMob() + to_chat(our_lizard, ("Something in front of your mouth catches fire!")) + +/datum/action/spell/cone/staggered/fire_breath/post_cast(mob/user, atom/target) + . = ..() + if(!isliving(user)) + return + + var/mob/living/living_cast_on = user + // When casting, throw the caster backwards a few tiles. + var/original_dir = living_cast_on.dir + living_cast_on.throw_at( + get_edge_target_turf(living_cast_on, turn(living_cast_on.dir, 180)), + range = self_throw_range, + speed = 2, + ) + // Try to set us to our original direction after, so we don't end up backwards. + living_cast_on.setDir(original_dir) + +/datum/action/spell/cone/staggered/fire_breath/calculate_cone_shape(current_level) + // This makes the cone shoot out into a 3 wide column of flames. + // You may be wondering, "that equation doesn't seem like it'd make a 3 wide column" + // well it does, and that's all that matters. + return (2 * current_level) - 1 + +/datum/action/spell/cone/staggered/fire_breath/do_turf_cone_effect(turf/target_turf, atom/caster, level) + // Further turfs experience less exposed_temperature and exposed_volume + new /obj/effect/hotspot(target_turf) // for style + target_turf.hotspot_expose(max(500, 900 - (100 * level)), max(50, 200 - (50 * level)), 1) + +/datum/action/spell/cone/staggered/fire_breath/do_mob_cone_effect(mob/living/target_mob, atom/caster, level) + // Further out targets take less immediate burn damage and get less fire stacks. + // The actual burn damage application is not blocked by fireproofing, like space dragons. + target_mob.apply_damage(max(10, 40 - (5 * level)), BURN) + target_mob.adjust_fire_stacks(max(2, 5 - level)) + target_mob.IgniteMob() + +/datum/action/spell/cone/staggered/firebreath/do_obj_cone_effect(obj/target_obj, atom/caster, level) + // Further out objects experience less exposed_temperature and exposed_volume + target_obj.fire_act(max(500, 900 - (100 * level)), max(50, 200 - (50 * level))) diff --git a/code/datums/mutations/olfaction.dm b/code/datums/mutations/olfaction.dm new file mode 100644 index 0000000000000..4551ae1d56496 --- /dev/null +++ b/code/datums/mutations/olfaction.dm @@ -0,0 +1,66 @@ +/datum/mutation/olfaction + name = "Transcendent Olfaction" + desc = "Your sense of smell is comparable to that of a canine." + quality = POSITIVE + difficulty = 12 + power_path = /datum/action/spell/olfaction + instability = 30 + energy_coeff = 1 + +/datum/action/spell/olfaction + name = "Remember the Scent" + desc = "Get a scent off of the item you're currently holding to track it. With an empty hand, you'll track the scent you've remembered." + cooldown_time = 10 SECONDS + spell_requirements = null + button_icon_state = "nose" + mindbound = FALSE + var/mob/living/carbon/tracking_target + var/list/mob/living/carbon/possible = list() + +/datum/action/spell/olfaction/on_cast(mob/user, atom/target) + . = ..() + var/atom/sniffed = user.get_active_held_item() + if(sniffed) + var/old_target = tracking_target + possible = list() + var/list/prints = sniffed.return_fingerprints() + for(var/mob/living/carbon/potential_target in GLOB.carbon_list) + if(prints[rustg_hash_string(RUSTG_HASH_MD5, potential_target.dna.uni_identity)]) + possible |= potential_target + if(!length(possible)) + to_chat(user, "Despite your best efforts, there are no scents to be found on [sniffed]...") + return + tracking_target = tgui_input_list(user, "Choose a scent to remember.", "Scent Tracking", sort_names(possible)) + if(!tracking_target) + if(!old_target) + to_chat(user,"You decide against remembering any scents. Instead, you notice your own nose in your peripheral vision. This goes on to remind you of that one time you started breathing manually and couldn't stop. What an awful day that was.") + return + tracking_target = old_target + on_the_trail(user) + return + to_chat(user,"You pick up the scent of [tracking_target]. The hunt begins.") + on_the_trail(user) + return + + if(!tracking_target) + to_chat(user,"You're not holding anything to smell, and you haven't smelled anything you can track. You smell your palm instead; it's kinda salty.") + return + + on_the_trail(user) + +/datum/action/spell/olfaction/proc/on_the_trail(mob/living/user) + if(!tracking_target) + to_chat(user,"You're not tracking a scent, but the game thought you were. Something's gone wrong! Report this as a bug.") + return + if(tracking_target == user) + to_chat(user,"You smell out the trail to yourself. Yep, it's you.") + return + if(usr.get_virtual_z_level() < tracking_target.get_virtual_z_level()) + to_chat(user,"The trail leads... way up above you? Huh. They must be really, really far away.") + return + else if(usr.get_virtual_z_level() > tracking_target.get_virtual_z_level()) + to_chat(user,"The trail leads... way down below you? Huh. They must be really, really far away.") + return + var/direction_text = "[dir2text(get_dir(usr, tracking_target))]" + if(direction_text) + to_chat(user,"You consider [tracking_target]'s scent. The trail leads [direction_text].") diff --git a/code/datums/mutations/overload.dm b/code/datums/mutations/overload.dm new file mode 100644 index 0000000000000..f10124c51163e --- /dev/null +++ b/code/datums/mutations/overload.dm @@ -0,0 +1,34 @@ +/datum/mutation/overload + name = "Overload" + desc = "Allows an Ethereal to overload their skin to cause a bright flash." + quality = POSITIVE + locked = TRUE + text_gain_indication = "Your skin feels more crackly." + instability = 30 + power_path = /datum/action/spell/overload + species_allowed = list(SPECIES_ETHEREAL) + +/datum/action/spell/overload + name = "Overload" + desc = "Concentrate to make your skin energize." + spell_requirements = null + cooldown_time = 60 SECONDS + button_icon_state = "blind" + mindbound = FALSE + var/max_distance = 4 + +/datum/action/spell/overload/on_cast(mob/user, atom/target) + . = ..() + if(!isethereal(user)) + return + + var/list/mob/targets = oviewers(max_distance, get_turf(user)) + to_chat(targets, "[user] emits a blinding light!") + for(var/mob/living/carbon/C in targets) + if(C.flash_act(1)) + C.Paralyze(10 + (5*max_distance)) + +/datum/mutation/overload/modify() + if(power_path) + var/datum/action/spell/overload/S = power_path + S.max_distance = 4 * GET_MUTATION_POWER(src) diff --git a/code/datums/mutations/sight.dm b/code/datums/mutations/sight.dm index 67b4925fdfeed..9cf610134e451 100644 --- a/code/datums/mutations/sight.dm +++ b/code/datums/mutations/sight.dm @@ -40,16 +40,70 @@ instability = 25 locked = TRUE traits = TRAIT_THERMAL_VISION + power_path = /datum/action/spell/thermal_vision -/datum/mutation/thermal/on_acquiring(mob/living/carbon/owner) +/datum/mutation/human/thermal/on_losing(mob/living/carbon/human/owner) if(..()) return - owner.update_sight() -/datum/mutation/thermal/on_losing(mob/living/carbon/owner) - if(..()) + // Something went wront and we still have the thermal vision from our power, no cheating. + if(HAS_TRAIT_FROM(owner, TRAIT_THERMAL_VISION, GENETIC_MUTATION)) + REMOVE_TRAIT(owner, TRAIT_THERMAL_VISION, GENETIC_MUTATION) + owner.update_sight() + + +/datum/mutation/human/thermal/modify() + . = ..() + var/datum/action/spell/thermal_vision/to_modify = . + if(!istype(to_modify)) // null or invalid return - owner.update_sight() + + to_modify.eye_damage = 10 * GET_MUTATION_SYNCHRONIZER(src) + to_modify.thermal_duration = 10 * GET_MUTATION_POWER(src) + + +/datum/action/spell/thermal_vision + name = "Activate Thermal Vision" + desc = "You can see thermal signatures, at the cost of your eyesight." + icon_icon = 'icons/hud/actions/actions_changeling.dmi' + button_icon_state = "augmented_eyesight" + toggleable = TRUE + + cooldown_time = 25 SECONDS + spell_requirements = NONE + mindbound = FALSE + /// How much eye damage is given on cast + var/eye_damage = 10 + /// The duration of the thermal vision + var/thermal_duration = 10 SECONDS + +/datum/action/spell/thermal_vision/Remove(mob/living/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_THERMAL_VISION, GENETIC_MUTATION) + remove_from.update_sight() + return ..() + +/datum/action/spell/thermal_vision/is_valid_spell(mob/user, atom/target) + return isliving(user) && !HAS_TRAIT(user, TRAIT_THERMAL_VISION) + +/datum/action/spell/thermal_vision/on_cast(mob/living/user, atom/target) + . = ..() + ADD_TRAIT(user, TRAIT_THERMAL_VISION, GENETIC_MUTATION) + user.update_sight() + to_chat(user, ("You focus your eyes intensely, as your vision becomes filled with heat signatures.")) + addtimer(CALLBACK(src, PROC_REF(deactivate)), thermal_duration) + +/datum/action/spell/thermal_vision/on_deactivate(mob/user, mob/cast_on) + if(QDELETED(cast_on) || !HAS_TRAIT_FROM(cast_on, TRAIT_THERMAL_VISION, GENETIC_MUTATION)) + return + + REMOVE_TRAIT(cast_on, TRAIT_THERMAL_VISION, GENETIC_MUTATION) + cast_on.update_sight() + to_chat(cast_on, ("You blink a few times, your vision returning to normal as a dull pain settles in your eyes.")) + + if(iscarbon(cast_on)) + var/mob/living/carbon/carbon_cast_on = cast_on + carbon_cast_on.adjustOrganLoss(ORGAN_SLOT_EYES, eye_damage) + //X-ray Vision lets you see through walls. /datum/mutation/thermal/x_ray diff --git a/code/datums/mutations/spores.dm b/code/datums/mutations/spores.dm new file mode 100644 index 0000000000000..9963ca8876d01 --- /dev/null +++ b/code/datums/mutations/spores.dm @@ -0,0 +1,48 @@ +//Psyphoza species mutation +/datum/mutation/spores + name = "Agaricale Pores" //Pores, not spores + desc = "An ancient mutation that gives psyphoza the ability to produce spores." + quality = POSITIVE + difficulty = 12 + locked = TRUE + power_path = /datum/action/spell/spores + instability = 30 + energy_coeff = 1 + power_coeff = 1 + +/datum/action/spell/spores + name = "Release Spores" + desc = "A rare genome that forces the subject to evict spores from their pores." + school = "evocation" + invocation = "" + spell_requirements = null + cooldown_time = 300 SECONDS + invocation_type = INVOCATION_NONE + button_icon_state = "smoke" + mindbound = FALSE + +/datum/action/spell/spores/on_cast(mob/user, atom/target) + . = ..() + //Setup reagents + var/datum/reagents/holder = new() + //If our user is a carbon, use their blood + var/mob/living/carbon/C = user + if(iscarbon(user) && C.blood_volume > 0) + C.blood_volume = max(0, C.blood_volume-15) + if(C.get_blood_id()) + holder.add_reagent(C.get_blood_id(), min(C.blood_volume, 15)) + else + holder.add_reagent(/datum/reagent/blood, min(C.blood_volume, 15)) + else + holder.add_reagent(/datum/reagent/drug/mushroomhallucinogen, 15) + + var/location = get_turf(user) + var/smoke_radius = round(sqrt(holder.total_volume / 2), 1) + var/datum/effect_system/smoke_spread/chem/S = new + S.attach(location) + playsound(location, 'sound/effects/smoke.ogg', 50, 1, -3) + if(S) + S.set_up(holder, smoke_radius, location, 0) + S.start() + if(holder?.my_atom) + holder.clear_reagents() diff --git a/code/datums/mutations/telepathy.dm b/code/datums/mutations/telepathy.dm new file mode 100644 index 0000000000000..fff451ab49577 --- /dev/null +++ b/code/datums/mutations/telepathy.dm @@ -0,0 +1,10 @@ +/datum/mutation/telepathy + name = "Telepathy" + desc = "A rare mutation that allows the user to telepathically communicate to others." + quality = POSITIVE + text_gain_indication = "You can hear your own voice echoing in your mind!" + text_lose_indication = "You don't hear your mind echo anymore." + difficulty = 12 + power_path = /datum/action/spell/telepathy + instability = 10 + energy_coeff = 1 diff --git a/code/datums/mutations/tongue_spike.dm b/code/datums/mutations/tongue_spike.dm new file mode 100644 index 0000000000000..9ddf9d52b4fca --- /dev/null +++ b/code/datums/mutations/tongue_spike.dm @@ -0,0 +1,178 @@ +/datum/mutation/tongue_spike + name = "Tongue Spike" + desc = "Allows a creature to voluntary shoot their tongue out as a deadly weapon." + quality = POSITIVE + text_gain_indication = ("Your feel like you can throw your voice.") + instability = 15 + power_path = /datum/action/spell/tongue_spike + + energy_coeff = 1 + synchronizer_coeff = 1 + +/datum/action/spell/tongue_spike + name = "Launch spike" + desc = "Shoot your tongue out in the direction you're facing, embedding it and dealing damage until they remove it." + icon_icon = 'icons/hud/unused/actions_genetic.dmi' + button_icon_state = "spike" + mindbound = FALSE + cooldown_time = 10 SECONDS + spell_requirements = SPELL_REQUIRES_HUMAN + + /// The type-path to what projectile we spawn to throw at someone. + var/spike_path = /obj/item/hardened_spike + +/datum/action/spell/tongue_spike/is_valid_spell(mob/user, atom/target) + return iscarbon(user) + +/datum/action/spell/tongue_spike/on_cast(mob/living/carbon/user, atom/target) + . = ..() + if(HAS_TRAIT(user, TRAIT_NODISMEMBER)) + to_chat(user, ("You concentrate really hard, but nothing happens.")) + return + + var/obj/item/organ/tongue/to_fire = locate() in user.internal_organs + if(!to_fire) + to_chat(user, ("You don't have a tongue to shoot!")) + return + + to_fire.Remove(user, special = TRUE) + var/obj/item/hardened_spike/spike = new spike_path(get_turf(user), user) + to_fire.forceMove(spike) + spike.throw_at(get_edge_target_turf(user, user.dir), 14, 4, user) + +/obj/item/hardened_spike + name = "biomass spike" + desc = "Hardened biomass, shaped into a spike. Very pointy!" + icon_state = "tonguespike" + force = 2 + throwforce = 15 //15 + 2 (WEIGHT_CLASS_SMALL) * 4 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = i didnt do the math + throw_speed = 4 + embedding = list( + "embedded_pain_multiplier" = 4, + "embed_chance" = 100, + "embedded_fall_chance" = 0, + "embedded_ignore_throwspeed_threshold" = TRUE, + ) + w_class = WEIGHT_CLASS_SMALL + sharpness = SHARP + custom_materials = list(/datum/material/biomass = 500) + /// What mob "fired" our tongue + var/datum/weakref/fired_by_ref + /// if we missed our target + var/missed = TRUE + +/obj/item/hardened_spike/Initialize(mapload, mob/living/carbon/source) + . = ..() + src.fired_by_ref = WEAKREF(source) + addtimer(CALLBACK(src, PROC_REF(check_embedded)), 5 SECONDS) + +/obj/item/hardened_spike/proc/check_embedded() + if(missed) + unembedded() + +/obj/item/hardened_spike/embedded(atom/target) + if(isbodypart(target)) + missed = FALSE + +/obj/item/hardened_spike/unembedded() + visible_message(("[src] cracks and twists, changing shape!")) + for(var/obj/tongue as anything in contents) + tongue.forceMove(get_turf(src)) + + qdel(src) + +/datum/mutation/tongue_spike/chem + name = "Chem Spike" + desc = "Allows a creature to voluntary shoot their tongue out as biomass, allowing a long range transfer of chemicals." + quality = POSITIVE + text_gain_indication = ("Your feel like you can really connect with people by throwing your voice.") + instability = 15 + locked = TRUE + power_path = /datum/action/spell/tongue_spike/chem + energy_coeff = 1 + synchronizer_coeff = 1 + +/datum/action/spell/tongue_spike/chem + name = "Launch chem spike" + desc = "Shoot your tongue out in the direction you're facing, \ + embedding it for a very small amount of damage. \ + While the other person has the spike embedded, \ + you can transfer your chemicals to them." + button_icon_state = "spikechem" + + spike_path = /obj/item/hardened_spike/chem + +/obj/item/hardened_spike/chem + name = "chem spike" + desc = "Hardened biomass, shaped into... something." + icon_state = "tonguespikechem" + throwforce = 2 //2 + 2 (WEIGHT_CLASS_SMALL) * 0 (EMBEDDED_IMPACT_PAIN_MULTIPLIER) = i didnt do the math again but very low or smthin + embedding = list( + "embedded_pain_multiplier" = 0, + "embed_chance" = 100, + "embedded_fall_chance" = 0, + "embedded_pain_chance" = 0, + "embedded_ignore_throwspeed_threshold" = TRUE, //never hurts once it's in you + ) + /// Whether the tongue's already embedded in a target once before + var/embedded_once_alread = FALSE + +/obj/item/hardened_spike/chem/embedded(mob/living/carbon/human/embedded_mob) + if(embedded_once_alread) + return + embedded_once_alread = TRUE + + var/mob/living/carbon/fired_by = fired_by_ref?.resolve() + if(!fired_by) + return + + var/datum/action/send_chems/chem_action = new(src) + chem_action.transfered_ref = WEAKREF(embedded_mob) + chem_action.Grant(fired_by) + + to_chat(fired_by, ("Link established! Use the \"Transfer Chemicals\" ability \ + to send your chemicals to the linked target!")) + +/obj/item/hardened_spike/chem/unembedded() + var/mob/living/carbon/fired_by = fired_by_ref?.resolve() + if(fired_by) + to_chat(fired_by, ("Link lost!")) + var/datum/action/send_chems/chem_action = locate() in fired_by.actions + QDEL_NULL(chem_action) + + return ..() + +/datum/action/send_chems + name = "Transfer Chemicals" + desc = "Send all of your reagents into whomever the chem spike is embedded in. One use." + background_icon_state = "bg_spell" + icon_icon = 'icons/hud/unused/actions_genetic.dmi' + button_icon_state = "spikechemswap" + check_flags = AB_CHECK_CONSCIOUS + + /// Weakref to the mob target that we transfer chemicals to on activation + var/datum/weakref/transfered_ref + +/datum/action/send_chems/New(master) + . = ..() + if(!istype(master, /obj/item/hardened_spike/chem)) + qdel(src) + +/datum/action/send_chems/on_activate(mob/user, atom/target) + if(!ishuman(owner) || !owner.reagents) + return FALSE + var/mob/living/carbon/human/transferer = owner + var/mob/living/carbon/human/transfered = transfered_ref?.resolve() + if(!ishuman(transfered)) + return FALSE + + to_chat(transfered, ("You feel a tiny prick!")) + transferer.reagents.trans_to(transfered, transferer.reagents.total_volume, 1, 1, 0, transfered_by = transferer) + + var/obj/item/hardened_spike/chem/chem_spike = target + var/obj/item/bodypart/spike_location = chem_spike.check_embedded() + + //this is where it would deal damage, if it transfers chems it removes itself so no damage + chem_spike.forceMove(get_turf(spike_location)) + chem_spike.visible_message(("[chem_spike] falls out of [spike_location]!")) + return TRUE diff --git a/code/datums/mutations/touch.dm b/code/datums/mutations/touch.dm index e0d6164274f40..0e68b9bc071fa 100644 --- a/code/datums/mutations/touch.dm +++ b/code/datums/mutations/touch.dm @@ -4,33 +4,39 @@ quality = POSITIVE locked = TRUE difficulty = 16 - power = /obj/effect/proc_holder/spell/targeted/touch/mutation/shock + power_path = /datum/action/spell/touch/shock instability = 30 locked = TRUE energy_coeff = 1 power_coeff = 1 -/obj/effect/proc_holder/spell/targeted/touch/mutation/shock - name = "Shock Touch" - desc = "Channel electricity to your hand to shock people with." - drawmessage = "You channel electricity into your hand." - dropmessage = "You let the electricity from your hand dissipate." - hand_path = /obj/item/melee/touch_attack/mutation/shock - charge_max = 10 SECONDS - action_icon_state = "zap" - -/obj/item/melee/touch_attack/mutation/shock +/obj/item/melee/touch_attack/shock name = "\improper shock touch" desc = "This is kind of like when you rub your feet on a shag rug so you can zap your friends, only a lot less safe." - on_use_sound = 'sound/weapons/zapbang.ogg' icon_state = "zapper" item_state = "zapper" -/obj/item/melee/touch_attack/mutation/shock/afterattack(atom/target, mob/living/carbon/user, proximity) +/datum/action/spell/touch/shock + name = "Shock Touch" + desc = "Channel electricity to your hand to shock people with." + button_icon_state = "zap" + sound = 'sound/weapons/zapbang.ogg' + cooldown_time = 10 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + mindbound = FALSE + hand_path = /obj/item/melee/touch_attack/shock + draw_message = ("You channel electricity into your hand.") + drop_message = ("You let the electricity from your hand dissipate.") + +/datum/action/spell/touch/shock/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return TRUE + +/obj/item/melee/touch_attack/shock/afterattack(atom/target, mob/living/carbon/user, proximity) if(QDELETED(target) || isturf(target)) return user.Beam(target, icon_state = "lightning[rand(1, 12)]", time = 5, maxdistance = 32) - var/zap = 15 * GET_MUTATION_POWER(parent_mutation) + var/zap = 15 if(iscarbon(target)) var/mob/living/carbon/ctarget = target if(ctarget.electrocute_act(zap, user, flags = SHOCK_NOSTUN)) //doesnt stun. never let this stun @@ -45,62 +51,5 @@ ltarget.visible_message("[user] electrocutes [target]!","[user] electrocutes you!") else to_chat(user,"The electricity doesn't seem to affect [target]...") - use_charge(user) + remove_hand_with_no_refund(user) return ..() - -/datum/mutation/acidooze - name = "Acidic Hands" - desc = "Allows an Oozeling to metabolize some of their blood into acid, concentrated on their hands." - quality = POSITIVE - locked = TRUE - instability = 30 - power = /obj/effect/proc_holder/spell/targeted/touch/mutation/acidooze - power_coeff = 1 - energy_coeff = 1 - synchronizer_coeff = 1 - species_allowed = list(SPECIES_OOZELING) - -/obj/effect/proc_holder/spell/targeted/touch/mutation/acidooze - name = "Acidic Hands" - desc = "Concentrate to make some of your blood become acidic." - clothes_req = FALSE - human_req = FALSE - charge_max = 10 SECONDS - action_icon_state = "summons" - hand_path = /obj/item/melee/touch_attack/mutation/acidooze - drawmessage = "You secrete acid into your hand." - dropmessage = "You let the acid in your hand dissipate." - -/obj/item/melee/touch_attack/mutation/acidooze - name = "\improper acidic hand" - desc = "Keep away from children, paperwork, and children doing paperwork." - icon = 'icons/effects/blood.dmi' - icon_state = "bloodhand_left" - item_state = "fleshtostone" - var/static/base_acid_volume = 15 - var/static/base_blood_cost = 20 - var/static/icon_left = "bloodhand_left" - var/static/icon_right = "bloodhand_right" - -/obj/item/melee/touch_attack/mutation/acidooze/equipped(mob/user, slot) - . = ..() - //these are intentionally inverted - icon_state = (user.get_held_index_of_item(src) % 2) ? icon_right : icon_left - -/obj/item/melee/touch_attack/mutation/acidooze/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || !isoozeling(user)) - return - if(!target || user.incapacitated()) - return FALSE - var/acid_volume = base_acid_volume * GET_MUTATION_POWER(parent_mutation) - var/blood_cost = base_blood_cost * GET_MUTATION_SYNCHRONIZER(parent_mutation) - if(user.blood_volume < (blood_cost * 2)) - to_chat(user, "You don't have enough blood to do that!") - return FALSE - if(target.acid_act(50, acid_volume)) - user.visible_message("[user] rubs globs of vile stuff all over [target].") - user.blood_volume = max(user.blood_volume - blood_cost, 0) - return ..() - else - to_chat(user, "You cannot dissolve this object.") - return FALSE diff --git a/code/datums/mutations/void_magnet.dm b/code/datums/mutations/void_magnet.dm new file mode 100644 index 0000000000000..62d87fe320112 --- /dev/null +++ b/code/datums/mutations/void_magnet.dm @@ -0,0 +1,43 @@ +/datum/mutation/void + name = "Void Magnet" + desc = "A rare genome that attracts odd forces not usually observed." + quality = MINOR_NEGATIVE //upsides and downsides + text_gain_indication = "You feel a heavy, dull force just beyond the walls watching you." + instability = 30 + power_path = /datum/action/spell/void + energy_coeff = 1 + synchronizer_coeff = 1 + +/datum/mutation/void/on_life(delta_time, times_fired) + // Move this onto the spell itself at some point? + var/datum/action/spell/void/curse = locate(power_path) in owner + if(!curse) + remove() + return + + if(!curse.is_valid_spell(owner, null)) + return + + //very rare, but enough to annoy you hopefully. + 0.5 probability for every 10 points lost in stability + if(DT_PROB((0.25 + ((100 - dna.stability) / 40)) * GET_MUTATION_SYNCHRONIZER(src), delta_time)) + curse.on_cast(owner, null) + +/datum/action/spell/void + name = "Convoke Void" //magic the gathering joke here + desc = "A rare genome that attracts odd forces not usually observed. May sometimes pull you in randomly." + button_icon_state = "void_magnet" + mindbound = FALSE + school = SCHOOL_EVOCATION + cooldown_time = 1 MINUTES + + invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + antimagic_flags = NONE + +/datum/action/spell/void/is_valid_spell(mob/user, atom/target) + return isturf(user.loc) + +/datum/action/spell/void/on_cast(mob/user, atom/target) + . = ..() + new /obj/effect/immortality_talisman/void(get_turf(user), user) diff --git a/code/datums/mutations/webbing.dm b/code/datums/mutations/webbing.dm new file mode 100644 index 0000000000000..b60ff561aff39 --- /dev/null +++ b/code/datums/mutations/webbing.dm @@ -0,0 +1,52 @@ +//spider webs +/datum/mutation/webbing + name = "Webbing Production" + desc = "Allows the user to lay webbing, and travel through it." + quality = POSITIVE + text_gain_indication = "Your skin feels webby." + instability = 15 + power_path = /datum/action/spell/lay_genetic_web + +/datum/mutation/human/webbing/on_acquiring(mob/living/carbon/human/owner) + if(..()) + return + ADD_TRAIT(owner, TRAIT_WEB_WEAVER, GENETIC_MUTATION) + +/datum/mutation/human/webbing/on_losing(mob/living/carbon/human/owner) + if(..()) + return + REMOVE_TRAIT(owner, TRAIT_WEB_WEAVER, GENETIC_MUTATION) + +// In the future this could be unified with the spider's web action +/datum/action/spell/lay_genetic_web + name = "Lay Web" + desc = "Drops a web. Only you will be able to traverse your web easily, making it pretty good for keeping you safe." + icon_icon = 'icons/hud/actions/actions_animal.dmi' + button_icon_state = "lay_web" + mindbound = FALSE + cooldown_time = 4 SECONDS //the same time to lay a web + spell_requirements = NONE + + /// How long it takes to lay a web + var/webbing_time = 4 SECONDS + /// The path of web that we create + var/web_path = /obj/structure/spider/stickyweb + +/datum/action/spell/lay_genetic_web/on_cast(mob/user, atom/target) + var/turf/web_spot = user.loc + if(!isturf(web_spot) || (locate(web_path) in web_spot)) + to_chat(user, ("You can't lay webs here!")) + reset_spell_cooldown() + return FALSE + + user.visible_message( + ("[user] begins to secrete a sticky substance."), + ("You begin to lay a web."), + ) + + if(!do_after(user, webbing_time, target = web_spot)) + to_chat(user, ("Your web spinning was interrupted!")) + return + + new web_path(web_spot, user) + return ..() diff --git a/code/datums/proximity_monitor/fields/timestop.dm b/code/datums/proximity_monitor/fields/timestop.dm index ceaa1b769cb38..d04b9c68829a8 100644 --- a/code/datums/proximity_monitor/fields/timestop.dm +++ b/code/datums/proximity_monitor/fields/timestop.dm @@ -29,13 +29,13 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/timestop) for(var/A in immune_atoms) immune[A] = TRUE for(var/mob/living/L in GLOB.player_list) - if(locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects + if(locate(/datum/action/spell.timestop) in L.actions) //People who can stop time are immune to its effects immune[L] = TRUE for(var/mob/living/simple_animal/hostile/holoparasite/G in GLOB.holoparasites) if(G?.summoner?.current) - if(((locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.spell_list) || (locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.summoner.current.mob_spell_list))) //It would only make sense that a person's stand would also be immune. + if(((locate(/datum/action/spell/timestop) in G.summoner.current.actions))) //It would only make sense that a person's stand would also be immune. immune[G] = TRUE - if(((locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.mind.spell_list) || (locate(/obj/effect/proc_holder/spell/aoe_turf/timestop) in G.mob_spell_list))) //It would only make sense that a person's stand would also be immune. + if(((locate(/datum/action/spell/timestop) in G.actions))) //It would only make sense that a person's stand would also be immune. immune[G.summoner.current] = TRUE for(var/mob/living/simple_animal/hostile/holoparasite/GG in GLOB.holoparasites) if(G.summoner == GG.summoner) @@ -189,7 +189,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/timestop) /datum/proximity_monitor/advanced/timestop/proc/freeze_mob(mob/living/L) frozen_mobs += L - if(L.anti_magic_check(check_anti_magic, check_holy)) + if(L.can_block_magic(MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE)) immune += L return L.Stun(20, ignore_canstun = TRUE) diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm index bbf4ded5ea828..61cd5ef070703 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs.dm @@ -589,7 +589,10 @@ /datum/status_effect/antimagic/on_apply() owner.visible_message("[owner] is coated with a dull aura!") - owner.AddComponent(/datum/component/anti_magic, MAGIC_TRAIT, _magic = TRUE, _holy = FALSE) + owner.AddComponent(/datum/component/anti_magic, \ + _source = MAGIC_TRAIT, \ + antimagic_flags = MAGIC_RESISTANCE, \ + ) //glowing wings overlay playsound(owner, 'sound/weapons/fwoosh.ogg', 75, 0) return ..() diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 9fcb61c6f3f9d..97d4c31a41645 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -99,7 +99,7 @@ rewarded = caster /datum/status_effect/bounty/on_apply() - to_chat(owner, "You hear something behind you talking... You have been marked for death by [rewarded]. If you die, they will be rewarded.") + to_chat(owner, ("You hear something behind you talking... \"You have been marked for death by [rewarded]. If you die, they will be rewarded.\"")) playsound(owner, 'sound/weapons/shotgunpump.ogg', 75, 0) return ..() @@ -113,10 +113,8 @@ to_chat(owner, "You hear something behind you talking... Bounty claimed.") playsound(owner, 'sound/weapons/shotgunshot.ogg', 75, 0) to_chat(rewarded, "You feel a surge of mana flow into you!") - for(var/obj/effect/proc_holder/spell/spell in rewarded.mind.spell_list) - spell.charge_counter = spell.charge_max - spell.recharging = FALSE - spell.update_icon() + for(var/datum/action/spell/spell in rewarded.actions) + spell.reset_spell_cooldown() rewarded.adjustBruteLoss(-25) rewarded.adjustFireLoss(-25) rewarded.adjustToxLoss(-25, FALSE, TRUE) diff --git a/code/datums/view.dm b/code/datums/view.dm index 32c2e80307188..6222083acc495 100644 --- a/code/datums/view.dm +++ b/code/datums/view.dm @@ -33,11 +33,13 @@ default = string apply() -/datum/view_data/proc/safeApplyFormat() +/datum/view_data/proc/afterViewChange() if(isZooming()) assertFormat() - return - resetFormat() + else + resetFormat() + var/datum/hud/our_hud = chief?.mob?.hud_used + our_hud.view_audit_buttons() // Make sure our hud's buttons are in our new size /datum/view_data/proc/assertFormat()//T-Pose winset(chief, "mapwindow.map", "zoom=0") @@ -101,7 +103,7 @@ /datum/view_data/proc/apply() chief?.change_view(getView()) - safeApplyFormat() + afterViewChange() /datum/view_data/proc/supress() is_suppressed = TRUE diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm index 3217233bbeda9..b3c93d51c84ae 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm @@ -720,7 +720,7 @@ spider.key = applicant.key if(fed) spider.fed += 3 - spider.lay_eggs.UpdateButtonIcon() + spider.lay_eggs.update_buttons() fed-- message_admins("[ADMIN_LOOKUPFLW(spider)] has been made into a spider by the midround ruleset.") log_game("DYNAMIC: [key_name(spider)] was spawned as a spider by the midround ruleset.") diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index ebe48dbb4474a..9f069d2b8bc72 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -470,7 +470,6 @@ V.special_role = "Clown Operative" GLOB.pre_setup_antags += V - ////////////////////////////////////////////// // // // METEOR // diff --git a/code/game/gamemodes/objectives/basic/steal_five.dm b/code/game/gamemodes/objectives/basic/steal_five.dm index e9c2e76062348..24a35393a8838 100644 --- a/code/game/gamemodes/objectives/basic/steal_five.dm +++ b/code/game/gamemodes/objectives/basic/steal_five.dm @@ -42,9 +42,9 @@ continue var/list/all_items = M.current.GetAllContents() //this should get things in cheesewheels, books, etc. for(var/obj/I in all_items) //Check for wanted items - if(istype(I, /obj/item/book/granter/spell)) - var/obj/item/book/granter/spell/spellbook = I - if(!spellbook.used || !spellbook.oneuse) //if the book still has powers... + if(istype(I, /obj/item/book/granter/action/spell)) + var/obj/item/book/granter/action/spell/spellbook = I + if(spellbook.uses > 0) //if the book still has powers... stolen_count++ //it counts. nice. else if(is_type_in_typecache(I, wanted_items)) stolen_count++ diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm index b9c03945aed38..103e567b8e783 100644 --- a/code/game/gamemodes/wizard/wizard.dm +++ b/code/game/gamemodes/wizard/wizard.dm @@ -50,8 +50,8 @@ if(isliving(wizard.current) && wizard.current.stat!=DEAD) return FALSE - for(var/obj/item/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead() - if(P.mind && P.mind.has_antag_datum(/datum/antagonist/wizard)) + for(var/datum/component/phylactery/P in GLOB.poi_list) //TODO : IsProperlyDead() + if(P.lich_mind && P.lich_mind.has_antag_datum(/datum/antagonist/wizard)) return FALSE if(SSevents.wizardmode) //If summon events was active, turn it off diff --git a/code/game/machinery/airlock_control.dm b/code/game/machinery/airlock_control.dm index 76100cebd76c3..73a0e27af70ad 100644 --- a/code/game/machinery/airlock_control.dm +++ b/code/game/machinery/airlock_control.dm @@ -161,4 +161,9 @@ SSradio.remove_object(src,frequency) return ..() +/obj/machinery/door/airlock/on_magic_unlock(datum/source, datum/action/spell/aoe/knock/spell, mob/living/caster) + // Airlocks should unlock themselves when knock is casted, THEN open up. + locked = FALSE + return ..() + #undef AIRLOCK_CONTROL_RANGE diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index 395d04d16fe8f..83fa2ec4323f9 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -90,22 +90,18 @@ /obj/machinery/computer/camera_advanced/proc/GrantActions(mob/living/user) if(off_action) - off_action.target = user off_action.Grant(user) actions += off_action if(jump_action) - jump_action.target = user jump_action.Grant(user) actions += jump_action if(move_up_action) - move_up_action.target = user move_up_action.Grant(user) actions += move_up_action if(move_down_action) - move_down_action.target = user move_down_action.Grant(user) actions += move_down_action @@ -339,23 +335,23 @@ icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "camera_off" -/datum/action/innate/camera_off/Activate() - if(!target || !isliving(target)) +/datum/action/innate/camera_off/on_activate() + if(!owner || !isliving(owner)) return - var/mob/living/C = target + var/mob/living/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control var/obj/machinery/computer/camera_advanced/console = remote_eye.origin - console.remove_eye_control(target) + console.remove_eye_control(owner) /datum/action/innate/camera_jump name = "Jump To Camera" icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "camera_jump" -/datum/action/innate/camera_jump/Activate() - if(!target || !isliving(target)) +/datum/action/innate/camera_jump/on_activate() + if(!owner || !isliving(owner)) return - var/mob/living/C = target + var/mob/living/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control var/obj/machinery/computer/camera_advanced/origin = remote_eye.origin @@ -392,10 +388,10 @@ icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "move_up" -/datum/action/innate/camera_multiz_up/Activate() - if(!target || !isliving(target)) +/datum/action/innate/camera_multiz_up/on_activate() + if(!owner || !isliving(owner)) return - var/mob/living/user_mob = target + var/mob/living/user_mob = owner var/mob/camera/ai_eye/remote/remote_eye = user_mob.remote_control if(remote_eye.zMove(UP, FALSE)) to_chat(user_mob, "You move upwards.") @@ -407,10 +403,10 @@ icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "move_down" -/datum/action/innate/camera_multiz_down/Activate() - if(!target || !isliving(target)) +/datum/action/innate/camera_multiz_down/on_activate() + if(!owner || !isliving(owner)) return - var/mob/living/user_mob = target + var/mob/living/user_mob = owner var/mob/camera/ai_eye/remote/remote_eye = user_mob.remote_control if(remote_eye.zMove(DOWN, FALSE)) to_chat(user_mob, "You move downwards.") diff --git a/code/game/machinery/computer/launchpad_control.dm b/code/game/machinery/computer/launchpad_control.dm index 1dae52e03c040..476360c82b82b 100644 --- a/code/game/machinery/computer/launchpad_control.dm +++ b/code/game/machinery/computer/launchpad_control.dm @@ -53,7 +53,7 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/launchpad) /obj/machinery/computer/launchpad/proc/teleport_checks(obj/machinery/launchpad/pad) if(QDELETED(pad)) return "ERROR: Launchpad not responding. Check launchpad integrity." - if(!pad.isAvailable()) + if(!pad.is_available()) return "ERROR: Launchpad not operative. Make sure the launchpad is ready and powered." if(pad.teleporting) return "ERROR: Launchpad busy." @@ -76,7 +76,7 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/launchpad) if(QDELETED(pad)) to_chat(user, "ERROR: Launchpad not responding. Check launchpad integrity.") return - if(!pad.isAvailable()) + if(!pad.is_available()) to_chat(user, "ERROR: Launchpad not operative. Make sure the launchpad is ready and powered.") return pad.doteleport(user, sending) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index cd77ad5afc593..6ac5a7c40d5b5 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -66,6 +66,10 @@ real_explosion_block = explosion_block explosion_block = EXPLOSION_BLOCK_PROC RegisterSignal(SSsecurity_level, COMSIG_SECURITY_LEVEL_CHANGED, PROC_REF(check_security_level)) + var/static/list/loc_connections = list( + COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock), + ) + AddElement(/datum/element/connect_loc, loc_connections) /obj/machinery/door/examine(mob/user) . = ..() @@ -446,3 +450,9 @@ return audible_message("[src] whirr[p_s()] as [p_they()] automatically lift[p_s()] access requirements!") playsound(src, 'sound/machines/boltsup.ogg', 50, TRUE) + +/// Signal proc for [COMSIG_ATOM_MAGICALLY_UNLOCKED]. Open up when someone casts knock. +/obj/machinery/door/proc/on_magic_unlock(datum/source, datum/action/spell/aoe/knock/spell, atom/caster) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(open)) diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm index 1c77d19629831..dc8fdd787d1f5 100644 --- a/code/game/machinery/launch_pad.dm +++ b/code/game/machinery/launch_pad.dm @@ -82,7 +82,7 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/launchpad) var/turf/target = locate(target_x, target_y, z) ghost.forceMove(target) -/obj/machinery/launchpad/proc/isAvailable() +/obj/machinery/launchpad/proc/is_available() if(machine_stat & NOPOWER) return FALSE if(panel_open) @@ -92,7 +92,7 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/launchpad) /obj/machinery/launchpad/proc/update_indicator() var/image/holder = hud_list[DIAG_LAUNCHPAD_HUD] var/turf/target_turf - if(isAvailable()) + if(is_available()) target_turf = locate(x + x_offset, y + y_offset, z) if(target_turf) holder.icon_state = indicator_icon @@ -145,7 +145,7 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/launchpad) indicator_icon = "launchpad_target" update_indicator() - if(QDELETED(src) || !isAvailable()) + if(QDELETED(src) || !is_available()) return teleporting = FALSE @@ -245,7 +245,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/machinery/launchpad/briefcase) briefcase = null return ..() -/obj/machinery/launchpad/briefcase/isAvailable() +/obj/machinery/launchpad/briefcase/is_available() if(closed) return FALSE return ..() @@ -377,7 +377,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/launchpad_remote) if(QDELETED(pad)) to_chat(user, "ERROR: Launchpad not responding. Check launchpad integrity.") return - if(!pad.isAvailable()) + if(!pad.is_available()) to_chat(user, "ERROR: Launchpad not operative. Make sure the launchpad is ready and powered.") return pad.doteleport(user, sending) diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index c616994b0468e..826a1a0e29daa 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -641,7 +641,7 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/porta_turret) icon_icon = 'icons/hud/actions/actions_mecha.dmi' button_icon_state = "mech_cycle_equip_off" -/datum/action/turret_toggle/Trigger() +/datum/action/turret_toggle/on_activate(mob/user, atom/target) var/obj/machinery/porta_turret/P = target if(!istype(P)) return @@ -652,7 +652,7 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/porta_turret) icon_icon = 'icons/hud/actions/actions_mecha.dmi' button_icon_state = "mech_eject" -/datum/action/turret_quit/Trigger() +/datum/action/turret_quit/on_activate(mob/user, atom/target) var/obj/machinery/porta_turret/P = target if(!istype(P)) return diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 04e0283801438..ad7cab33684be 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -97,3 +97,17 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/decal/cleanable) return bloodiness else return FALSE + +/** + * Gets the color associated with the any blood present on this decal. If there is no blood, returns null. + */ +/obj/effect/decal/cleanable/proc/get_blood_color() + switch(blood_state) + if(BLOOD_STATE_HUMAN) + return rgb(149, 10, 10) + if(BLOOD_STATE_XENO) + return rgb(43, 186, 0) + if(BLOOD_STATE_OIL) + return rgb(22, 22, 22) + + return null diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm index fc8577bf8a700..a7658cdd0bc20 100644 --- a/code/game/objects/effects/forcefields.dm +++ b/code/game/objects/effects/forcefields.dm @@ -7,27 +7,49 @@ density = TRUE CanAtmosPass = ATMOS_PASS_DENSITY z_flags = Z_BLOCK_IN_DOWN | Z_BLOCK_IN_UP - var/timeleft = 300 //Set to 0 for permanent forcefields (ugh) + /// If set, how long the force field lasts after it's created. Set to 0 to have infinite duration forcefields. + var/initial_duration = 30 SECONDS CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/forcefield) /obj/effect/forcefield/Initialize(mapload, ntimeleft) . = ..() - if(isnum_safe(ntimeleft)) - timeleft = ntimeleft - if(timeleft) - QDEL_IN(src, timeleft) + if(initial_duration > 0 SECONDS) + QDEL_IN(src, initial_duration) /obj/effect/forcefield/singularity_pull() return +/// The wizard's forcefield, summoned by forcewall +/obj/effect/forcefield/wizard + /// Flags for what antimagic can just ignore our forcefields + var/antimagic_flags = MAGIC_RESISTANCE + /// A weakref to whoever casted our forcefield. + var/datum/weakref/caster_weakref + +/obj/effect/forcefield/wizard/Initialize(mapload, mob/caster, flags = MAGIC_RESISTANCE) + . = ..() + if(caster) + caster_weakref = WEAKREF(caster) + antimagic_flags = flags + +/obj/effect/forcefield/wizard/CanAllowThrough(atom/movable/mover, border_dir) + if(IS_WEAKREF_OF(mover, caster_weakref)) + return TRUE + if(isliving(mover)) + var/mob/living/living_mover = mover + if(living_mover.can_block_magic(antimagic_flags)) + return TRUE + + return ..() + /obj/effect/forcefield/cult desc = "An unholy shield that blocks all attacks." name = "glowing wall" icon = 'icons/effects/cult_effects.dmi' icon_state = "cultshield" CanAtmosPass = ATMOS_PASS_NO - timeleft = 200 + initial_duration = 20 SECONDS ///////////Mimewalls/////////// @@ -39,7 +61,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/forcefield) /obj/effect/forcefield/mime/advanced name = "invisible blockade" desc = "You're gonna be here awhile." - timeleft = 600 + initial_duration = 1 MINUTES CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/forcefield/mime) diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm new file mode 100644 index 0000000000000..7f9e7faefc9ab --- /dev/null +++ b/code/game/objects/effects/phased_mob.dm @@ -0,0 +1,88 @@ +/obj/effect/dummy/phased_mob + name = "water" + anchored = TRUE + flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + invisibility = INVISIBILITY_OBSERVER + movement_type = FLOATING + /// The movable which's jaunting in this dummy + var/atom/movable/jaunter + /// The delay between moves while jaunted + var/movedelay = 0 + /// The speed of movement while jaunted + var/movespeed = 0 + +/obj/effect/dummy/phased_mob/Initialize(mapload, atom/movable/jaunter) + . = ..() + if(jaunter) + set_jaunter(jaunter) + +/// Sets [new_jaunter] as our jaunter, forcemoves them into our contents +/obj/effect/dummy/phased_mob/proc/set_jaunter(atom/movable/new_jaunter) + jaunter = new_jaunter + jaunter.forceMove(src) + if(ismob(jaunter)) + var/mob/mob_jaunter = jaunter + mob_jaunter.reset_perspective(src) + +/obj/effect/dummy/phased_mob/Destroy() + jaunter = null // If a mob was left in the jaunter on qdel, they'll be dumped into nullspace + return ..() + +/// Removes [jaunter] from our phased mob +/obj/effect/dummy/phased_mob/proc/eject_jaunter() + if(!jaunter) + CRASH("Phased mob ([type]) attempted to eject null jaunter.") + var/turf/eject_spot = get_turf(src) + if(!eject_spot) //You're in nullspace you clown! + return + + var/area/destination_area = get_area(eject_spot) + if(destination_area.teleport_restriction == TELEPORT_ALLOW_NONE) + // this ONLY happens if someone uses a phasing effect + // to try to land in a TELEPORT_ALLOW_NONE zone after it is created, AKA trying to exploit. + if(isliving(jaunter)) + var/mob/living/living_cheaterson = jaunter + to_chat(living_cheaterson, ("This area has a heavy universal force occupying it, and you are scattered to the cosmos!")) + if(ishuman(living_cheaterson)) + shake_camera(living_cheaterson, 20, 1) + addtimer(CALLBACK(living_cheaterson, TYPE_PROC_REF(/mob/living/carbon, vomit)), 2 SECONDS) + jaunter.forceMove(find_safe_turf(z)) + + else + jaunter.forceMove(eject_spot) + qdel(src) + +/obj/effect/dummy/phased_mob/Exited(atom/movable/gone, direction) + . = ..() + if(gone == jaunter) + jaunter = null + +/obj/effect/dummy/phased_mob/ex_act() + return FALSE + +/obj/effect/dummy/phased_mob/bullet_act(blah) + return BULLET_ACT_FORCE_PIERCE +/obj/effect/dummy/phased_mob/relaymove(mob/living/user, direction) + var/turf/newloc = phased_check(user, direction) + if(!newloc) + return + setDir(direction) + forceMove(newloc) +/// Checks if the conditions are valid to be able to phase. Returns a turf destination if positive. +/obj/effect/dummy/phased_mob/proc/phased_check(mob/living/user, direction) + RETURN_TYPE(/turf) + if (movedelay > world.time || !direction) + return + var/turf/newloc = get_step(src,direction) + if(!newloc) + return + var/area/destination_area = newloc.loc + movedelay = world.time + movespeed + if(newloc.flags_1 & NOJAUNT_1) + to_chat(user, ("Some strange aura is blocking the way.")) + return + if(destination_area.teleport_restriction == TELEPORT_ALLOW_NONE || SSmapping.level_trait(newloc.z, ZTRAIT_NOPHASE)) + to_chat(user, ("Some dull, universal force is blocking the way. It's overwhelmingly oppressive force feels dangerous.")) + return + return newloc diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm index a5c65f954adf5..5efd354807f9e 100644 --- a/code/game/objects/effects/spawners/lootdrop.dm +++ b/code/game/objects/effects/spawners/lootdrop.dm @@ -554,8 +554,8 @@ name = "dungeon lite" loot = list(/obj/item/melee/classic_baton/police = 11, /obj/item/melee/classic_baton/police/telescopic = 12, - /obj/item/book/granter/spell/smoke = 10, - /obj/item/book/granter/spell/blind = 10, + /obj/item/book/granter/action/spell/smoke = 10, + /obj/item/book/granter/action/spell/blind = 10, /obj/item/storage/firstaid/regular = 45, /obj/item/storage/firstaid/toxin = 35, /obj/item/storage/firstaid/brute = 27, @@ -578,9 +578,9 @@ /obj/item/gun/magic/wand/fireball/inert = 3, /obj/item/pneumatic_cannon = 15, /obj/item/melee/transforming/energy/sword = 7, - /obj/item/book/granter/spell/knock = 15, - /obj/item/book/granter/spell/summonitem = 20, - /obj/item/book/granter/spell/forcewall = 17, + /obj/item/book/granter/action/spell/knock = 15, + /obj/item/book/granter/action/spell/summonitem = 20, + /obj/item/book/granter/action/spell/forcewall = 17, /obj/item/storage/backpack/holding = 12, /obj/item/grenade/spawnergrenade/manhacks = 6, /obj/item/grenade/spawnergrenade/spesscarp = 7, @@ -589,7 +589,7 @@ /obj/item/stack/sheet/mineral/uranium{amount = 15} = 10, /obj/item/stack/sheet/mineral/plasma{amount = 15} = 10, /obj/item/stack/sheet/mineral/gold{amount = 15} = 10, - /obj/item/book/granter/spell/barnyard = 4, + /obj/item/book/granter/action/spell/barnyard = 4, /obj/item/pickaxe/drill/diamonddrill = 6, /obj/item/borg/upgrade/vtec = 7, /obj/item/borg/upgrade/disablercooler = 7) @@ -607,9 +607,9 @@ /obj/item/gun/magic/wand/resurrection/inert = 15, /obj/item/gun/magic/wand/resurrection = 10, /obj/item/uplink/old = 2, - /obj/item/book/granter/spell/charge = 12, + /obj/item/book/granter/action/spell/charge = 12, /obj/item/grenade/clusterbuster/spawner_manhacks = 15, - /obj/item/book/granter/spell/fireball = 10, + /obj/item/book/granter/action/spell/fireball = 10, /obj/item/pickaxe/drill/jackhammer = 30, /obj/item/borg/upgrade/syndicate = 13, /obj/item/borg/upgrade/selfrepair = 17) diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index b6e2940429b69..0e83748730b26 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -82,7 +82,7 @@ var/list/mob/living/potential_spawns = list(/mob/living/simple_animal/hostile/poison/giant_spider/guard, /mob/living/simple_animal/hostile/poison/giant_spider/hunter, /mob/living/simple_animal/hostile/poison/giant_spider/nurse, - /mob/living/simple_animal/hostile/poison/giant_spider/netcaster) + /mob/living/simple_animal/hostile/poison/giant_spider/netcaster,) // The types of spiders the egg sac produces when we proc an enriched spawn var/list/mob/living/potential_enriched_spawns = list(/mob/living/simple_animal/hostile/poison/giant_spider/guard, /mob/living/simple_animal/hostile/poison/giant_spider/hunter, @@ -240,9 +240,6 @@ /obj/structure/spider/spiderling/viper grow_as = /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper -/obj/structure/spider/spiderling/netcaster - grow_as = /mob/living/simple_animal/hostile/poison/giant_spider/netcaster - /obj/structure/spider/spiderling/Bump(atom/user) if(istype(user, /obj/structure/table)) forceMove(user.loc) @@ -311,7 +308,7 @@ if(amount_grown >= 100) if(!grow_as) if(prob(3)) - grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider/netcaster, /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother) + grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother) else grow_as = pick(/mob/living/simple_animal/hostile/poison/giant_spider, /mob/living/simple_animal/hostile/poison/giant_spider/hunter, /mob/living/simple_animal/hostile/poison/giant_spider/nurse) var/mob/living/simple_animal/hostile/poison/giant_spider/S = new grow_as(src.loc) diff --git a/code/game/objects/empulse.dm b/code/game/objects/empulse.dm index 242c2acbc863e..2066bcee00930 100644 --- a/code/game/objects/empulse.dm +++ b/code/game/objects/empulse.dm @@ -1,4 +1,5 @@ /proc/empulse(turf/epicenter, heavy_range, light_range, log=0, magic=FALSE, holy=FALSE) + var/antimagic_flag if(!epicenter) return @@ -20,12 +21,15 @@ for(var/turf/T as() in spiral_range_turfs(light_range, epicenter)) //Blessing protects from holy EMPS if(holy && T.is_holy()) + antimagic_flag |= MAGIC_RESISTANCE_HOLY continue for(var/atom/A as() in T) //Magic check. if(ismob(A)) var/mob/M = A - if(M.anti_magic_check(magic, holy, TRUE, T==epicenter)) + if(magic) + antimagic_flag |= MAGIC_RESISTANCE + if(M.can_block_magic(antimagic_flag)) continue empulse_atoms += A empulse_atoms += T diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 86b723c7d20e0..f5b88ac699891 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -239,7 +239,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) . = ..() for(var/path in actions_types) - new path(src) + add_item_action(path) actions_types = null if(force_string) @@ -267,11 +267,55 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) if(ismob(loc)) var/mob/m = loc m.temporarilyRemoveItemFromInventory(src, TRUE) - for(var/X in actions) - qdel(X) + + // Handle cleaning up our actions list + for(var/datum/action/action as anything in actions) + remove_item_action(action) QDEL_NULL(rpg_loot) return ..() +/// Called when an action associated with our item is deleted +/obj/item/proc/on_action_deleted(datum/source) + SIGNAL_HANDLER + + if(!(source in actions)) + CRASH("An action ([source.type]) was deleted that was associated with an item ([src]), but was not found in the item's actions list.") + + LAZYREMOVE(actions, source) + +/// Adds an item action to our list of item actions. +/// Item actions are actions linked to our item, that are granted to mobs who equip us. +/// This also ensures that the actions are properly tracked in the actions list and removed if they're deleted. +/// Can be be passed a typepath of an action or an instance of an action. +/obj/item/proc/add_item_action(action_or_action_type) + + var/datum/action/action + if(ispath(action_or_action_type, /datum/action)) + action = new action_or_action_type(src) + else if(istype(action_or_action_type, /datum/action)) + action = action_or_action_type + else + CRASH("item add_item_action got a type or instance of something that wasn't an action.") + + LAZYADD(actions, action) + RegisterSignal(action, COMSIG_PARENT_QDELETING, PROC_REF(on_action_deleted)) + if(ismob(loc)) + // We're being held or are equipped by someone while adding an action? + // Then they should also probably be granted the action, given it's in a correct slot + var/mob/holder = loc + give_item_action(action, holder, holder.get_slot_by_item(src)) + + return action + +/// Removes an instance of an action from our list of item actions. +/obj/item/proc/remove_item_action(datum/action/action) + if(!action) + return + + UnregisterSignal(action, COMSIG_PARENT_QDELETING) + LAZYREMOVE(actions, action) + qdel(action) + /obj/item/proc/check_allowed_items(atom/target, not_inside, target_self) if(((src in target) && !target_self) || (!isturf(target.loc) && !isturf(target) && not_inside)) return 0 @@ -680,10 +724,9 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) /obj/item/proc/dropped(mob/user, silent = FALSE) SHOULD_CALL_PARENT(TRUE) - for(var/X in actions) - var/datum/action/A = X - A.Remove(user) - + // Remove any item actions we temporary gave out. + for(var/datum/action/action_item_has as anything in actions) + action_item_has.Remove(user) item_flags &= ~BEING_REMOVED item_flags &= ~PICKED_UP SEND_SIGNAL(src, COMSIG_ITEM_DROPPED, user) @@ -736,12 +779,9 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) visual_equipped(user, slot, initial) SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot) SEND_SIGNAL(user, COMSIG_MOB_EQUIPPED_ITEM, src, slot) - - for(var/X in actions) - var/datum/action/A = X - if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot. - A.Grant(user) - + // Give out actions our item has to people who equip it. + for(var/datum/action/action as anything in actions) + give_item_action(action, user, slot) if(item_flags & SLOWS_WHILE_IN_HAND || slowdown) user.update_equipment_speed_mods() if(ismonkey(user)) //Only generate icons if we have to @@ -755,6 +795,18 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) user.update_equipment_speed_mods() +/// Gives one of our item actions to a mob, when equipped to a certain slot +/obj/item/proc/give_item_action(datum/action/action, mob/to_who, slot) + // Some items only give their actions buttons when in a specific slot. + if(!item_action_slot_check(slot, to_who)) + // There is a chance we still have our item action currently, + // and are moving it from a "valid slot" to an "invalid slot". + // So call Remove() here regardless, even if excessive. + action.Remove(to_who) + return + + action.Grant(to_who) + //sometimes we only want to grant the item's action if it's equipped in a specific slot. /obj/item/proc/item_action_slot_check(slot, mob/user) if(slot == ITEM_SLOT_BACKPACK || slot == ITEM_SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there @@ -1415,8 +1467,7 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) */ /obj/item/proc/update_action_buttons(status_only = FALSE, force = FALSE) for(var/datum/action/current_action as anything in actions) - current_action.UpdateButtonIcon(status_only, force) - + current_action.update_buttons(status_only, force) /** * * An interrupt for offering an item to other people, called mainly from [/mob/living/carbon/proc/give], in case you want to run your own offer behavior instead. * diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm index a332fb9a6ef93..d8847cf396881 100644 --- a/code/game/objects/items/RCD.dm +++ b/code/game/objects/items/RCD.dm @@ -1117,6 +1117,9 @@ GLOBAL_VAR_INIT(icon_holographic_window, init_holographic_window()) desc = "It contains the design for chairs, stools, tables, and glass tables." upgrade = RCD_UPGRADE_FURNISHING +/datum/action/item_action/pick_color + name = "Choose A Color" + /datum/action/item_action/rcd_scan name = "Destruction Scan" desc = "Scans the surrounding area for destruction. Scanned structures will rebuild significantly faster." diff --git a/code/game/objects/items/RCL.dm b/code/game/objects/items/RCL.dm index 427ab2285a133..8fc6ab4979b9a 100644 --- a/code/game/objects/items/RCL.dm +++ b/code/game/objects/items/RCL.dm @@ -346,3 +346,13 @@ icon_state = "rclg-1" item_state = "rclg-1" return ..() + +/datum/action/item_action/rcl_col + name = "Change Cable Color" + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "rcl_rainbow" + +/datum/action/item_action/rcl_gui + name = "Toggle Fast Wiring Gui" + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "rcl_gui" diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index 8a111a65ce590..660e2d6f0a9dc 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -524,6 +524,7 @@ update_label("John Doe", "Clowny") /obj/item/card/id/golem, /obj/item/card/id/pass), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) if(!proximity) diff --git a/code/game/objects/items/chainsaw.dm b/code/game/objects/items/chainsaw.dm index 435f1c3d5367f..d9371fab7c9c6 100644 --- a/code/game/objects/items/chainsaw.dm +++ b/code/game/objects/items/chainsaw.dm @@ -143,3 +143,6 @@ /obj/item/chainsaw/energy/doom/attack(mob/living/target) ..() target.Knockdown(4) + +/datum/action/item_action/startchainsaw + name = "Pull The Starting Cord" diff --git a/code/game/objects/items/chromosome.dm b/code/game/objects/items/chromosome.dm index 994e394468adf..a58c78045a720 100644 --- a/code/game/objects/items/chromosome.dm +++ b/code/game/objects/items/chromosome.dm @@ -34,9 +34,11 @@ HM.power_coeff = power_coeff if(HM.energy_coeff != -1) HM.energy_coeff = energy_coeff - HM.can_chromosome = 2 + HM.can_chromosome = CHROMOSOME_USED HM.chromosome_name = name - HM.modify() + // Do the actual modification + if(HM.modify()) + HM.modified = TRUE qdel(src) /proc/generate_chromosome() diff --git a/code/game/objects/items/debug_items.dm b/code/game/objects/items/debug_items.dm index 78325763604a4..80b436c037aaf 100644 --- a/code/game/objects/items/debug_items.dm +++ b/code/game/objects/items/debug_items.dm @@ -188,12 +188,6 @@ ranged = TRUE upgrade_flags = RPD_UPGRADE_UNWRENCH -/obj/item/spellbook/debug - name = "\improper Robehator's spell book" - uses = 200 - everything_robeless = TRUE - bypass_lock = TRUE - //Debug suit /obj/item/clothing/head/helmet/space/hardsuit/debug name = "\improper Central Command black hardsuit helmet" @@ -303,7 +297,6 @@ /obj/item/disk/data/debug=1, /obj/item/uplink/debug=1, /obj/item/uplink/nuclear/debug=1, - /obj/item/spellbook/debug=1, /obj/item/storage/box/beakers/bluespace=1, /obj/item/storage/box/beakers/variety=1, /obj/item/storage/box/material=1 diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index ee41bc6076dcf..e52f86c093c9a 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -585,7 +585,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/temp_visual/medical_holosign) for(var/X in found.actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.update_buttons() found.burn_pickup = TRUE /obj/item/flashlight/spotlight //invisible lighting source diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index 98593e67104d8..5637c08f2a10b 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -41,25 +41,23 @@ // Syndicate device disguised as a multitool; it will turn red when an AI camera is nearby. /obj/item/multitool/ai_detect + actions_types = list(/datum/action/item_action/toggle_multitool) var/detect_state = PROXIMITY_NONE var/rangealert = 8 //Glows red when inside var/rangewarning = 20 //Glows yellow when inside var/hud_type = DATA_HUD_AI_DETECT var/hud_on = FALSE var/mob/camera/ai_eye/remote/ai_detector/eye - var/datum/action/item_action/toggle_multitool/toggle_action /obj/item/multitool/ai_detect/Initialize(mapload) . = ..() START_PROCESSING(SSfastprocess, src) eye = new /mob/camera/ai_eye/remote/ai_detector() - toggle_action = new /datum/action/item_action/toggle_multitool(src) /obj/item/multitool/ai_detect/Destroy() STOP_PROCESSING(SSfastprocess, src) if(hud_on && ismob(loc)) remove_hud(loc) - QDEL_NULL(toggle_action) QDEL_NULL(eye) return ..() @@ -149,9 +147,7 @@ name = "Toggle AI detector HUD" check_flags = NONE -/datum/action/item_action/toggle_multitool/Trigger() - if(!..()) - return FALSE +/datum/action/item_action/toggle_multitool/on_activate(mob/user, atom/target) if(target) var/obj/item/multitool/ai_detect/M = target M.toggle_hud(owner) diff --git a/code/game/objects/items/devices/spyglasses.dm b/code/game/objects/items/devices/spyglasses.dm index 27f30310894f5..8e94fd7b8e12a 100644 --- a/code/game/objects/items/devices/spyglasses.dm +++ b/code/game/objects/items/devices/spyglasses.dm @@ -36,6 +36,9 @@ linked_bug.linked_glasses = null return ..() +/datum/action/item_action/activate_remote_view + name = "Activate Remote View" + desc = "Activates the Remote View of your spy sunglasses." /obj/item/clothing/accessory/spy_bug name = "pocket protector" diff --git a/code/game/objects/items/dna_injector.dm b/code/game/objects/items/dna_injector.dm index 9c23ff1b0c7ae..db7df49f20a19 100644 --- a/code/game/objects/items/dna_injector.dm +++ b/code/game/objects/items/dna_injector.dm @@ -348,6 +348,7 @@ /obj/item/dnainjector/antiradioactive name = "\improper DNA injector (Anti-Radioactive)" remove_mutations = list(RADIOACTIVE) + /obj/item/dnainjector/olfaction name = "\improper DNA injector (Olfaction)" add_mutations = list(OLFACTION) diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm deleted file mode 100644 index b9e0a8ab618fd..0000000000000 --- a/code/game/objects/items/granters.dm +++ /dev/null @@ -1,470 +0,0 @@ - -///books that teach things (intrinsic actions like bar flinging, spells like fireball or smoke, or martial arts)/// - -/obj/item/book/granter - due_date = 0 // Game time in deciseconds - unique = 1 // 0 Normal book, 1 Should not be treated as normal book, unable to be copied, unable to be modified - var/list/remarks = list() //things to read about while learning. - var/pages_to_mastery = 3 //Essentially controls how long a mob must keep the book in his hand to actually successfully learn - var/reading = FALSE //sanity - var/oneuse = TRUE //default this is true, but admins can var this to 0 if we wanna all have a pass around of the rod form book - var/used = FALSE //only really matters if oneuse but it might be nice to know if someone's used it for admin investigations perhaps - -/obj/item/book/granter/proc/turn_page(mob/user) - playsound(user, pick('sound/effects/pageturn1.ogg','sound/effects/pageturn2.ogg','sound/effects/pageturn3.ogg'), 30, 1) - if(do_after(user,50, user)) - if(remarks.len) - to_chat(user, "[pick(remarks)]") - else - to_chat(user, "You keep reading...") - return TRUE - return FALSE - -/obj/item/book/granter/proc/recoil(mob/user) //nothing so some books can just return - -/obj/item/book/granter/proc/already_known(mob/user) - return FALSE - -/obj/item/book/granter/proc/on_reading_start(mob/user) - to_chat(user, "You start reading [name]...") - -/obj/item/book/granter/proc/on_reading_stopped(mob/user) - to_chat(user, "You stop reading...") - -/obj/item/book/granter/proc/on_reading_finished(mob/user) - to_chat(user, "You finish reading [name]!") - -/obj/item/book/granter/proc/onlearned(mob/user) - used = TRUE - - -/obj/item/book/granter/attack_self(mob/user) - if(reading) - to_chat(user, "You're already reading this!") - return FALSE - if(!user.can_read(src)) - return FALSE - if(already_known(user)) - return FALSE - if(used) - if(oneuse) - recoil(user) - return FALSE - on_reading_start(user) - reading = TRUE - for(var/i in 1 to pages_to_mastery) - if(!turn_page(user)) - on_reading_stopped() - reading = FALSE - return - if(do_after(user,50, user)) - on_reading_finished(user) - reading = FALSE - return TRUE - -///ACTION BUTTONS/// - -/obj/item/book/granter/action - var/granted_action - var/actionname = "catching bugs" //might not seem needed but this makes it so you can safely name action buttons toggle this or that without it fucking up the granter, also caps - -/obj/item/book/granter/action/already_known(mob/user) - if(!granted_action) - return TRUE - for(var/datum/action/A in user.actions) - if(A.type == granted_action) - to_chat(user, "You already know all about [actionname].") - return TRUE - return FALSE - -/obj/item/book/granter/action/on_reading_start(mob/user) - to_chat(user, "You start reading about [actionname]...") - -/obj/item/book/granter/action/on_reading_finished(mob/user) - to_chat(user, "You feel like you've got a good handle on [actionname]!") - var/datum/action/G = new granted_action - G.Grant(user) - onlearned(user) - -/obj/item/book/granter/action/origami - granted_action = /datum/action/innate/origami - name = "The Art of Origami" - desc = "A meticulously in-depth manual explaining the art of paper folding." - icon_state = "origamibook" - actionname = "origami" - oneuse = TRUE - remarks = list("Dead-stick stability...", "Symmetry seems to play a rather large factor...", "Accounting for crosswinds... really?", "Drag coefficients of various paper types...", "Thrust to weight ratios?", "Positive dihedral angle?", "Center of gravity forward of the center of lift...") - -/datum/action/innate/origami - name = "Origami Folding" - desc = "Toggles your ability to catch robust paper airplanes." - button_icon_state = "origami_off" - check_flags = NONE - -/datum/action/innate/origami/Activate() - to_chat(owner, "You will now catch origami planes.") - button_icon_state = "origami_on" - active = TRUE - UpdateButtonIcon() - -/datum/action/innate/origami/Deactivate() - to_chat(owner, "You will no longer catch origami planes.") - button_icon_state = "origami_off" - active = FALSE - UpdateButtonIcon() - -///SPELLS/// - -/obj/item/book/granter/spell - var/spell - var/spellname = "conjure bugs" - -/obj/item/book/granter/spell/already_known(mob/user) - if(!spell) - return TRUE - for(var/obj/effect/proc_holder/spell/knownspell in user.mind.spell_list) - if(knownspell.type == spell) - if(user.mind) - if(iswizard(user)) - to_chat(user,"You're already far more versed in this spell than this flimsy how-to book can provide.") - else - to_chat(user,"You've already read this one.") - return TRUE - return FALSE - -/obj/item/book/granter/spell/on_reading_start(mob/user) - to_chat(user, "You start reading about casting [spellname]...") - -/obj/item/book/granter/spell/on_reading_finished(mob/user) - to_chat(user, "You feel like you've experienced enough to cast [spellname]!") - var/obj/effect/proc_holder/spell/S = new spell - user.mind.AddSpell(S) - user.log_message("learned the spell [spellname] ([S])", LOG_ATTACK, color="orange") - onlearned(user) - -/obj/item/book/granter/spell/recoil(mob/user) - user.visible_message("[src] glows in a black light!") - -/obj/item/book/granter/spell/onlearned(mob/user) - ..() - if(oneuse) - user.visible_message("[src] glows dark for a second!") - -/obj/item/book/granter/spell/fireball - spell = /obj/effect/proc_holder/spell/aimed/fireball - spellname = "fireball" - icon_state ="bookfireball" - desc = "This book feels warm to the touch." - remarks = list("Aim...AIM, FOOL!", "Just catching them on fire won't do...", "Accounting for crosswinds... really?", "I think I just burned my hand...", "Why the dumb stance? It's just a flick of the hand...", "OMEE... ONI... Ugh...", "What's the difference between a fireball and a pyroblast...") - -/obj/item/book/granter/spell/fireball/recoil(mob/user) - ..() - explosion(user.loc, 1, 0, 2, 3, FALSE, FALSE, 2, magic = TRUE) - qdel(src) - -/obj/item/book/granter/spell/sacredflame - spell = /obj/effect/proc_holder/spell/targeted/sacred_flame - spellname = "sacred flame" - icon_state ="booksacredflame" - desc = "Become one with the flames that burn within... and invite others to do so as well." - remarks = list("Well, it's one way to stop an attacker...", "I'm gonna need some good gear to stop myself from burning to death...", "Keep a fire extinguisher handy, got it...", "I think I just burned my hand...", "Apply flame directly to chest for proper ignition...", "No pain, no gain...", "One with the flame...") - -/obj/item/book/granter/spell/smoke - spell = /obj/effect/proc_holder/spell/targeted/smoke - spellname = "smoke" - icon_state ="booksmoke" - desc = "This book is overflowing with the dank arts." - remarks = list("Smoke Bomb! Heh...", "Smoke bomb would do just fine too...", "Wait, there's a machine that does the same thing in chemistry?", "This book smells awful...", "Why all these weed jokes? Just tell me how to cast it...", "Wind will ruin the whole spell, good thing we're in space... Right?", "So this is how the spider clan does it...") - -/obj/item/book/granter/spell/smoke/lesser //Chaplain smoke book - spell = /obj/effect/proc_holder/spell/targeted/smoke/lesser - -/obj/item/book/granter/spell/smoke/recoil(mob/user) - ..() - to_chat(user,"Your stomach rumbles...") - if(user.nutrition) - user.set_nutrition(200) - if(user.nutrition <= 0) - user.set_nutrition(0) - -/obj/item/book/granter/spell/blind - spell = /obj/effect/proc_holder/spell/targeted/blind - spellname = "blind" - icon_state ="bookblind" - desc = "This book looks blurry, no matter how you look at it." - remarks = list("Well I can't learn anything if I can't read the damn thing!", "Why would you use a dark font on a dark background...", "Ah, I can't see an- Oh, I'm fine...", "I can't see my hand...!", "I'm manually blinking, damn you book...", "I can't read this page, but somehow I feel like I learned something from it...", "Hey, who turned off the lights?") - -/obj/item/book/granter/spell/blind/recoil(mob/user) - ..() - to_chat(user,"You go blind!") - user.adjust_blindness(10) - -/obj/item/book/granter/spell/mindswap - spell = /obj/effect/proc_holder/spell/targeted/mind_transfer - spellname = "mindswap" - icon_state ="bookmindswap" - desc = "This book's cover is pristine, though its pages look ragged and torn." - var/mob/stored_swap //Used in used book recoils to store an identity for mindswaps - remarks = list("If you mindswap from a mouse, they will be helpless when you recover...", "Wait, where am I...?", "This book is giving me a horrible headache...", "This page is blank, but I feel words popping into my head...", "GYNU... GYRO... Ugh...", "The voices in my head need to stop, I'm trying to read here...", "I don't think anyone will be happy when I cast this spell...") - -/obj/item/book/granter/spell/mindswap/onlearned() - spellname = pick("fireball","smoke","blind","forcewall","knock","barnyard","charge") - icon_state = "book[spellname]" - name = "spellbook of [spellname]" //Note, desc doesn't change by design - ..() - -/obj/item/book/granter/spell/mindswap/recoil(mob/user) - ..() - if(stored_swap in GLOB.dead_mob_list) - stored_swap = null - if(!stored_swap) - stored_swap = user - to_chat(user,"For a moment you feel like you don't even know who you are anymore.") - return - if(stored_swap == user) - to_chat(user,"You stare at the book some more, but there doesn't seem to be anything else to learn...") - return - var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new - if(swapper.cast(list(stored_swap), user, TRUE, TRUE)) - to_chat(user,"You're suddenly somewhere else... and someone else?!") - to_chat(stored_swap,"Suddenly you're staring at [src] again... where are you, who are you?!") - else - user.visible_message("[src] fizzles slightly as it stops glowing!") //if the mind_transfer failed to transfer mobs, likely due to the target being catatonic. - - stored_swap = null - -/obj/item/book/granter/spell/forcewall - spell = /obj/effect/proc_holder/spell/targeted/forcewall - spellname = "forcewall" - icon_state ="bookforcewall" - desc = "This book has a dedication to mimes everywhere inside the front cover." - remarks = list("I can go through the wall! Neat.", "Why are there so many mime references...?", "This would cause much grief in a hallway...", "This is some surprisingly strong magic to create a wall nobody can pass through...", "Why the dumb stance? It's just a flick of the hand...", "Why are the pages so hard to turn, is this even paper?", "I can't mo Oh, i'm fine...") - -/obj/item/book/granter/spell/forcewall/recoil(mob/living/user) - ..() - to_chat(user,"You suddenly feel very solid!") - user.Stun(40, ignore_canstun = TRUE) - user.petrify(60) - -/obj/item/book/granter/spell/knock - spell = /obj/effect/proc_holder/spell/aoe_turf/knock - spellname = "knock" - icon_state ="bookknock" - desc = "This book is hard to hold closed properly." - remarks = list("Open Sesame!", "So THAT'S the magic password!", "Slow down, book. I still haven't finished this page...", "The book won't stop moving!", "I think this is hurting the spine of the book...", "I can't get to the next page, it's stuck t- I'm good, it just turned to the next page on it's own.", "Yeah, staff of doors does the same thing. Go figure...") - -/obj/item/book/granter/spell/knock/recoil(mob/living/user) - ..() - to_chat(user,"You're knocked down!") - user.Paralyze(40) - -/obj/item/book/granter/spell/barnyard - spell = /obj/effect/proc_holder/spell/targeted/barnyardcurse - spellname = "barnyard" - icon_state ="bookhorses" - desc = "This book is more horse than your mind has room for." - remarks = list("Moooooooo!","Moo!","Moooo!", "NEEIIGGGHHHH!", "NEEEIIIIGHH!", "NEIIIGGHH!", "HAAWWWWW!", "HAAAWWW!", "Oink!", "Squeeeeeeee!", "Oink Oink!", "Ree!!", "Reee!!", "REEE!!", "REEEEE!!") - -/obj/item/book/granter/spell/barnyard/recoil(mob/living/carbon/user) - if(ishuman(user)) - to_chat(user,"HORSIE HAS RISEN") - var/obj/item/clothing/magichead = new /obj/item/clothing/mask/horsehead/cursed(user.drop_location()) - if(!user.dropItemToGround(user.wear_mask)) - qdel(user.wear_mask) - user.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, TRUE, TRUE) - qdel(src) - else - to_chat(user,"I say thee neigh") //It still lives here - -/obj/item/book/granter/spell/charge - spell = /obj/effect/proc_holder/spell/targeted/charge - spellname = "charge" - icon_state ="bookcharge" - desc = "This book is made of 100% postconsumer wizard." - remarks = list("I feel ALIVE!", "I CAN TASTE THE MANA!", "What a RUSH!", "I'm FLYING through these pages!", "THIS GENIUS IS MAKING IT!", "This book is ACTION PAcKED!", "HE'S DONE IT", "LETS GOOOOOOOOOOOO") - -/obj/item/book/granter/spell/charge/recoil(mob/user) - ..() - to_chat(user,"[src] suddenly feels very warm!") - empulse(src, 1, 1, magic=TRUE) - -/obj/item/book/granter/spell/summonitem - spell = /obj/effect/proc_holder/spell/targeted/summonitem - spellname = "instant summons" - icon_state ="booksummons" - desc = "This book is bright and garish, very hard to miss." - remarks = list("I can't look away from the book!", "The words seem to pop around the page...", "I just need to focus on one item...", "Make sure to have a good grip on it when casting...", "Slow down, book. I still haven't finished this page...", "Sounds pretty great with some other magical artifacts...", "Magicians must love this one.") - -/obj/item/book/granter/spell/summonitem/recoil(mob/user) - ..() - to_chat(user,"[src] suddenly vanishes!") - qdel(src) - -/obj/item/book/granter/spell/random - icon_state = "random_book" - -/obj/item/book/granter/spell/random/Initialize(mapload) - . = ..() - var/static/banned_spells = list(/obj/item/book/granter/spell/mimery_blockade, /obj/item/book/granter/spell/mimery_guns) - var/real_type = pick(subtypesof(/obj/item/book/granter/spell) - banned_spells) - new real_type(loc) - return INITIALIZE_HINT_QDEL - -///MARTIAL ARTS/// - -/obj/item/book/granter/martial - var/martial - var/martialname = "bug jitsu" - var/greet = "You feel like you have mastered the art in breaking code. Nice work, jackass." - - -/obj/item/book/granter/martial/already_known(mob/user) - if(!martial) - return TRUE - var/datum/martial_art/MA = martial - if(user.mind.has_martialart(initial(MA.id))) - to_chat(user,"You already know [martialname]!") - return TRUE - return FALSE - -/obj/item/book/granter/martial/on_reading_start(mob/user) - to_chat(user, "You start reading about [martialname]...") - -/obj/item/book/granter/martial/on_reading_finished(mob/user) - to_chat(user, "[greet]") - var/datum/martial_art/MA = new martial - MA.teach(user) - user.log_message("learned the martial art [martialname] ([MA])", LOG_ATTACK, color="orange") - onlearned(user) - -/obj/item/book/granter/martial/cqc - martial = /datum/martial_art/cqc - name = "old manual" - martialname = "close quarters combat" - desc = "A small, black manual. There are drawn instructions of tactical hand-to-hand combat." - greet = "You've mastered the basics of CQC." - icon_state = "cqcmanual" - remarks = list("Kick... Slam...", "Lock... Kick...", "Strike their abdomen, neck and back for critical damage...", "Slam... Lock...", "I could probably combine this with some other martial arts!", "Words that kill...", "The last and final moment is yours...") - -/obj/item/book/granter/martial/cqc/onlearned(mob/living/carbon/user) - ..() - if(oneuse == TRUE) - to_chat(user, "[src] beeps ominously...") - -/obj/item/book/granter/martial/cqc/recoil(mob/living/carbon/user) - to_chat(user, "[src] explodes!") - playsound(src,'sound/effects/explosion1.ogg',40,1) - user.flash_act(1, 1) - user.adjustBruteLoss(6) - user.adjustFireLoss(6) - qdel(src) - -/obj/item/book/granter/martial/carp - martial = /datum/martial_art/the_sleeping_carp - name = "mysterious scroll" - martialname = "sleeping carp" - desc = "A scroll filled with strange markings. It seems to be drawings of some sort of martial art." - greet = "You have learned the ancient martial art of the Sleeping Carp! Your hand-to-hand combat has become much more effective, and you are now able to deflect any projectiles \ - directed toward you. However, you are also unable to use any ranged weaponry. You can learn more about your newfound art by using the Recall Teachings verb in the Sleeping Carp tab." - icon = 'icons/obj/wizard.dmi' - icon_state = "scroll2" - remarks = list("I must prove myself worthy to the masters of the sleeping carp...", "Stance means everything...", "Focus... And you'll be able to incapacitate any foe in seconds...", "I must pierce armor for maximum damage...", "I don't think this would combine with other martial arts...", "Grab them first so they don't retaliate...", "I must prove myself worthy of this power...") - -/obj/item/book/granter/martial/carp/onlearned(mob/living/carbon/user) - ..() - if(oneuse == TRUE) - desc = "It's completely blank." - name = "empty scroll" - icon_state = "blankscroll" - -/obj/item/book/granter/martial/tribal_claw - martial = /datum/martial_art/tribal_claw - name = "old scroll" - martialname = "tribal claw" - desc = "A scroll filled with ancient draconic markings." - greet = "You have learned the ancient martial art of the Tribal Claw! You are now able to use your tail and claws in a fight much better than before. \ - Check the combos you are now able to perform using the Recall Teachings verb in the Tribal Claw tab" - icon = 'icons/obj/wizard.dmi' - icon_state = "scroll2" - remarks = list("I must prove myself worthy to the masters of the Knoises clan...", "Use your tail to surprise any enemy...", "Your sharp claws can disorient them...", "I don't think this would combine with other martial arts...", "Ooga Booga...") - -/obj/item/book/granter/martial/tribal_claw/onlearned(mob/living/carbon/user) - ..() - if(!oneuse) - return - desc = "It's completely blank." - name = "empty scroll" - icon_state = "blankscroll" - -/obj/item/book/granter/martial/tribal_claw/already_known(mob/user) - if(islizard(user)) - return FALSE - else - to_chat(user, "You try to read the scroll but can't comprehend any of it.") - return TRUE - -/obj/item/book/granter/martial/plasma_fist - martial = /datum/martial_art/plasma_fist - name = "frayed scroll" - martialname = "plasma fist" - desc = "An aged and frayed scrap of paper written in shifting runes. There are hand-drawn illustrations of pugilism." - greet = "You have learned the ancient martial art of Plasma Fist. Your combos are extremely hard to pull off, but include some of the most deadly moves ever seen including \ - the plasma fist, which when pulled off will make someone violently explode." - icon = 'icons/obj/wizard.dmi' - icon_state ="scroll2" - remarks = list("Balance...", "Power...", "Control...", "Mastery...", "Vigilance...", "Skill...") - -/obj/item/book/granter/martial/plasma_fist/onlearned(mob/living/carbon/user) - ..() - if(oneuse == TRUE) - desc = "It's completely blank." - name = "empty scroll" - icon_state = "blankscroll" - -/obj/item/book/granter/martial/karate - martial = /datum/martial_art/karate - name = "dusty scroll" - martialname = "karate" - desc = "A dusty scroll filled with martial lessons. There seems to be drawings of some sort of martial art." - greet = "You have learned the ancient martial art of Karate! Your hand-to-hand combat has become more effective but require skill to combo effectively.\ - You can learn more about your newfound art by using the Recall Teachings verb in the Karate tab." - icon = 'icons/obj/wizard.dmi' - icon_state = "scroll2" - remarks = list("I must prove myself worthy to the masters of Karate...", "Disable their legs so they can't escape...", "Strike at pressure points to daze my foes...", "Stomp their head for maximum damage...", "I don't think this would combine with other martial arts...", "Wind them with a flying knee...", "I must practice to fully grasp these teachings...") - -/obj/item/book/granter/martial/karate/onlearned(mob/living/carbon/user) - ..() - if(oneuse == TRUE) - desc = "It's completely blank." - name = "empty scroll" - icon_state = "blankscroll" - -// I did not include mushpunch's grant, it is not a book and the item does it just fine. - -//Crafting Recipe books - -/obj/item/book/granter/crafting_recipe - var/list/crafting_recipe_types = list() - -/obj/item/book/granter/crafting_recipe/on_reading_finished(mob/user) - . = ..() - if(!user.mind) - return - for(var/crafting_recipe_type in crafting_recipe_types) - var/datum/crafting_recipe/R = crafting_recipe_type - user.mind.teach_crafting_recipe(crafting_recipe_type) - to_chat(user,"You learned how to make [initial(R.name)].") - -/obj/item/book/granter/crafting_recipe/cooking_sweets_101 - name = "Cooking Desserts 101" - desc = "A cook book that teaches you some more of the newest desserts. AI approved, and a best seller on Honkplanet." - crafting_recipe_types = list( - /datum/crafting_recipe/food/mimetart, - /datum/crafting_recipe/food/berrytart, - /datum/crafting_recipe/food/cocoalavatart, - /datum/crafting_recipe/food/clowncake, - /datum/crafting_recipe/food/vanillacake - ) - icon_state = "cooking_learing_sweets" - oneuse = FALSE - remarks = list("So that is how icing is made!", "Placing fruit on top? How simple...", "Huh layering cake seems harder then this...", "This book smells like candy", "A clown must have made this page, or they forgot to spell check it before printing...", "Wait, a way to cook slime to be safe?") diff --git a/code/game/objects/items/granters/_granters.dm b/code/game/objects/items/granters/_granters.dm new file mode 100644 index 0000000000000..5078cd89d56b9 --- /dev/null +++ b/code/game/objects/items/granters/_granters.dm @@ -0,0 +1,106 @@ +/** + * Books that teach things. + * + * (Intrinsic actions like bar flinging, spells like fireball or smoke, or martial arts) + */ +/obj/item/book/granter + due_date = 0 + unique = 1 + /// Flavor messages displayed to mobs reading the granter + var/list/remarks = list() + /// Controls how long a mob must keep the book in his hand to actually successfully learn + var/pages_to_mastery = 3 + /// Sanity, whether it's currently being read + var/reading = FALSE + /// The amount of uses on the granter. + var/uses = 1 + /// The sounds played as the user's reading the book. + var/list/book_sounds = list( + 'sound/effects/pageturn1.ogg', + 'sound/effects/pageturn2.ogg', + 'sound/effects/pageturn3.ogg', + ) + +/obj/item/book/granter/attack_self(mob/living/user) + if(reading) + to_chat(user, ("You're already reading this!")) + return FALSE + if(user.is_blind()) + to_chat(user, ("You are blind and can't read anything!")) + return FALSE + if(!isliving(user) || !user.can_read(src)) + return FALSE + if(!can_learn(user)) + return FALSE + + if(uses <= 0) + recoil(user) + return FALSE + + on_reading_start(user) + reading = TRUE + for(var/i in 1 to pages_to_mastery) + if(!turn_page(user)) + on_reading_stopped() + reading = FALSE + return + if(do_after(user, 5 SECONDS, src)) + uses-- + on_reading_finished(user) + reading = FALSE + + return TRUE + +/// Called when the user starts to read the granter. +/obj/item/book/granter/proc/on_reading_start(mob/living/user) + to_chat(user, ("You start reading [name]...")) + +/// Called when the reading is interrupted without finishing. +/obj/item/book/granter/proc/on_reading_stopped(mob/living/user) + to_chat(user, ("You stop reading...")) + +/// Called when the reading is completely finished. This is where the actual granting should happen. +/obj/item/book/granter/proc/on_reading_finished(mob/living/user) + to_chat(user, ("You finish reading [name]!")) + +/// The actual "turning over of the page" flavor bit that happens while someone is reading the granter. +/obj/item/book/granter/proc/turn_page(mob/living/user) + playsound(user, pick(book_sounds), 30, TRUE) + + if(!do_after(user, 5 SECONDS, src)) + return FALSE + + to_chat(user, ("[length(remarks) ? pick(remarks) : "You keep reading..."]")) + return TRUE + +/// Effects that occur whenever the book is read when it has no uses left. +/obj/item/book/granter/proc/recoil(mob/living/user) + +/// Checks if the user can learn whatever this granter... grants +/obj/item/book/granter/proc/can_learn(mob/living/user) + return TRUE + +// Generic action giver +/obj/item/book/granter/action + /// The typepath of action that is given + var/datum/action/granted_action + /// The name of the action, formatted in a more text-friendly way. + var/action_name = "" + +/obj/item/book/granter/action/can_learn(mob/living/user) + if(!granted_action) + CRASH("Someone attempted to learn [type], which did not have an action set.") + if(locate(granted_action) in user.actions) + to_chat(user, ("You already know all about [action_name]!")) + return FALSE + return TRUE + +/obj/item/book/granter/action/on_reading_start(mob/living/user) + to_chat(user, ("You start reading about [action_name]...")) + +/obj/item/book/granter/action/on_reading_finished(mob/living/user) + to_chat(user, ("You feel like you've got a good handle on [action_name]!")) + // Action goes on the mind as the user actually learns the thing in your brain + var/datum/action/new_action = new granted_action(user.mind || user) + new_action.Grant(user) + new_action.update_buttons() diff --git a/code/game/objects/items/granters/crafting/_crafting_granter.dm b/code/game/objects/items/granters/crafting/_crafting_granter.dm new file mode 100644 index 0000000000000..4c59aee6c44dd --- /dev/null +++ b/code/game/objects/items/granters/crafting/_crafting_granter.dm @@ -0,0 +1,11 @@ +/obj/item/book/granter/crafting_recipe + /// A list of all recipe types we grant on learn + var/list/crafting_recipe_types = list() + +/obj/item/book/granter/crafting_recipe/on_reading_finished(mob/user) + . = ..() + if(!user.mind) + return + for(var/datum/crafting_recipe/crafting_recipe_type as anything in crafting_recipe_types) + user.mind.teach_crafting_recipe(crafting_recipe_type) + to_chat(user, ("You learned how to make [initial(crafting_recipe_type.name)].")) diff --git a/code/game/objects/items/granters/crafting/desserts.dm b/code/game/objects/items/granters/crafting/desserts.dm new file mode 100644 index 0000000000000..d508ff9c0e883 --- /dev/null +++ b/code/game/objects/items/granters/crafting/desserts.dm @@ -0,0 +1,19 @@ +/obj/item/book/granter/crafting_recipe/cooking_sweets_101 + name = "Cooking Desserts 101" + desc = "A cook book that teaches you some more of the newest desserts. AI approved, and a best seller on Honkplanet." + crafting_recipe_types = list( + /datum/crafting_recipe/food/mimetart, + /datum/crafting_recipe/food/berrytart, + /datum/crafting_recipe/food/clowncake, + /datum/crafting_recipe/food/vanillacake + ) + icon_state = "cooking_learing_sweets" + uses = INFINITY + remarks = list( + "So that is how icing is made!", + "Placing fruit on top? How simple...", + "Huh layering cake seems harder then this...", + "This book smells like candy", + "A clown must have made this page, or they forgot to spell check it before printing...", + "Wait, a way to cook slime to be safe?", + ) diff --git a/code/game/objects/items/granters/magic/_spell_granter.dm b/code/game/objects/items/granters/magic/_spell_granter.dm new file mode 100644 index 0000000000000..60191b370ab82 --- /dev/null +++ b/code/game/objects/items/granters/magic/_spell_granter.dm @@ -0,0 +1,93 @@ +/obj/item/book/granter/action/spell + +/obj/item/book/granter/action/spell/Initialize(mapload) + . = ..() + RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge)) + +/** + * Signal proc for [COMSIG_ITEM_MAGICALLY_CHARGED] + * + * Refreshes uses on our spell granter, or make it quicker to read if it's already infinite use + */ +/obj/item/book/granter/action/spell/proc/on_magic_charge(datum/source, datum/action/spell/spell, mob/living/caster) + SIGNAL_HANDLER + + // What're the odds someone uses 2000 uses of an infinite use book? + if(uses >= INFINITY - 2000) + to_chat(caster, ("This book is infinite use and can't be recharged, \ + yet the magic has improved it somehow...")) + pages_to_mastery = max(pages_to_mastery - 1, 1) + return COMPONENT_ITEM_CHARGED|COMPONENT_ITEM_BURNT_OUT + + if(prob(80)) + caster.dropItemToGround(src, TRUE) + visible_message(("[src] catches fire and burns to ash!")) + new /obj/effect/decal/cleanable/ash(drop_location()) + qdel(src) + return COMPONENT_ITEM_BURNT_OUT + + uses++ + return COMPONENT_ITEM_CHARGED + +/obj/item/book/granter/action/spell/can_learn(mob/living/user) + if(!granted_action) + CRASH("Someone attempted to learn [type], which did not have an spell set.") + if(locate(granted_action) in user.actions) + if(IS_WIZARD(user)) + to_chat(user, ("You're already far more versed in the spell [action_name] \ + than this flimsy how-to book can provide!")) + else + to_chat(user, ("You've already know the spell [action_name]!")) + return FALSE + return TRUE + +/obj/item/book/granter/action/spell/on_reading_start(mob/living/user) + to_chat(user, ("You start reading about casting [action_name]...")) + +/obj/item/book/granter/action/spell/on_reading_finished(mob/living/user) + to_chat(user, ("You feel like you've experienced enough to cast [action_name]!")) + var/datum/action/spell/new_spell = new granted_action(user.mind || user) + new_spell.Grant(user) + user.log_message("learned the spell [action_name] ([new_spell])", LOG_ATTACK, color = "orange") + if(uses <= 0) + user.visible_message(("[src] glows dark for a second!")) + +/obj/item/book/granter/action/spell/recoil(mob/living/user) + user.visible_message(("[src] glows in a black light!")) + +/// Simple granter that's replaced with a random spell granter on Initialize. +/obj/item/book/granter/action/spell/random + icon_state = "random_book" + +/obj/item/book/granter/action/spell/random/Initialize(mapload) + . = ..() + var/static/list/banned_spells = list( + /obj/item/book/granter/action/spell/true_random, + ) + typesof(/obj/item/book/granter/action/spell/mime) + + var/real_type = pick(subtypesof(/obj/item/book/granter/action/spell) - banned_spells) + new real_type(loc) + + return INITIALIZE_HINT_QDEL + +/// A more volatile granter that can potentially have any spell within. Use wisely. +/obj/item/book/granter/action/spell/true_random + icon_state = "random_book" + desc = "You feel as if anything could be gained from this book." + /// A list of schools we probably shouldn't grab, for various reasons + var/static/list/blacklisted_schools = list(SCHOOL_UNSET, SCHOOL_HOLY, SCHOOL_MIME) + +/obj/item/book/granter/action/spell/true_random/Initialize(mapload) + . = ..() + + var/static/list/spell_options + if(!spell_options) + spell_options = subtypesof(/datum/action/spell) + for(var/datum/action/spell/spell as anything in spell_options) + if(initial(spell.school) in blacklisted_schools) + spell_options -= spell + if(initial(spell.name) == "Spell") // Abstract types + spell_options -= spell + + granted_action = pick(spell_options) + action_name = LOWER_TEXT(initial(granted_action.name)) diff --git a/code/game/objects/items/granters/magic/barnyard.dm b/code/game/objects/items/granters/magic/barnyard.dm new file mode 100644 index 0000000000000..a396b8de9da9c --- /dev/null +++ b/code/game/objects/items/granters/magic/barnyard.dm @@ -0,0 +1,34 @@ +/obj/item/book/granter/action/spell/barnyard + granted_action = /datum/action/spell/pointed/barnyardcurse + action_name = "barnyard" + icon_state ="bookhorses" + desc = "This book is more horse than your mind has room for." + remarks = list( + "Moooooooo!", + "Moo!", + "Moooo!", + "NEEIIGGGHHHH!", + "NEEEIIIIGHH!", + "NEIIIGGHH!", + "HAAWWWWW!", + "HAAAWWW!", + "Oink!", + "Squeeeeeeee!", + "Oink Oink!", + "Ree!!", + "Reee!!", + "REEE!!", + "REEEEE!!", + ) + +/obj/item/book/granter/action/spell/barnyard/recoil(mob/living/user) + if(ishuman(user)) + to_chat(user, "HORSIE HAS RISEN") + var/obj/item/clothing/magic_mask = new /obj/item/clothing/mask/horsehead/cursed(user.drop_location()) + var/mob/living/carbon/human/human_user = user + if(!user.dropItemToGround(human_user.wear_mask)) + qdel(human_user.wear_mask) + user.equip_to_slot_if_possible(magic_mask, ITEM_SLOT_MASK, TRUE, TRUE) + qdel(src) + else + to_chat(user,("I say thee neigh")) //It still lives here diff --git a/code/game/objects/items/granters/magic/blind.dm b/code/game/objects/items/granters/magic/blind.dm new file mode 100644 index 0000000000000..06eb8f8ef2799 --- /dev/null +++ b/code/game/objects/items/granters/magic/blind.dm @@ -0,0 +1,19 @@ +/obj/item/book/granter/action/spell/blind + granted_action = /datum/action/spell/pointed/blind + action_name = "blind" + icon_state = "bookblind" + desc = "This book looks blurry, no matter how you look at it." + remarks = list( + "Well I can't learn anything if I can't read the damn thing!", + "Why would you use a dark font on a dark background...", + "Ah, I can't see an Oh, I'm fine...", + "I can't see my hand...!", + "I'm manually blinking, damn you book...", + "I can't read this page, but somehow I feel like I learned something from it...", + "Hey, who turned off the lights?", + ) + +/obj/item/book/granter/action/spell/blind/recoil(mob/living/user) + . = ..() + to_chat(user, ("You go blind!")) + user.set_blindness(100) diff --git a/code/game/objects/items/granters/magic/charge.dm b/code/game/objects/items/granters/magic/charge.dm new file mode 100644 index 0000000000000..bcd6d086046d9 --- /dev/null +++ b/code/game/objects/items/granters/magic/charge.dm @@ -0,0 +1,20 @@ +/obj/item/book/granter/action/spell/charge + granted_action = /datum/action/spell/charge + action_name = "charge" + icon_state ="bookcharge" + desc = "This book is made of 100% postconsumer wizard." + remarks = list( + "I feel ALIVE!", + "I CAN TASTE THE MANA!", + "What a RUSH!", + "I'm FLYING through these pages!", + "THIS GENIUS IS MAKING IT!", + "This book is ACTION PAcKED!", + "HE'S DONE IT", + "LETS GOOOOOOOOOOOO", + ) + +/obj/item/book/granter/action/spell/charge/recoil(mob/living/user) + . = ..() + to_chat(user,("[src] suddenly feels very warm!")) + empulse(src, 1, 1) diff --git a/code/game/objects/items/granters/magic/fireball.dm b/code/game/objects/items/granters/magic/fireball.dm new file mode 100644 index 0000000000000..4337f1a4363c2 --- /dev/null +++ b/code/game/objects/items/granters/magic/fireball.dm @@ -0,0 +1,26 @@ +/obj/item/book/granter/action/spell/fireball + granted_action = /datum/action/spell/pointed/projectile/fireball + action_name = "fireball" + icon_state ="bookfireball" + desc = "This book feels warm to the touch." + remarks = list( + "Aim...AIM, FOOL!", + "Just catching them on fire won't do...", + "Accounting for crosswinds... really?", + "I think I just burned my hand...", + "Why the dumb stance? It's just a flick of the hand...", + "OMEE... ONI... Ugh...", + "What's the difference between a fireball and a pyroblast...", + ) + +/obj/item/book/granter/action/spell/fireball/recoil(mob/living/user) + . = ..() + explosion( + user, + devastation_range = 1, + light_impact_range = 2, + flame_range = 2, + flash_range = 3, + adminlog = FALSE, + ) + qdel(src) diff --git a/code/game/objects/items/granters/magic/forcewall.dm b/code/game/objects/items/granters/magic/forcewall.dm new file mode 100644 index 0000000000000..5ac7cef9acd5a --- /dev/null +++ b/code/game/objects/items/granters/magic/forcewall.dm @@ -0,0 +1,20 @@ +/obj/item/book/granter/action/spell/forcewall + granted_action = /datum/action/spell/forcewall + action_name = "forcewall" + icon_state ="bookforcewall" + desc = "This book has a dedication to mimes everywhere inside the front cover." + remarks = list( + "I can go through the wall! Neat.", + "Why are there so many mime references...?", + "This would cause much grief in a hallway...", + "This is some surprisingly strong magic to create a wall nobody can pass through...", + "Why the dumb stance? It's just a flick of the hand...", + "Why are the pages so hard to turn, is this even paper?", + "I can't mo Oh, i'm fine...", + ) + +/obj/item/book/granter/action/spell/forcewall/recoil(mob/living/user) + . = ..() + to_chat(user, ("You suddenly feel very solid!")) + user.Stun(4 SECONDS, ignore_canstun = TRUE) + user.petrify(6 SECONDS) diff --git a/code/game/objects/items/granters/magic/knock.dm b/code/game/objects/items/granters/magic/knock.dm new file mode 100644 index 0000000000000..8889b955ad6d2 --- /dev/null +++ b/code/game/objects/items/granters/magic/knock.dm @@ -0,0 +1,19 @@ +/obj/item/book/granter/action/spell/knock + granted_action = /datum/action/spell/aoe/knock + action_name = "knock" + icon_state ="bookknock" + desc = "This book is hard to hold closed properly." + remarks = list( + "Open Sesame!", + "So THAT'S the magic password!", + "Slow down, book. I still haven't finished this page...", + "The book won't stop moving!", + "I think this is hurting the spine of the book...", + "I can't get to the next page, it's stuck t- I'm good, it just turned to the next page on it's own.", + "Yeah, staff of doors does the same thing. Go figure...", + ) + +/obj/item/book/granter/action/spell/knock/recoil(mob/living/user) + . = ..() + to_chat(user, ("You're knocked down!")) + user.Paralyze(4 SECONDS) diff --git a/code/game/objects/items/granters/magic/mime.dm b/code/game/objects/items/granters/magic/mime.dm new file mode 100644 index 0000000000000..ba6c53ab25b0b --- /dev/null +++ b/code/game/objects/items/granters/magic/mime.dm @@ -0,0 +1,28 @@ +/obj/item/book/granter/action/spell/mime + name = "Guide to Mimery Vol 0" + desc = "The missing entry into the legendary saga. Unfortunately it doesn't teach you anything." + icon_state ="bookmime" + remarks = list("...") + +/obj/item/book/granter/action/spell/mime/attack_self(mob/user) + . = ..() + if(!.) + return + + // Gives the user a vow ability if they don't have one + var/datum/action/spell/vow_of_silence/vow = locate() in user.actions + if(!vow && user.mind) + vow = new(user.mind) + vow.Grant(user) + +/obj/item/book/granter/action/spell/mime/mimery_blockade + granted_action = /datum/action/spell/forcewall/mime + action_name = "Invisible Blockade" + name = "Guide to Advanced Mimery Vol 1" + desc = "The pages don't make any sound when turned." + +/obj/item/book/granter/action/spell/mime/mimery_guns + granted_action = /datum/action/spell/pointed/projectile/finger_guns + action_name = "Finger Guns" + name = "Guide to Advanced Mimery Vol 2" + desc = "There aren't any words written..." diff --git a/code/game/objects/items/granters/magic/mindswap.dm b/code/game/objects/items/granters/magic/mindswap.dm new file mode 100644 index 0000000000000..8b2c415181819 --- /dev/null +++ b/code/game/objects/items/granters/magic/mindswap.dm @@ -0,0 +1,57 @@ +/obj/item/book/granter/action/spell/mindswap + granted_action = /datum/action/spell/pointed/mind_transfer + action_name = "mindswap" + icon_state ="bookmindswap" + desc = "This book's cover is pristine, though its pages look ragged and torn." + remarks = list( + "If you mindswap from a mouse, they will be helpless when you recover...", + "Wait, where am I...?", + "This book is giving me a horrible headache...", + "This page is blank, but I feel words popping into my head...", + "GYNU... GYRO... Ugh...", + "The voices in my head need to stop, I'm trying to read here...", + "I don't think anyone will be happy when I cast this spell...", + ) + /// Mob used in book recoils to store an identity for mindswaps + var/datum/weakref/stored_swap_ref + +/obj/item/book/granter/action/spell/mindswap/on_reading_finished() + . = ..() + visible_message(("[src] begins to shake and shift.")) + action_name = pick( + "fireball", + "smoke", + "blind", + "forcewall", + "knock", + "barnyard", + "charge", + ) + icon_state = "book[action_name]" + name = "spellbook of [action_name]" + +/obj/item/book/granter/action/spell/mindswap/recoil(mob/living/user) + . = ..() + var/mob/living/real_stored_swap = stored_swap_ref?.resolve() + if(QDELETED(real_stored_swap)) + stored_swap_ref = WEAKREF(user) + to_chat(user, ("For a moment you feel like you don't even know who you are anymore.")) + return + if(real_stored_swap.stat == DEAD) + stored_swap_ref = null + return + if(real_stored_swap == user) + to_chat(user, ("You stare at the book some more, but there doesn't seem to be anything else to learn...")) + return + + var/datum/action/spell/pointed/mind_transfer/swapper = new(src) + + if(swapper.swap_minds(user, real_stored_swap)) + to_chat(user, ("You're suddenly somewhere else... and someone else?!")) + to_chat(real_stored_swap, ("Suddenly you're staring at [src] again... where are you, who are you?!")) + + else + // if the mind_transfer failed to transfer mobs (likely due to the target being catatonic). + user.visible_message(("[src] fizzles slightly as it stops glowing!")) + + stored_swap_ref = null diff --git a/code/game/objects/items/granters/magic/sacredflame.dm b/code/game/objects/items/granters/magic/sacredflame.dm new file mode 100644 index 0000000000000..6e725bbba378c --- /dev/null +++ b/code/game/objects/items/granters/magic/sacredflame.dm @@ -0,0 +1,14 @@ +/obj/item/book/granter/action/spell/sacredflame + granted_action = /datum/action/spell/aoe/sacred_flame + action_name = "sacred flame" + icon_state ="booksacredflame" + desc = "Become one with the flames that burn within... and invite others to do so as well." + remarks = list( + "Well, it's one way to stop an attacker...", + "I'm gonna need some good gear to stop myself from burning to death...", + "Keep a fire extinguisher handy, got it...", + "I think I just burned my hand...", + "Apply flame directly to chest for proper ignition...", + "No pain, no gain...", + "One with the flame...", + ) diff --git a/code/game/objects/items/granters/magic/smoke.dm b/code/game/objects/items/granters/magic/smoke.dm new file mode 100644 index 0000000000000..9234015a26305 --- /dev/null +++ b/code/game/objects/items/granters/magic/smoke.dm @@ -0,0 +1,26 @@ +/obj/item/book/granter/action/spell/smoke + granted_action = /datum/action/spell/smoke + action_name = "smoke" + icon_state ="booksmoke" + desc = "This book is overflowing with the dank arts." + remarks = list( + "Smoke Bomb! Heh...", + "Smoke bomb would do just fine too...", + "Wait, there's a machine that does the same thing in chemistry?", + "This book smells awful...", + "Why all these weed jokes? Just tell me how to cast it...", + "Wind will ruin the whole spell, good thing we're in space... Right?", + "So this is how the spider clan does it...", + ) + +/obj/item/book/granter/action/spell/smoke/recoil(mob/living/user) + . = ..() + to_chat(user,("Your stomach rumbles...")) + if(user.nutrition) + user.set_nutrition(200) + if(user.nutrition <= 0) + user.set_nutrition(0) + +// Chaplain's smoke book +/obj/item/book/granter/action/spell/smoke/lesser + granted_action = /datum/action/spell/smoke/lesser diff --git a/code/game/objects/items/granters/magic/summon_item.dm b/code/game/objects/items/granters/magic/summon_item.dm new file mode 100644 index 0000000000000..474dce5344237 --- /dev/null +++ b/code/game/objects/items/granters/magic/summon_item.dm @@ -0,0 +1,19 @@ +/obj/item/book/granter/action/spell/summonitem + granted_action = /datum/action/spell/summonitem + action_name = "instant summons" + icon_state ="booksummons" + desc = "This book is bright and garish, very hard to miss." + remarks = list( + "I can't look away from the book!", + "The words seem to pop around the page...", + "I just need to focus on one item...", + "Make sure to have a good grip on it when casting...", + "Slow down, book. I still haven't finished this page...", + "Sounds pretty great with some other magical artifacts...", + "Magicians must love this one.", + ) + +/obj/item/book/granter/action/spell/summonitem/recoil(mob/living/user) + . = ..() + to_chat(user,("[src] suddenly vanishes!")) + qdel(src) diff --git a/code/game/objects/items/granters/martial_arts/_martial_arts.dm b/code/game/objects/items/granters/martial_arts/_martial_arts.dm new file mode 100644 index 0000000000000..4511157132092 --- /dev/null +++ b/code/game/objects/items/granters/martial_arts/_martial_arts.dm @@ -0,0 +1,24 @@ +/obj/item/book/granter/martial + /// The martial arts type we give + var/datum/martial_art/martial + /// The name of the martial arts, formatted in a more text-friendly way. + var/martial_name = "" + /// The text given to the user when they learn the martial arts + var/greet = "" + +/obj/item/book/granter/martial/can_learn(mob/user) + if(!martial) + CRASH("Someone attempted to learn [type], which did not have a martial arts set.") + if(user.mind.has_martialart(initial(martial.id))) + to_chat(user, ("You already know [martial_name]!")) + return FALSE + return TRUE + +/obj/item/book/granter/martial/on_reading_start(mob/user) + to_chat(user, ("You start reading about [martial_name]...")) + +/obj/item/book/granter/martial/on_reading_finished(mob/user) + to_chat(user, "[greet]") + var/datum/martial_art/martial_to_learn = new martial() + martial_to_learn.teach(user) + user.log_message("learned the martial art [martial_name] ([martial_to_learn])", LOG_ATTACK, color = "orange") diff --git a/code/game/objects/items/granters/martial_arts/cqc.dm b/code/game/objects/items/granters/martial_arts/cqc.dm new file mode 100644 index 0000000000000..12a520d32c25b --- /dev/null +++ b/code/game/objects/items/granters/martial_arts/cqc.dm @@ -0,0 +1,29 @@ +/obj/item/book/granter/martial/cqc + martial = /datum/martial_art/cqc + name = "old manual" + martial_name = "close quarters combat" + desc = "A small, black manual. There are drawn instructions of tactical hand-to-hand combat." + greet = "You've mastered the basics of CQC." + icon_state = "cqcmanual" + remarks = list( + "Kick... Slam...", + "Lock... Kick...", + "Strike their abdomen, neck and back for critical damage...", + "Slam... Lock...", + "I could probably combine this with some other martial arts!", + "Words that kill...", + "The last and final moment is yours...", + ) + +/obj/item/book/granter/martial/cqc/on_reading_finished(mob/living/carbon/user) + . = ..() + if(uses <= 0) + to_chat(user, ("[src] beeps ominously...")) + +/obj/item/book/granter/martial/cqc/recoil(mob/living/user) + to_chat(user, ("[src] explodes!")) + playsound(src,'sound/effects/explosion1.ogg',40,TRUE) + user.flash_act(1, 1) + user.adjustBruteLoss(6) + user.adjustFireLoss(6) + qdel(src) diff --git a/code/game/objects/items/granters/martial_arts/karate.dm b/code/game/objects/items/granters/martial_arts/karate.dm new file mode 100644 index 0000000000000..c98155e7decf5 --- /dev/null +++ b/code/game/objects/items/granters/martial_arts/karate.dm @@ -0,0 +1,17 @@ +/obj/item/book/granter/martial/karate + martial = /datum/martial_art/karate + name = "dusty scroll" + martial_name = "karate" + desc = "A dusty scroll filled with martial lessons. There seems to be drawings of some sort of martial art." + greet = "You have learned the ancient martial art of Karate! Your hand-to-hand combat has become more effective but require skill to combo effectively.\ + You can learn more about your newfound art by using the Recall Teachings verb in the Karate tab." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll2" + remarks = list("I must prove myself worthy to the masters of Karate...", "Disable their legs so they can't escape...", "Strike at pressure points to daze my foes...", "Stomp their head for maximum damage...", "I don't think this would combine with other martial arts...", "Wind them with a flying knee...", "I must practice to fully grasp these teachings...") + +/obj/item/book/granter/martial/karate/on_reading_finished(mob/living/carbon/user) + ..() + if(uses <= 0) + desc = "It's completely blank." + name = "empty scroll" + icon_state = "blankscroll" diff --git a/code/game/objects/items/granters/martial_arts/plasma_fist.dm b/code/game/objects/items/granters/martial_arts/plasma_fist.dm new file mode 100644 index 0000000000000..066a638c5d660 --- /dev/null +++ b/code/game/objects/items/granters/martial_arts/plasma_fist.dm @@ -0,0 +1,32 @@ +/obj/item/book/granter/martial/plasma_fist + martial = /datum/martial_art/plasma_fist + name = "frayed scroll" + martial_name = "plasma fist" + desc = "An aged and frayed scrap of paper written in shifting runes. There are hand-drawn illustrations of pugilism." + greet = "You have learned the ancient martial art of Plasma Fist. Your combos are extremely hard to pull off, but include some of the most deadly moves ever seen including \ + the plasma fist, which when pulled off will make someone violently explode." + icon = 'icons/obj/wizard.dmi' + icon_state ="scroll2" + remarks = list( + "Balance...", + "Power...", + "Control...", + "Mastery...", + "Vigilance...", + "Skill...", + ) + +/obj/item/book/granter/martial/plasma_fist/on_reading_finished(mob/living/carbon/user) + . = ..() + update_appearance() + +/obj/item/book/granter/martial/plasma_fist/update_appearance(updates) + . = ..() + if(uses <= 0) + name = "empty scroll" + desc = "It's completely blank." + icon_state = "blankscroll" + else + name = initial(name) + desc = initial(desc) + icon_state = initial(icon_state) diff --git a/code/game/objects/items/granters/martial_arts/sleeping_carp.dm b/code/game/objects/items/granters/martial_arts/sleeping_carp.dm new file mode 100644 index 0000000000000..c50a062eae5d8 --- /dev/null +++ b/code/game/objects/items/granters/martial_arts/sleeping_carp.dm @@ -0,0 +1,35 @@ +/obj/item/book/granter/martial/carp + martial = /datum/martial_art/the_sleeping_carp + name = "mysterious scroll" + martial_name = "sleeping carp" + desc = "A scroll filled with strange markings. It seems to be drawings of some sort of martial art." + greet = "You have learned the ancient martial art of the Sleeping Carp! Your hand-to-hand combat has become much more effective, and you are now able to deflect any projectiles \ + directed toward you while in Throw Mode. Your body has also hardened itself, granting extra protection against lasting wounds that would otherwise mount during extended combat. \ + However, you are also unable to use any ranged weaponry. You can learn more about your newfound art by using the Recall Teachings verb in the Sleeping Carp tab." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll2" + worn_icon_state = "scroll" + remarks = list( + "Wait, a high protein diet is really all it takes to become stabproof...?", + "Overwhelming force, immovable object...", + "Focus... And you'll be able to incapacitate any foe in seconds...", + "I must pierce armor for maximum damage...", + "I don't think this would combine with other martial arts...", + "Become one with the carp...", + "Glub...", + ) + +/obj/item/book/granter/martial/carp/on_reading_finished(mob/living/carbon/user) + . = ..() + update_appearance() + +/obj/item/book/granter/martial/carp/update_appearance(updates) + . = ..() + if(uses <= 0) + name = "empty scroll" + desc = "It's completely blank." + icon_state = "blankscroll" + else + name = initial(name) + desc = initial(desc) + icon_state = initial(icon_state) diff --git a/code/game/objects/items/granters/martial_arts/tribal_claw.dm b/code/game/objects/items/granters/martial_arts/tribal_claw.dm new file mode 100644 index 0000000000000..bd29667ee830c --- /dev/null +++ b/code/game/objects/items/granters/martial_arts/tribal_claw.dm @@ -0,0 +1,29 @@ +/obj/item/book/granter/martial/tribal_claw + martial = /datum/martial_art/tribal_claw + name = "mysterious scroll" + martial_name = "tribal claw" + desc = "A scroll filled with strange markings. It seems to be drawings of some sort of martial art." + greet = "You have learned the ancient martial art of the Sleeping Carp! Your hand-to-hand combat has become much more effective, and you are now able to deflect any projectiles \ + directed toward you while in Throw Mode. Your body has also hardened itself, granting extra protection against lasting wounds that would otherwise mount during extended combat. \ + However, you are also unable to use any ranged weaponry. You can learn more about your newfound art by using the Recall Teachings verb in the Sleeping Carp tab." + icon = 'icons/obj/wizard.dmi' + icon_state = "scroll2" + worn_icon_state = "scroll" + remarks = list( + "Something something lizard" //Pretty please helpsies with this + ) + +/obj/item/book/granter/martial/tribal_claw/on_reading_finished(mob/living/carbon/user) + . = ..() + update_appearance() + +/obj/item/book/granter/martial/tribal_claw/update_appearance(updates) + . = ..() + if(uses <= 0) + name = "empty scroll" + desc = "It's completely blank." + icon_state = "blankscroll" + else + name = initial(name) + desc = initial(desc) + icon_state = initial(icon_state) diff --git a/code/game/objects/items/granters/oragami.dm b/code/game/objects/items/granters/oragami.dm new file mode 100644 index 0000000000000..1065387d6bd9f --- /dev/null +++ b/code/game/objects/items/granters/oragami.dm @@ -0,0 +1,33 @@ +/obj/item/book/granter/action/origami + granted_action = /datum/action/innate/origami + name = "The Art of Origami" + desc = "A meticulously in-depth manual explaining the art of paper folding." + icon_state = "origamibook" + action_name = "origami" + remarks = list( + "Dead-stick stability...", + "Symmetry seems to play a rather large factor...", + "Accounting for crosswinds... really?", + "Drag coefficients of various paper types...", + "Thrust to weight ratios?", + "Positive dihedral angle?", + "Center of gravity forward of the center of lift...", + ) + +/datum/action/innate/origami + name = "Origami Folding" + desc = "Toggles your ability to fold and catch robust paper airplanes." + button_icon_state = "origami_off" + check_flags = NONE + +/datum/action/innate/origami/on_activate() + to_chat(owner, "You will now fold origami planes.") + button_icon_state = "origami_on" + active = TRUE + update_buttons() + +/datum/action/innate/origami/on_deactivate(mob/user, atom/target) + to_chat(owner, "You will no longer fold origami planes.") + button_icon_state = "origami_off" + active = FALSE + update_buttons() diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index 3575bc0699e0f..b09adf5ee3f77 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -2,11 +2,11 @@ /obj/item/clothing/suit/chaplainsuit/armor/templar/Initialize(mapload) . = ..() - AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, TRUE, null, FALSE) + AddComponent(/datum/component/anti_magic, INNATE_TRAIT, MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY) /obj/item/clothing/suit/hooded/chaplain_hoodie/leader/Initialize(mapload) . = ..() - AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, TRUE, null, FALSE) //makes the leader hoodie immune without giving the follower hoodies immunity + AddComponent(/datum/component/anti_magic, INNATE_TRAIT, MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY) //makes the leader hoodie immune without giving the follower hoodies immunity /obj/item/choice_beacon/radial/holy name = "armaments beacon" @@ -159,9 +159,12 @@ obj_flags = UNIQUE_RENAME var/chaplain_spawnable = TRUE -/obj/item/nullrod/Initialize(mapload) + +/obj/item/nullrod/ComponentInitialize() . = ..() - AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, TRUE, null, FALSE) + AddComponent(/datum/component/anti_magic, \ + _source = INNATE_TRAIT, \ + antimagic_flags = (MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) AddComponent(/datum/component/effect_remover, \ success_feedback = "You disrupt the magic of %THEEFFECT with %THEWEAPON.", \ success_forcesay = "BEGONE FOUL MAGIKS!!", \ diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm index 968b0d4e55443..ca5fcf8e96e75 100644 --- a/code/game/objects/items/implants/implant.dm +++ b/code/game/objects/items/implants/implant.dm @@ -7,7 +7,9 @@ icon_state = "generic" //Shows up as the action button icon item_flags = ABSTRACT | DROPDEL actions_types = list(/datum/action/item_action/hands_free/activate) - var/activated = TRUE //1 for implant types that can be activated, 0 for ones that are "always on" like mindshield implants + // This gives the user an action button that allows them to activate the implant. + // If the implant needs no action button, then null this out. + // Or, if you want to add a unique action button, then replace this. var/mob/living/imp_in = null var/implant_color = "b" var/allow_multiple = FALSE @@ -26,6 +28,11 @@ /obj/item/implant/ui_action_click() activate("action_button") +/obj/item/implant/item_action_slot_check(slot, mob/user) + return user == imp_in + + + /obj/item/implant/proc/can_be_implanted_in(mob/living/target) // for human-only and other special requirements return TRUE @@ -78,10 +85,8 @@ forceMove(target) imp_in = target target.implants += src - if(activated) - for(var/X in actions) - var/datum/action/A = X - A.Grant(target) + for(var/datum/action/implant_action as anything in actions) + implant_action.Grant(target) if(ishuman(target)) var/mob/living/carbon/human/H = target H.sec_hud_set_implants() @@ -104,10 +109,8 @@ user.implants -= src imp_in = target target.implants += src - if(activated) - for(var/X in actions) - var/datum/action/A = X - A.Grant(target) + for(var/datum/action/implant_action as anything in actions) + implant_action.Grant(target) if(ishuman(target)) var/mob/living/carbon/human/H = target H.sec_hud_set_implants() @@ -119,9 +122,8 @@ moveToNullspace() imp_in = null source.implants -= src - for(var/X in actions) - var/datum/action/A = X - A.Grant(source) + for(var/datum/action/implant_action as anything in actions) + implant_action.Remove(source) if(ishuman(source)) var/mob/living/carbon/human/H = source H.sec_hud_set_implants() diff --git a/code/game/objects/items/implants/implant_abductor.dm b/code/game/objects/items/implants/implant_abductor.dm index b551e06e56d69..f3d53268d8c1b 100644 --- a/code/game/objects/items/implants/implant_abductor.dm +++ b/code/game/objects/items/implants/implant_abductor.dm @@ -3,7 +3,6 @@ desc = "Returns you to the mothership." icon = 'icons/obj/abductor.dmi' icon_state = "implant" - activated = 1 var/obj/machinery/abductor/pad/home COOLDOWN_DECLARE(abductor_implant_cooldown) diff --git a/code/game/objects/items/implants/implant_camera.dm b/code/game/objects/items/implants/implant_camera.dm index d05228a2cd4a0..7068a80d09780 100644 --- a/code/game/objects/items/implants/implant_camera.dm +++ b/code/game/objects/items/implants/implant_camera.dm @@ -1,7 +1,7 @@ /obj/item/implant/camera name = "camera implant" desc = "Watchful eye inside you." - activated = FALSE + actions_types = null var/obj/machinery/camera/camera /obj/item/implant/camera/get_data() diff --git a/code/game/objects/items/implants/implant_chem.dm b/code/game/objects/items/implants/implant_chem.dm index fbbbf51a79aac..48afcf0d7888f 100644 --- a/code/game/objects/items/implants/implant_chem.dm +++ b/code/game/objects/items/implants/implant_chem.dm @@ -2,7 +2,7 @@ name = "chem implant" desc = "Injects things." icon_state = "reagents" - activated = FALSE + actions_types = null /obj/item/implant/chem/get_data() var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implant_clown.dm b/code/game/objects/items/implants/implant_clown.dm index ae1fef109dd9e..c91014f6824f3 100644 --- a/code/game/objects/items/implants/implant_clown.dm +++ b/code/game/objects/items/implants/implant_clown.dm @@ -1,6 +1,6 @@ /obj/item/implant/sad_trombone name = "sad trombone implant" - activated = 0 + actions_types = null /obj/item/implant/sad_trombone/get_data() var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implant_deathrattle.dm b/code/game/objects/items/implants/implant_deathrattle.dm index 539e076e09bc2..ee6ee295a9f3f 100644 --- a/code/game/objects/items/implants/implant_deathrattle.dm +++ b/code/game/objects/items/implants/implant_deathrattle.dm @@ -67,7 +67,7 @@ name = "deathrattle implant" desc = "Hope no one else dies, prepare for when they do." - activated = FALSE + actions_types = null /obj/item/implant/deathrattle/can_be_implanted_in(mob/living/target) // Can be implanted in anything that's a mob. Syndicate cyborgs, talking fish, humans... diff --git a/code/game/objects/items/implants/implant_exile.dm b/code/game/objects/items/implants/implant_exile.dm index 6d5e29292905f..7ecea9c8a8513 100644 --- a/code/game/objects/items/implants/implant_exile.dm +++ b/code/game/objects/items/implants/implant_exile.dm @@ -4,7 +4,7 @@ /obj/item/implant/exile name = "exile implant" desc = "Prevents you from returning from away missions." - activated = 0 + actions_types = null /obj/item/implant/exile/get_data() var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implant_explosive.dm b/code/game/objects/items/implants/implant_explosive.dm index ee76e644aa2b7..55613e30df7f8 100644 --- a/code/game/objects/items/implants/implant_explosive.dm +++ b/code/game/objects/items/implants/implant_explosive.dm @@ -124,3 +124,7 @@ /obj/item/implanter/explosive_macro name = "implanter (macrobomb)" imp_type = /obj/item/implant/explosive/macro + +/datum/action/item_action/explosive_implant + check_flags = NONE + name = "Activate Explosive Implant" diff --git a/code/game/objects/items/implants/implant_krav_maga.dm b/code/game/objects/items/implants/implant_krav_maga.dm index 373658b386461..ec4b185ee1335 100644 --- a/code/game/objects/items/implants/implant_krav_maga.dm +++ b/code/game/objects/items/implants/implant_krav_maga.dm @@ -3,7 +3,6 @@ desc = "Teaches you the arts of Krav Maga in 5 short instructional videos beamed directly into your eyeballs." icon = 'icons/obj/wizard.dmi' icon_state ="scroll2" - activated = 1 var/datum/martial_art/krav_maga/style = new /obj/item/implant/krav_maga/get_data() diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm index f990ca463f537..0b73f71eed237 100644 --- a/code/game/objects/items/implants/implant_mindshield.dm +++ b/code/game/objects/items/implants/implant_mindshield.dm @@ -1,7 +1,7 @@ /obj/item/implant/mindshield name = "mindshield implant" desc = "Protects against brainwashing." - activated = 0 + actions_types = null /obj/item/implant/mindshield/get_data() var/dat = {"Implant Specifications:
@@ -33,7 +33,6 @@ qdel(src) return FALSE - var/datum/antagonist/rev/rev = target.mind.has_antag_datum(/datum/antagonist/rev) if(rev) rev.remove_revolutionary(FALSE, user) diff --git a/code/game/objects/items/implants/implant_misc.dm b/code/game/objects/items/implants/implant_misc.dm index 1cc169de68e8f..cad80ebce74ed 100644 --- a/code/game/objects/items/implants/implant_misc.dm +++ b/code/game/objects/items/implants/implant_misc.dm @@ -2,7 +2,7 @@ name = "firearms authentication implant" desc = "Lets you shoot your guns." icon_state = "auth" - activated = 0 + actions_types = null /obj/item/implant/weapons_auth/get_data() var/dat = {"Implant Specifications:
@@ -75,7 +75,7 @@ /obj/item/implant/health name = "health implant" - activated = 0 + actions_types = null var/healthstring = "" var/list/raw_data = list() @@ -102,7 +102,7 @@ /obj/item/implant/radio name = "internal radio implant" - activated = TRUE + actions_types = null var/obj/item/radio/radio var/radio_key var/subspace_transmission = FALSE diff --git a/code/game/objects/items/implants/implant_spell.dm b/code/game/objects/items/implants/implant_spell.dm index e925a17b2936b..9cac8fd8394b8 100644 --- a/code/game/objects/items/implants/implant_spell.dm +++ b/code/game/objects/items/implants/implant_spell.dm @@ -1,36 +1,58 @@ /obj/item/implant/spell name = "spell implant" desc = "Allows you to cast a spell as if you were a wizard." - activated = FALSE + actions_types = null - var/autorobeless = TRUE // Whether to automagically make the spell robeless on implant - var/obj/effect/proc_holder/spell/spell +/// Whether to make the spell robeless + var/make_robeless = TRUE + /// The typepath of the spell we give to people. Instantiated in Initialize + var/datum/action/spell/spell_type + /// The actual spell we give to the person on implant + var/datum/action/spell/spell_to_give +/obj/item/implant/spell/Initialize(mapload) + . = ..() + if(!spell_type) + return + + spell_to_give = new spell_type(src) + + if(make_robeless && (spell_to_give.spell_requirements & SPELL_REQUIRES_WIZARD_GARB)) + spell_to_give.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB + +/obj/item/implant/spell/Destroy() + QDEL_NULL(spell_to_give) + return ..() /obj/item/implant/spell/get_data() var/dat = {"Implant Specifications:
Name: Spell Implant
Life: 4 hours after death of host
Implant Details:
- Function: [spell ? "Allows a non-wizard to cast [spell] as if they were a wizard." : "None"]"} + Function: [spell_to_give ? "Allows a non-wizard to cast [spell_to_give] as if they were a wizard." : "None."]"} return dat /obj/item/implant/spell/implant(mob/living/target, mob/user, silent = FALSE, force = FALSE) . = ..() - if (.) - if (!spell) - return FALSE - if (autorobeless && spell.clothes_req) - spell.clothes_req = FALSE - target.AddSpell(spell) - return TRUE - -/obj/item/implant/spell/removed(mob/target, silent = FALSE, special = 0) + if (!.) + return + + if (!spell_to_give) + return FALSE + + spell_to_give.Grant(target) + return TRUE + +/obj/item/implant/spell/removed(mob/living/source, silent = FALSE, special = 0) . = ..() - if (.) - target.RemoveSpell(spell) - if(target.stat != DEAD && !silent) - to_chat(target, "The knowledge of how to cast [spell] slips out from your mind.") + if (!.) + return FALSE + + if(spell_to_give) + spell_to_give.Remove(source) + if(source.stat != DEAD && !silent) + to_chat(source, ("The knowledge of how to cast [spell_to_give] slips out from your mind.")) + return TRUE /obj/item/implanter/spell name = "implanter (spell)" diff --git a/code/game/objects/items/implants/implant_track.dm b/code/game/objects/items/implants/implant_track.dm index 0f4095cfe8195..eb023b0b2a48a 100644 --- a/code/game/objects/items/implants/implant_track.dm +++ b/code/game/objects/items/implants/implant_track.dm @@ -1,7 +1,7 @@ /obj/item/implant/tracking name = "tracking implant" desc = "Track with this." - activated = FALSE + actions_types = null ///for how many deciseconds after user death will the implant work? var/lifespan_postmortem = 6000 ///will people implanted with this act as teleporter beacons? diff --git a/code/game/objects/items/mail.dm b/code/game/objects/items/mail.dm index 05f64a388edeb..7e6f1a6e67ba0 100644 --- a/code/game/objects/items/mail.dm +++ b/code/game/objects/items/mail.dm @@ -48,7 +48,7 @@ /obj/item/firing_pin, /obj/item/storage/lockbox/loyalty, /obj/item/grenade/clusterbuster/cleaner, - /obj/item/book/granter/spell/mimery_blockade, + /obj/item/book/granter/action/spell/mime, /obj/item/gun/ballistic/rifle/boltaction/enchanted, /obj/item/melee/classic_baton/police/telescopic, /obj/item/reagent_containers/cup/bottle/random_virus/minor, diff --git a/code/game/objects/items/mjolnir.dm b/code/game/objects/items/mjolnir.dm index c314335a8705b..6dd45f4b0c5d8 100644 --- a/code/game/objects/items/mjolnir.dm +++ b/code/game/objects/items/mjolnir.dm @@ -114,7 +114,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/anchored_mjolnir) //How did this even happen? /obj/structure/anchored_mjolnir/Destroy() - if (contained) + if(contained) QDEL_NULL(contained) return ..() diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 42c09695b12c5..90af7317aa12c 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -661,6 +661,8 @@ crew_monitor.Grant(R) icon_state = "scanner" +/datum/action/item_action/crew_monitor + name = "Interface With Crew Monitor" /obj/item/borg/upgrade/pinpointer/deactivate(mob/living/silicon/robot/R, user = usr) . = ..() diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm index 487a0aa3c815a..ac8a1e7082e81 100644 --- a/code/game/objects/items/scrolls.dm +++ b/code/game/objects/items/scrolls.dm @@ -3,13 +3,27 @@ desc = "A scroll for moving around." icon = 'icons/obj/wizard.dmi' icon_state = "scroll" - var/uses = 4 + var/uses = 4 /// Number of uses the scroll gets. + var/smoke_amt = 20 //How much smoke after teleporting + actions_types = list(/datum/action/spell/teleport/area_teleport/wizard/scroll) w_class = WEIGHT_CLASS_SMALL item_state = "paper" throw_speed = 3 throw_range = 7 resistance_flags = FLAMMABLE +/obj/item/teleportation_scroll/Initialize(mapload) + . = ..() + // In the future, this can be generalized into just "magic scrolls that give you a specific spell". + var/datum/action/spell/teleport/area_teleport/wizard/scroll/teleport = locate() in actions + if(teleport) + teleport.name = name + teleport.icon_icon = icon + teleport.button_icon_state = icon_state + +/obj/item/teleportation_scroll/item_action_slot_check(slot, mob/user) + return (slot == ITEM_SLOT_HANDS) + /obj/item/teleportation_scroll/apprentice name = "lesser scroll of teleportation" uses = 1 @@ -17,55 +31,24 @@ /obj/item/teleportation_scroll/attack_self(mob/user) - user.set_machine(src) - var/dat = "Teleportation Scroll:
" - dat += "Number of uses: [src.uses]
" - dat += "
" - dat += "Four uses, use them wisely:
" - dat += "Teleport
" - dat += "Kind regards,
Wizards Federation

P.S. Don't forget to bring your gear, you'll need it to cast most spells.
" - user << browse(dat, "window=scroll") - onclose(user, "scroll") - return - -/obj/item/teleportation_scroll/Topic(href, href_list) - ..() - if (usr.stat != CONSCIOUS || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED) || src.loc != usr) + . = ..() + if(.) return - if (!ishuman(usr)) - return 1 - var/mob/living/carbon/human/H = usr - if(H.is_holding(src)) - H.set_machine(src) - if (href_list["spell_teleport"]) - if(uses) - teleportscroll(H) - if(H) - attack_self(H) - return -/obj/item/teleportation_scroll/proc/teleportscroll(mob/user) - - var/A = tgui_input_list(user, "Area to jump to", "BOOYEA", items = GLOB.teleportlocs) - if(!src || QDELETED(src) || !user || !user.is_holding(src) || user.incapacitated() || !A || !uses) + if(!uses) return - var/area/thearea = GLOB.teleportlocs[A] - - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(2, user.loc) - smoke.attach(user) - smoke.start() - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!T.is_blocked_turf()) - L += T - - if(!L.len) - to_chat(user, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") + if(!ishuman(user)) return - - if(do_teleport(user, pick(L), channel = TELEPORT_CHANNEL_MAGIC, bypass_area_restriction = TRUE)) - smoke.start() - uses-- - else - to_chat(user, "The spell matrix was disrupted by something near the destination.") + var/mob/living/carbon/human/human_user = user + if(human_user.incapacitated() || !human_user.is_holding(src)) + return + var/datum/action/spell/teleport/area_teleport/wizard/scroll/teleport = locate() in actions + if(!teleport) + to_chat(user, ("[src] seems to be a faulty teleportation scroll, and has no magic associated.")) + return + if(!teleport.trigger(user)) + return + if(--uses <= 0) + to_chat(user, ("[src] runs out of uses and crumbles to dust!")) + qdel(src) + return TRUE diff --git a/code/game/objects/items/signs.dm b/code/game/objects/items/signs.dm index e6148861fd798..c8656a7335fad 100644 --- a/code/game/objects/items/signs.dm +++ b/code/game/objects/items/signs.dm @@ -44,3 +44,19 @@ user.visible_message("[user] waves around blank sign.") user.changeNext_move(CLICK_CD_MELEE) +/datum/crafting_recipe/picket_sign + name = "Picket Sign" + result = /obj/item/picket_sign + reqs = list(/obj/item/stack/rods = 1, + /obj/item/stack/sheet/cardboard = 2) + time = 80 + category = CAT_MISC + +/datum/action/item_action/nano_picket_sign + name = "Retext Nano Picket Sign" + +/datum/action/item_action/nano_picket_sign/on_activate(mob/user, atom/target) + if(!istype(target, /obj/item/picket_sign)) + return + var/obj/item/picket_sign/sign = target + sign.retext(owner) diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm index 1a60b72761a23..9ff3d6a515f58 100644 --- a/code/game/objects/items/storage/book.dm +++ b/code/game/objects/items/storage/book.dm @@ -38,7 +38,9 @@ /obj/item/storage/book/bible/ComponentInitialize() . = ..() - AddComponent(/datum/component/anti_magic, src, FALSE, TRUE, _allowed_slots = ITEM_SLOT_HANDS) + AddComponent(/datum/component/anti_magic, \ + _source = src, \ + antimagic_flags = (MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) /obj/item/storage/book/bible/suicide_act(mob/living/user) user.visible_message("[user] is offering [user.p_them()]self to [deity_name]! It looks like [user.p_theyre()] trying to commit suicide!") diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index cfbaa73a9a3c7..c17e78ee41de5 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -155,7 +155,7 @@ new /obj/item/clothing/suit/hooded/chaplain_hoodie(src) new /obj/item/card/id/syndicate(src) new /obj/item/clothing/shoes/chameleon/noslip(src) //because slipping while being a dark lord sucks - new /obj/item/book/granter/spell/summonitem(src) + new /obj/item/book/granter/action/spell/summonitem(src) if("white_whale_holy_grail") //Unique items that don't appear anywhere else new /obj/item/pneumatic_cannon/speargun(src) @@ -514,8 +514,8 @@ new /obj/item/gun/ballistic/revolver/reverse(src) /obj/item/storage/box/syndie_kit/mimery/PopulateContents() - new /obj/item/book/granter/spell/mimery_blockade(src) - new /obj/item/book/granter/spell/mimery_guns(src) + new /obj/item/book/granter/action/spell/mime/mimery_blockade(src) + new /obj/item/book/granter/action/spell/mime/mimery_guns(src) /obj/item/storage/box/syndie_kit/centcom_costume/PopulateContents() new /obj/item/clothing/under/rank/centcom/official(src) diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm index b2e2363db8956..37f7a3dab9e0f 100644 --- a/code/game/objects/items/tanks/watertank.dm +++ b/code/game/objects/items/tanks/watertank.dm @@ -725,3 +725,6 @@ reagents.trans_to(user,used_amount,multiplier=usage_ratio) update_icon() user.update_inv_back() //for overlays update + +/datum/action/item_action/activate_injector + name = "Activate Injector" diff --git a/code/game/objects/items/trinkets/dicefate.dm b/code/game/objects/items/trinkets/dicefate.dm index 624261eb43b88..a4ab70f8487b3 100644 --- a/code/game/objects/items/trinkets/dicefate.dm +++ b/code/game/objects/items/trinkets/dicefate.dm @@ -137,7 +137,7 @@ //Random One-use spellbook T.visible_message("A magical looking book drops to the floor!") do_smoke(0, drop_location()) - new /obj/item/book/granter/spell/random(drop_location()) + new /obj/item/book/granter/action/spell/random(drop_location()) if(16) //Servant & Servant Summon T.visible_message("A Dice Servant appears in a cloud of smoke!") @@ -157,9 +157,9 @@ message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") H.key = C.key - var/obj/effect/proc_holder/spell/targeted/summonmob/S = new + var/datum/action/spell/summonmob/S = new S.target_mob = H - user.mind.AddSpell(S) + S.Grant(user) if(17) //Tator Kit @@ -192,26 +192,22 @@ glasses = /obj/item/clothing/glasses/monocle gloves = /obj/item/clothing/gloves/color/white -/obj/effect/proc_holder/spell/targeted/summonmob +/datum/action/spell/summonmob name = "Summon Servant" desc = "This spell can be used to call your servant, whenever you need it." - charge_max = 100 - clothes_req = 0 + cooldown_time = 30 SECONDS invocation = "JE VES" invocation_type = INVOCATION_WHISPER - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = 1 var/mob/living/target_mob + button_icon_state = "summons" - action_icon_state = "summons" +/datum/action/spell/summonmob/on_cast(mob/user, atom/target) + . = ..() -/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr) if(!target_mob) return - var/turf/Start = get_turf(user) + var/turf/Start = get_turf(owner) for(var/direction in GLOB.alldirs) var/turf/T = get_step(Start,direction) if(!T.density) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index cd9ed72415e04..af7b45464c8f6 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -75,6 +75,10 @@ if (mapload && !opened) . = INITIALIZE_HINT_LATELOAD populate_contents_immediate() + var/static/list/loc_connections = list( + COMSIG_ATOM_MAGICALLY_UNLOCKED = PROC_REF(on_magic_unlock), + ) + AddElement(/datum/element/connect_loc, loc_connections) update_icon() /obj/structure/closet/LateInitialize() @@ -653,3 +657,9 @@ var/custom_data = item.on_object_saved(depth++) dat += "[custom_data ? ",\n[custom_data]" : ""]" return dat + +/obj/structure/closet/proc/on_magic_unlock(datum/source, datum/action/spell/aoe/knock/spell, mob/living/caster) + SIGNAL_HANDLER + + locked = FALSE + INVOKE_ASYNC(src, PROC_REF(open)) diff --git a/code/game/objects/structures/gym/weight_machine_action.dm b/code/game/objects/structures/gym/weight_machine_action.dm index 80491aa50ba88..524373306f052 100644 --- a/code/game/objects/structures/gym/weight_machine_action.dm +++ b/code/game/objects/structures/gym/weight_machine_action.dm @@ -10,14 +10,11 @@ ///Reference to the weightpress we are created inside of. var/obj/structure/weightmachine/weightpress -/datum/action/push_weights/IsAvailable(feedback = FALSE) +/datum/action/push_weights/is_available(feedback = FALSE) if(DOING_INTERACTION_WITH_TARGET(owner, weightpress)) return FALSE return TRUE -/datum/action/push_weights/Trigger(trigger_flags) - . = ..() - if(!.) - return FALSE +/datum/action/push_weights/on_activate(mob/user, atom/target) weightpress.perform_workout(owner) diff --git a/code/game/objects/structures/popout_cake.dm b/code/game/objects/structures/popout_cake.dm index 083ecf02a838c..0138a759449c3 100644 --- a/code/game/objects/structures/popout_cake.dm +++ b/code/game/objects/structures/popout_cake.dm @@ -170,13 +170,13 @@ var/obj/structure/popout_cake/cake = null -/datum/action/item_action/pull_string/IsAvailable() +/datum/action/item_action/pull_string/is_available() if(..()) if(!cake.used_string) return TRUE return FALSE -/datum/action/item_action/pull_string/Trigger() +/datum/action/item_action/pull_string/on_activate(mob/user, atom/target) if(cake.used_string) to_chat(usr, "The string is loose, it's already been used!") return diff --git a/code/game/objects/structures/traps.dm b/code/game/objects/structures/traps.dm index 529d541797a49..8388424163985 100644 --- a/code/game/objects/structures/traps.dm +++ b/code/game/objects/structures/traps.dm @@ -10,7 +10,7 @@ var/last_trigger = 0 var/time_between_triggers = 600 //takes a minute to recharge var/charges = INFINITY - var/checks_antimagic = TRUE + var/antimagic_flags = MAGIC_RESISTANCE var/list/static/ignore_typecache var/list/mob/immune_minds = list() @@ -77,7 +77,7 @@ var/mob/M = AM if(M.mind in immune_minds) return - if(checks_antimagic && M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) flare() return if(charges <= 0) @@ -106,7 +106,7 @@ icon_state = "bounty_trap_on" stun_time = 200 sparks = FALSE //the item version gives them off to prevent runtimes (see Destroy()) - checks_antimagic = FALSE + antimagic_flags = null var/obj/item/bountytrap/stored_item var/caught = FALSE diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 1cbb100d8b406..328b3e87ee887 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -14,6 +14,9 @@ CREATION_TEST_IGNORE_SELF(/turf) ///used for guaranteeing there is only one oranges_ear per turf when assigned, speeds up view() iteration var/mob/oranges_ear/assigned_oranges_ear + + /// Turf bitflags, see code/__DEFINES/flags.dm + var/turf_flags = NONE /// If there's a tile over a basic floor that can be ripped out var/overfloor_placed = FALSE /// How accessible underfloor pieces such as wires, pipes, etc are on this turf. Can be HIDDEN, VISIBLE, or INTERACTABLE. @@ -620,3 +623,17 @@ CREATION_TEST_IGNORE_SELF(/turf) if(!ismopable(movable_content)) continue movable_content.wash(clean_types) + +/** + * Checks whether the specified turf is blocked by something dense inside it, but ignores anything with the climbable trait + * + * Works similar to is_blocked_turf(), but ignores climbables and has less options. Primarily added for jaunting checks + */ +/turf/proc/is_blocked_turf_ignore_climbable() + if(density) + return TRUE + + for(var/atom/movable/atom_content as anything in contents) + if(atom_content.density && !(atom_content.flags_1 & ON_BORDER_1) && !HAS_TRAIT(atom_content, TRAIT_CLIMBABLE)) + return TRUE + return FALSE diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 1a89b834bf8f7..c343c805ddb85 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -659,6 +659,7 @@ if(!ai_number) to_chat(usr, "No AIs located" ) + /datum/admins/proc/manage_free_slots() if(!check_rights()) return diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 26b46dab28dcb..f842bb97c5dbd 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -108,7 +108,8 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list( /client/proc/load_circuit, /client/proc/healall, /client/proc/spawn_floor_cluwne, - /client/proc/spawnhuman + /client/proc/spawnhuman, + /client/proc/debug_spell_requirements, )) GLOBAL_PROTECT(admin_verbs_fun) GLOBAL_LIST_INIT(admin_verbs_spawn, list( @@ -187,7 +188,20 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/cmd_display_init_log, /client/proc/cmd_display_overlay_log, /client/proc/reload_configuration, - /client/proc/give_all_spells, + /client/proc/remove_all_spells, + /client/proc/give_all_spells_aoe, + /client/proc/give_all_spell_aoe_rev, + /client/proc/give_all_spells_cone, + /client/proc/give_all_spells_conjure, + /client/proc/give_all_spells_conjure_item, + /client/proc/give_all_spells_jaunt, + /client/proc/give_all_spells_pointed, + /client/proc/give_all_mutations, + /client/proc/give_all_action_mutations, + /client/proc/give_all_spells_projectile, + /client/proc/give_all_spells_shapeshift, + /client/proc/give_all_spells_teleport, + /client/proc/give_all_spells_touch, /datum/admins/proc/create_or_modify_area, /datum/admins/proc/fixcorruption, #ifdef TESTING @@ -628,41 +642,77 @@ GLOBAL_PROTECT(admin_verbs_hideable) message_admins("[key_name_admin(usr)] has modified Dynamic Explosion Scale: [ex_scale]") /client/proc/give_spell(mob/T in GLOB.mob_list) + var/mob/spell_recipient = T set category = "Fun" set name = "Give Spell" set desc = "Gives a spell to a mob." - + var/which = tgui_alert(usr, "Chose by name or by type path?", "Chose option", list("Name", "Typepath")) + if(!which) + return + if(QDELETED(spell_recipient)) + to_chat(usr, ("The intended spell recipient no longer exists.")) + return var/list/spell_list = list() - var/type_length = length_char("/obj/effect/proc_holder/spell") + 2 - for(var/A in GLOB.spells) - spell_list[copytext_char("[A]", type_length)] = A - var/obj/effect/proc_holder/spell/S = input("Choose the spell to give to that guy", "ABRAKADABRA") as null|anything in sort_list(spell_list) - if(!S) + for(var/datum/action/spell/to_add as anything in subtypesof(/datum/action/spell)) + var/spell_name = initial(to_add.name) + if(spell_name == "Spell") // abstract or un-named spells should be skipped. + continue + + if(which == "Name") + spell_list[spell_name] = to_add + else + spell_list += to_add + + var/chosen_spell = tgui_input_list(usr, "Choose the spell to give to [spell_recipient]", "ABRAKADABRA", sort_list(spell_list)) + if(isnull(chosen_spell)) + return + var/datum/action/spell/spell_path = which == "Typepath" ? chosen_spell : spell_list[chosen_spell] + if(!ispath(spell_path)) + return + + var/robeless = (tgui_alert(usr, "Would you like to force this spell to be robeless?", "Robeless Casting?", list("Force Robeless", "Use Spell Setting")) == "Force Robeless") + if(QDELETED(spell_recipient)) + to_chat(usr, ("The intended spell recipient no longer exists.")) return SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - log_admin("[key_name(usr)] gave [key_name(T)] the spell [S].") - message_admins("[key_name_admin(usr)] gave [key_name_admin(T)] the spell [S].") + log_admin("[key_name(usr)] gave [key_name(spell_recipient)] the spell [chosen_spell][robeless ? " (Forced robeless)" : ""].") + message_admins("[key_name_admin(usr)] gave [key_name_admin(spell_recipient)] the spell [chosen_spell][robeless ? " (Forced robeless)" : ""].") - S = spell_list[S] - if(T.mind) - T.mind.AddSpell(new S) - else - T.AddSpell(new S) - message_admins("Spells given to mindless mobs will not be transferred in mindswap or cloning!") + var/datum/action/spell/new_spell = new spell_path(spell_recipient.mind || spell_recipient) + + if(robeless) + new_spell.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB + + new_spell.Grant(spell_recipient) + + if(!spell_recipient.mind) + to_chat(usr, ("Spells given to mindless mobs will belong to the mob and not their mind, \ + and as such will not be transferred if their mind changes body (Such as from Mindswap).")) /client/proc/remove_spell(mob/T in GLOB.mob_list) set category = "Fun" set name = "Remove Spell" set desc = "Remove a spell from the selected mob." + var/mob/removal_target = T + var/list/target_spell_list = list() + for(var/datum/action/spell/spell in removal_target.actions) + target_spell_list[spell.name] = spell + + if(!length(target_spell_list)) + return + + var/chosen_spell = tgui_input_list(usr, "Choose the spell to remove from [removal_target]", "ABRAKADABRA", sort_list(target_spell_list)) + if(isnull(chosen_spell)) + return + var/datum/action/spell/to_remove = target_spell_list[chosen_spell] + if(!istype(to_remove)) + return - if(T?.mind) - var/obj/effect/proc_holder/spell/S = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in sort_list(T.mind.spell_list) - if(S) - T.mind.RemoveSpell(S) - log_admin("[key_name(usr)] removed the spell [S] from [key_name(T)].") - message_admins("[key_name_admin(usr)] removed the spell [S] from [key_name_admin(T)].") - SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + qdel(to_remove) + log_admin("[key_name(usr)] removed the spell [chosen_spell] from [key_name(removal_target)].") + message_admins("[key_name_admin(usr)] removed the spell [chosen_spell] from [key_name_admin(removal_target)].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Remove Spell") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/give_disease(mob/living/T in GLOB.mob_living_list) set category = "Fun" @@ -855,6 +905,45 @@ GLOBAL_PROTECT(admin_verbs_hideable) qdel(query_burn_book) qdel(query_library_print) +/// Debug verb for seeing at a glance what all spells have as set requirements +/client/proc/debug_spell_requirements() + set name = "Show Spell Requirements" + set category = "Debug" + + var/header = "Name Requirements" + var/all_requirements = list() + for(var/datum/action/spell/spell as anything in typesof(/datum/action/spell)) + if(initial(spell.name) == "Spell") + continue + + var/list/real_reqs = list() + var/reqs = initial(spell.spell_requirements) + if(reqs & SPELL_CASTABLE_AS_BRAIN) + real_reqs += "Castable as brain" + if(reqs & SPELL_CASTABLE_WHILE_PHASED) + real_reqs += "Castable phased" + if(reqs & SPELL_REQUIRES_HUMAN) + real_reqs += "Must be human" + if(reqs & SPELL_REQUIRES_MIME_VOW) + real_reqs += "Must be miming" + if(reqs & SPELL_REQUIRES_MIND) + real_reqs += "Must have a mind" + if(reqs & SPELL_REQUIRES_NO_ANTIMAGIC) + real_reqs += "Must have no antimagic" + if(reqs & SPELL_REQUIRES_OFF_CENTCOM) + real_reqs += "Must be off central command z-level" + if(reqs & SPELL_REQUIRES_WIZARD_GARB) + real_reqs += "Must have wizard clothes" + + all_requirements += "[initial(spell.name)] [english_list(real_reqs, "No requirements")]" + + var/page_style = "" + var/page_contents = "[page_style][header][jointext(all_requirements, "")]
" + var/datum/browser/popup = new(mob, "spellreqs", "Spell Requirements", 600, 400) + popup.set_content(page_contents) + popup.open() + + #ifdef SENDMAPS_PROFILE /client/proc/display_sendmaps() set name = "Send Maps Profile" @@ -862,3 +951,5 @@ GLOBAL_PROTECT(admin_verbs_hideable) src << link("?debug=profile&type=sendmaps&window=test") #endif + + diff --git a/code/modules/admin/battle_royale.dm b/code/modules/admin/battle_royale.dm index a6fdcab189448..2e73bd1fc1635 100644 --- a/code/modules/admin/battle_royale.dm +++ b/code/modules/admin/battle_royale.dm @@ -34,7 +34,7 @@ GLOBAL_LIST_INIT(battle_royale_basic_loot, list( /obj/item/gun/energy/disabler, /obj/item/construction/rcd, /obj/item/clothing/glasses/chameleon/flashproof, - /obj/item/book/granter/spell/knock, + /obj/item/book/granter/action/spell/knock, /obj/item/clothing/glasses/sunglasses/advanced, /obj/item/clothing/glasses/thermal/eyepatch, /obj/item/clothing/glasses/thermal/syndi, @@ -195,6 +195,7 @@ GLOBAL_DATUM(battle_royale, /datum/battle_royale_controller) var/list/death_wall var/field_delay = 15 var/debug_mode = FALSE + var/datum/action/spell/aoe/knock/knock = new /datum/action/spell/aoe/knock /datum/battle_royale_controller/Destroy(force, ...) QDEL_LIST(death_wall) @@ -338,7 +339,7 @@ GLOBAL_DATUM(battle_royale, /datum/battle_royale_controller) //Assistant gang H.equipOutfit(/datum/outfit/job/assistant) //Give them a spell - H.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock) + knock.Grant(H) H.key = key //Give weapons key var/obj/item/implant/weapons_auth/W = new @@ -357,7 +358,7 @@ GLOBAL_DATUM(battle_royale, /datum/battle_royale_controller) /datum/battle_royale_controller/proc/end_grace() for(var/mob/M in GLOB.player_list) - M.RemoveSpell(/obj/effect/proc_holder/spell/aoe_turf/knock) + knock.Remove(M) M.status_flags -= GODMODE REMOVE_TRAIT(M, TRAIT_PACIFISM, BATTLE_ROYALE_TRAIT) to_chat(M, "You are no longer a pacifist. Be the last [M.gender == MALE ? "man" : "woman"] standing.") diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm index a2aa5a888839c..65fb6908dfe79 100644 --- a/code/modules/admin/fun_balloon.dm +++ b/code/modules/admin/fun_balloon.dm @@ -107,7 +107,7 @@ /obj/effect/forcefield/arena_shuttle name = "portal" - timeleft = 0 + initial_duration = 0 var/list/warp_points = list() /obj/effect/forcefield/arena_shuttle/Initialize(mapload) @@ -143,7 +143,7 @@ /obj/effect/forcefield/arena_shuttle_entrance name = "portal" - timeleft = 0 + initial_duration = 0 var/list/warp_points = list() /obj/effect/forcefield/arena_shuttle_entrance/Bumped(atom/movable/AM) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index de8984cee7b1c..74566871e8b2b 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -928,14 +928,131 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that message_admins("[key_name_admin(src)] modified \the [C.name] at [AREACOORD(C)] - Gas: [gas_to_add], Moles: [amount], Temp: [temp].") log_admin("[key_name_admin(src)] modified \the [C.name] at [AREACOORD(C)] - Gas: [gas_to_add], Moles: [amount], Temp: [temp].") -/client/proc/give_all_spells() + +/client/proc/give_all_spells_touch() + set category = "Debug" + set name = "Give all touch spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/touch)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_aoe() + set category = "Debug" + set name = "Give all aoe spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/aoe)) + if(ispath(power, /datum/action/spell/aoe/revenant)) + continue + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spell_aoe_rev() + set category = "Debug" + set name = "Give all revenant aoe spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/aoe/revenant)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_cone() + set category = "Debug" + set name = "Give all cone spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/cone)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_conjure() + set category = "Debug" + set name = "Give all conjure spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/conjure)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_conjure_item() + set category = "Debug" + set name = "Give all conjure item spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/conjure_item)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_jaunt() + set category = "Debug" + set name = "Give all jaunt spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/jaunt)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_pointed() + set category = "Debug" + set name = "Give all pointed spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/pointed)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_projectile() set category = "Debug" - set name = "Give all spells" + set name = "Give all projectile spells" if(!check_rights(R_DEBUG)) return - for(var/type in GLOB.spells) - var/obj/effect/proc_holder/spell/spell = new type - mob.AddSpell(spell) + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/basic_projectile)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_shapeshift() + set category = "Debug" + set name = "Give all shapeshift spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/shapeshift)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/give_all_spells_teleport() + set category = "Debug" + set name = "Give all teleport spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in subtypesof(/datum/action/spell/teleport)) + GRANT_ACTION_MOB(power, mob) + +/client/proc/remove_all_spells() + set category = "Debug" + set name = "Remove all spells" + if(!check_rights(R_DEBUG)) + return + for (var/datum/action/spell/power as anything in mob.actions) + if(istype(power, /datum/action/spell)) + power.Remove(mob) + +/client/proc/give_all_action_mutations() + set category = "Debug" + set name = "Give all action mutations" + if(!check_rights(R_DEBUG)) + return + var/mob/living/carbon/human/human = mob + if (!istype(human)) + return + for (var/datum/mutation/mutation as anything in subtypesof(/datum/mutation)) + if (!initial(mutation.power_path)) + continue + human.dna.add_mutation(mutation) + +/client/proc/give_all_mutations() + set category = "Debug" + set name = "Give all mutations" + if(!check_rights(R_DEBUG)) + return + var/mob/living/carbon/human/human = mob + if (!istype(human)) + return + for (var/datum/mutation/test as anything in subtypesof(/datum/mutation)) + if(tgui_alert(mob, "Do you want to [test] yourself?", "", list("Yes", "No")) == "Yes") + human.dna.add_mutation(test) + /// A debug verb to check the sources of currently running timers /client/proc/check_timer_sources() diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 3eedbeba4c723..c5c3d15226e4f 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -90,6 +90,7 @@ GLOBAL_LIST(admin_antag_list) give_antag_moodies() if(count_against_dynamic_roll_chance && new_body.stat != DEAD) new_body.add_to_current_living_antags() + new_body.update_action_buttons() //This handles the application of antag huds/special abilities /datum/antagonist/proc/apply_innate_effects(mob/living/mob_override) @@ -117,7 +118,7 @@ GLOBAL_LIST(admin_antag_list) if(info_button) to_chat(owner.current, "For more info, read the panel. \ You can always come back to it using the button in the top left.") - info_button?.Trigger() + info_button?.trigger() greet() apply_innate_effects() give_antag_moodies() @@ -127,6 +128,7 @@ GLOBAL_LIST(admin_antag_list) owner.current.client.holder.auto_deadmin() if(count_against_dynamic_roll_chance && owner.current.stat != DEAD && owner.current.client) owner.current.add_to_current_living_antags() + owner.current.update_action_buttons() //in the future, this should entirely replace greet. /datum/antagonist/proc/make_info_button() @@ -169,6 +171,7 @@ GLOBAL_LIST(admin_antag_list) owner.current.remove_from_current_living_antags() if(!silent && owner.current) farewell() + owner.current.update_action_buttons() var/datum/team/team = get_team() if(team) team.remove_member(owner) @@ -398,24 +401,20 @@ GLOBAL_LIST(admin_antag_list) name = "Open Special Role Information:" button_icon_state = "round_end" -/datum/action/antag_info/New(Target) +/datum/action/antag_info/New(master) . = ..() - name = "Open [target] Information" - -/datum/action/antag_info/Trigger(trigger_flags) - . = ..() - if(!.) - return + name = "Open [master] Information" +/datum/action/antag_info/on_activate(mob/user, atom/target) target.ui_interact(owner) -/datum/action/antag_info/IsAvailable(feedback = FALSE) - if(!target) +/datum/action/antag_info/is_available(feedback = FALSE) + if(!master) stack_trace("[type] was used without a target antag datum!") return FALSE . = ..() if(!.) return - if(!owner.mind || !(target in owner.mind.antag_datums)) + if(!owner.mind || !(master in owner.mind.antag_datums)) return FALSE return TRUE diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index 95bfc64a6e204..b5f455808415d 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -230,7 +230,7 @@ var/shatter_msg = "You shatter the bottle, no turning back now!" var/veil_msg = "You sense a dark presence lurking just beyond the veil..." - var/mob/living/demon_type = /mob/living/simple_animal/slaughter + var/mob/living/demon_type = /mob/living/simple_animal/hostile/imp/slaughter var/antag_type = /datum/antagonist/slaughter @@ -256,16 +256,14 @@ /obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) - var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(T) - var/mob/living/simple_animal/slaughter/S = new demon_type(holder) - S.holder = holder + var/mob/living/simple_animal/hostile/imp/slaughter/S = new demon_type(T) + new /obj/effect/dummy/phased_mob(T, S) S.key = C.key S.mind.assigned_role = S.name S.mind.special_role = S.name S.mind.add_antag_datum(antag_type) - to_chat(S, S.playstyle_string) - to_chat(S, "You are currently not currently in the same plane of existence as the station. \ - Ctrl+Click a blood pool to manifest.") + to_chat(S, ("You are currently not currently in the same plane of existence as the station. \ + Use your Blood Crawl ability near a pool of blood to manifest and wreak havoc.")) /obj/item/antag_spawner/slaughter_demon/laughter name = "vial of tickles" @@ -275,5 +273,5 @@ color = "#FF69B4" // HOT PINK veil_msg = "You sense an adorable presence lurking just beyond the veil..." - demon_type = /mob/living/simple_animal/slaughter/laughter + demon_type = /mob/living/simple_animal/hostile/imp/slaughter/laughter antag_type = /datum/antagonist/slaughter/laughter diff --git a/code/modules/antagonists/abductor/machinery/camera.dm b/code/modules/antagonists/abductor/machinery/camera.dm index be861bd50e849..729de391d00fe 100644 --- a/code/modules/antagonists/abductor/machinery/camera.dm +++ b/code/modules/antagonists/abductor/machinery/camera.dm @@ -2,12 +2,12 @@ name = "Human Observation Console" var/team_number = 0 networks = list("ss13", "abductor") - var/datum/action/innate/teleport_in/tele_in_action = new - var/datum/action/innate/teleport_out/tele_out_action = new - var/datum/action/innate/teleport_self/tele_self_action = new - var/datum/action/innate/vest_mode_swap/vest_mode_action = new - var/datum/action/innate/vest_disguise_swap/vest_disguise_action = new - var/datum/action/innate/set_droppoint/set_droppoint_action = new + var/datum/action/innate/teleport_in/tele_in_action + var/datum/action/innate/teleport_out/tele_out_action + var/datum/action/innate/teleport_self/tele_self_action + var/datum/action/innate/vest_mode_swap/vest_mode_action + var/datum/action/innate/vest_disguise_swap/vest_disguise_action + var/datum/action/innate/set_droppoint/set_droppoint_action var/obj/machinery/abductor/console/console lock_override = TRUE @@ -22,36 +22,39 @@ reveal_camera_mob = TRUE camera_mob_icon_state = "abductor_camera" +/obj/machinery/computer/camera_advanced/abductor/Initialize(mapload) + . = ..() + tele_in_action = new(src) + tele_out_action = new(src) + tele_self_action = new(src) + vest_mode_action = new(src) + vest_disguise_action = new(src) + set_droppoint_action = new(src) + /obj/machinery/computer/camera_advanced/abductor/GrantActions(mob/living/carbon/user) ..() if(tele_in_action) - tele_in_action.target = console.pad tele_in_action.Grant(user) actions += tele_in_action if(tele_out_action) - tele_out_action.target = console tele_out_action.Grant(user) actions += tele_out_action if(tele_self_action) - tele_self_action.target = console.pad tele_self_action.Grant(user) actions += tele_self_action if(vest_mode_action) - vest_mode_action.target = console vest_mode_action.Grant(user) actions += vest_mode_action if(vest_disguise_action) - vest_disguise_action.target = console vest_disguise_action.Grant(user) actions += vest_disguise_action if(set_droppoint_action) - set_droppoint_action.target = console set_droppoint_action.Grant(user) actions += set_droppoint_action @@ -63,12 +66,12 @@ icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' button_icon_state = "beam_down" -/datum/action/innate/teleport_in/Activate() - if(!target || !iscarbon(owner)) +/datum/action/innate/teleport_in/on_activate() + if(!master || !iscarbon(owner)) return var/mob/living/carbon/human/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control - var/obj/machinery/abductor/pad/P = target + var/obj/machinery/abductor/pad/P = master var/turf/target_loc = get_turf(remote_eye) if(istype(get_area(target_loc), /area/ai_monitored)) @@ -106,10 +109,10 @@ icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' button_icon_state = "beam_up" -/datum/action/innate/teleport_out/Activate() - if(!target || !iscarbon(owner)) +/datum/action/innate/teleport_out/on_activate() + if(!master || !iscarbon(owner)) return - var/obj/machinery/abductor/console/console = target + var/obj/machinery/abductor/console/console = master console.TeleporterRetrieve() @@ -118,12 +121,12 @@ icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' button_icon_state = "beam_down" -/datum/action/innate/teleport_self/Activate() - if(!target || !iscarbon(owner)) +/datum/action/innate/teleport_self/on_activate() + if(!master || !iscarbon(owner)) return var/mob/living/carbon/human/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control - var/obj/machinery/abductor/pad/P = target + var/obj/machinery/abductor/pad/P = master var/turf/target_loc = get_turf(remote_eye) if(istype(get_area(target_loc), /area/ai_monitored)) @@ -161,10 +164,10 @@ icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' button_icon_state = "vest_mode" -/datum/action/innate/vest_mode_swap/Activate() - if(!target || !iscarbon(owner)) +/datum/action/innate/vest_mode_swap/on_activate() + if(!master || !iscarbon(owner)) return - var/obj/machinery/abductor/console/console = target + var/obj/machinery/abductor/console/console = master console.FlipVest() @@ -173,10 +176,10 @@ icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' button_icon_state = "vest_disguise" -/datum/action/innate/vest_disguise_swap/Activate() - if(!target || !iscarbon(owner)) +/datum/action/innate/vest_disguise_swap/on_activate() + if(!master || !iscarbon(owner)) return - var/obj/machinery/abductor/console/console = target + var/obj/machinery/abductor/console/console = master console.SelectDisguise(remote=1) /datum/action/innate/set_droppoint @@ -184,12 +187,12 @@ icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' button_icon_state = "set_drop" -/datum/action/innate/set_droppoint/Activate() - if(!target || !iscarbon(owner)) +/datum/action/innate/set_droppoint/on_activate() + if(!master || !iscarbon(owner)) return var/mob/living/carbon/human/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control - var/obj/machinery/abductor/console/console = target + var/obj/machinery/abductor/console/console = master console.SetDroppoint(remote_eye.loc,owner) diff --git a/code/modules/antagonists/blob/blob.dm b/code/modules/antagonists/blob/blob.dm index 99fd7dce1fc29..8fd79b6468160 100644 --- a/code/modules/antagonists/blob/blob.dm +++ b/code/modules/antagonists/blob/blob.dm @@ -77,7 +77,7 @@ icon_icon = 'icons/mob/blob.dmi' button_icon_state = "blob" -/datum/action/innate/blobpop/Activate() +/datum/action/innate/blobpop/on_activate() var/mob/old_body = owner var/datum/antagonist/blob/blobtag = owner.mind.has_antag_datum(/datum/antagonist/blob) if(!blobtag) diff --git a/code/modules/antagonists/changeling/cellular_emporium.dm b/code/modules/antagonists/changeling/cellular_emporium.dm index 30213fc58647b..8da6023f9a1b0 100644 --- a/code/modules/antagonists/changeling/cellular_emporium.dm +++ b/code/modules/antagonists/changeling/cellular_emporium.dm @@ -81,11 +81,10 @@ /datum/action/innate/cellular_emporium/New(our_target) . = ..() - button.name = name if(istype(our_target, /datum/cellular_emporium)) cellular_emporium = our_target else CRASH("cellular_emporium action created with non emporium") -/datum/action/innate/cellular_emporium/Activate() +/datum/action/innate/cellular_emporium/on_activate() cellular_emporium.ui_interact(owner) diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index 91c676ddb9ab5..715fdcc504da6 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -6,16 +6,16 @@ name = "Prototype Sting - Debug button, ahelp this" background_icon_state = "bg_changeling" icon_icon = 'icons/hud/actions/actions_changeling.dmi' + button_icon_state = null + check_flags = AB_CHECK_CONSCIOUS var/needs_button = TRUE//for passive abilities like hivemind that dont need a button var/helptext = "" // Details var/chemical_cost = 0 // negative chemical cost is for passive abilities (chemical glands) var/dna_cost = -1 //cost of the sting in dna points. 0 = auto-purchase (see changeling.dm), -1 = cannot be purchased var/req_dna = 0 //amount of dna needed to use this ability. Changelings always have atleast 1 var/req_human = 0 //if you need to be human to use this ability - ///Maximum stat before the ability is blocked. For example, `UNCONSCIOUS` prevents it from being used when in hard crit or dead, while `DEAD` allows the ability to be used on any stat values. - var/req_stat = CONSCIOUS + var/req_absorbs = 0 //similar to req_dna, but only gained from absorbing, not DNA sting var/ignores_fakedeath = FALSE // usable with the FAKEDEATH flag - var/active = FALSE//used by a few powers that toggle /* changeling code now relies on on_purchase to grant powers. @@ -29,10 +29,10 @@ the same goes for Remove(). if you override Remove(), call parent or else your p if(needs_button) Grant(user)//how powers are added rather than the checks in mob.dm -/datum/action/changeling/Trigger() - var/mob/user = owner - if(!user || !user.mind || !user.mind.has_antag_datum(/datum/antagonist/changeling)) - return +/datum/action/changeling/is_available() + return ..() && owner.mind && owner.mind.has_antag_datum(/datum/antagonist/changeling) + +/datum/action/changeling/on_activate(mob/user, atom/target) try_to_sting(user) /datum/action/changeling/proc/try_to_sting(mob/user, mob/target) @@ -53,25 +53,24 @@ the same goes for Remove(). if you override Remove(), call parent or else your p //Fairly important to remember to return 1 on success >.< /datum/action/changeling/proc/can_sting(mob/living/user, mob/target) + if (!is_available(user)) + return FALSE if(!ishuman(user) && !ismonkey(user)) //typecast everything from mob to carbon from this point onwards - return 0 + return FALSE if(req_human && !ishuman(user)) to_chat(user, "We cannot do that in this form!") - return 0 + return FALSE var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) if(c.chem_charges < chemical_cost) to_chat(user, "We require at least [chemical_cost] unit\s of chemicals to do that!") - return 0 + return FALSE if(c.absorbedcount < req_dna) to_chat(user, "We require at least [req_dna] sample\s of compatible DNA.") - return 0 - if(req_stat < user.stat) - to_chat(user, "We are incapacitated.") - return 0 + return FALSE if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) to_chat(user, "We are incapacitated.") - return 0 - return 1 + return FALSE + return TRUE /datum/action/changeling/proc/can_be_used_by(mob/user) if(!user || QDELETED(user)) diff --git a/code/modules/antagonists/changeling/powers/adrenaline.dm b/code/modules/antagonists/changeling/powers/adrenaline.dm index ba864cabef976..5995f59b1b6a1 100644 --- a/code/modules/antagonists/changeling/powers/adrenaline.dm +++ b/code/modules/antagonists/changeling/powers/adrenaline.dm @@ -6,7 +6,7 @@ chemical_cost = 20 dna_cost = 2 req_human = 1 - req_stat = UNCONSCIOUS + check_flags = AB_CHECK_DEAD //Recover from stuns. /datum/action/changeling/adrenaline/sting_action(mob/living/user) diff --git a/code/modules/antagonists/changeling/powers/augmented_eyesight.dm b/code/modules/antagonists/changeling/powers/augmented_eyesight.dm index 1790440e11801..b6a8c93edf967 100644 --- a/code/modules/antagonists/changeling/powers/augmented_eyesight.dm +++ b/code/modules/antagonists/changeling/powers/augmented_eyesight.dm @@ -8,7 +8,7 @@ button_icon_state = "augmented_eyesight" chemical_cost = 0 dna_cost = 2 //Would be 1 without thermal vision - active = FALSE + toggleable = TRUE /datum/action/changeling/augmented_eyesight/on_purchase(mob/user) //The ability starts inactive, so we should be protected from flashes. ..() @@ -25,21 +25,26 @@ ..() var/obj/item/organ/eyes/E = user.getorganslot(ORGAN_SLOT_EYES) if(E) - if(!active) - E.sight_flags |= SEE_MOBS | SEE_OBJS | SEE_TURFS //Add sight flags to the user's eyes - E.flash_protect = -1 //Adjust the user's eyes' flash protection - to_chat(user, "We adjust our eyes to sense prey through walls.") - active = TRUE //Defined in code/modules/spells/spell.dm - else - E.sight_flags ^= SEE_MOBS | SEE_OBJS | SEE_TURFS //Remove sight flags from the user's eyes - E.flash_protect = 2 //Adjust the user's eyes' flash protection - to_chat(user, "We adjust our eyes to protect them from bright lights.") - active = FALSE + E.sight_flags |= SEE_MOBS | SEE_OBJS | SEE_TURFS //Add sight flags to the user's eyes + E.flash_protect = -1 //Adjust the user's eyes' flash protection + to_chat(user, "We adjust our eyes to sense prey through walls.") user.update_sight() else to_chat(user, "We can't adjust our eyes if we don't have any!") return 1 +/datum/action/changeling/augmented_eyesight/on_deactivate(mob/living/carbon/user, mob/living/carbon/target) + if(!istype(user)) + return + var/obj/item/organ/eyes/E = user.getorganslot(ORGAN_SLOT_EYES) + if(E) + E.sight_flags ^= SEE_MOBS | SEE_OBJS | SEE_TURFS //Remove sight flags from the user's eyes + E.flash_protect = 2 //Adjust the user's eyes' flash protection + to_chat(user, "We adjust our eyes to protect them from bright lights.") + user.update_sight() + else + to_chat(user, "We can't adjust our eyes if we don't have any!") + return 1 /datum/action/changeling/augmented_eyesight/Remove(mob/user) //Get rid of x-ray vision and flash protection when the user refunds this ability var/obj/item/organ/eyes/E = user.getorganslot(ORGAN_SLOT_EYES) diff --git a/code/modules/antagonists/changeling/powers/chameleon_skin.dm b/code/modules/antagonists/changeling/powers/chameleon_skin.dm index 5a00ef333a1f6..35e661061042d 100644 --- a/code/modules/antagonists/changeling/powers/chameleon_skin.dm +++ b/code/modules/antagonists/changeling/powers/chameleon_skin.dm @@ -6,7 +6,6 @@ dna_cost = 2 chemical_cost = 1 req_human = 1 - req_stat = CONSCIOUS /datum/action/changeling/refractive_chitin/sting_action(mob/living/user) var/mob/living/carbon/human/H = user //SHOULD always be human, because req_human = 1 diff --git a/code/modules/antagonists/changeling/powers/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm index 1d068e1352658..dfd3aa4d9362b 100644 --- a/code/modules/antagonists/changeling/powers/fakedeath.dm +++ b/code/modules/antagonists/changeling/powers/fakedeath.dm @@ -5,7 +5,7 @@ chemical_cost = 15 dna_cost = 0 req_dna = 1 - req_stat = DEAD + check_flags = NONE ignores_fakedeath = TRUE var/revive_ready = FALSE @@ -18,7 +18,7 @@ name = "Reviving Stasis" desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." button_icon_state = "fake_death" - UpdateButtonIcon() + update_buttons() chemical_cost = 15 to_chat(user, "We have revived ourselves.") else @@ -43,7 +43,7 @@ name = "Revive" desc = "We arise once more." button_icon_state = "revive" - UpdateButtonIcon() + update_buttons() chemical_cost = 0 revive_ready = TRUE diff --git a/code/modules/antagonists/changeling/powers/fleshmend.dm b/code/modules/antagonists/changeling/powers/fleshmend.dm index 29563833a1607..dcb4803e3adcb 100644 --- a/code/modules/antagonists/changeling/powers/fleshmend.dm +++ b/code/modules/antagonists/changeling/powers/fleshmend.dm @@ -5,7 +5,7 @@ button_icon_state = "fleshmend" chemical_cost = 25 dna_cost = 2 - req_stat = HARD_CRIT + check_flags = AB_CHECK_DEAD //Starts healing you every second for 10 seconds. //Can be used whilst unconscious. diff --git a/code/modules/antagonists/changeling/powers/gaseous_pores.dm b/code/modules/antagonists/changeling/powers/gaseous_pores.dm index 27687dba66b2b..94e7e7393aede 100644 --- a/code/modules/antagonists/changeling/powers/gaseous_pores.dm +++ b/code/modules/antagonists/changeling/powers/gaseous_pores.dm @@ -5,7 +5,7 @@ button_icon_state = "smoke" chemical_cost = 25 dna_cost = 1 - req_stat = UNCONSCIOUS + check_flags = AB_CHECK_DEAD var/range = 4 /obj/effect/particle_effect/smoke/confusing/changeling diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm index 7f75daae24be3..794b0948645a8 100644 --- a/code/modules/antagonists/changeling/powers/headcrab.dm +++ b/code/modules/antagonists/changeling/powers/headcrab.dm @@ -6,7 +6,7 @@ chemical_cost = 20 dna_cost = 0 req_human = 1 - req_stat = DEAD + check_flags = NONE ignores_fakedeath = TRUE /datum/action/changeling/headcrab/sting_action(mob/user) diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm index 182570df096dd..4048bfd442450 100644 --- a/code/modules/antagonists/changeling/powers/panacea.dm +++ b/code/modules/antagonists/changeling/powers/panacea.dm @@ -5,7 +5,7 @@ button_icon_state = "panacea" chemical_cost = 20 dna_cost = 1 - req_stat = HARD_CRIT + check_flags = AB_CHECK_DEAD //Heals the things that the other regenerative abilities don't. /datum/action/changeling/panacea/sting_action(mob/user) diff --git a/code/modules/antagonists/changeling/powers/regenerate.dm b/code/modules/antagonists/changeling/powers/regenerate.dm index 130ced301e540..11e0e2a98aaf8 100644 --- a/code/modules/antagonists/changeling/powers/regenerate.dm +++ b/code/modules/antagonists/changeling/powers/regenerate.dm @@ -5,7 +5,7 @@ button_icon_state = "regenerate" chemical_cost = 10 dna_cost = 1 - req_stat = HARD_CRIT + check_flags = AB_CHECK_DEAD /datum/action/changeling/regenerate/sting_action(mob/living/user) ..() @@ -48,7 +48,7 @@ chemical_cost = 15 dna_cost = 2 req_human = TRUE - req_stat = DEAD + check_flags = NONE ignores_fakedeath = TRUE /datum/action/changeling/limbsnake/sting_action(mob/user) diff --git a/code/modules/antagonists/changeling/powers/strained_muscles.dm b/code/modules/antagonists/changeling/powers/strained_muscles.dm index 7fca2f89425ab..055be965cc067 100644 --- a/code/modules/antagonists/changeling/powers/strained_muscles.dm +++ b/code/modules/antagonists/changeling/powers/strained_muscles.dm @@ -10,26 +10,26 @@ dna_cost = 1 req_human = 1 var/stacks = 0 //Increments every 5 seconds; damage increases over time - active = FALSE //Whether or not you are a hedgehog + toggleable = TRUE /datum/action/changeling/strained_muscles/sting_action(mob/living/carbon/user) ..() - active = !active - if(active) - to_chat(user, "Our muscles tense and strengthen.") - else - user.remove_movespeed_modifier(/datum/movespeed_modifier/strained_muscles) - to_chat(user, "Our muscles relax.") - if(stacks >= 10) - to_chat(user, "We collapse in exhaustion.") - user.Paralyze(60) - user.emote("gasp") - + to_chat(user, "Our muscles tense and strengthen.") INVOKE_ASYNC(src, PROC_REF(muscle_loop), user) - return TRUE +/datum/action/changeling/strained_muscles/on_deactivate(mob/living/carbon/user, atom/target) + user.remove_movespeed_modifier(/datum/movespeed_modifier/strained_muscles) + to_chat(user, "Our muscles relax.") + if(stacks >= 10) + to_chat(user, "We collapse in exhaustion.") + user.Paralyze(60) + user.emote("gasp") + /datum/action/changeling/strained_muscles/proc/muscle_loop(mob/living/carbon/user) + // Skip until the next sleep so that we have the active var set + sleep(1) + while(active) user.add_movespeed_modifier(/datum/movespeed_modifier/strained_muscles) if(user.stat != CONSCIOUS || user.staminaloss >= 90) diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index a56a3e5a4a38e..7bc738527f217 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -3,20 +3,14 @@ /datum/action/changeling/sting//parent path, not meant for users afaik name = "Tiny Prick" desc = "Stabby stabby" + toggleable = TRUE var/stealthy = FALSE -/datum/action/changeling/sting/Trigger() - var/mob/user = owner - if(!user || !user.mind) - return - var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(!changeling) - return - if(!changeling.chosen_sting) - set_sting(user) - else - unset_sting(user) - return +/datum/action/changeling/sting/on_activate(mob/user, atom/target) + set_sting(user) + +/datum/action/changeling/sting/on_deactivate(mob/user, atom/target) + unset_sting(user) /datum/action/changeling/sting/proc/set_sting(mob/user) to_chat(user, "We prepare our sting. Alt+click or click the middle mouse button on a target to sting them.") @@ -74,15 +68,14 @@ button_icon_state = "sting_transform" chemical_cost = 20 dna_cost = 3 + cooldown_time = TRANSFORM_STING_COOLDOWN var/datum/changelingprofile/selected_dna = null - COOLDOWN_DECLARE(next_sting) -/datum/action/changeling/sting/transformation/Trigger() - var/mob/user = usr +/datum/action/changeling/sting/transformation/is_available() + return ..() && owner.mind.has_antag_datum(/datum/antagonist/changeling) + +/datum/action/changeling/sting/transformation/on_activate(mob/user, atom/target) var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling.chosen_sting) - unset_sting(user) - return selected_dna = changeling.select_dna("Select the target DNA: ", "Target DNA") if(!selected_dna) return @@ -97,9 +90,6 @@ if((HAS_TRAIT(target, TRAIT_HUSK)) || !iscarbon(target) || (NOTRANSSTING in target.dna.species.species_traits)) to_chat(user, "Our sting appears ineffective against its DNA.") return FALSE - if(!COOLDOWN_FINISHED(src, next_sting)) - to_chat(user, "Our retrovirus is not ready yet!") - return FALSE return TRUE /datum/action/changeling/sting/transformation/sting_action(mob/user, mob/target) @@ -115,8 +105,7 @@ C = C.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_DEFAULTMSG | TR_KEEPAI) var/datum/status_effect/ling_transformation/previous_transformation = C.has_status_effect(STATUS_EFFECT_LING_TRANSFORMATION) C.apply_status_effect(STATUS_EFFECT_LING_TRANSFORMATION, new_dna, istype(previous_transformation) ? previous_transformation.original_dna : null) - COOLDOWN_START(src, next_sting, TRANSFORM_STING_COOLDOWN) - + start_cooldown() /datum/action/changeling/sting/false_armblade name = "False Armblade Sting" diff --git a/code/modules/antagonists/clock_cult/enchantments/antimagic.dm b/code/modules/antagonists/clock_cult/enchantments/antimagic.dm index 13a6bdff7ec82..6ff5190198387 100644 --- a/code/modules/antagonists/clock_cult/enchantments/antimagic.dm +++ b/code/modules/antagonists/clock_cult/enchantments/antimagic.dm @@ -3,4 +3,4 @@ /datum/component/enchantment/anti_magic/apply_effect(obj/item/target) examine_description = "It has been blessed with the gift of magic protection, preventing all magic from affecting the wielder." - target.AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, TRUE) + target.AddComponent(/datum/component/anti_magic, INNATE_TRAIT, (MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) diff --git a/code/modules/antagonists/clock_cult/items/brass_clothing.dm b/code/modules/antagonists/clock_cult/items/brass_clothing.dm index f1bbde15854e0..032ca8aa50eaf 100644 --- a/code/modules/antagonists/clock_cult/items/brass_clothing.dm +++ b/code/modules/antagonists/clock_cult/items/brass_clothing.dm @@ -35,7 +35,7 @@ if(istype(user, /mob/living/carbon/human/consistent) || istype(user, /mob/living/carbon/human/dummy)) //Fake people need not apply (it fucks up my unit tests) return - if(is_servant_of_ratvar(user) && allow_any) + if(is_servant_of_ratvar(user) || allow_any) return to_chat(user, "You feel a shock of energy surge through your body!") user.dropItemToGround(src, TRUE) diff --git a/code/modules/antagonists/clock_cult/items/clockwork_slab.dm b/code/modules/antagonists/clock_cult/items/clockwork_slab.dm index 337142e251de7..21a375faf6ce9 100644 --- a/code/modules/antagonists/clock_cult/items/clockwork_slab.dm +++ b/code/modules/antagonists/clock_cult/items/clockwork_slab.dm @@ -15,7 +15,7 @@ GLOBAL_LIST_INIT(clockwork_slabs, list()) var/holder_class var/list/scriptures = list() - + var/empowerment var/charge_overlay var/calculated_cogs = 0 @@ -93,6 +93,12 @@ GLOBAL_LIST_INIT(clockwork_slabs, list()) calculated_cogs += difference cogs += difference +/obj/item/clockwork/clockwork_slab/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + INVOKE_ASYNC(active_scripture, TYPE_PROC_REF(/datum/clockcult/scripture/slab, on_slab_attack), target, user) + if(active_scripture) + active_scripture.end_invokation() + //==================================// // ! Quick bind spell handling ! // //==================================// @@ -102,6 +108,7 @@ GLOBAL_LIST_INIT(clockwork_slabs, list()) return if(quick_bound_scriptures[position]) //Unbind the scripture that is quickbound + quick_bound_scriptures.Remove(M) qdel(quick_bound_scriptures[position]) //Put the quickbound action onto the slab, the slab should grant when picked up var/datum/action/innate/clockcult/quick_bind/quickbound = new diff --git a/code/modules/antagonists/clock_cult/items/clockwork_weapon.dm b/code/modules/antagonists/clock_cult/items/clockwork_weapon.dm index 9caee25a385a9..13e5ee785353d 100644 --- a/code/modules/antagonists/clock_cult/items/clockwork_weapon.dm +++ b/code/modules/antagonists/clock_cult/items/clockwork_weapon.dm @@ -23,17 +23,22 @@ bleed_force = BLEED_CUT max_integrity = 200 var/clockwork_hint = "" - var/obj/effect/proc_holder/spell/targeted/summon_spear/SS + var/datum/action/spell/summon_spear/SS + +/obj/item/clockwork/weapon/Destroy() + if(SS) + SS.Remove(SS.owner) + . = ..() + /obj/item/clockwork/weapon/pickup(mob/user) ..() if(!user.mind) return - user.mind.RemoveSpell(SS) - if(is_servant_of_ratvar(user)) + if(is_servant_of_ratvar(user) && !SS) SS = new SS.marked_item = src - user.mind.AddSpell(SS) + SS.Grant(user) /obj/item/clockwork/weapon/examine(mob/user) . = ..() @@ -63,7 +68,7 @@ force += force_buff . = ..() force -= force_buff - if(!QDELETED(target) && target.stat != DEAD && !is_servant_of_ratvar(target) && !target.anti_magic_check(magic=FALSE,holy=TRUE,major=FALSE)) + if(!QDELETED(target) && target.stat != DEAD && !is_servant_of_ratvar(target) && !target.can_block_magic(MAGIC_RESISTANCE_HOLY)) hit_effect(target, user) /obj/item/clockwork/weapon/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) @@ -73,7 +78,7 @@ if(isliving(hit_atom)) var/mob/living/target = hit_atom if(!.) - if(!target.anti_magic_check(magic=FALSE,holy=TRUE) && !is_servant_of_ratvar(target)) + if(!target.can_block_magic(MAGIC_RESISTANCE_HOLY) && !is_servant_of_ratvar(target)) hit_effect(target, throwingdatum?.thrower, TRUE) /obj/item/clockwork/weapon/proc/hit_effect(mob/living/target, mob/living/user, thrown=FALSE) diff --git a/code/modules/antagonists/clock_cult/mobs/eminence.dm b/code/modules/antagonists/clock_cult/mobs/eminence.dm index a393206e19a12..973b439ed7bfc 100644 --- a/code/modules/antagonists/clock_cult/mobs/eminence.dm +++ b/code/modules/antagonists/clock_cult/mobs/eminence.dm @@ -40,12 +40,12 @@ var/mob/living/selected_mob = null - var/obj/effect/proc_holder/spell/targeted/eminence/reebe/spell_reebe - var/obj/effect/proc_holder/spell/targeted/eminence/station/spell_station - var/obj/effect/proc_holder/spell/targeted/eminence/servant_warp/spell_servant_warp - var/obj/effect/proc_holder/spell/targeted/eminence/mass_recall/mass_recall - var/obj/effect/proc_holder/spell/targeted/eminence/linked_abscond/linked_abscond - var/obj/effect/proc_holder/spell/targeted/eminence/trigger_event/trigger_event + var/datum/action/spell/eminence/reebe/spell_reebe + var/datum/action/spell/eminence/station/spell_station + var/datum/action/spell/eminence/servant_warp/spell_servant_warp + var/datum/action/spell/eminence/mass_recall/mass_recall + var/datum/action/spell/eminence/linked_abscond/linked_abscond + var/datum/action/spell/eminence/trigger_event/trigger_event /mob/living/simple_animal/eminence/ClickOn(atom/A, params) . = ..() @@ -77,18 +77,19 @@ . = ..() GLOB.clockcult_eminence = src //Add spells + spell_reebe = new - AddSpell(spell_reebe) + spell_reebe.Grant(src) spell_station = new - AddSpell(spell_station) + spell_station.Grant(src) spell_servant_warp = new - AddSpell(spell_servant_warp) + spell_servant_warp.Grant(src) mass_recall = new - AddSpell(mass_recall) + mass_recall.Grant(src) linked_abscond = new - AddSpell(linked_abscond) + linked_abscond.Grant(src) trigger_event = new - AddSpell(trigger_event) + trigger_event.Grant(src) //Wooooo, you are a ghost AddComponent(/datum/component/tracking_beacon, "ghost", null, null, TRUE, "#9e4d91", TRUE, TRUE, "#490066") internal_radio = new(src) @@ -155,37 +156,34 @@ //Eminence abilities -/obj/effect/proc_holder/spell/targeted/eminence +/datum/action/spell/eminence invocation = "none" invocation_type = INVOCATION_NONE - action_icon = 'icons/hud/actions/actions_clockcult.dmi' - action_icon_state = "ratvarian_spear" - action_background_icon_state = "bg_clock" - clothes_req = FALSE - charge_max = 0 - cooldown_min = 0 - range = -1 - include_user = TRUE + icon_icon = 'icons/hud/actions/actions_clockcult.dmi' + button_icon_state = "ratvarian_spear" + background_icon_state = "bg_clock" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC var/cog_cost -/obj/effect/proc_holder/spell/targeted/eminence/can_cast(mob/user) +/datum/action/spell/eminence/can_cast_spell(feedback = TRUE) . = ..() - var/mob/living/simple_animal/eminence/eminence = user + var/mob/living/simple_animal/eminence/eminence = owner if(!istype(eminence)) return FALSE if(eminence.cogs < cog_cost) return FALSE -/obj/effect/proc_holder/spell/targeted/eminence/proc/consume_cogs(mob/living/simple_animal/eminence/eminence) +/datum/action/spell/eminence/proc/consume_cogs(mob/living/simple_animal/eminence/eminence) eminence.cogs -= cog_cost //=====Warp to Reebe===== -/obj/effect/proc_holder/spell/targeted/eminence/reebe +/datum/action/spell/eminence/reebe name = "Jump to Reebe" desc = "Teleport yourself to Reebe." - action_icon_state = "Abscond" + button_icon_state = "Abscond" -/obj/effect/proc_holder/spell/targeted/eminence/reebe/cast(mob/living/user) +/datum/action/spell/eminence/reebe/on_cast(mob/living/user, atom/target) + . = ..() var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.celestial_gateway if(G) user.abstract_move(get_turf(G)) @@ -195,12 +193,13 @@ to_chat(user, "There is no Ark!") //=====Warp to station===== -/obj/effect/proc_holder/spell/targeted/eminence/station +/datum/action/spell/eminence/station name = "Jump to Station" desc = "Teleport yourself to the station." - action_icon_state = "warp_down" + button_icon_state = "warp_down" -/obj/effect/proc_holder/spell/targeted/eminence/station/cast(mob/living/user) +/datum/action/spell/eminence/station/on_cast(mob/user, atom/target) + . = ..() if(!is_station_level(user.z)) user.abstract_move(get_turf(pick(GLOB.generic_event_spawns))) SEND_SOUND(user, sound('sound/magic/magic_missile.ogg')) @@ -209,14 +208,15 @@ to_chat(user, "You're already on the station!") //=====Teleport to servant===== -/obj/effect/proc_holder/spell/targeted/eminence/servant_warp +/datum/action/spell/eminence/servant_warp name = "Jump to Servant" desc = "Teleport yourself to a specific servant." - action_icon_state = "Spatial Warp" + button_icon_state = "Spatial Warp" -/obj/effect/proc_holder/spell/targeted/eminence/servant_warp/cast(list/targets, mob/user) +/datum/action/spell/eminence/servant_warp/on_cast(mob/user, atom/target) + . = ..() //Get a list of all servants - var/datum/mind/choice = input(user, "Select servant", "Warp to...", null) in GLOB.all_servants_of_ratvar + var/datum/mind/choice = input(user, "Select servant", "Warp to...", null) in GLOB.all_servants_of_ratvar //List targets spell might have been better, for now this will do var/mob/living/M if(!choice) return @@ -236,51 +236,54 @@ flash_color(user, flash_color = "#AF0AAF", flash_time = 25) //=====Mass Recall===== -/obj/effect/proc_holder/spell/targeted/eminence/mass_recall +/datum/action/spell/eminence/mass_recall name = "Initiate Mass Recall" desc = "Initiates a mass recall, warping everyone to the Ark. Can only be used 1 time." - action_icon_state = "Spatial Gateway" + button_icon_state = "Spatial Gateway" -/obj/effect/proc_holder/spell/targeted/eminence/mass_recall/cast(list/targets, mob/living/user) +/datum/action/spell/eminence/mass_recall/on_cast(mob/living/user, atom/target) + . = ..() var/obj/structure/destructible/clockwork/massive/celestial_gateway/C = GLOB.celestial_gateway if(!C) return C.begin_mass_recall() - user.RemoveSpell(src) + Remove(user) //=====Linked Abscond===== -/obj/effect/proc_holder/spell/targeted/eminence/linked_abscond +/datum/action/spell/eminence/linked_abscond name = "Linked Abscond" desc = "Warps a target to Reebe if they are still for 7 seconds. Costs 1 cog." - action_icon_state = "Linked Abscond" - charge_max = 1800 + button_icon_state = "Linked Abscond" + cooldown_time = 600 SECONDS cog_cost = 1 -/obj/effect/proc_holder/spell/targeted/eminence/linked_abscond/can_cast(mob/user) +/datum/action/spell/eminence/linked_abscond/can_cast_spell(feedback) + . = ..() if(!..()) return FALSE - var/mob/living/simple_animal/eminence/E = user + var/mob/living/simple_animal/eminence/E = owner if(!istype(E)) return FALSE if(E.selected_mob && is_servant_of_ratvar(E.selected_mob)) return TRUE return FALSE -/obj/effect/proc_holder/spell/targeted/eminence/linked_abscond/cast(list/targets, mob/living/user) +/datum/action/spell/eminence/linked_abscond/on_cast(mob/user, atom/target) + . = ..() var/mob/living/simple_animal/eminence/E = user if(!istype(E)) to_chat(E, "You are not the Eminence! (This is a bug)") - revert_cast(user) + reset_spell_cooldown() return FALSE if(!E.selected_mob || !is_servant_of_ratvar(E.selected_mob)) E.selected_mob = null to_chat(user, "You need to select a valid target by clicking on them.") - revert_cast(user) + reset_spell_cooldown() return FALSE var/mob/living/L = E.selected_mob if(!istype(L)) to_chat(E, "You cannot do that on this mob!") - revert_cast(user) + reset_spell_cooldown() return FALSE to_chat(E, "You begin recalling [L]...") to_chat(L, "The Eminence is summoning you...") @@ -293,18 +296,19 @@ return TRUE else to_chat(E, "You fail to recall [L].") - revert_cast(user) + reset_spell_cooldown() return FALSE //Trigger event -/obj/effect/proc_holder/spell/targeted/eminence/trigger_event +/datum/action/spell/eminence/trigger_event name = "Manipulate Reality" desc = "Manipulate reality causing global events to occur. Costs 5 cogs" - action_icon_state = "Geis" - charge_max = 3000 + button_icon_state = "Geis" + cooldown_time = 600 SECONDS cog_cost = 5 -/obj/effect/proc_holder/spell/targeted/eminence/trigger_event/cast(list/targets, mob/user) +/datum/action/spell/eminence/trigger_event/on_cast(mob/user, atom/target) + . = ..() var/picked_event = input(user, "Pick an event to run", "Manipulate Reality", null) in list( "Anomaly", "Brand Intelligence", @@ -318,7 +322,7 @@ "Processor Overload" ) if(!picked_event) - revert_cast(user) + reset_spell_cooldown() return if(picked_event == "Anomaly") picked_event = pick("Anomaly: Energetic Flux", "Anomaly: Pyroclastic", "Anomaly: Gravitational", "Anomaly: Bluespace") @@ -330,7 +334,7 @@ INVOKE_ASYNC(eminence, TYPE_PROC_REF(/mob/living/simple_animal/eminence, run_global_event), E) consume_cogs(user) return - revert_cast(user) + reset_spell_cooldown() //Internal Radio /obj/item/radio/borg/eminence diff --git a/code/modules/antagonists/clock_cult/scriptures/_clockwork_scripture.dm b/code/modules/antagonists/clock_cult/scriptures/_clockwork_scripture.dm index a0c66bbc3054a..e97f1024855e1 100644 --- a/code/modules/antagonists/clock_cult/scriptures/_clockwork_scripture.dm +++ b/code/modules/antagonists/clock_cult/scriptures/_clockwork_scripture.dm @@ -1,3 +1,8 @@ + +#define KINDLE 0 +#define MANACLES 1 +#define COMPROMISE 2 + /datum/clockcult/scripture var/name = "" var/desc = "" @@ -156,26 +161,21 @@ var/uses = 1 var/after_use_text = "" end_on_invokation = FALSE - - var/obj/effect/proc_holder/slab/PH + var/timeout_time = 0 + var/allow_mobility = TRUE //if moving and swapping hands is allowed during the while var/uses_left var/time_left = 0 var/loop_timer_id + var/empowerment -/datum/clockcult/scripture/slab/New() - PH = new - PH.parent_scripture = src - ..() /datum/clockcult/scripture/slab/Destroy() if(progress) QDEL_NULL(progress) - if(!QDELETED(PH)) - PH.remove_ranged_ability() - QDEL_NULL(PH) return ..() + /datum/clockcult/scripture/slab/invoke() progress = new(invoker, use_time, invoking_slab) uses_left = uses @@ -183,7 +183,8 @@ invoking_slab.charge_overlay = slab_overlay invoking_slab.update_icon() invoking_slab.active_scripture = src - PH.add_ranged_ability(invoker, "You prepare [name]. Click on a target to use.") + invoking_slab.empowerment = empowerment + to_chat(invoker, "You prepare [name]. Click on a target to use.") count_down() invoke_success() @@ -198,16 +199,6 @@ else end_invokation() -/datum/clockcult/scripture/slab/proc/click_on(atom/A) - if(!invoker.can_interact_with(A)) - return - if(apply_effects(A)) - uses_left -- - if(uses_left <= 0) - if(after_use_text) - clockwork_say(invoker, text2ratvar(after_use_text), TRUE) - end_invokation() - /datum/clockcult/scripture/slab/proc/end_invokation() //Remove the timer if there is one currently active if(loop_timer_id) @@ -215,20 +206,25 @@ loop_timer_id = null to_chat(invoker, "You are no longer invoking [name]") progress.end_progress() - PH.remove_ranged_ability() invoking_slab.charge_overlay = null invoking_slab.update_icon() invoking_slab.active_scripture = null + empowerment = null end_invoke() -/datum/clockcult/scripture/slab/proc/apply_effects(atom/A) - return TRUE - -/obj/effect/proc_holder/slab - var/datum/clockcult/scripture/slab/parent_scripture -/obj/effect/proc_holder/slab/InterceptClickOn(mob/living/caller, params, atom/A) - parent_scripture?.click_on(A) +/datum/clockcult/scripture/slab/proc/on_slab_attack(atom/target, mob/user) + switch(empowerment) + if(KINDLE) + kindle(user, target) + end_invokation() + if(MANACLES) + hateful_manacles(user, target) + end_invokation() + if(COMPROMISE) + sentinels_compromise(user, target) + end_invokation() + return //==================================// // ! Quick bind spell ! // @@ -236,13 +232,13 @@ /datum/action/innate/clockcult icon_icon = 'icons/hud/actions/actions_clockcult.dmi' + button_icon_state = null background_icon_state = "bg_clock" buttontooltipstyle = "brass" check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS /datum/action/innate/clockcult/quick_bind name = "Quick Bind" - button_icon_state = "telerune" desc = "A quick bound spell." var/obj/item/clockwork/clockwork_slab/activation_slab var/datum/clockcult/scripture/scripture @@ -259,20 +255,13 @@ if(scripture.power_cost) desc += "
Draws [scripture.power_cost]W from the ark per use." ..(M) - button.locked = TRUE - button.ordered = TRUE - -/datum/action/innate/clockcult/quick_bind/Remove(mob/M) - if(activation_slab.invoking_scripture == scripture) - activation_slab.invoking_scripture = null - ..(M) -/datum/action/innate/clockcult/quick_bind/IsAvailable() +/datum/action/innate/clockcult/quick_bind/is_available() if(!is_servant_of_ratvar(owner) || owner.incapacitated()) return FALSE return ..() -/datum/action/innate/clockcult/quick_bind/Activate() +/datum/action/innate/clockcult/quick_bind/on_activate() if(!activation_slab) return if(!activation_slab.invoking_scripture) @@ -288,7 +277,7 @@ button_icon_state = "hierophant" desc = "Transmit a message to your allies through the Hierophant." -/datum/action/innate/clockcult/transmit/IsAvailable() +/datum/action/innate/clockcult/transmit/is_available() if(!is_servant_of_ratvar(owner)) Remove(owner) return FALSE @@ -296,10 +285,8 @@ return FALSE return ..() -/datum/action/innate/clockcult/transmit/Activate() +/datum/action/innate/clockcult/transmit/on_activate() hierophant_message(tgui_input_text(owner, "What do you want to tell your allies?", "Hierophant Transmit", "", encode = FALSE), owner, "") /datum/action/innate/clockcult/transmit/Grant(mob/M) ..(M) - button.locked = TRUE - button.ordered = TRUE diff --git a/code/modules/antagonists/clock_cult/scriptures/hateful_manacles.dm b/code/modules/antagonists/clock_cult/scriptures/hateful_manacles.dm deleted file mode 100644 index 9bbe1b74c3611..0000000000000 --- a/code/modules/antagonists/clock_cult/scriptures/hateful_manacles.dm +++ /dev/null @@ -1,45 +0,0 @@ -//==================================// -// ! Hateful Manacles ! // -//==================================// -/datum/clockcult/scripture/slab/hateful_manacles - name = "Hateful Manacles" - desc = "Forms replicant manacles around a target's wrists that function like handcuffs, restraining the target." - tip = "Handcuff a target at close range to subdue them for conversion or vitality extraction." - button_icon_state = "Hateful Manacles" - power_cost = 25 - invokation_time = 15 - invokation_text = list("Shackle the heretic...", "Break them in body and spirit!") - slab_overlay = "hateful_manacles" - use_time = 200 - cogs_required = 0 - category = SPELLTYPE_SERVITUDE - -/datum/clockcult/scripture/slab/hateful_manacles/apply_effects(atom/A) - . = ..() - var/mob/living/carbon/M = A - if(!istype(M)) - return FALSE - if(is_servant_of_ratvar(M)) - return FALSE - if(M.handcuffed) - to_chat(invoker, "[M] is already restrained!") - return FALSE - playsound(M, 'sound/weapons/handcuffs.ogg', 30, TRUE, -2) - M.visible_message("[invoker] forms a well of energy around [M], brass appearing at their wrists!",\ - "[invoker] is trying to restrain you!") - if(do_after(invoker, 30, target=M)) - if(M.handcuffed) - return FALSE - var/obj/item/restraints/handcuffs/clockwork/restraints = new(M) - if (!restraints.apply_cuffs(M, invoker)) - qdel(restraints) - return TRUE - restraints.item_flags |= DROPDEL - log_combat(invoker, M, "handcuffed", src) - return TRUE - return FALSE - -/obj/item/restraints/handcuffs/clockwork - name = "replicant manacles" - desc = "Heavy manacles made out of freezing-cold metal. It looks like brass, but feels much more solid." - icon_state = "brass_manacles" diff --git a/code/modules/antagonists/clock_cult/scriptures/kindle.dm b/code/modules/antagonists/clock_cult/scriptures/kindle.dm deleted file mode 100644 index ae944eba40d30..0000000000000 --- a/code/modules/antagonists/clock_cult/scriptures/kindle.dm +++ /dev/null @@ -1,71 +0,0 @@ - -//==================================// -// ! Kindle ! // -//==================================// -/datum/clockcult/scripture/slab/kindle - name = "Kindle" - desc = "Stuns and mutes a target from a short range. Significantly less effective on Reebe." - tip = "Stuns and mutes a target from a short range." - button_icon_state = "Kindle" - power_cost = 125 - invokation_time = 30 - invokation_text = list("Divinity, show them your light!") - after_use_text = "Let the power flow through you!" - slab_overlay = "volt" - use_time = 150 - cogs_required = 1 - category = SPELLTYPE_SERVITUDE - -/datum/clockcult/scripture/slab/kindle/apply_effects(atom/A) - var/mob/living/M = A - if(!istype(M)) - return FALSE - if(!is_servant_of_ratvar(invoker)) - M = invoker - if(is_servant_of_ratvar(M)) - return FALSE - //Anti magic abilities - var/anti_magic_source = M.anti_magic_check(holy = TRUE) - if(anti_magic_source) - M.mob_light(color = LIGHT_COLOR_HOLY_MAGIC, range = 2, duration = 100) - var/mutable_appearance/forbearance = mutable_appearance('icons/effects/genetics.dmi', "servitude", CALCULATE_MOB_OVERLAY_LAYER(MUTATIONS_LAYER)) - M.add_overlay(forbearance) - addtimer(CALLBACK(M, TYPE_PROC_REF(/atom, cut_overlay), forbearance), 100) - M.visible_message("[M] stares blankly, as a field of energy flows around them.", \ - "You feel a slight shock as a wave of energy flows past you.") - playsound(invoker, 'sound/magic/mm_hit.ogg', 50, TRUE) - return TRUE - //Blood Cultist Effect - if(iscultist(M)) - M.mob_light(color = LIGHT_COLOR_BLOOD_MAGIC, range = 2, duration = 300) - M.stuttering += 15 - M.Jitter(15) - var/mob_color = M.color - M.color = LIGHT_COLOR_BLOOD_MAGIC - animate(M, color = mob_color, time = 300) - M.say("Fwebar uloft'gib mirlig yro'fara!") - to_chat(invoker, "You fail to stun [M]!") - playsound(invoker, 'sound/magic/mm_hit.ogg', 50, TRUE) - return TRUE - //Successful Invokation - invoker.mob_light(color = LIGHT_COLOR_CLOCKWORK, range = 2, duration = 10) - if(!is_reebe(invoker.z)) - if(!HAS_TRAIT(M, TRAIT_MINDSHIELD)) - M.Paralyze(150) - else - to_chat(invoker, "[M] seems somewhat resistant to your powers!") - M.confused = clamp(M.confused, 50, INFINITY) - if(issilicon(M)) - var/mob/living/silicon/S = M - S.emp_act(EMP_HEAVY) - else if(iscarbon(M)) - var/mob/living/carbon/C = M - C.silent += 6 - C.stuttering += 15 - C.Jitter(15) - if(M.client) - var/client_color = M.client.color - M.client.color = "#BE8700" - animate(M.client, color = client_color, time = 25) - playsound(invoker, 'sound/magic/staff_animation.ogg', 50, TRUE) - return TRUE diff --git a/code/modules/antagonists/clock_cult/scriptures/sentinels_compromise.dm b/code/modules/antagonists/clock_cult/scriptures/sentinels_compromise.dm deleted file mode 100644 index e7922c0a0072e..0000000000000 --- a/code/modules/antagonists/clock_cult/scriptures/sentinels_compromise.dm +++ /dev/null @@ -1,54 +0,0 @@ -/datum/clockcult/scripture/slab/sentinelscompromise - name = "Sentinel's Compromise" - use_time = 80 - slab_overlay = "compromise" - desc = "Heal any servant within view, but half of their damage healed will be given to you in the form of toxin damage." - tip = "Use on any servant in trouble to heal their wounds." - invokation_time = 10 - button_icon_state = "Sentinel's Compromise" - category = SPELLTYPE_PRESERVATION - cogs_required = 1 - power_cost = 80 - -/datum/clockcult/scripture/slab/sentinelscompromise/click_on(atom/A) - if(!(invoker in viewers(7, get_turf(A)))) - return - if(!ishuman(invoker)) - to_chat(invoker, "Non humanoid servants can't use this power!") - return - var/mob/living/M = A - if(!istype(M)) - return - if(!is_servant_of_ratvar(M)) - return - if(apply_effects(A)) - uses_left -- - if(uses_left <= 0) - if(after_use_text) - clockwork_say(invoker, text2ratvar(after_use_text), TRUE) - end_invokation() - -/datum/clockcult/scripture/slab/sentinelscompromise/apply_effects(mob/living/M) - if(M.stat == DEAD) - return FALSE - var/total_damage = (M.getBruteLoss() + M.getFireLoss() + M.getOxyLoss() + M.getCloneLoss()) * 0.6 - M.adjustBruteLoss(-M.getBruteLoss() * 0.6, FALSE) - M.adjustFireLoss(-M.getFireLoss() * 0.6, FALSE) - M.adjustOxyLoss(-M.getOxyLoss() * 0.6, FALSE) - M.adjustCloneLoss(-M.getCloneLoss() * 0.6, TRUE) - M.blood_volume = BLOOD_VOLUME_NORMAL - M.reagents.remove_reagent(/datum/reagent/water/holywater, INFINITY) - M.set_nutrition(NUTRITION_LEVEL_FULL) - M.bodytemperature = BODYTEMP_NORMAL - M.set_blindness(0) - M.set_blurriness(0) - M.set_dizziness(0) - M.cure_nearsighted() - M.cure_blind() - M.cure_husk() - M.hallucination = 0 - new /obj/effect/temp_visual/heal(get_turf(M), "#f8d984") - playsound(M, 'sound/magic/magic_missile.ogg', 50, TRUE) - playsound(invoker, 'sound/magic/magic_missile.ogg', 50, TRUE) - invoker.adjustToxLoss(min(total_damage/2, 80), TRUE, TRUE) - return TRUE diff --git a/code/modules/antagonists/clock_cult/scriptures/sigil_of_vitality.dm b/code/modules/antagonists/clock_cult/scriptures/sigil_of_vitality.dm index 004b5316b3621..6f2b7151562d7 100644 --- a/code/modules/antagonists/clock_cult/scriptures/sigil_of_vitality.dm +++ b/code/modules/antagonists/clock_cult/scriptures/sigil_of_vitality.dm @@ -5,7 +5,7 @@ name = "Vitality Matrix" desc = "Summons a vitality matrix, which drains the life force of non servants, and can be used to heal or revive servants. Requires 2 invokers." tip = "Heal and revive dead servants, while draining the health from non servants." - button_icon_state = "Sigil of Vitality" + button_icon_state = "Vitality Matrix" power_cost = 300 invokation_time = 50 invokation_text = list("My life in your hands.") @@ -32,7 +32,7 @@ return TRUE if(M.stat == DEAD) return FALSE - var/amc = M.anti_magic_check(magic=FALSE,holy=TRUE) + var/amc = M.can_block_magic(MAGIC_RESISTANCE_HOLY) if(amc) return FALSE if(HAS_TRAIT(M, TRAIT_NODEATH)) @@ -73,7 +73,7 @@ else visible_message("\The [src] fails to heal [M]!", "There is insufficient vitality to heal your wounds!") else - if(M.anti_magic_check(magic=FALSE,holy=TRUE)) + if(M.can_block_magic(MAGIC_RESISTANCE_HOLY)) return if(is_convertable_to_clockcult(M) && !GLOB.gateway_opening) visible_message("\The [src] refuses to siphon [M]'s vitality, their mind has great potential!") diff --git a/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/hateful_manacles.dm b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/hateful_manacles.dm new file mode 100644 index 0000000000000..44aa11f481f03 --- /dev/null +++ b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/hateful_manacles.dm @@ -0,0 +1,66 @@ +//==================================// +// ! Hateful Manacles ! // +//==================================// +/datum/clockcult/scripture/slab/hateful_manacles + name = "Hateful Manacles" + desc = "Forms replicant manacles around a target's wrists that function like handcuffs, restraining the target." + tip = "Handcuff a target at close range to subdue them for conversion or vitality extraction." + button_icon_state = "Hateful Manacles" + power_cost = 25 + invokation_time = 15 + invokation_text = list("Shackle the heretic...", "Break them in body and spirit!") + slab_overlay = "hateful_manacles" + use_time = 200 + cogs_required = 0 + category = SPELLTYPE_SERVITUDE + empowerment = MANACLES + +//For the Hateful Manacles scripture; applies replicant handcuffs to the clicked_on. + +/datum/clockcult/scripture/slab/proc/hateful_manacles(mob/living/caller, atom/clicked_on) + empowerment = null + var/turf/T = caller.loc + if(!isturf(T)) + return FALSE + + if(iscarbon(clicked_on) && clicked_on.Adjacent(caller)) + var/mob/living/carbon/L = clicked_on + if(is_servant_of_ratvar(L)) + to_chat(caller, ("\"[L.p_theyre(TRUE)] a servant.\"")) + return FALSE + else if(L.stat) + to_chat(caller, ("\"There is use in shackling the dead, but for examples.\"")) + return FALSE + else if (istype(L.handcuffed, /obj/item/restraints/handcuffs/clockwork)) + to_chat(caller, ("\"[L.p_theyre(TRUE)] already helpless, no?\"")) + return FALSE + + playsound(caller.loc, 'sound/weapons/handcuffs.ogg', 30, TRUE) + caller.visible_message(("[caller] begins forming manacles around [L]'s wrists!"), \ + ("You begin shaping replicant alloy into manacles around [L]'s wrists...")) + to_chat(L, ("[caller] begins forming manacles around your wrists!")) + if(do_after(caller, 3 SECONDS, L)) + if(!(istype(L.handcuffed,/obj/item/restraints/handcuffs/clockwork))) + var/obj/item/restraints/handcuffs/clockwork/restraints = new(L) + if (!restraints.apply_cuffs(L, caller)) + qdel(restraints) + return TRUE + restraints.item_flags |= DROPDEL + + to_chat(caller, ("You shackle [L].")) + log_combat(caller, L, "handcuffed") + else + to_chat(caller, ("You fail to shackle [L].")) + return TRUE + +/obj/item/restraints/handcuffs/clockwork + name = "replicant manacles" + desc = "Heavy manacles made out of freezing-cold metal. It looks like brass, but feels much more solid." + icon_state = "brass_manacles" + item_state = "brass_manacles" + item_flags = DROPDEL + +/obj/item/restraints/handcuffs/clockwork/dropped(mob/user) + user.visible_message(("[user]'s [name] come apart at the seams!"), \ + ("Your [name] break apart as they're removed!")) + . = ..() diff --git a/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/kindle.dm b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/kindle.dm new file mode 100644 index 0000000000000..ef0d3d27e1b36 --- /dev/null +++ b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/kindle.dm @@ -0,0 +1,45 @@ +//==================================// +// ! Kindle ! // +//==================================// +/datum/clockcult/scripture/slab/kindle + name = "Kindle" + desc = "Stuns and mutes a target from a short range. Significantly less effective on Reebe." + tip = "Stuns and mutes a target from a short range." + button_icon_state = "Kindle" + power_cost = 125 + invokation_time = 30 + invokation_text = list("Divinity, show them your light!") + after_use_text = "Let the power flow through you!" + slab_overlay = "volt" + use_time = 150 + cogs_required = 1 + category = SPELLTYPE_SERVITUDE + empowerment = KINDLE + +//For the Kindle scripture; stuns and mutes a clicked_on non-servant. + +/datum/clockcult/scripture/slab/proc/kindle(mob/living/caller, mob/living/clicked_on) + empowerment = null + to_chat(caller, ("You release the light of Ratvar!")) + clockwork_say(caller, text2ratvar("Purge all untruths and honor Engine!")) + if(isliving(clicked_on)) + var/mob/living/L = clicked_on + if(is_servant_of_ratvar(L) || L.stat) + return BULLET_ACT_HIT + var/atom/O = L.can_block_magic(MAGIC_RESISTANCE_HOLY) + playsound(L, 'sound/magic/fireball.ogg', 50, TRUE, frequency = 1.25) + if(O) + if(isitem(O)) + L.visible_message(("[L]'s eyes flare with dim light!"), \ + ("Your [O] glows white-hot against you as it absorbs [src]'s power!")) + else if(ismob(O)) + L.visible_message(("[L]'s eyes flare with dim light!")) + playsound(L, 'sound/weapons/sear.ogg', 50, TRUE) + else + L.visible_message(("[L]'s eyes blaze with brilliant light!"), \ + ("Your vision suddenly screams with white-hot light!")) + L.Paralyze(5 SECONDS) + L.flash_act(1, 1) + if(iscultist(L)) + L.adjustFireLoss(15) + return TRUE diff --git a/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/sentinels_compromise.dm b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/sentinels_compromise.dm new file mode 100644 index 0000000000000..fd2fb69b4261f --- /dev/null +++ b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/sentinels_compromise.dm @@ -0,0 +1,63 @@ +/datum/clockcult/scripture/slab/sentinelscompromise + name = "Sentinel's Compromise" + use_time = 80 + slab_overlay = "compromise" + desc = "Heal any servant within view, but half of their damage healed will be given to you in the form of toxin damage." + tip = "Use on any servant in trouble to heal their wounds." + invokation_time = 10 + button_icon_state = "Sentinel's Compromise" + category = SPELLTYPE_PRESERVATION + cogs_required = 1 + power_cost = 80 + empowerment = COMPROMISE + +//For the Sentinel's Compromise scripture; heals a clicked_on servant. + +/datum/clockcult/scripture/slab/proc/sentinels_compromise(mob/living/caller, atom/clicked_on) + empowerment = null + var/turf/T = caller.loc + if(!isturf(T)) + return FALSE + + if(isliving(clicked_on) && (clicked_on in view(7, get_turf(caller)))) + var/mob/living/L = clicked_on + if(!is_servant_of_ratvar(L)) + to_chat(caller, ("\"[L] does not yet serve Ratvar.\"")) + return TRUE + if(L.stat == DEAD) + to_chat(caller, ("\"[L.p_theyre(TRUE)] dead. [text2ratvar("Oh, child. To have your life cut short...")]\"")) + return TRUE + + var/brutedamage = L.getBruteLoss() + var/burndamage = L.getFireLoss() + var/oxydamage = L.getOxyLoss() + var/totaldamage = brutedamage + burndamage + oxydamage + if(!totaldamage && (!L.reagents || !L.reagents.has_reagent(/datum/reagent/water/holywater))) + to_chat(caller, ("\"[L] is unhurt and untainted.\"")) + return TRUE + to_chat(caller, ("You bathe [L == caller ? "yourself":"[L]"] in Inath-neq's power!")) + var/clicked_onturf = get_turf(L) + var/has_holy_water = (L.reagents && L.reagents.has_reagent(/datum/reagent/water/holywater)) + var/healseverity = max(round(totaldamage*0.05, 1), 1) //shows the general severity of the damage you just healed, 1 glow per 20 + for(var/i in 1 to healseverity) + new /obj/effect/temp_visual/heal(clicked_onturf, "#1E8CE1") + if(totaldamage) + L.adjustBruteLoss(-brutedamage, TRUE, FALSE) + L.adjustFireLoss(-burndamage, TRUE, FALSE) + L.adjustOxyLoss(-oxydamage) + caller.adjustToxLoss(totaldamage * 0.5, TRUE, TRUE) + clockwork_say(caller, text2ratvar("[has_holy_water ? "Heal tainted" : "Mend wounded"] flesh!")) + log_combat(caller, L, "healed with Sentinel's Compromise") + L.visible_message(("A blue light washes over [L], [has_holy_water ? "causing [L.p_them()] to briefly glow as it mends" : " mending"] [L.p_their()] bruises and burns!"), \ + ("You feel Inath-neq's power healing your wounds[has_holy_water ? " and purging the darkness within you" : ""], but a deep nausea overcomes you!")) + else + clockwork_say(caller, text2ratvar("Purge foul darkness!")) + log_combat(caller, L, "purged of holy water with Sentinel's Compromise") + L.visible_message(("A blue light washes over [L], causing [L.p_them()] to briefly glow!"), \ + ("You feel Inath-neq's power purging the darkness within you!")) + playsound(clicked_onturf, 'sound/magic/staff_healing.ogg', 50, 1) + + if(has_holy_water) + L.reagents.remove_reagent(/datum/reagent/water/holywater, 1000) + + return TRUE diff --git a/code/modules/antagonists/clock_cult/scriptures/vanguard.dm b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/vanguard.dm similarity index 92% rename from code/modules/antagonists/clock_cult/scriptures/vanguard.dm rename to code/modules/antagonists/clock_cult/scriptures/slab_empowerments/vanguard.dm index 116552ddb93b3..f8cda7ceba3f8 100644 --- a/code/modules/antagonists/clock_cult/scriptures/vanguard.dm +++ b/code/modules/antagonists/clock_cult/scriptures/slab_empowerments/vanguard.dm @@ -12,8 +12,7 @@ var/last_recorded_stam_dam = 0 var/total_stamina_damage = 0 -/datum/clockcult/scripture/slab/vanguard/click_on(atom/A) - return FALSE +//Only you are safe :) /datum/clockcult/scripture/slab/vanguard/invoke_success() ADD_TRAIT(invoker, TRAIT_STUNIMMUNE, VANGUARD_TRAIT) @@ -37,3 +36,9 @@ REMOVE_TRAIT(invoker, TRAIT_NOSTAMCRIT, VANGUARD_TRAIT) REMOVE_TRAIT(invoker, TRAIT_NOLIMBDISABLE, VANGUARD_TRAIT) ..() + +// Due to how files are formatted this is put here. + +#undef KINDLE +#undef MANACLES +#undef COMPROMISE diff --git a/code/modules/antagonists/clock_cult/scriptures/summon_spear.dm b/code/modules/antagonists/clock_cult/scriptures/summon_spear.dm index 2cc55b6fe1b3b..0aedaf88fbfd9 100644 --- a/code/modules/antagonists/clock_cult/scriptures/summon_spear.dm +++ b/code/modules/antagonists/clock_cult/scriptures/summon_spear.dm @@ -1,20 +1,19 @@ -/obj/effect/proc_holder/spell/targeted/summon_spear +/datum/action/spell/summon_spear // not conjure since this is like the marvel movie name = "Summon Weapon" desc = "Summons your weapon from across time and space." - charge_max = 20 + cooldown_time = 20 SECONDS invocation = "none" invocation_type = INVOCATION_NONE - action_icon = 'icons/hud/actions/actions_clockcult.dmi' - action_icon_state = "ratvarian_spear" - action_background_icon_state = "bg_clock" - clothes_req = FALSE - range = -1 - include_user = TRUE + icon_icon = 'icons/hud/actions/actions_clockcult.dmi' + button_icon_state = "ratvarian_spear" + background_icon_state = "bg_clock" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC var/obj/item/marked_item -/obj/effect/proc_holder/spell/targeted/summon_spear/cast(list/targets, mob/user) +/datum/action/spell/summon_spear/on_cast(mob/user, atom/target) + . = ..() if(QDELETED(marked_item)) qdel(src) diff --git a/code/modules/antagonists/clock_cult/structure/clockwork_camera.dm b/code/modules/antagonists/clock_cult/structure/clockwork_camera.dm index 0cc0a9d8a8a3a..d3f117cad4c78 100644 --- a/code/modules/antagonists/clock_cult/structure/clockwork_camera.dm +++ b/code/modules/antagonists/clock_cult/structure/clockwork_camera.dm @@ -9,12 +9,12 @@ button_icon_state = "warp_down" var/warping = FALSE -/datum/action/innate/clockcult/warp/IsAvailable() +/datum/action/innate/clockcult/warp/is_available() if(!is_servant_of_ratvar(owner) || owner.incapacitated()) return FALSE return ..() -/datum/action/innate/clockcult/warp/Activate() +/datum/action/innate/clockcult/warp/on_activate() if(!isliving(owner)) return if(GLOB.gateway_opening) @@ -72,7 +72,7 @@ /obj/machinery/computer/camera_advanced/ratvar/Initialize(mapload) . = ..() START_PROCESSING(SSobj, src) - warp_action = new + warp_action = new(src) /obj/machinery/computer/camera_advanced/ratvar/Destroy() STOP_PROCESSING(SSobj, src) @@ -92,7 +92,6 @@ /obj/machinery/computer/camera_advanced/ratvar/GrantActions(mob/living/user) . = ..() if(warp_action) - warp_action.target = src warp_action.Grant(user) actions += warp_action diff --git a/code/modules/antagonists/clock_cult/structure/clockwork_sigil.dm b/code/modules/antagonists/clock_cult/structure/clockwork_sigil.dm index a6fab9ceedb9a..0f290ba99c170 100644 --- a/code/modules/antagonists/clock_cult/structure/clockwork_sigil.dm +++ b/code/modules/antagonists/clock_cult/structure/clockwork_sigil.dm @@ -65,7 +65,7 @@ var/mob/living/M = AM if(!istype(M)) return FALSE - var/amc = M.anti_magic_check(magic=FALSE,holy=TRUE) + var/amc = M.can_block_magic(MAGIC_RESISTANCE_HOLY) if(amc) return FALSE return TRUE diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index df3ce5c123a0f..f73eff65fdde5 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -2,37 +2,38 @@ name = "Prepare Blood Magic" button_icon_state = "carve" desc = "Prepare blood magic by carving runes into your flesh. This is easier with an empowering rune." + default_button_position = DEFAULT_BLOODSPELLS var/list/spells = list() var/channeling = FALSE -/datum/action/innate/cult/blood_magic/Grant() - ..() - button.screen_loc = DEFAULT_BLOODSPELLS - button.moved = DEFAULT_BLOODSPELLS - button.ordered = FALSE - /datum/action/innate/cult/blood_magic/Remove() for(var/X in spells) qdel(X) ..() -/datum/action/innate/cult/blood_magic/IsAvailable() +/datum/action/innate/cult/blood_magic/is_available() if(!iscultist(owner)) return FALSE return ..() /datum/action/innate/cult/blood_magic/proc/Positioning() - var/list/screen_loc_split = splittext(button.screen_loc,",") - var/list/screen_loc_X = splittext(screen_loc_split[1],":") - var/list/screen_loc_Y = splittext(screen_loc_split[2],":") - var/pix_X = text2num(screen_loc_X[2]) - for(var/datum/action/innate/cult/blood_spell/B in spells) - if(B.button.locked) - var/order = pix_X+spells.Find(B)*31 - B.button.screen_loc = "[screen_loc_X[1]]:[order],[screen_loc_Y[1]]:[screen_loc_Y[2]]" - B.button.moved = B.button.screen_loc - -/datum/action/innate/cult/blood_magic/Activate() + for(var/datum/hud/hud as anything in viewers) + var/our_view = hud.mymob?.client?.view || "15x15" + var/atom/movable/screen/movable/action_button/button = viewers[hud] + var/position = screen_loc_to_offset(button.screen_loc) + var/spells_iterated = 0 + for(var/datum/action/innate/cult/blood_spell/blood_spell in spells) + spells_iterated += 1 + if(blood_spell.positioned) + continue + var/atom/movable/screen/movable/action_button/moving_button = blood_spell.viewers[hud] + if(!moving_button) + continue + var/our_x = position[1] + spells_iterated * world.icon_size // Offset any new buttons into our list + hud.position_action(moving_button, offset_to_screen_loc(our_x, position[2], our_view)) + blood_spell.positioned = TRUE + +/datum/action/innate/cult/blood_magic/on_activate() var/rune = FALSE var/limit = RUNELESS_MAX_BLOODCHARGE if(locate(/obj/effect/rune/empower) in range(1, owner)) @@ -94,6 +95,7 @@ var/base_desc //To allow for updating tooltips var/invocation var/health_cost = 0 + var/positioned = FALSE /datum/action/innate/cult/blood_spell/Grant(mob/living/owner, datum/action/innate/cult/blood_magic/BM) if(health_cost) @@ -101,9 +103,7 @@ base_desc = desc desc += "
Has [charges] use\s remaining." all_magic = BM - ..() - button.locked = TRUE - button.ordered = FALSE + return ..() /datum/action/innate/cult/blood_spell/Remove() if(all_magic) @@ -113,12 +113,12 @@ hand_magic = null ..() -/datum/action/innate/cult/blood_spell/IsAvailable() +/datum/action/innate/cult/blood_spell/is_available() if(!iscultist(owner) || owner.incapacitated() || !charges) return FALSE return ..() -/datum/action/innate/cult/blood_spell/Activate() +/datum/action/innate/cult/blood_spell/on_activate() if(magic_path) //If this spell flows from the hand if(!hand_magic) hand_magic = new magic_path(owner, src) @@ -158,7 +158,7 @@ invocation = "Ta'gh fara'qha fel d'amar det!" check_flags = AB_CHECK_CONSCIOUS -/datum/action/innate/cult/blood_spell/emp/Activate() +/datum/action/innate/cult/blood_spell/emp/on_activate() owner.whisper(invocation, language = /datum/language/common) owner.visible_message("[owner]'s hand flashes a bright blue!", \ "You speak the cursed words, emitting an EMP blast from your hand.") @@ -195,7 +195,7 @@ /// The item given to the cultist when the spell is invoked. Typepath. var/obj/item/summoned_type = /obj/item/melee/cultblade/dagger -/datum/action/innate/cult/blood_spell/dagger/Activate() +/datum/action/innate/cult/blood_spell/dagger/on_activate() var/turf/owner_turf = get_turf(owner) owner.whisper(invocation, language = /datum/language/common) owner.visible_message("[owner]'s hand glows red for a moment.", \ @@ -215,68 +215,47 @@ name = "Hallucinations" desc = "Gives hallucinations to a target at range. A silent and invisible spell." button_icon_state = "horror" - var/obj/effect/proc_holder/horror/PH charges = 4 check_flags = AB_CHECK_CONSCIOUS + requires_target = TRUE + enable_text = ("You prepare to horrify a target...") + disable_text = ("You dispel the magic...") -/datum/action/innate/cult/blood_spell/horror/New() - PH = new() - PH.attached_action = src - ..() +/datum/action/innate/cult/blood_spell/horror/InterceptClickOn(mob/living/caller, params, atom/clicked_on) + var/turf/caller_turf = get_turf(caller) + if(!isturf(caller_turf)) + return FALSE -/datum/action/innate/cult/blood_spell/horror/Destroy() - var/obj/effect/proc_holder/horror/destroy = PH - . = ..() - if(destroy && !QDELETED(destroy)) - QDEL_NULL(destroy) + if(!ishuman(clicked_on) || get_dist(caller, clicked_on) > 7) + return FALSE -/datum/action/innate/cult/blood_spell/horror/Activate() - PH.toggle(owner) //the important bit - return TRUE + var/mob/living/carbon/human/human_clicked = clicked_on + if(IS_CULTIST(human_clicked)) + return FALSE -/obj/effect/proc_holder/horror - active = FALSE - ranged_mousepointer = 'icons/effects/cult_target.dmi' - var/datum/action/innate/cult/blood_spell/attached_action + return ..() -/obj/effect/proc_holder/horror/Destroy() - var/datum/action/innate/cult/blood_spell/AA = attached_action - . = ..() - if(AA && !QDELETED(AA)) - QDEL_NULL(AA) +/datum/action/innate/cult/blood_spell/horror/on_activate(mob/user, mob/living/target) + if (!istype(target)) + return FALSE + target.hallucination = max(target.hallucination, 120) + SEND_SOUND(user, sound('sound/effects/ghost.ogg', FALSE, TRUE, 50)) -/obj/effect/proc_holder/horror/proc/toggle(mob/user) - if(active) - remove_ranged_ability("You dispel the magic...") - else - add_ranged_ability(user, "You prepare to horrify a target...") + var/image/sparkle_image = image('icons/effects/cult_effects.dmi', target, "bloodsparkles", ABOVE_MOB_LAYER) + target.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/cult, "cult_apoc", sparkle_image, NONE) -/obj/effect/proc_holder/horror/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - if(ranged_ability_user.incapacitated() || !iscultist(caller)) - remove_ranged_ability() - return - var/turf/T = get_turf(ranged_ability_user) - if(!isturf(T)) - return FALSE - if(ranged_ability_user in viewers(7, get_turf(target))) - if(!ishuman(target) || iscultist(target)) - return - var/mob/living/carbon/human/H = target - H.hallucination = max(H.hallucination, 120) - SEND_SOUND(ranged_ability_user, sound('sound/effects/ghost.ogg',0,1,50)) - var/image/C = image('icons/effects/cult_effects.dmi',H,"bloodsparkles", ABOVE_MOB_LAYER) - add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/cult, "cult_apoc", C, NONE) - addtimer(CALLBACK(H,TYPE_PROC_REF(/atom, remove_alt_appearance),"cult_apoc",TRUE), 2400, TIMER_OVERRIDE|TIMER_UNIQUE) - to_chat(ranged_ability_user,"[H] has been cursed with living nightmares!") - attached_action.charges-- - attached_action.desc = attached_action.base_desc - attached_action.desc += "
Has [attached_action.charges] use\s remaining." - attached_action.UpdateButtonIcon() - if(attached_action.charges <= 0) - remove_ranged_ability("You have exhausted the spell's power!") - qdel(src) + addtimer(CALLBACK(target, TYPE_PROC_REF(/atom, remove_alt_appearance), "cult_apoc", TRUE), 4 MINUTES, TIMER_OVERRIDE|TIMER_UNIQUE) + to_chat(user, ("[target] has been cursed with living nightmares!")) + + charges-- + desc = base_desc + desc += "
Has [charges] use\s remaining." + update_buttons() + if(charges <= 0) + to_chat(user, ("You have exhausted the spell's power!")) + qdel(src) + + return TRUE /datum/action/innate/cult/blood_spell/veiling name = "Conceal Presence" @@ -287,7 +266,7 @@ var/revealing = FALSE //if it reveals or not check_flags = AB_CHECK_CONSCIOUS -/datum/action/innate/cult/blood_spell/veiling/Activate() +/datum/action/innate/cult/blood_spell/veiling/on_activate() if(!revealing) owner.visible_message("Thin grey dust falls from [owner]'s hand!", \ "You invoke the veiling spell, hiding nearby runes.") @@ -326,7 +305,7 @@ qdel(src) desc = base_desc desc += "
Has [charges] use\s remaining." - UpdateButtonIcon() + update_buttons() /datum/action/innate/cult/blood_spell/manipulation name = "Blood Rites" @@ -378,7 +357,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/melee/blood_magic) source.charges = uses source.desc = source.base_desc source.desc += "
Has [uses] use\s remaining." - source.UpdateButtonIcon() + source.update_buttons() ..() /obj/item/melee/blood_magic/attack_self(mob/living/user) @@ -407,7 +386,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/melee/blood_magic) else if(source) source.desc = source.base_desc source.desc += "
Has [uses] use\s remaining." - source.UpdateButtonIcon() + source.update_buttons() //Stun /obj/item/melee/blood_magic/stun @@ -427,7 +406,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/melee/blood_magic) "You attempt to stun [L] with the spell!") user.mob_light(range = 3, color = LIGHT_COLOR_BLOOD_MAGIC, duration = 0.2 SECONDS) - var/anti_magic_source = L.anti_magic_check(holy = TRUE) + var/anti_magic_source = L.can_block_magic(MAGIC_RESISTANCE_HOLY) if(anti_magic_source) L.mob_light(range = 2, color = LIGHT_COLOR_HOLY_MAGIC, duration = 10 SECONDS) diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index a778d6cf0bd65..dc95be34b316c 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -236,7 +236,7 @@ /datum/team/cult name = "Bloodcult" - var/blood_target + var/atom/blood_target var/image/blood_target_image var/blood_target_reset_timer @@ -246,6 +246,64 @@ var/cult_risen = FALSE var/cult_ascendent = FALSE +/// Sets a blood target for the cult. +/datum/team/cult/proc/set_blood_target(atom/new_target, mob/marker, duration = 90 SECONDS) + if(QDELETED(new_target)) + CRASH("A null or invalid target was passed to set_blood_target.") + + if(blood_target_reset_timer) + return FALSE + + blood_target = new_target + RegisterSignal(blood_target, COMSIG_PARENT_QDELETING, PROC_REF(unset_blood_target_and_timer)) + var/area/target_area = get_area(new_target) + + blood_target_image = image('icons/effects/mouse_pointers/cult_target.dmi', new_target, "glow", ABOVE_MOB_LAYER) + blood_target_image.appearance_flags = RESET_COLOR + blood_target_image.pixel_x = -new_target.pixel_x + blood_target_image.pixel_y = -new_target.pixel_y + + for(var/datum/mind/cultist as anything in members) + if(!cultist.current) + continue + if(cultist.current.stat == DEAD || !cultist.current.client) + continue + + to_chat(cultist.current, (("[marker] has marked [blood_target] in the [target_area.name] as the cult's top priority, get there immediately!"))) + SEND_SOUND(cultist.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'), 0, 1, 75)) + cultist.current.client.images += blood_target_image + + blood_target_reset_timer = addtimer(CALLBACK(src, PROC_REF(unset_blood_target)), duration, TIMER_STOPPABLE) + return TRUE + +/// Unsets out blood target, clearing the images from all the cultists. +/datum/team/cult/proc/unset_blood_target() + blood_target_reset_timer = null + + for(var/datum/mind/cultist as anything in members) + if(!cultist.current) + continue + if(cultist.current.stat == DEAD || !cultist.current.client) + continue + + if(QDELETED(blood_target)) + to_chat(cultist.current, (("The blood mark's target is lost!"))) + else + to_chat(cultist.current, (("The blood mark has expired!"))) + cultist.current.client.images -= blood_target_image + + UnregisterSignal(blood_target, COMSIG_PARENT_QDELETING) + blood_target = null + + QDEL_NULL(blood_target_image) + +/// Unsets our blood target when they get deleted. +/datum/team/cult/proc/unset_blood_target_and_timer(datum/source) + SIGNAL_HANDLER + + deltimer(blood_target_reset_timer) + unset_blood_target() + /datum/team/cult/proc/check_size() if(cult_ascendent) return diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm index b85fde36dd9e7..17b6ef98dcbe9 100644 --- a/code/modules/antagonists/cult/cult_comms.dm +++ b/code/modules/antagonists/cult/cult_comms.dm @@ -3,10 +3,12 @@ /datum/action/innate/cult icon_icon = 'icons/hud/actions/actions_cult.dmi' background_icon_state = "bg_demon" + button_icon_state = null buttontooltipstyle = "cult" check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_INCAPACITATED|AB_CHECK_CONSCIOUS + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' -/datum/action/innate/cult/IsAvailable() +/datum/action/innate/cult/is_available() if(!iscultist(owner)) return FALSE return ..() @@ -17,9 +19,9 @@ button_icon_state = "cult_comms" check_flags = AB_CHECK_CONSCIOUS -/datum/action/innate/cult/comm/Activate() +/datum/action/innate/cult/comm/on_activate() var/input = tgui_input_text(usr, "Please choose a message to tell to the other acolytes.", "Voice of Blood", "") - if(!input || !IsAvailable()) + if(!input || !is_available()) return if(CHAT_FILTER_CHECK(input)) to_chat(usr, "You cannot send a message that contains a word prohibited in IC chat!") @@ -58,7 +60,7 @@ name = "Spiritual Communion" desc = "Conveys a message from the spirit realm that all cultists can hear." -/datum/action/innate/cult/comm/spirit/IsAvailable() +/datum/action/innate/cult/comm/spirit/is_available() if(iscultist(owner.mind.current)) return TRUE @@ -79,15 +81,15 @@ name = "Assert Leadership" button_icon_state = "cultvote" -/datum/action/innate/cult/mastervote/IsAvailable() +/datum/action/innate/cult/mastervote/is_available() var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE) if(!C || C.cult_team.cult_vote_called || !ishuman(owner)) return FALSE return ..() -/datum/action/innate/cult/mastervote/Activate() +/datum/action/innate/cult/mastervote/on_activate() var/choice = alert(owner, "The mantle of leadership is heavy. Success in this role requires an expert level of communication and experience. Are you sure?",, "Yes", "No") - if(choice == "Yes" && IsAvailable()) + if(choice == "Yes" && is_available()) var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE) pollCultists(owner,C.cult_team) @@ -144,7 +146,7 @@ to_chat(B.current,"[Nominee] has won the cult's support and is now their master. Follow [Nominee.p_their()] orders to the best of your ability!") return TRUE -/datum/action/innate/cult/master/IsAvailable() +/datum/action/innate/cult/master/is_available() if(!owner.mind || !owner.mind.has_antag_datum(/datum/antagonist/cult/master) || GLOB.cult_narsie) return 0 return ..() @@ -155,7 +157,7 @@ button_icon_state = "sintouch" check_flags = AB_CHECK_CONSCIOUS -/datum/action/innate/cult/master/finalreck/Activate() +/datum/action/innate/cult/master/finalreck/on_activate() var/datum/antagonist/cult/antag = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE) if(!antag) return @@ -219,163 +221,115 @@ name = "Mark Target" desc = "Marks a target for the cult." button_icon_state = "cult_mark" - var/obj/effect/proc_holder/cultmark/CM - var/cooldown = 0 - var/base_cooldown = 1200 - -/datum/action/innate/cult/master/cultmark/New(Target) - CM = new() - CM.attached_action = src - ..() - -/datum/action/innate/cult/master/cultmark/IsAvailable() - if(cooldown > world.time) - if(!CM.active) - to_chat(owner, "You need to wait [DisplayTimeText(cooldown - world.time)] before you can mark another target!") + requires_target = TRUE + cooldown_time = 2 MINUTES + enable_text = "You prepare to mark a target for your cult. Click a target to mark them!" + disable_text = "You cease the marking ritual." + /// The duration of the mark itself + var/cult_mark_duration = 90 SECONDS + +/datum/action/innate/cult/master/cultmark/InterceptClickOn(mob/caller, params, atom/clicked_on) + var/turf/caller_turf = get_turf(caller) + if(!isturf(caller_turf)) return FALSE - return ..() -/datum/action/innate/cult/master/cultmark/Destroy() - QDEL_NULL(CM) + if(!(clicked_on in view(7, caller_turf))) + return FALSE return ..() -/datum/action/innate/cult/master/cultmark/Activate() - CM.toggle(owner) //the important bit - return TRUE - -/obj/effect/proc_holder/cultmark - active = FALSE - ranged_mousepointer = 'icons/effects/cult_target.dmi' - var/datum/action/innate/cult/master/cultmark/attached_action +/datum/action/innate/cult/master/cultmark/on_activate(mob/user, atom/target) + var/datum/antagonist/cult/cultist = user.mind.has_antag_datum(/datum/antagonist/cult, TRUE) + if(!cultist) + CRASH("[type] was casted by someone without a cult antag datum.") -/obj/effect/proc_holder/cultmark/Destroy() - attached_action = null - return ..() - -/obj/effect/proc_holder/cultmark/proc/toggle(mob/user) - if(active) - remove_ranged_ability("You cease the marking ritual.") - else - add_ranged_ability(user, "You prepare to mark a target for your cult...") - -/obj/effect/proc_holder/cultmark/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - if(ranged_ability_user.incapacitated()) - remove_ranged_ability() - return - var/turf/T = get_turf(ranged_ability_user) - if(!isturf(T)) + var/datum/team/cult/cult_team = cultist.get_team() + if(!cult_team) + CRASH("[type] was casted by a cultist without a cult team datum.") + if(cult_team.blood_target) + to_chat(user, ("The cult has already designated a target!")) return FALSE - var/datum/antagonist/cult/C = caller.mind.has_antag_datum(/datum/antagonist/cult,TRUE) - - if(ranged_ability_user in viewers(7, get_turf(target))) - if(C.cult_team.blood_target) - to_chat(ranged_ability_user, "The cult has already designated a target!") - return FALSE - C.cult_team.blood_target = target - var/area/A = get_area(target) - attached_action.cooldown = world.time + attached_action.base_cooldown - addtimer(CALLBACK(attached_action.owner, TYPE_PROC_REF(/mob, update_action_buttons_icon)), attached_action.base_cooldown) - C.cult_team.blood_target_image = image('icons/effects/cult_target.dmi', target, "glow", ABOVE_MOB_LAYER) - C.cult_team.blood_target_image.appearance_flags = RESET_COLOR - C.cult_team.blood_target_image.pixel_x = -target.pixel_x - C.cult_team.blood_target_image.pixel_y = -target.pixel_y - for(var/datum/mind/B in SSticker.mode.cult) - if(B.current && B.current.stat != DEAD && B.current.client) - to_chat(B.current, "[ranged_ability_user] has marked [C.cult_team.blood_target] in the [A.name] as the cult's top priority, get there immediately!") - SEND_SOUND(B.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'),0,1,75)) - B.current.client.images += C.cult_team.blood_target_image - attached_action.owner.update_action_buttons_icon() - remove_ranged_ability("The marking rite is complete! It will last for 90 seconds.") - C.cult_team.blood_target_reset_timer = addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reset_blood_target),C.cult_team), 900, TIMER_STOPPABLE) + if(cult_team.set_blood_target(target, user, cult_mark_duration)) + disable_text = "The marking rite is complete! It will last for [DisplayTimeText(cult_mark_duration)] seconds." + unset_click_ability(user) + disable_text = initial(disable_text) + start_cooldown() + update_buttons() return TRUE - return FALSE + unset_click_ability(user) + return TRUE -/proc/reset_blood_target(datum/team/cult/team) - for(var/datum/mind/B in team.members) - if(B.current && B.current.stat != DEAD && B.current.client) - if(team.blood_target) - to_chat(B.current,"The blood mark has expired!") - B.current.client.images -= team.blood_target_image - QDEL_NULL(team.blood_target_image) - team.blood_target = null +/datum/action/innate/cult/ghostmark //Ghost version + name = "Mark a Blood Target for the Cult" + desc = "Marks whatever you are orbiting for the entire cult to track." + button_icon_state = "cult_mark" + /// The duration of the mark on the target + var/cult_mark_duration = 60 SECONDS + /// The cooldown between marks - the ability can be used in between cooldowns, but can't mark (only clear) + var/cult_mark_cooldown_duration = 60 SECONDS + /// The actual cooldown tracked of the action + COOLDOWN_DECLARE(cult_mark_cooldown) + +/datum/action/innate/cult/ghostmark/is_available() + return ..() && istype(owner, /mob/dead/observer) + +/datum/action/innate/cult/ghostmark/on_activate() + var/datum/antagonist/cult/cultist = owner.mind?.has_antag_datum(/datum/antagonist/cult, TRUE) + if(!cultist) + CRASH("[type] was casted by someone without a cult antag datum.") + + var/datum/team/cult/cult_team = cultist.get_team() + if(!cult_team) + CRASH("[type] was casted by a cultist without a cult team datum.") + + if(cult_team.blood_target) + if(!COOLDOWN_FINISHED(src, cult_mark_cooldown)) + cult_team.unset_blood_target_and_timer() + to_chat(owner, ("You have cleared the cult's blood target!")) + return TRUE + + to_chat(owner, ("The cult has already designated a target!")) + return FALSE + if(!COOLDOWN_FINISHED(src, cult_mark_cooldown)) + to_chat(owner, ("You aren't ready to place another blood mark yet!")) + return FALSE -/datum/action/innate/cult/master/cultmark/ghost - name = "Mark a Blood Target for the Cult" - desc = "Marks a target for the entire cult to track." + var/atom/mark_target = owner.orbiting?.parent || get_turf(owner) + if(!mark_target) + return FALSE -/datum/action/innate/cult/master/cultmark/ghost/IsAvailable() - if(istype(owner, /mob/dead/observer) && iscultist(owner.mind.current)) + if(cult_team.set_blood_target(mark_target, owner, 60 SECONDS)) + to_chat(owner, ("You have marked [mark_target] for the cult! It will last for [DisplayTimeText(cult_mark_duration)].")) + COOLDOWN_START(src, cult_mark_cooldown, cult_mark_cooldown_duration) + update_button_status() + addtimer(CALLBACK(src, PROC_REF(reset_button)), cult_mark_cooldown_duration + 1) return TRUE - else - qdel(src) -/datum/action/innate/cult/ghostmark //Ghost version - name = "Blood Mark your Target" - desc = "Marks whatever you are orbitting - for the entire cult to track." - button_icon_state = "cult_mark" - var/tracking = FALSE - var/cooldown = 0 - var/base_cooldown = 600 + to_chat(owner, ("The marking failed!")) + return FALSE -/datum/action/innate/cult/ghostmark/IsAvailable() - if(istype(owner, /mob/dead/observer) && iscultist(owner.mind.current)) - return TRUE +/datum/action/innate/cult/ghostmark/proc/update_button_status() + if(!owner) + return + if(COOLDOWN_FINISHED(src, cult_mark_duration)) + name = initial(name) + desc = initial(desc) + button_icon_state = initial(button_icon_state) else - qdel(src) + name = "Clear the Blood Mark" + desc = "Remove the Blood Mark you previously set." + button_icon_state = "emp" + + update_buttons() /datum/action/innate/cult/ghostmark/proc/reset_button() - if(owner) - name = "Blood Mark your Target" - desc = "Marks whatever you are orbitting - for the entire cult to track." - button_icon_state = "cult_mark" - owner.update_action_buttons_icon() - SEND_SOUND(owner, 'sound/magic/enter_blood.ogg') - to_chat(owner,"Your previous mark is gone - you are now ready to create a new blood mark.") - -/datum/action/innate/cult/ghostmark/Activate() - var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult,TRUE) - if(C.cult_team.blood_target) - if(cooldown>world.time) - reset_blood_target(C.cult_team) - to_chat(owner, "You have cleared the cult's blood target!") - deltimer(C.cult_team.blood_target_reset_timer) - return - else - to_chat(owner, "The cult has already designated a target!") - return - if(cooldown>world.time) - to_chat(owner, "You aren't ready to place another blood mark yet!") + if(QDELETED(owner) || QDELETED(src)) return - target = owner.orbiting?.parent || get_turf(owner) - if(!target) - return - C.cult_team.blood_target = target - var/area/A = get_area(target) - cooldown = world.time + base_cooldown - addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob, update_action_buttons_icon)), base_cooldown) - C.cult_team.blood_target_image = image('icons/effects/cult_target.dmi', target, "glow", ABOVE_MOB_LAYER) - C.cult_team.blood_target_image.appearance_flags = RESET_COLOR - C.cult_team.blood_target_image.pixel_x = -target.pixel_x - C.cult_team.blood_target_image.pixel_y = -target.pixel_y - SEND_SOUND(owner, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'),0,1,75)) - owner.client.images += C.cult_team.blood_target_image - for(var/datum/mind/B in SSticker.mode.cult) - if(B.current && B.current.stat != DEAD && B.current.client) - to_chat(B.current, "[owner] has marked [C.cult_team.blood_target] in the [A.name] as the cult's top priority, get there immediately!") - SEND_SOUND(B.current, sound(pick('sound/hallucinations/over_here2.ogg','sound/hallucinations/over_here3.ogg'),0,1,75)) - B.current.client.images += C.cult_team.blood_target_image - to_chat(owner,"You have marked the [target] for the cult! It will last for [DisplayTimeText(base_cooldown)].") - name = "Clear the Blood Mark" - desc = "Remove the Blood Mark you previously set." - button_icon_state = "emp" - owner.update_action_buttons_icon() - C.cult_team.blood_target_reset_timer = addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(reset_blood_target),C.cult_team), base_cooldown, TIMER_STOPPABLE) - addtimer(CALLBACK(src, PROC_REF(reset_button)), base_cooldown) + SEND_SOUND(owner, 'sound/magic/enter_blood.ogg') + to_chat(owner, ("Your previous mark is gone - you are now ready to create a new blood mark.")) + update_button_status() //////// ELDRITCH PULSE ///////// @@ -386,82 +340,83 @@ desc = "Seize upon a fellow cultist or cult structure and teleport it to a nearby location." icon_icon = 'icons/hud/actions/actions_spells.dmi' button_icon_state = "arcane_barrage" - var/obj/effect/proc_holder/pulse/PM - var/cooldown = 0 - var/base_cooldown = 150 - var/throwing = FALSE - var/mob/living/throwee - -/datum/action/innate/cult/master/pulse/New() - PM = new() - PM.attached_action = src - ..() - -/datum/action/innate/cult/master/pulse/IsAvailable() - if(!owner.mind || !owner.mind.has_antag_datum(/datum/antagonist/cult/master)) + requires_target = TRUE + enable_text = "You prepare to tear through the fabric of reality... Click a target to sieze them!" + disable_text = "You cease your preparations." + cooldown_time = 15 SECONDS + /// Weakref to whoever we're currently about to toss + var/datum/weakref/throwee_ref + +/datum/action/innate/cult/master/pulse/InterceptClickOn(mob/living/caller, params, atom/clicked_on) + var/turf/caller_turf = get_turf(caller) + if(!isturf(caller_turf)) return FALSE - if(cooldown > world.time) - if(!PM.active) - to_chat(owner, "You need to wait [DisplayTimeText(cooldown - world.time)] before you can pulse again!") + + if(!(clicked_on in view(7, caller_turf))) return FALSE - return ..() -/datum/action/innate/cult/master/pulse/Destroy() - PM.attached_action = null //What the fuck is even going on here. - QDEL_NULL(PM) + if(clicked_on == caller) + return FALSE return ..() +/datum/action/innate/cult/master/pulse/on_activate(mob/user, atom/target) + var/atom/throwee = throwee_ref?.resolve() -/datum/action/innate/cult/master/pulse/Activate() - PM.toggle(owner) //the important bit - return TRUE + if(QDELETED(throwee)) + to_chat(user, "You lost your target!") + throwee = null + throwee_ref = null + return FALSE -/obj/effect/proc_holder/pulse - active = FALSE - ranged_mousepointer = 'icons/effects/throw_target.dmi' - var/datum/action/innate/cult/master/pulse/attached_action + if(throwee) + if(get_dist(throwee, target) >= 16) + to_chat(user, "You can't teleport [target.p_them()] that far!") + return FALSE -/obj/effect/proc_holder/pulse/Destroy() - attached_action = null - return ..() + var/turf/throwee_turf = get_turf(throwee) + + playsound(throwee_turf, 'sound/magic/exit_blood.ogg') + new /obj/effect/temp_visual/cult/sparks(throwee_turf, user.dir) + throwee.visible_message( + "A pulse of magic whisks [throwee] away!", + "A pulse of blood magic whisks you away...", + ) + + if(!do_teleport(throwee, target, channel = TELEPORT_CHANNEL_CULT)) + to_chat(user, "The teleport fails!") + throwee.visible_message( + "...Except they don't go very far", + "...Except you don't appear to have moved very far.", + ) + return FALSE + throwee_turf.Beam(target, icon_state = "sendbeam", time = 0.4 SECONDS) + new /obj/effect/temp_visual/cult/sparks(get_turf(target), user.dir) + throwee.visible_message( + "[throwee] appears suddenly in a pulse of magic!", + "...And you appear elsewhere.", + ) -/obj/effect/proc_holder/pulse/proc/toggle(mob/user) - if(active) - remove_ranged_ability("You cease your preparations...") - attached_action.throwing = FALSE + start_cooldown() + to_chat(user, "A pulse of blood magic surges through you as you shift [throwee] through time and space.") + user.click_intercept = null + throwee_ref = null + update_buttons() + + return TRUE else - add_ranged_ability(user, "You prepare to tear through the fabric of reality...") + if(isliving(target)) + var/mob/living/living_clicked = target + if(!IS_CULTIST(living_clicked)) + return FALSE + SEND_SOUND(user, sound('sound/weapons/thudswoosh.ogg')) + to_chat(user, "You reach through the veil with your mind's eye and seize [target]! Click anywhere nearby to teleport [living_clicked.p_them()]!") + throwee_ref = WEAKREF(target) + return TRUE + + if(istype(target, /obj/structure/destructible/cult)) + to_chat(user, "You reach through the veil with your mind's eye and lift [target]! Click anywhere nearby to teleport it!") + throwee_ref = WEAKREF(target) + return TRUE -/obj/effect/proc_holder/pulse/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - if(ranged_ability_user.incapacitated()) - remove_ranged_ability() - return - var/turf/T = get_turf(ranged_ability_user) - if(!isturf(T)) - return FALSE - if(ranged_ability_user in viewers(7, get_turf(target))) - if((!(iscultist(target) || istype(target, /obj/structure/destructible/cult)) || target == caller) && !(attached_action.throwing)) - return - if(!attached_action.throwing) - attached_action.throwing = TRUE - attached_action.throwee = target - SEND_SOUND(ranged_ability_user, sound('sound/weapons/thudswoosh.ogg')) - to_chat(ranged_ability_user,"You reach through the veil with your mind's eye and seize [target]!") - return - else - new /obj/effect/temp_visual/cult/sparks(get_turf(attached_action.throwee), ranged_ability_user.dir) - var/distance = get_dist(attached_action.throwee, target) - if(distance >= 16) - return - playsound(target,'sound/magic/exit_blood.ogg') - attached_action.throwee.Beam(target,icon_state="sendbeam", time = 4) - attached_action.throwee.forceMove(get_turf(target)) - new /obj/effect/temp_visual/cult/sparks(get_turf(target), ranged_ability_user.dir) - attached_action.throwing = FALSE - attached_action.cooldown = world.time + attached_action.base_cooldown - remove_ranged_ability("A pulse of blood magic surges through you as you shift [attached_action.throwee] through time and space.") - caller.update_action_buttons_icon() - addtimer(CALLBACK(caller, TYPE_PROC_REF(/mob, update_action_buttons_icon)), attached_action.base_cooldown) + return FALSE diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index ae43b26f59559..ce7938c71469c 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -69,7 +69,7 @@ Striking a noncultist, however, will tear their flesh."} "Your arm throbs and your brain hurts!") user.adjustStaminaLoss(rand(force/2,force)) user.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(force/10,force/2)) - if (target.anti_magic_check(magic = FALSE, holy = TRUE)) + if (target.can_block_magic(MAGIC_RESISTANCE_HOLY)) force = 15 else force = 22 @@ -101,7 +101,7 @@ Striking a noncultist, however, will tear their flesh."} phasein = /obj/effect/temp_visual/dir_setting/cult/phase phaseout = /obj/effect/temp_visual/dir_setting/cult/phase/out -/datum/action/innate/dash/cult/IsAvailable() +/datum/action/innate/dash/cult/is_available() if(iscultist(owner) && current_charges) return TRUE else @@ -120,7 +120,7 @@ Striking a noncultist, however, will tear their flesh."} return if(ismob(hit_atom)) var/mob/M = hit_atom - if(M.anti_magic_check(magic = FALSE, holy = TRUE)) + if(M.can_block_magic(MAGIC_RESISTANCE_HOLY)) M.visible_message("[src] passes right through [M]!") return . = ..() @@ -609,7 +609,7 @@ Striking a noncultist, however, will tear their flesh."} else L.visible_message("[src] bounces off of [L], as if repelled by an unseen force!") else if(!..()) - if(!L.anti_magic_check(magic=FALSE,holy=TRUE)) + if(!L.can_block_magic(MAGIC_RESISTANCE_HOLY)) L.Knockdown(50) break_spear(T) else @@ -651,10 +651,8 @@ Striking a noncultist, however, will tear their flesh."} /datum/action/innate/cult/spear/Grant(mob/user, obj/blood_spear) . = ..() spear = blood_spear - button.screen_loc = "6:157,4:-2" - button.moved = "6:157,4:-2" -/datum/action/innate/cult/spear/Activate() +/datum/action/innate/cult/spear/on_activate() if(owner == spear.loc || cooldown > world.time) return var/ST = get_turf(spear) @@ -848,7 +846,7 @@ Striking a noncultist, however, will tear their flesh."} if (attack_type == MELEE_ATTACK && ishuman(hitby.loc)) // Cannot block someone who has the bible on their side var/mob/living/carbon/human/attacker = hitby.loc - if (attacker.anti_magic_check(magic = FALSE, holy = TRUE)) + if (attacker.can_block_magic(MAGIC_RESISTANCE_HOLY)) owner.visible_message("[owner] fails to block the attack from [attacker]!", "You fail to block the empowered attack!") return FALSE . = ..() @@ -888,7 +886,7 @@ Striking a noncultist, however, will tear their flesh."} else L.visible_message("[src] bounces off of [L], as if repelled by an unseen force!") else if(!..()) - if(!L.anti_magic_check(magic=FALSE,holy=TRUE)) + if(!L.can_block_magic(MAGIC_RESISTANCE_HOLY)) L.Knockdown(30) if(D?.thrower) for(var/mob/living/Next in orange(2, T)) diff --git a/code/modules/antagonists/cult/rune_spawn_action.dm b/code/modules/antagonists/cult/rune_spawn_action.dm index dc1b0c0109b65..261cf8e798c36 100644 --- a/code/modules/antagonists/cult/rune_spawn_action.dm +++ b/code/modules/antagonists/cult/rune_spawn_action.dm @@ -14,7 +14,7 @@ var/obj/effect/temp_visual/cult/rune_spawn/rune_center_type var/rune_color -/datum/action/innate/cult/create_rune/IsAvailable() +/datum/action/innate/cult/create_rune/is_available() if(!rune_type || cooldown > world.time) return FALSE return ..() @@ -34,7 +34,7 @@ return TRUE -/datum/action/innate/cult/create_rune/Activate() +/datum/action/innate/cult/create_rune/on_activate() var/turf/T = get_turf(owner) if(turf_check(T)) var/chosen_keyword @@ -101,7 +101,7 @@ rune_center_type = /obj/effect/temp_visual/cult/rune_spawn/rune4/center rune_color = RUNE_COLOR_DARKRED -/datum/action/innate/cult/create_rune/wall/Activate() +/datum/action/innate/cult/create_rune/wall/on_activate() . = ..() var/obj/effect/rune/wall/W = locate(/obj/effect/rune/wall) in owner.loc if(W) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 7bc21a124e288..27ea04437f711 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -269,7 +269,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/rune/malformed) to_chat(M, "You need at least two invokers to convert [convertee]!") log_game("Offer rune failed - tried conversion with one invoker") return 0 - if(convertee.anti_magic_check(TRUE, TRUE, major = FALSE) || istype(convertee.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) //Not major because it can be spammed + if(convertee.can_block_magic(MAGIC_RESISTANCE_HOLY) || istype(convertee.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) //Not major because it can be spammed for(var/M in invokers) to_chat(M, "Something is shielding [convertee]'s mind!") log_game("Offer rune failed - convertee had anti-magic") @@ -373,7 +373,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/rune/malformed) . = ..() var/mob/living/user = invokers[1] //the first invoker is always the user for(var/datum/action/innate/cult/blood_magic/BM in user.actions) - BM.Activate() + BM.trigger() /obj/effect/rune/teleport cultist_name = "Teleport" @@ -836,7 +836,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/rune/wall) set_light(6, 1, color) for(var/mob/living/L in viewers(T)) if(!iscultist(L) && L.blood_volume) - var/atom/I = L.anti_magic_check(magic=FALSE,holy=TRUE,major = FALSE) + var/atom/I = L.can_block_magic(MAGIC_RESISTANCE_HOLY) if(I) if(isitem(I)) to_chat(L, "[I] suddenly burns hotly before returning to normal!") @@ -864,7 +864,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/rune/wall) set_light(6, 1, color) for(var/mob/living/L in viewers(T)) if(!iscultist(L) && L.blood_volume) - if(L.anti_magic_check(magic=FALSE,holy=TRUE,major = FALSE)) + if(L.can_block_magic(MAGIC_RESISTANCE_HOLY)) continue L.take_overall_damage(tick_damage*multiplier, tick_damage*multiplier) diff --git a/code/modules/antagonists/fugitive/fugitive_outfits.dm b/code/modules/antagonists/fugitive/fugitive_outfits.dm index a6f1100af3c26..6d2c1826b89c0 100644 --- a/code/modules/antagonists/fugitive/fugitive_outfits.dm +++ b/code/modules/antagonists/fugitive/fugitive_outfits.dm @@ -38,8 +38,7 @@ H.hair_color = "000" H.facial_hair_color = H.hair_color H.update_body() - if(H.mind) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null)) + var/list/no_drops = list() no_drops += H.get_item_by_slot(ITEM_SLOT_FEET) no_drops += H.get_item_by_slot(ITEM_SLOT_ICLOTHING) @@ -49,6 +48,8 @@ for(var/i in no_drops) var/obj/item/I = i ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT) + var/datum/action/spell/aoe/knock/waldos_key = new /datum/action/spell/aoe/knock/ + waldos_key.Grant(H) /datum/outfit/synthetic name = "Factory Error Synth" diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm index ade30cc37094a..46202d5f2f88c 100644 --- a/code/modules/antagonists/heretic/heretic_antag.dm +++ b/code/modules/antagonists/heretic/heretic_antag.dm @@ -186,7 +186,7 @@ var/mob/living/our_mob = mob_override || owner.current handle_clown_mutation(our_mob, "Ancient knowledge described to you has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") our_mob.faction |= FACTION_HERETIC - RegisterSignal(our_mob, COMSIG_MOB_PRE_CAST_SPELL, PROC_REF(on_spell_cast)) + RegisterSignals(our_mob, list(COMSIG_MOB_PRE_SPELL_CAST, COMSIG_MOB_SPELL_ACTIVATED), PROC_REF(on_spell_cast)) RegisterSignal(our_mob, COMSIG_MOB_ITEM_AFTERATTACK, PROC_REF(on_item_afterattack)) RegisterSignal(our_mob, COMSIG_MOB_LOGIN, PROC_REF(fix_influence_network)) update_heretic_icons_added() @@ -195,7 +195,7 @@ var/mob/living/our_mob = mob_override || owner.current handle_clown_mutation(our_mob, removing = FALSE) our_mob.faction -= FACTION_HERETIC - UnregisterSignal(our_mob, list(COMSIG_MOB_PRE_CAST_SPELL, COMSIG_MOB_ITEM_AFTERATTACK, COMSIG_MOB_LOGIN)) + UnregisterSignal(our_mob, list(COMSIG_MOB_PRE_SPELL_CAST, COMSIG_MOB_SPELL_ACTIVATED, COMSIG_MOB_ITEM_AFTERATTACK, COMSIG_MOB_LOGIN)) update_heretic_icons_removed() /datum/antagonist/heretic/proc/update_heretic_icons_added() @@ -216,19 +216,18 @@ knowledge.on_gain(new_body) /* - * Signal proc for [COMSIG_MOB_PRE_CAST_SPELL]. + * Signal proc for [COMSIG_MOB_PRE_SPELL_CAST] and [COMSIG_MOB_SPELL_ACTIVATED]. * - * Checks if our heretic has TRAIT_ALLOW_HERETIC_CASTING. + * Checks if our heretic has [TRAIT_ALLOW_HERETIC_CASTING] or is ascended. * If so, allow them to cast like normal. - * If not, cancel the cast. + * If not, cancel the cast, and returns [SPELL_CANCEL_CAST]. */ -/datum/antagonist/heretic/proc/on_spell_cast(mob/living/source, obj/effect/proc_holder/spell/spell) +/datum/antagonist/heretic/proc/on_spell_cast(mob/living/source, datum/action/spell/spell) SIGNAL_HANDLER - // Non-Heretic spells, we don't care - if(!spell.requires_heretic_focus) + // Heretic spells are of the forbidden school, otherwise we don't care + if(spell.school != SCHOOL_FORBIDDEN) return - // If we've got the trait, we don't care if(HAS_TRAIT(source, TRAIT_ALLOW_HERETIC_CASTING)) return @@ -237,8 +236,8 @@ return // We shouldn't be able to cast this! Cancel it. - source.balloon_alert(source, "You need a focus") - return COMPONENT_CANCEL_SPELL + source.balloon_alert(source, "you need a focus!") + return SPELL_CANCEL_CAST /* * Signal proc for [COMSIG_MOB_ITEM_AFTERATTACK]. diff --git a/code/modules/antagonists/heretic/heretic_knowledge.dm b/code/modules/antagonists/heretic/heretic_knowledge.dm index 7615aeebc43dd..da083c4b6beaf 100644 --- a/code/modules/antagonists/heretic/heretic_knowledge.dm +++ b/code/modules/antagonists/heretic/heretic_knowledge.dm @@ -15,6 +15,8 @@ var/desc = "Basic knowledge of forbidden arts." /// What's shown to the heretic when the knowledge is aquired var/gain_text + /// The abstract parent type of the knowledge, used in determine mutual exclusivity in some cases + var/datum/heretic_knowledge/abstract_parent_type = /datum/heretic_knowledge /// The knowledge this unlocks next after learning. var/list/next_knowledge = list() /// What knowledge is incompatible with this. Knowledge in this list cannot be researched with this current knowledge. @@ -55,7 +57,7 @@ * * user - the heretic which we're applying things to */ /datum/heretic_knowledge/proc/on_gain(mob/user) - + return /** * Called when the knowledge is removed from a mob, * either due to a heretic being de-heretic'd or bodyswap memery. @@ -64,7 +66,7 @@ * * user - the heretic which we're removing things from */ /datum/heretic_knowledge/proc/on_lose(mob/user) - + return /** * Determines if a heretic can actually attempt to invoke the knowledge as a ritual. * By default, we can only invoke knowledge with rituals associated. @@ -154,23 +156,27 @@ * A knowledge subtype that grants the heretic a certain spell. */ /datum/heretic_knowledge/spell - /// The proc holder spell we add to the heretic. Type-path, becomes an instance via on_research(). - var/obj/effect/proc_holder/spell/spell_to_add - -/datum/heretic_knowledge/spell/Destroy(force, ...) - if(istype(spell_to_add)) - QDEL_NULL(spell_to_add) - return ..() - -/datum/heretic_knowledge/spell/on_research(mob/user) - spell_to_add = new spell_to_add() + abstract_parent_type = /datum/heretic_knowledge/spell + /// Spell path we add to the heretic. Type-path. + var/datum/action/spell/spell_to_add + /// The spell we actually created. + var/datum/weakref/created_spell_ref + +/datum/heretic_knowledge/spell/Destroy() + QDEL_NULL(created_spell_ref) return ..() /datum/heretic_knowledge/spell/on_gain(mob/user) - user.mind.AddSpell(spell_to_add) + // Added spells are tracked on the body, and not the mind, + // because we handle heretic mind transfers + // via the antag datum (on_gain and on_lose). + var/datum/action/spell/created_spell = created_spell_ref?.resolve() || new spell_to_add(user) + created_spell.Grant(user) + created_spell_ref = WEAKREF(created_spell) /datum/heretic_knowledge/spell/on_lose(mob/user) - user.mind.RemoveSpell(spell_to_add) + var/datum/action/spell/created_spell = created_spell_ref?.resolve() + created_spell?.Remove(user) /* * A knowledge subtype for knowledge that can only diff --git a/code/modules/antagonists/heretic/heretic_living_heart.dm b/code/modules/antagonists/heretic/heretic_living_heart.dm index 6369ca8ce13a0..9452e045a8493 100644 --- a/code/modules/antagonists/heretic/heretic_living_heart.dm +++ b/code/modules/antagonists/heretic/heretic_living_heart.dm @@ -39,7 +39,7 @@ organ_parent.icon = 'icons/obj/heretic.dmi' organ_parent.icon_state = "living_heart" - action.UpdateButtonIcon() + action.update_buttons() /datum/component/living_heart/Destroy(force, silent) QDEL_NULL(action) @@ -74,34 +74,25 @@ background_icon_state = "bg_ecult" /// Whether the target radial is currently opened. var/radial_open = FALSE - /// How long we have to wait between tracking uses. - var/track_cooldown_length = 8 SECONDS - /// The cooldown between button uses. - COOLDOWN_DECLARE(track_cooldown) + cooldown_time = 8 SECONDS /datum/action/item_action/organ_action/track_target/Grant(mob/granted) if(!IS_HERETIC(granted)) return return ..() -/datum/action/item_action/organ_action/track_target/IsAvailable() +/datum/action/item_action/organ_action/track_target/is_available() . = ..() if(!.) return if(!IS_HERETIC(owner)) return FALSE - if(!HAS_TRAIT(target, TRAIT_LIVING_HEART)) - return FALSE - if(!COOLDOWN_FINISHED(src, track_cooldown)) + if(!HAS_TRAIT(master, TRAIT_LIVING_HEART)) return FALSE if(radial_open) return FALSE -/datum/action/item_action/organ_action/track_target/Trigger(trigger_flags) - . = ..() - if(!.) - return - +/datum/action/item_action/organ_action/track_target/on_activate(mob/user, atom/target) var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(owner) if(!LAZYLEN(heretic_datum.sac_targets)) owner.balloon_alert(owner, "No targets, visit a rune") @@ -137,7 +128,7 @@ . = track_sacrifice_target(tracked_mob) if(.) - COOLDOWN_START(src, track_cooldown, track_cooldown_length) + start_cooldown() playsound(owner, 'sound/effects/singlebeat.ogg', vol = 50, vary = TRUE, extrarange = SILENCED_SOUND_EXTRARANGE) /datum/action/item_action/organ_action/track_target/proc/track_sacrifice_target(mob/living/carbon/tracked) @@ -177,6 +168,6 @@ return FALSE if(!IS_HERETIC(owner)) return FALSE - if(!HAS_TRAIT(target, TRAIT_LIVING_HEART)) + if(!HAS_TRAIT(master, TRAIT_LIVING_HEART)) return FALSE return TRUE diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm index cbdca6a8bf1f3..57a5513336200 100644 --- a/code/modules/antagonists/heretic/items/heretic_blades.dm +++ b/code/modules/antagonists/heretic/items/heretic_blades.dm @@ -31,7 +31,7 @@ /obj/item/melee/sickly_blade/attack_self(mob/user) var/turf/safe_turf = find_safe_turf(zlevels = z, extended_safety_checks = TRUE) if(IS_HERETIC_OR_MONSTER(user)) - if(do_teleport(user, safe_turf, channel = TELEPORT_CHANNEL_MAGIC)) + if(do_teleport(user, safe_turf, channel = TELEPORT_CHANNEL_MAGIC_SELF)) to_chat(user, "As you shatter [src], you feel a gust of energy flow through your body. [after_use_message]") else to_chat(user, "You shatter [src], but your plea goes unanswered.") diff --git a/code/modules/antagonists/heretic/knowledge/ash_lore.dm b/code/modules/antagonists/heretic/knowledge/ash_lore.dm index 14ace765f7fe2..0022a62040e49 100644 --- a/code/modules/antagonists/heretic/knowledge/ash_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/ash_lore.dm @@ -93,7 +93,7 @@ /datum/heretic_knowledge/essence, /datum/heretic_knowledge/medallion, ) - spell_to_add = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash + spell_to_add = /datum/action/spell/jaunt/ethereal_jaunt/ash cost = 1 route = HERETIC_PATH_ASH @@ -136,8 +136,10 @@ mark.on_effect() // Also refunds 75% of charge! - for(var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/grasp in user.mind.spell_list) - grasp.charge_counter = min(round(grasp.charge_counter + grasp.charge_max * 0.75), grasp.charge_max) + var/datum/action/spell/touch/mansus_grasp/grasp = locate() in user.actions + if(grasp) + grasp.reduce_cooldown(grasp.cooldown_time * 0.75) + grasp.update_buttons() /datum/heretic_knowledge/knowledge_ritual/ash next_knowledge = list(/datum/heretic_knowledge/mad_mask) @@ -213,7 +215,7 @@ /datum/heretic_knowledge/summon/ashy, /datum/heretic_knowledge/spell/cleave, ) - spell_to_add = /obj/effect/proc_holder/spell/targeted/fiery_rebirth + spell_to_add = /datum/action/spell/aoe/fiery_rebirth cost = 1 route = HERETIC_PATH_ASH @@ -254,7 +256,10 @@ /datum/heretic_knowledge/final/ash_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) . = ..() - user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/fire_sworn) + priority_announce("[generate_heretic_text()] Fear the blaze, for the Ashlord, [user.real_name] has ascended! The flames shall consume all! [generate_heretic_text()]","[generate_heretic_text()]", ANNOUNCER_SPANOMALIES) + var/datum/action/spell/fire_sworn/circle_spell = new(user.mind) + circle_spell.Grant(user) + var/datum/action/spell/fire_cascade/big/screen_wide_fire_spell = new(user.mind) + screen_wide_fire_spell.Grant(user) for(var/trait in traits_to_apply) ADD_TRAIT(user, trait, MAGIC_TRAIT) diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm index 08c3ddefe2b77..b2acd8c8ac35e 100644 --- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm @@ -92,19 +92,19 @@ // Skeletons can't become husks, and monkeys are monkeys. if(!ishuman(target) || isskeleton(target) || ismonkey(target)) target.balloon_alert(source, "Invalid body") - return COMPONENT_BLOCK_CHARGE_USE + return COMPONENT_BLOCK_HAND_USE var/mob/living/carbon/human/human_target = target human_target.grab_ghost() if(!human_target.mind || !human_target.client) target.balloon_alert(source, "No soul") - return COMPONENT_BLOCK_CHARGE_USE + return COMPONENT_BLOCK_HAND_USE if(HAS_TRAIT(human_target, TRAIT_HUSK)) target.balloon_alert(source, "Husked") - return COMPONENT_BLOCK_CHARGE_USE + return COMPONENT_BLOCK_HAND_USE if(LAZYLEN(created_items) >= limit) target.balloon_alert(source, "At ghoul limit") - return COMPONENT_BLOCK_CHARGE_USE + return COMPONENT_BLOCK_HAND_USE LAZYADD(created_items, WEAKREF(human_target)) log_game("[key_name(source)] created a ghoul, controlled by [key_name(human_target)].") @@ -350,7 +350,9 @@ /datum/heretic_knowledge/final/flesh_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc) . = ..() - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/shed_human_form) + priority_announce("[generate_heretic_text()] Ever-coiling vortex. Reality unfolded. ARMS OUTREACHED, THE LORD OF THE NIGHT, [user.real_name] has ascended! Fear the ever-twisting hand! [generate_heretic_text()]", "[generate_heretic_text()]", ANNOUNCER_SPANOMALIES) + var/datum/action/spell/shed_human_form/worm_spell = new(user.mind) + worm_spell.Grant(user) var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user) var/datum/heretic_knowledge/limited_amount/flesh_grasp/grasp_ghoul = heretic_datum.get_knowledge(/datum/heretic_knowledge/limited_amount/flesh_grasp) diff --git a/code/modules/antagonists/heretic/knowledge/rust_lore.dm b/code/modules/antagonists/heretic/knowledge/rust_lore.dm index 7d79afcba0717..afae1971fe400 100644 --- a/code/modules/antagonists/heretic/knowledge/rust_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/rust_lore.dm @@ -196,7 +196,7 @@ /datum/heretic_knowledge/curse/corrosion, /datum/heretic_knowledge/crucible, ) - spell_to_add = /obj/effect/proc_holder/spell/aoe_turf/rust_conversion + spell_to_add = /datum/action/spell/aoe/rust_conversion cost = 1 route = HERETIC_PATH_RUST @@ -238,7 +238,7 @@ /datum/heretic_knowledge/final/rust_final, /datum/heretic_knowledge/summon/rusty, ) - spell_to_add = /obj/effect/proc_holder/spell/cone/staggered/entropic_plume + spell_to_add = /datum/action/spell/cone/staggered/entropic_plume cost = 1 route = HERETIC_PATH_RUST diff --git a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm index bb0df2ef29368..997721b3ebd29 100644 --- a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm +++ b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm @@ -48,6 +48,6 @@ /datum/heretic_knowledge/summon/stalker, /datum/heretic_knowledge/spell/voidpull, ) - spell_to_add = /obj/effect/proc_holder/spell/pointed/blood_siphon + spell_to_add = /datum/action/spell/pointed/blood_siphon cost = 1 route = HERETIC_PATH_SIDE diff --git a/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm b/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm index 9d674ee44082c..5d297822689c7 100644 --- a/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm +++ b/code/modules/antagonists/heretic/knowledge/side_rust_ash.dm @@ -56,6 +56,6 @@ /datum/heretic_knowledge/spell/entropic_plume, /datum/heretic_knowledge/spell/flame_birth, ) - spell_to_add = /obj/effect/proc_holder/spell/pointed/cleave + spell_to_add = /datum/action/spell/pointed/cleave cost = 1 route = HERETIC_PATH_SIDE diff --git a/code/modules/antagonists/heretic/knowledge/starting_lore.dm b/code/modules/antagonists/heretic/knowledge/starting_lore.dm index ea1c00d41a830..eccb3832bdb8c 100644 --- a/code/modules/antagonists/heretic/knowledge/starting_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/starting_lore.dm @@ -27,7 +27,7 @@ GLOBAL_LIST_INIT(heretic_start_knowledge, initialize_starting_knowledge()) /datum/heretic_knowledge/limited_amount/base_flesh, /datum/heretic_knowledge/limited_amount/base_void, ) - spell_to_add = /obj/effect/proc_holder/spell/targeted/touch/mansus_grasp + spell_to_add = /datum/action/spell/touch/mansus_grasp cost = 0 route = HERETIC_PATH_START diff --git a/code/modules/antagonists/heretic/knowledge/void_lore.dm b/code/modules/antagonists/heretic/knowledge/void_lore.dm index 162662fc648cc..c2fe4bf618aed 100644 --- a/code/modules/antagonists/heretic/knowledge/void_lore.dm +++ b/code/modules/antagonists/heretic/knowledge/void_lore.dm @@ -173,7 +173,7 @@ /datum/heretic_knowledge/rune_carver, /datum/heretic_knowledge/crucible, ) - spell_to_add = /obj/effect/proc_holder/spell/pointed/void_phase + spell_to_add = /datum/action/spell/pointed/void_phase cost = 1 route = HERETIC_PATH_VOID @@ -222,7 +222,7 @@ /datum/heretic_knowledge/spell/blood_siphon, /datum/heretic_knowledge/summon/rusty ) - spell_to_add = /obj/effect/proc_holder/spell/targeted/void_pull + spell_to_add = /datum/action/spell/aoe/void_pull cost = 1 route = HERETIC_PATH_VOID diff --git a/code/modules/antagonists/heretic/magic/aggressive_spread.dm b/code/modules/antagonists/heretic/magic/aggressive_spread.dm index 145242373a589..73b0e1ed70334 100644 --- a/code/modules/antagonists/heretic/magic/aggressive_spread.dm +++ b/code/modules/antagonists/heretic/magic/aggressive_spread.dm @@ -1,27 +1,38 @@ - -/obj/effect/proc_holder/spell/aoe_turf/rust_conversion +/datum/action/spell/aoe/rust_conversion name = "Aggressive Spread" desc = "Spreads rust onto nearby surfaces." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "corrode" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "corrode" + sound = 'sound/items/welder.ogg' + + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + invocation = "A'GRSV SPR'D" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - charge_max = 300 //twice as long as mansus grasp - clothes_req = FALSE - range = 3 - -/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/cast(list/targets, mob/user = usr) - playsound(user, 'sound/items/welder.ogg', 75, TRUE) - for(var/turf/T in targets) - ///What we want is the 3 tiles around the user and the tile under him to be rusted, so min(dist,1)-1 causes us to get 0 for these tiles, rest of the tiles are based on chance - var/chance = 100 - (max(get_dist(T,user),1)-1)*100/(range+1) - if(!prob(chance)) - continue - T.rust_heretic_act() - -/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small + spell_requirements = NONE + + aoe_radius = 3 + +/datum/action/spell/aoe/rust_conversion/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/turf/nearby_turf in range(aoe_radius, center)) + things += nearby_turf + + return things + +/datum/action/spell/aoe/rust_conversion/cast_on_thing_in_aoe(turf/victim, atom/caster) + // We have less chance of rusting stuff that's further + var/distance_to_caster = get_dist(victim, caster) + var/chance_of_not_rusting = (max(distance_to_caster, 1) - 1) * 100 / (aoe_radius + 1) + + if(prob(chance_of_not_rusting)) + return + + victim.rust_heretic_act() + +/datum/action/spell/aoe/rust_conversion/small name = "Rust Conversion" desc = "Spreads rust onto nearby surfaces." - range = 2 + aoe_radius = 2 diff --git a/code/modules/antagonists/heretic/magic/ash_ascension.dm b/code/modules/antagonists/heretic/magic/ash_ascension.dm index 60845038645f3..ebab1bec91907 100644 --- a/code/modules/antagonists/heretic/magic/ash_ascension.dm +++ b/code/modules/antagonists/heretic/magic/ash_ascension.dm @@ -1,111 +1,129 @@ -/obj/effect/proc_holder/spell/targeted/fire_sworn +/// Creates a constant Ring of Fire around the caster for a set duration of time, which follows them. +/datum/action/spell/fire_sworn name = "Oath of Flame" desc = "For a minute, you will passively create a ring of fire around you." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "fire_ring" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "fire_ring" + + school = SCHOOL_FORBIDDEN + cooldown_time = 70 SECONDS + invocation = "FL'MS" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - clothes_req = FALSE - range = -1 - include_user = TRUE - charge_max = 700 - ///how long it lasts + spell_requirements = NONE + + /// The radius of the fire ring + var/fire_radius = 1 + /// How long it the ring lasts var/duration = 1 MINUTES - ///who casted it right now - var/mob/current_user - ///Determines if you get the fire ring effect - var/has_fire_ring = FALSE -/obj/effect/proc_holder/spell/targeted/fire_sworn/cast(list/targets, mob/user) - . = ..() - current_user = user - has_fire_ring = TRUE - addtimer(CALLBACK(src, PROC_REF(remove), user), duration, TIMER_OVERRIDE|TIMER_UNIQUE) +/datum/action/spell/fire_sworn/Remove(mob/living/remove_from) + remove_from.remove_status_effect(/datum/status_effect/fire_ring) + return ..() -/obj/effect/proc_holder/spell/targeted/fire_sworn/proc/remove() - has_fire_ring = FALSE - current_user = null +/datum/action/spell/fire_sworn/is_valid_spell(mob/user, atom/target) + return isliving(user) -/obj/effect/proc_holder/spell/targeted/fire_sworn/process(delta_time) +/datum/action/spell/fire_sworn/on_cast(mob/living/user, atom/target) . = ..() - if(!has_fire_ring) - return - if(current_user.stat == DEAD) - remove() + user.apply_status_effect(/datum/status_effect/fire_ring, duration, fire_radius) + +/// Simple status effect for adding a ring of fire around a mob. +/datum/status_effect/fire_ring + id = "fire_ring" + tick_interval = 0.1 SECONDS + status_type = STATUS_EFFECT_REFRESH + alert_type = null + /// The radius of the ring around us. + var/ring_radius = 1 + +/datum/status_effect/fire_ring/on_creation(mob/living/new_owner, duration = 1 MINUTES, radius = 1) + src.duration = duration + src.ring_radius = radius + return ..() + +/datum/status_effect/fire_ring/tick(delta_time, times_fired) + if(QDELETED(owner) || owner.stat == DEAD) + qdel(src) return - if(!isturf(current_user.loc)) + + if(!isturf(owner.loc)) return - for(var/turf/nearby_turf as anything in RANGE_TURFS(1, current_user)) + for(var/turf/nearby_turf as anything in RANGE_TURFS(1, owner)) new /obj/effect/hotspot(nearby_turf) nearby_turf.hotspot_expose(750, 25 * delta_time, 1) - for(var/mob/living/fried_living in nearby_turf.contents - current_user) - fried_living.adjustFireLoss(2.5 * delta_time) + for(var/mob/living/fried_living in nearby_turf.contents - owner) + fried_living.apply_damage(2.5 * delta_time, BURN) -/obj/effect/proc_holder/spell/aoe_turf/fire_cascade - name = "Fire Cascade" +/// Creates one, large, expanding ring of fire around the caster, which does not follow them. +/datum/action/spell/fire_cascade + name = "Lesser Fire Cascade" desc = "Heats the air around you." - requires_heretic_focus = TRUE - charge_max = 300 //twice as long as mansus grasp - clothes_req = FALSE + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "fire_ring" + sound = 'sound/items/welder.ogg' + + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + invocation = "C'SC'DE" invocation_type = INVOCATION_WHISPER - range = 4 - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "fire_ring" - action_background_icon_state = "bg_ecult" - -/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/cast(list/targets, mob/user = usr) - INVOKE_ASYNC(src, PROC_REF(fire_cascade), user, range) - -/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/proc/fire_cascade(atom/centre, max_range) - playsound(get_turf(centre), 'sound/items/welder.ogg', 75, TRUE) - var/current_range = 1 - for(var/i in 0 to max_range) - for(var/turf/nearby_turf as anything in spiral_range_turfs(current_range, centre)) + spell_requirements = NONE + + /// The radius the flames will go around the caster. + var/flame_radius = 4 + +/datum/action/spell/fire_cascade/on_cast(mob/user, atom/target) + . = ..() + INVOKE_ASYNC(src, PROC_REF(fire_cascade), get_turf(user), flame_radius) + +/// Spreads a huge wave of fire in a radius around us, staggered between levels +/datum/action/spell/fire_cascade/proc/fire_cascade(atom/centre, flame_radius = 1) + for(var/i in 0 to flame_radius) + for(var/turf/nearby_turf as anything in spiral_range_turfs(i + 1, centre)) new /obj/effect/hotspot(nearby_turf) nearby_turf.hotspot_expose(750, 50, 1) for(var/mob/living/fried_living in nearby_turf.contents - centre) - fried_living.adjustFireLoss(5) + fried_living.apply_damage(5, BURN) - current_range++ stoplag(0.3 SECONDS) -/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big - range = 6 +/datum/action/spell/fire_cascade/big + name = "Greater Fire Cascade" + flame_radius = 6 -// Currently unused. -/obj/effect/proc_holder/spell/pointed/ash_final +// Currently unused - releases streams of fire around the caster. +/datum/action/spell/pointed/ash_beams name = "Nightwatcher's Rite" - desc = "A powerful spell that releases 5 streams of fire away from you." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "flames" - action_background_icon_state = "bg_ecult" + desc = "A powerful spell that releases five streams of eldritch fire towards the target." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "flames" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' + + school = SCHOOL_FORBIDDEN + cooldown_time = 300 + invocation = "F'RE" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - charge_max = 300 - range = 15 - clothes_req = FALSE - -/obj/effect/proc_holder/spell/pointed/ash_final/cast(list/targets, mob/user) - for(var/X in targets) - var/T - T = line_target(-25, range, X, user) - INVOKE_ASYNC(src, PROC_REF(fire_line), user, T) - T = line_target(10, range, X, user) - INVOKE_ASYNC(src, PROC_REF(fire_line), user, T) - T = line_target(0, range, X, user) - INVOKE_ASYNC(src, PROC_REF(fire_line), user, T) - T = line_target(-10, range, X, user) - INVOKE_ASYNC(src, PROC_REF(fire_line), user, T) - T = line_target(25, range, X, user) - INVOKE_ASYNC(src, PROC_REF(fire_line), user, T) - return ..() + spell_requirements = NONE -/obj/effect/proc_holder/spell/pointed/ash_final/proc/line_target(offset, range, atom/at , atom/user) + /// The length of the flame line spit out. + var/flame_line_length = 15 + +/datum/action/spell/pointed/ash_beams/is_valid_spell(mob/user, atom/target) + return TRUE + +/datum/action/spell/pointed/ash_beams/on_cast(mob/user, atom/target) + . = ..() + var/static/list/offsets = list(-25, -10, 0, 10, 25) + for(var/offset in offsets) + INVOKE_ASYNC(src, PROC_REF(fire_line), owner, line_target(offset, flame_line_length, target, owner)) + +/datum/action/spell/pointed/ash_beams/proc/line_target(offset, range, atom/at, atom/user) if(!at) return var/angle = ATAN2(at.x - user.x, at.y - user.y) + offset @@ -117,22 +135,20 @@ T = check return (getline(user, T) - get_turf(user)) -/obj/effect/proc_holder/spell/pointed/ash_final/proc/fire_line(atom/source, list/turfs) +/datum/action/spell/pointed/ash_beams/proc/fire_line(atom/source, list/turfs) var/list/hit_list = list() for(var/turf/T in turfs) if(istype(T, /turf/closed)) break - for(var/mob/living/L in T.contents) - if(L.anti_magic_check()) - L.visible_message("The spell bounces off of [L]!","The spell bounces off of you!") + if(L.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) + L.visible_message(("The spell bounces off of [L]!"), ("The spell bounces off of you!")) continue if((L in hit_list) || L == source) continue hit_list += L L.adjustFireLoss(20) - to_chat(L, "You're hit by [source]'s eldritch flames!") - + to_chat(L, ("You're hit by [source]'s eldritch flames!")) new /obj/effect/hotspot(T) T.hotspot_expose(700,50,1) // deals damage to mechs @@ -141,4 +157,4 @@ continue hit_list += M M.take_damage(45, BURN, MELEE, 1) - sleep(1.5) + sleep(0.15 SECONDS) diff --git a/code/modules/antagonists/heretic/magic/ash_jaunt.dm b/code/modules/antagonists/heretic/magic/ash_jaunt.dm index 88434e26f3742..95a0d5c8ddeca 100644 --- a/code/modules/antagonists/heretic/magic/ash_jaunt.dm +++ b/code/modules/antagonists/heretic/magic/ash_jaunt.dm @@ -1,30 +1,38 @@ -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash +/datum/action/spell/jaunt/ethereal_jaunt/ash name = "Ashen Passage" desc = "A short range spell that allows you to pass unimpeded through walls." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "ash_shift" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "ash_shift" + sound = null + + school = SCHOOL_FORBIDDEN + cooldown_time = 15 SECONDS + invocation = "ASH'N P'SSG'" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - charge_max = 150 - range = -1 - jaunt_in_time = 13 - jaunt_duration = 10 + spell_requirements = NONE + + exit_jaunt_sound = null + jaunt_duration = 1.1 SECONDS + jaunt_in_time = 1.3 SECONDS + jaunt_out_time = 0.6 SECONDS jaunt_in_type = /obj/effect/temp_visual/dir_setting/ash_shift jaunt_out_type = /obj/effect/temp_visual/dir_setting/ash_shift/out -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long - jaunt_duration = 50 - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/play_sound() +/datum/action/spell/jaunt/ethereal_jaunt/ash/do_steam_effects() return +/datum/action/spell/jaunt/ethereal_jaunt/ash/long + name = "Ashen Walk" + desc = "A long range spell that allows you pass unimpeded through multiple walls." + jaunt_duration = 5 SECONDS + /obj/effect/temp_visual/dir_setting/ash_shift name = "ash_shift" icon = 'icons/mob/mob.dmi' icon_state = "ash_shift2" - duration = 13 + duration = 1.3 SECONDS /obj/effect/temp_visual/dir_setting/ash_shift/out icon_state = "ash_shift" diff --git a/code/modules/antagonists/heretic/magic/blood_cleave.dm b/code/modules/antagonists/heretic/magic/blood_cleave.dm index b077a2ef3fa95..456b63874c425 100644 --- a/code/modules/antagonists/heretic/magic/blood_cleave.dm +++ b/code/modules/antagonists/heretic/magic/blood_cleave.dm @@ -1,61 +1,58 @@ -/obj/effect/proc_holder/spell/pointed/cleave +/datum/action/spell/pointed/cleave name = "Cleave" desc = "Causes severe bleeding on a target and several targets around them." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "cleave" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "cleave" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' + + school = SCHOOL_FORBIDDEN + cooldown_time = 35 SECONDS + invocation = "CL'VE" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - charge_max = 350 - clothes_req = FALSE - range = 9 - -/obj/effect/proc_holder/spell/pointed/cleave/cast(list/targets, mob/user) - if(!length(targets)) - revert_cast() - user.balloon_alert(user, "No targets") - return FALSE - if(!can_target(targets[1], user)) - revert_cast() - return FALSE - - for(var/mob/living/carbon/human/nearby_human in range(1, targets[1])) - targets |= nearby_human - - for(var/mob/living/carbon/human/victim as anything in targets) - if(victim == user) + spell_requirements = NONE + + cast_range = 9 + /// The radius of the cleave effect + var/cleave_radius = 1 + +/datum/action/spell/pointed/cleave/is_valid_spell(mob/user, atom/target) + return ..() && ishuman(target) + +/datum/action/spell/pointed/cleave/on_cast(mob/user, atom/target) + . = ..() + var/list/mob/living/carbon/human/nearby = list(target) + for(var/mob/living/carbon/human/nearby_human in range(cleave_radius, target)) + nearby += nearby_human + + for(var/mob/living/carbon/human/victim as anything in nearby) + if(victim == owner) continue - if(victim.anti_magic_check()) + if(victim.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) victim.visible_message( - "[victim]'s body flashes in a fiery glow, but repels the blaze!", - "Your body begins to flash in a fiery glow, but you are protected!" + ("[victim]'s flashes in a firey glow, but repels the blaze!"), + ("Your body begins to flash a firey glow, but you are protected!!") ) continue - if(!victim.blood_volume) continue - victim.visible_message( - "[victim]'s veins are shredded from within as an unholy blaze erupts from [victim.p_their()] blood!", - "Your veins burst from within and unholy flame erupts from your blood!" + ("[victim]'s veins are shredded from within as an unholy blaze erupts from [victim.p_their()] blood!"), + ("Your veins burst from within and unholy flame erupts from your blood!") ) + var/obj/item/bodypart/bodypart = pick(victim.bodyparts) + victim.apply_damage(20, BURN, bodypart) - victim.add_bleeding(BLEED_DEEP_WOUND) - victim.adjustFireLoss(20) new /obj/effect/temp_visual/cleave(victim.drop_location()) -/obj/effect/proc_holder/spell/pointed/cleave/can_target(atom/target, mob/user, silent) - if(!ishuman(target)) - if(!silent) - target.balloon_alert(user, "Invalid target") - return FALSE return TRUE -/obj/effect/proc_holder/spell/pointed/cleave/long - charge_max = 650 +/datum/action/spell/pointed/cleave/long + name = "Lesser Cleave" + cooldown_time = 65 SECONDS /obj/effect/temp_visual/cleave - icon = 'icons/effects/heretic.dmi' + icon = 'icons/effects/eldritch.dmi' icon_state = "cleave" duration = 6 diff --git a/code/modules/antagonists/heretic/magic/blood_siphon.dm b/code/modules/antagonists/heretic/magic/blood_siphon.dm index d143adaeedc06..b6a60dfd7e68f 100644 --- a/code/modules/antagonists/heretic/magic/blood_siphon.dm +++ b/code/modules/antagonists/heretic/magic/blood_siphon.dm @@ -1,50 +1,67 @@ -/obj/effect/proc_holder/spell/pointed/blood_siphon +/datum/action/spell/pointed/blood_siphon name = "Blood Siphon" - desc = "A spell that heals your wounds while damaging the enemy." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "blood_siphon" - action_background_icon_state = "bg_ecult" + desc = "A touch spell that heals your wounds while damaging the enemy. \ + It has a chance to transfer wounds between you and your enemy." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "blood_siphon" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' + + school = SCHOOL_FORBIDDEN + cooldown_time = 15 SECONDS + invocation = "FL'MS O'ET'RN'ITY" invocation_type = INVOCATION_WHISPER - charge_max = 150 - clothes_req = FALSE - range = 9 - -/obj/effect/proc_holder/spell/pointed/blood_siphon/cast(list/targets, mob/living/user) - if(!istype(user)) - revert_cast() - return - var/mob/living/target = targets[1] - if(!istype(target)) - user.balloon_alert(user, "Invalid target") - return - playsound(user, 'sound/magic/demon_attack1.ogg', vol = 75, vary = TRUE) - if(target.anti_magic_check()) - user.balloon_alert(user, "Spell blocked") + spell_requirements = NONE + + cast_range = 9 + +/datum/action/spell/pointed/blood_siphon/can_cast_spell(feedback = TRUE) + return ..() && isliving(owner) + +/datum/action/spell/pointed/blood_siphon/is_valid_spell(mob/user, atom/target) + return ..() && isliving(target) + +/datum/action/spell/pointed/blood_siphon/on_cast(mob/living/user, mob/living/target) + . = ..() + playsound(owner, 'sound/magic/demon_attack1.ogg', 75, TRUE) + if(target.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) + owner.balloon_alert(owner, "spell blocked!") target.visible_message( - "The spell bounces off of [target]!", - "The spell bounces off of you!", + ("The spell bounces off of [target]!"), + ("The spell bounces off of you!"), ) - return + return FALSE target.visible_message( - "[target] turns pale as a red glow envelops [target.p_them()]!", - "You turn pale as a red glow enevelops you!", + ("[target] turns pale as a red glow envelops [target.p_them()]!"), + ("You pale as a red glow enevelops you!"), ) - target.take_overall_damage(brute = 20) - user.heal_overall_damage(brute = 20) + var/mob/living/living_owner = owner + target.adjustBruteLoss(20) + living_owner.adjustBruteLoss(-20) - if(!user.blood_volume) - return + if(!target.blood_volume || !living_owner.blood_volume) + return TRUE target.blood_volume -= 20 - if(user.blood_volume < BLOOD_VOLUME_MAXIMUM) // we dont want to explode from casting - user.blood_volume += 20 + if(living_owner.blood_volume < BLOOD_VOLUME_MAXIMUM) // we dont want to explode from casting + living_owner.blood_volume += 20 -/obj/effect/proc_holder/spell/pointed/blood_siphon/can_target(atom/target, mob/user, silent) - if(!isliving(target)) - if(!silent) - target.balloon_alert(user, "Invalid target") - return FALSE + if(!iscarbon(target) || !iscarbon(owner)) + return TRUE +/* Missing wounds for this bit of code to work + var/mob/living/carbon/carbon_target = cast_on + var/mob/living/carbon/carbon_user = owner + for(var/obj/item/bodypart/bodypart as anything in carbon_user.bodyparts) + for(var/datum/wound/iter_wound as anything in bodypart.wounds) + if(prob(50)) + continue + var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in carbon_target.bodyparts + if(!target_bodypart) + continue + iter_wound.remove_wound() + iter_wound.apply_wound(target_bodypart) +*/ return TRUE diff --git a/code/modules/antagonists/heretic/magic/eldritch_blind.dm b/code/modules/antagonists/heretic/magic/eldritch_blind.dm index d7c32c297b3d6..656775e79918e 100644 --- a/code/modules/antagonists/heretic/magic/eldritch_blind.dm +++ b/code/modules/antagonists/heretic/magic/eldritch_blind.dm @@ -1,5 +1,7 @@ // Given to heretic monsters. -/obj/effect/proc_holder/spell/targeted/blind/eldritch - action_background_icon_state = "bg_ecult" +/datum/action/spell/pointed/blind/eldritch + name = "Eldritch Blind" + background_icon_state = "bg_ecult" + school = SCHOOL_FORBIDDEN invocation = "E'E'S" - range = 10 + cast_range = 10 diff --git a/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm b/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm index e5f168252db8e..d80d38acb2c11 100644 --- a/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm +++ b/code/modules/antagonists/heretic/magic/eldritch_emplosion.dm @@ -1,13 +1,11 @@ // Given to heretic monsters. -/obj/effect/proc_holder/spell/targeted/emplosion/eldritch +/datum/action/spell/emp/eldritch name = "Energetic Pulse" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS invocation = "E'P" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - clothes_req = FALSE - range = -1 - include_user = TRUE - charge_max = 300 + spell_requirements = NONE emp_heavy = 6 emp_light = 10 diff --git a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm index 31dbc0f174823..b9c8aa4649793 100644 --- a/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm +++ b/code/modules/antagonists/heretic/magic/eldritch_shapeshift.dm @@ -1,9 +1,9 @@ // Given to heretic monsters. -/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch - action_background_icon_state = "bg_ecult" +/datum/action/spell/shapeshift/eldritch + school = SCHOOL_FORBIDDEN + background_icon_state = "bg_ecult" invocation = "SH'PE" invocation_type = INVOCATION_WHISPER - clothes_req = FALSE possible_shapes = list( /mob/living/simple_animal/mouse, /mob/living/simple_animal/pet/dog/corgi, @@ -11,4 +11,4 @@ /mob/living/simple_animal/bot/secbot, /mob/living/simple_animal/pet/fox, /mob/living/simple_animal/pet/cat, - ) +) diff --git a/code/modules/antagonists/heretic/magic/eldritch_telepathy.dm b/code/modules/antagonists/heretic/magic/eldritch_telepathy.dm index 19b2d63ab5dc0..af8b7674485cb 100644 --- a/code/modules/antagonists/heretic/magic/eldritch_telepathy.dm +++ b/code/modules/antagonists/heretic/magic/eldritch_telepathy.dm @@ -1,6 +1,7 @@ // Given to heretic monsters. -/obj/effect/proc_holder/spell/targeted/telepathy/eldritch - action_background_icon_state = "bg_ecult" - invocation = "" - invocation_type = INVOCATION_WHISPER - clothes_req = FALSE +/datum/action/spell/telepathy/eldritch + name = "Eldritch Telepathy" + school = SCHOOL_FORBIDDEN + background_icon_state = "bg_ecult" + invocation_type = INVOCATION_NONE + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND diff --git a/code/modules/antagonists/heretic/magic/flesh_ascension.dm b/code/modules/antagonists/heretic/magic/flesh_ascension.dm index ad0cbd96f83e2..75645da85779e 100644 --- a/code/modules/antagonists/heretic/magic/flesh_ascension.dm +++ b/code/modules/antagonists/heretic/magic/flesh_ascension.dm @@ -1,66 +1,72 @@ -/obj/effect/proc_holder/spell/targeted/shed_human_form +/datum/action/spell/shed_human_form name = "Shed form" - desc = "Shed your fragile form, become one with the arms, become one with the emperor." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "worm_ascend" + desc = "Shed your fragile form, become one with the arms, become one with the emperor. \ + Causes heavy amounts of brain damage and sanity loss to nearby mortals." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "worm_ascend" + + school = SCHOOL_FORBIDDEN + cooldown_time = 10 SECONDS + invocation = "REALITY UNCOIL!" invocation_type = INVOCATION_SHOUT - requires_heretic_focus = TRUE - clothes_req = FALSE - action_background_icon_state = "bg_ecult" - range = -1 - include_user = TRUE - charge_max = 100 + spell_requirements = NONE + /// The length of our new wormy when we shed. var/segment_length = 10 + /// The radius around us that we cause brain damage / sanity damage to. + var/scare_radius = 9 + +/datum/action/spell/shed_human_form/is_valid_spell(mob/user, atom/target) + return isliving(user) -/obj/effect/proc_holder/spell/targeted/shed_human_form/cast(list/targets, mob/user) +/datum/action/spell/shed_human_form/on_cast(mob/living/user, atom/target) . = ..() - var/mob/living/target = user - var/mob/living/mob_inside = locate() in target.contents - target - - if(!mob_inside) - var/mob/living/simple_animal/hostile/heretic_summon/armsy/prime/outside = new(user.loc, TRUE, segment_length) - target.mind.transfer_to(outside, TRUE) - target.forceMove(outside) - target.apply_status_effect(STATUS_EFFECT_STASIS, STASIS_ASCENSION_EFFECT) - for(var/mob/living/carbon/human/nearby_human in view(9, outside) - target) + if(istype(user, /mob/living/simple_animal/hostile/heretic_summon/armsy/prime)) + var/mob/living/simple_animal/hostile/heretic_summon/armsy/prime/old_armsy = user + var/mob/living/our_heretic = locate() in old_armsy + + if(our_heretic.remove_status_effect(/datum/status_effect/grouped/stasis, STASIS_ASCENSION_EFFECT)) + our_heretic.forceMove(old_armsy.loc) + + old_armsy.mind.transfer_to(our_heretic, TRUE) + segment_length = old_armsy.get_length() + qdel(old_armsy) + + else + var/mob/living/simple_animal/hostile/heretic_summon/armsy/prime/new_armsy = new(user.loc, TRUE, segment_length) + + user.mind.transfer_to(new_armsy, TRUE) + user.forceMove(new_armsy) + user.apply_status_effect(/datum/status_effect/grouped/stasis, STASIS_ASCENSION_EFFECT) + + // They see the very reality uncoil before their eyes. + for(var/mob/living/carbon/human/nearby_human in view(scare_radius, new_armsy)) if(IS_HERETIC_OR_MONSTER(nearby_human)) continue SEND_SIGNAL(nearby_human, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus) - ///They see the very reality uncoil before their eyes. + if(prob(25)) - var/trauma = pick(subtypesof(BRAIN_TRAUMA_MILD) + subtypesof(BRAIN_TRAUMA_SEVERE)) - nearby_human.gain_trauma(new trauma(), TRAUMA_RESILIENCE_LOBOTOMY) - return - - if(iscarbon(mob_inside)) - var/mob/living/simple_animal/hostile/heretic_summon/armsy/prime/armsy = target - if(mob_inside.remove_status_effect(STATUS_EFFECT_STASIS, STASIS_ASCENSION_EFFECT)) - mob_inside.forceMove(armsy.loc) - armsy.mind.transfer_to(mob_inside, TRUE) - segment_length = armsy.get_length() - qdel(armsy) - return - -/obj/effect/proc_holder/spell/targeted/worm_contract + var/datum/brain_trauma/trauma = pick(subtypesof(BRAIN_TRAUMA_MILD) + subtypesof(BRAIN_TRAUMA_SEVERE)) + nearby_human.gain_trauma(trauma, TRAUMA_RESILIENCE_LOBOTOMY) + +/datum/action/spell/worm_contract name = "Force Contract" desc = "Forces your body to contract onto a single tile." - invocation_type = "none" - requires_heretic_focus = TRUE - clothes_req = FALSE - action_background_icon_state = "bg_ecult" - range = -1 - include_user = TRUE - charge_max = 300 - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "worm_contract" - -/obj/effect/proc_holder/spell/targeted/worm_contract/cast(list/targets, mob/user) - . = ..() - if(!istype(user, /mob/living/simple_animal/hostile/heretic_summon/armsy)) - to_chat(user, "You try to contract your muscles, but nothing happens...") - return + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "worm_contract" - var/mob/living/simple_animal/hostile/heretic_summon/armsy/lord_of_night = user - lord_of_night.contract_next_chain_into_single_tile() + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + + invocation_type = INVOCATION_NONE + spell_requirements = NONE + +/datum/action/spell/worm_contract/is_valid_spell(mob/user, atom/target) + return istype(user, /mob/living/simple_animal/hostile/heretic_summon/armsy) + +/datum/action/spell/worm_contract/on_cast(mob/living/simple_animal/hostile/heretic_summon/armsy/user, atom/target) + . = ..() + user.contract_next_chain_into_single_tile() diff --git a/code/modules/antagonists/heretic/magic/madness_touch.dm b/code/modules/antagonists/heretic/magic/madness_touch.dm deleted file mode 100644 index 55c66ca5646cb..0000000000000 --- a/code/modules/antagonists/heretic/magic/madness_touch.dm +++ /dev/null @@ -1,32 +0,0 @@ - -// Currently unused -/obj/effect/proc_holder/spell/pointed/touch/mad_touch - name = "Touch of Madness" - desc = "A touch spell that drains your enemy's sanity." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "mad_touch" - action_background_icon_state = "bg_ecult" - requires_heretic_focus = TRUE - charge_max = 150 - clothes_req = FALSE - invocation_type = "none" - range = 2 - -/obj/effect/proc_holder/spell/pointed/touch/mad_touch/can_target(atom/target, mob/user, silent) - if(!ishuman(target)) - if(!silent) - target.balloon_alert(user, "Invalid target") - return FALSE - return TRUE - -/obj/effect/proc_holder/spell/pointed/touch/mad_touch/cast(list/targets, mob/user) - . = ..() - for(var/mob/living/carbon/target in targets) - if(ishuman(targets)) - var/mob/living/carbon/human/tar = target - if(tar.anti_magic_check()) - tar.visible_message("The spell bounces off of [target]!","The spell bounces off of you!") - return - if(target.mind && !IS_HERETIC(target)) - to_chat(user, "[target.name] has been cursed!") - SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus) diff --git a/code/modules/antagonists/heretic/magic/manse_link.dm b/code/modules/antagonists/heretic/magic/manse_link.dm index 2c5b63f902d16..3fb5ad5757bfa 100644 --- a/code/modules/antagonists/heretic/magic/manse_link.dm +++ b/code/modules/antagonists/heretic/magic/manse_link.dm @@ -1,38 +1,66 @@ -/obj/effect/proc_holder/spell/pointed/manse_link - name = "Mansus Link" - desc = "Piercing through reality, connecting minds. This spell allows you to add people to a Mansus Net, allowing them to communicate with each other from afar." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "mansus_link" - action_background_icon_state = "bg_ecult" - invocation = "PI'RC' TH' M'ND" - invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - charge_max = 300 - clothes_req = FALSE - range = 10 - -/obj/effect/proc_holder/spell/pointed/manse_link/can_target(atom/target, mob/user, silent) - if(!isliving(target)) - return FALSE - return TRUE +/datum/action/spell/pointed/manse_link + name = "Manse Link" + desc = "This spell allows you to pierce through reality and connect minds to one another \ + via your Mansus Link. All minds connected to your Mansus Link will be able to communicate discreetly across great distances." + background_icon_state = "bg_heretic" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "mansus_link" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' -/obj/effect/proc_holder/spell/pointed/manse_link/cast(list/targets, mob/user) - var/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/originator = user + school = SCHOOL_FORBIDDEN + cooldown_time = 20 SECONDS - var/mob/living/target = targets[1] + invocation = "PI'RC' TH' M'ND." + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_CASTABLE_WITHOUT_INVOCATION | SPELL_REQUIRES_NO_ANTIMAGIC - to_chat(originator, "You begin linking [target]'s mind to yours...") - to_chat(target, "You feel your mind being pulled... connected... intertwined with the very fabric of reality...") - if(!do_after(originator, 6 SECONDS, target = target, hidden = TRUE)) - revert_cast() - return - if(!originator.link_mob(target)) - revert_cast() - to_chat(originator, "You can't seem to link [target]'s mind...") - to_chat(target, "The foreign presence leaves your mind.") + cast_range = 7 + + /// The time it takes to link to a mob. + var/link_time = 6 SECONDS + +/datum/action/spell/pointed/manse_link/New(Target) + . = ..() + if(!istype(Target, /datum/component/mind_linker)) + stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.") + qdel(src) + +/datum/action/spell/pointed/manse_link/is_valid_spell(mob/user, atom/target) + . = ..() + if(!.) + return FALSE + return isliving(target) + +/datum/action/spell/pointed/manse_link/pre_cast(mob/living/cast_on, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) return - to_chat(originator, "You connect [target]'s mind to your mansus link!") + // If we fail to link, cancel the spell. + if(!do_linking(cast_on)) + return . | SPELL_CANCEL_CAST + +/** +* The actual process of linking [linkee] to our network. +*/ +/datum/action/spell/pointed/manse_link/proc/do_linking(mob/living/linkee) + var/datum/component/mind_linker/linker = master + if(linkee.stat == DEAD) + to_chat(owner, ("They're dead!")) + return FALSE + to_chat(owner, ("You begin linking [linkee]'s mind to yours...")) + to_chat(linkee, ("You feel your mind being pulled somewhere... connected... intertwined with the very fabric of reality...")) + if(!do_after(owner, link_time, linkee)) + to_chat(owner, ("You fail to link to [linkee]'s mind.")) + to_chat(linkee, ("The foreign presence leaves your mind.")) + return FALSE + if(QDELETED(src) || QDELETED(owner) || QDELETED(linkee)) + return FALSE + if(!linker.link_mob(linkee)) + to_chat(owner, ("You can't seem to link to [linkee]'s mind.")) + to_chat(linkee, ("The foreign presence leaves your mind.")) + return FALSE + return TRUE /datum/action/innate/mansus_speech name = "Mansus Link" @@ -47,7 +75,7 @@ . = ..() src.originator = originator -/datum/action/innate/mansus_speech/Activate() +/datum/action/innate/mansus_speech/on_activate() var/mob/living/living_owner = owner if(!originator?.linked_mobs[living_owner]) CRASH("Uh oh, a Mansus Link ([type]) got somehow called Activate() [isnull(originator) ? "without an originator Raw Prophet" : "without being in the originator's linked_mobs list"].") diff --git a/code/modules/antagonists/heretic/magic/mansus_grasp.dm b/code/modules/antagonists/heretic/magic/mansus_grasp.dm index 1967df5b5028a..37cf58215757c 100644 --- a/code/modules/antagonists/heretic/magic/mansus_grasp.dm +++ b/code/modules/antagonists/heretic/magic/mansus_grasp.dm @@ -1,96 +1,80 @@ -/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp +/datum/action/spell/touch/mansus_grasp name = "Mansus Grasp" desc = "A touch spell that lets you channel the power of the Old Gods through your grip." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "mansus_grasp" + sound = 'sound/items/welder.ogg' + + school = SCHOOL_EVOCATION + cooldown_time = 10 SECONDS + + invocation = "R'CH T'H TR'TH!" + invocation_type = INVOCATION_SHOUT + // Mimes can cast it. Chaplains can cast it. Anyone can cast it, so long as they have a hand. + spell_requirements = SPELL_CASTABLE_WITHOUT_INVOCATION + hand_path = /obj/item/melee/touch_attack/mansus_fist - charge_max = 100 - clothes_req = FALSE - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "mansus_grasp" - action_background_icon_state = "bg_ecult" + +/datum/action/spell/touch/mansus_grasp/can_cast_spell(feedback = TRUE) + return ..() && !!IS_HERETIC(owner) + +/datum/action/spell/touch/mansus_grasp/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + if(!isliving(victim)) + return FALSE + + var/mob/living/living_hit = victim + if(living_hit.can_block_magic(antimagic_flags)) + victim.visible_message( + ("The spell bounces off of [victim]!"), + ("The spell bounces off of you!"), + ) + return FALSE + + if(SEND_SIGNAL(caster, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, victim) & COMPONENT_BLOCK_HAND_USE) + return FALSE + + living_hit.apply_damage(10, BRUTE) + if(iscarbon(victim)) + var/mob/living/carbon/carbon_hit = victim + carbon_hit.silent = 3 SECONDS + carbon_hit.slurring = 7 SECONDS + carbon_hit.AdjustKnockdown(5 SECONDS) + carbon_hit.adjustStaminaLoss(80) + + return TRUE /obj/item/melee/touch_attack/mansus_fist name = "Mansus Grasp" - desc = "A sinister looking aura that distorts the flow of reality around it. Causes knockdown and major stamina damage in addition to some brute. It gains additional beneficial effects as you expand your knowledge of the Mansus." + desc = "A sinister looking aura that distorts the flow of reality around it. \ + Causes knockdown, minor bruises, and major stamina damage. \ + It gains additional beneficial effects as you expand your knowledge of the Mansus." icon_state = "mansus_grasp" item_state = "mansus_grasp" - catchphrase = "R'CH T'H TR'TH!" - on_use_sound = 'sound/items/welder.ogg' -CREATION_TEST_IGNORE_SUBTYPES(/obj/item/melee/touch_attack/mansus_fist) - -/obj/item/melee/touch_attack/mansus_fist/Initialize(mapload, obj/effect/proc_holder/spell/targeted/touch/_spell) +/obj/item/melee/touch_attack/mansus_fist/Initialize(mapload) . = ..() AddComponent(/datum/component/effect_remover, \ success_feedback = "You remove %THEEFFECT.", \ on_clear_callback = CALLBACK(src, PROC_REF(after_clear_rune)), \ effects_we_clear = list(/obj/effect/heretic_rune)) - /* * Callback for effect_remover component. */ /obj/item/melee/touch_attack/mansus_fist/proc/after_clear_rune(obj/effect/target, mob/living/user) - use_charge(user, whisper = TRUE) + var/datum/action/spell/touch/mansus_grasp/grasp = spell_which_made_us?.resolve() + grasp?.spell_feedback() -/obj/item/melee/touch_attack/mansus_fist/ignition_effect(atom/to_light, mob/user) - . = "[user] effortlessly snaps [user.p_their()] fingers near [to_light], igniting it with eldritch energies. Fucking badass!" - use_charge(user) - -/obj/item/melee/touch_attack/mansus_fist/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - if(!proximity_flag || !IS_HERETIC(user) || target == user) - return - if(ishuman(target) && antimagic_check(target, user)) - return ..() - - if(isliving(target)) - if(on_mob_hit(target, user)) - return - else - if(SEND_SIGNAL(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, target)) - use_charge(user) - return - - return ..() - -/** - * Checks if the [target] has some form of anti-magic. - * - * Returns TRUE If the attack was blocked. FALSE otherwise. - */ -/obj/item/melee/touch_attack/mansus_fist/proc/antimagic_check(mob/living/carbon/human/target, mob/living/carbon/user) - if(target.anti_magic_check()) - target.visible_message( - "The spell bounces off of [target]!", - "The spell bounces off of you!", - ) - return TRUE - return FALSE - -/** - * Called with [hit] is successfully hit by a mansus grasp by [heretic]. - * - * Sends signal COMSIG_HERETIC_MANSUS_GRASP_ATTACK. - * If it returns COMPONENT_BLOCK_CHARGE_USE, the proc returns FALSE. - * Otherwise, returns TRUE. - */ -/obj/item/melee/touch_attack/mansus_fist/proc/on_mob_hit(mob/living/hit, mob/living/heretic) - if(SEND_SIGNAL(heretic, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, hit) & COMPONENT_BLOCK_CHARGE_USE) - return FALSE + remove_hand_with_no_refund(user) - hit.adjustBruteLoss(10) - if(iscarbon(hit)) - var/mob/living/carbon/carbon_hit = hit - carbon_hit.AdjustKnockdown(5 SECONDS) - carbon_hit.adjustStaminaLoss(80) - carbon_hit.adjustBruteLoss(10) - carbon_hit.silent = 3 SECONDS - - use_charge(heretic) - - return TRUE +/obj/item/melee/touch_attack/mansus_fist/ignition_effect(atom/to_light, mob/user) + . = ("[user] effortlessly snaps [user.p_their()] fingers near [to_light], igniting it with eldritch energies. Fucking badass!") + remove_hand_with_no_refund(user) -/obj/item/melee/touch_attack/mansus_fist/suicide_act(mob/living/user) - user.visible_message("[user] covers [user.p_their()] face with [user.p_their()] sickly-looking hand! It looks like [user.p_theyre()] trying to commit suicide!") +/obj/item/melee/touch_attack/mansus_fist/suicide_act(mob/user) + user.visible_message(("[user] covers [user.p_their()] face with [user.p_their()] sickly-looking hand! It looks like [user.p_theyre()] trying to commit suicide!")) var/mob/living/carbon/carbon_user = user //iscarbon already used in spell's parent + var/datum/action/spell/touch/mansus_grasp/source = locate() in user.actions if(!IS_HERETIC(user)) return @@ -100,15 +84,13 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/melee/touch_attack/mansus_fist) return SHAME if(escape_our_torment > 20) //Stops us from infinitely stunning ourselves if we're just not taking the damage return FIRELOSS - if(prob(70)) carbon_user.adjustFireLoss(20) - playsound(carbon_user, 'sound/items/welder.ogg', 70, vary = TRUE) + playsound(carbon_user, 'sound/effects/wounds/sizzle1.ogg', 70, vary = TRUE) if(prob(50)) carbon_user.emote("scream") - carbon_user.stuttering += 13 - - on_mob_hit(user, user) + //carbon_user.adjust_timed_status_effect(26 SECONDS, /datum/status_effect/speech/stutter) + source?.cast_on_hand_hit(src, user, user) escape_our_torment++ stoplag(0.4 SECONDS) diff --git a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm index 86d6d4e332477..c08174f718885 100644 --- a/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm +++ b/code/modules/antagonists/heretic/magic/nightwatcher_rebirth.dm @@ -1,44 +1,55 @@ -/obj/effect/proc_holder/spell/targeted/fiery_rebirth +/datum/action/spell/aoe/fiery_rebirth name = "Nightwatcher's Rebirth" - desc = "A spell that extinguishes you and drains nearby heathens engulfed in flames of their life force, \ - healing you for each victim drained. Those in critical condition will have the last of their vitality drained, killing them." + desc = "A spell that extinguishes you drains nearby heathens engulfed in flames of their life force, \ + healing you for each victim drained. Those in critical condition \ + will have the last of their vitality drained, killing them." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "smoke" + + school = SCHOOL_FORBIDDEN + cooldown_time = 1 MINUTES + invocation = "GL'RY T' TH' N'GHT'W'TCH'ER" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - clothes_req = FALSE - action_background_icon_state = "bg_ecult" - range = -1 - include_user = TRUE - charge_max = 600 - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "smoke" - -/obj/effect/proc_holder/spell/targeted/fiery_rebirth/cast(list/targets, mob/living/carbon/human/user) - if(!istype(user)) - revert_cast() - return - var/did_something = user.on_fire // This might be a false negative if the user has items on fire but they themselves are not. + spell_requirements = SPELL_REQUIRES_HUMAN + +/datum/action/spell/aoe/fiery_rebirth/on_cast(mob/living/carbon/human/user, atom/target) user.ExtinguishMob() + return ..() - for(var/mob/living/carbon/target in view(7, user)) - if(!target.mind || !target.client || target.stat == DEAD || !target.on_fire || IS_HERETIC_OR_MONSTER(target)) +/datum/action/spell/aoe/fiery_rebirth/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/mob/living/carbon/nearby_mob in range(aoe_radius, center)) + if(nearby_mob == owner || nearby_mob == center) + continue + if(!nearby_mob.mind || !nearby_mob.client) + continue + if(IS_HERETIC_OR_MONSTER(nearby_mob)) continue - //This is essentially a death mark, use this to finish your opponent quicker. - if(HAS_TRAIT(target, TRAIT_CRITICAL_CONDITION) && !HAS_TRAIT(target, TRAIT_NODEATH)) - target.investigate_log("has been killed by fiery rebirth.", INVESTIGATE_DEATHS) - target.death() + if(nearby_mob.stat == DEAD || !nearby_mob.on_fire) + continue + + things += nearby_mob + + return things + +/datum/action/spell/aoe/fiery_rebirth/cast_on_thing_in_aoe(mob/living/carbon/victim, mob/living/carbon/human/caster) + new /obj/effect/temp_visual/eldritch_smoke(victim.drop_location()) - target.take_overall_damage(burn = 20) - new /obj/effect/temp_visual/eldritch_smoke(target.drop_location()) - user.heal_overall_damage(brute = 10, burn = 10, stamina = 10, updating_health = FALSE) - user.adjustToxLoss(-10, updating_health = FALSE, forced = TRUE) - user.adjustOxyLoss(-10) - did_something = TRUE + //This is essentially a death mark, use this to finish your opponent quicker. + if(HAS_TRAIT(victim, TRAIT_CRITICAL_CONDITION) && !HAS_TRAIT(victim, TRAIT_NODEATH)) + victim.death() + victim.apply_damage(20, BURN) - if(!did_something) - revert_cast() + // Heal the caster for every victim damaged + caster.adjustBruteLoss(-10, FALSE) + caster.adjustFireLoss(-10, FALSE) + caster.adjustToxLoss(-10, FALSE) + caster.adjustOxyLoss(-10, FALSE) + caster.adjustStaminaLoss(-10) /obj/effect/temp_visual/eldritch_smoke - icon = 'icons/effects/heretic.dmi' + icon = 'icons/effects/eldritch.dmi' icon_state = "smoke" duration = 10 diff --git a/code/modules/antagonists/heretic/magic/rust_wave.dm b/code/modules/antagonists/heretic/magic/rust_wave.dm index 83cbe626475c1..1c957f5a90e20 100644 --- a/code/modules/antagonists/heretic/magic/rust_wave.dm +++ b/code/modules/antagonists/heretic/magic/rust_wave.dm @@ -1,49 +1,48 @@ // Shoots out in a wave-like, what rust heretics themselves get -/obj/effect/proc_holder/spell/cone/staggered/entropic_plume +/datum/action/spell/cone/staggered/entropic_plume name = "Entropic Plume" - desc = "Spews forth a disorienting plume that causes enemies to strike each other, briefly blinds them (increasing with range) and poisons them (decreasing with range). Also spreads rust in the path of the plume." - action_background_icon_state = "bg_ecult" - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "entropic_plume" + desc = "Spews forth a disorienting plume that causes enemies to strike each other, briefly blinds them(increasing with range) and poisons them(decreasing with range). Also spreads rust in the path of the plume." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "entropic_plume" + + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + invocation = "'NTR'P'C PL'M'" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - clothes_req = FALSE - charge_max = 300 + spell_requirements = NONE + cone_levels = 5 respect_density = TRUE -/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/cast(list/targets,mob/user = usr) +/datum/action/spell/cone/staggered/entropic_plume/on_cast(mob/user, atom/target) . = ..() - new /obj/effect/temp_visual/dir_setting/entropic(get_step(user,user.dir), user.dir) + new /obj/effect/temp_visual/dir_setting/entropic(get_step(user, user.dir), user.dir) -/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/do_turf_cone_effect(turf/target_turf, level) - . = ..() +/datum/action/spell/cone/staggered/entropic_plume/do_turf_cone_effect(turf/target_turf, atom/caster, level) target_turf.rust_heretic_act() -/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/do_mob_cone_effect(mob/living/victim, level) - . = ..() - if(victim.anti_magic_check() || IS_HERETIC_OR_MONSTER(victim)) +/datum/action/spell/cone/staggered/entropic_plume/do_mob_cone_effect(mob/living/victim, atom/caster, level) + if(victim.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY) || IS_HERETIC_OR_MONSTER(victim)) return victim.apply_status_effect(/datum/status_effect/amok) - victim.apply_status_effect(/datum/status_effect/cloudstruck, (level * 10)) + victim.apply_status_effect(/datum/status_effect/cloudstruck, (level * 1 SECONDS)) if(iscarbon(victim)) var/mob/living/carbon/carbon_victim = victim - carbon_victim.reagents.add_reagent(/datum/reagent/eldritch, min(1, 6 - level)) + carbon_victim.reagents?.add_reagent(/datum/reagent/eldritch, min(1, 6 - level)) -/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/calculate_cone_shape(current_level) +/datum/action/spell/cone/staggered/entropic_plume/calculate_cone_shape(current_level) if(current_level == cone_levels) return 5 - else if(current_level == cone_levels-1) + else if(current_level == cone_levels - 1) return 3 else return 2 - /obj/effect/temp_visual/dir_setting/entropic icon = 'icons/effects/160x160.dmi' icon_state = "entropic_plume" duration = 3 SECONDS - /obj/effect/temp_visual/dir_setting/entropic/setDir(dir) . = ..() switch(dir) @@ -59,20 +58,23 @@ pixel_x = -128 // Shoots a straight line of rusty stuff ahead of the caster, what rust monsters get -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave +/datum/action/spell/basic_projectile/rust_wave name = "Patron's Reach" desc = "Channels energy into your hands to release a wave of rust." - proj_type = /obj/projectile/magic/spell/rust_wave - requires_heretic_focus = TRUE - charge_max = 350 - clothes_req = FALSE - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "rust_wave" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "rust_wave" + + school = SCHOOL_FORBIDDEN + cooldown_time = 35 SECONDS + invocation = "SPR'D TH' WO'D" invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + + projectile_type = /obj/projectile/magic/aoe/rust_wave -/obj/projectile/magic/spell/rust_wave +/obj/projectile/magic/aoe/rust_wave name = "Patron's Reach" icon_state = "eldritch_projectile" alpha = 180 @@ -80,11 +82,11 @@ damage_type = TOX hitsound = 'sound/weapons/punch3.ogg' trigger_range = 0 - ignored_factions = list(FACTION_HERETIC) + //ignored_factions = list(FACTION_HERETIC) I am not touching projectile code with a ten metre pole range = 15 speed = 1 -/obj/projectile/magic/spell/rust_wave/Moved(atom/OldLoc, Dir) +/obj/projectile/magic/aoe/rust_wave/Moved(atom/OldLoc, Dir) . = ..() playsound(src, 'sound/items/welder.ogg', 75, TRUE) var/list/turflist = list() @@ -102,10 +104,10 @@ var/turf/T = X T.rust_heretic_act() -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave/short - name = "Small Patron's Reach" - proj_type = /obj/projectile/magic/spell/rust_wave/short +/datum/action/spell/basic_projectile/rust_wave/short + name = "Lesser Patron's Reach" + projectile_type = /obj/projectile/magic/aoe/rust_wave/short -/obj/projectile/magic/spell/rust_wave/short +/obj/projectile/magic/aoe/rust_wave/short range = 7 speed = 2 diff --git a/code/modules/antagonists/heretic/magic/void_phase.dm b/code/modules/antagonists/heretic/magic/void_phase.dm index 6fded7d6018fa..5191948120591 100644 --- a/code/modules/antagonists/heretic/magic/void_phase.dm +++ b/code/modules/antagonists/heretic/magic/void_phase.dm @@ -1,45 +1,63 @@ -/obj/effect/proc_holder/spell/pointed/void_phase +/datum/action/spell/pointed/void_phase name = "Void Phase" - desc = "Lets you blink to your pointed destination, causes 3x3 AoE damage bubble around your destination and your current location. It has a minimum range of 3 tiles and a maximum range of 9 tiles." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "voidblink" - action_background_icon_state = "bg_ecult" + desc = "Let's you blink to your pointed destination, causes 3x3 aoe damage bubble \ + around your pointed destination and your current location. \ + It has a minimum range of 3 tiles and a maximum range of 9 tiles." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "voidblink" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' + + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + invocation = "RE'L'TY PH'S'E" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - selection_type = "range" - clothes_req = FALSE - range = 9 - charge_max = 300 + spell_requirements = NONE -/obj/effect/proc_holder/spell/pointed/void_phase/can_target(atom/target, mob/user, silent) - . = ..() - if(get_dist(get_turf(user), get_turf(target)) < 3 ) - user.balloon_alert(user, "Too close") + cast_range = 9 + /// The minimum range to cast the phase. + var/min_cast_range = 3 + /// The radius of damage around the void bubble + var/damage_radius = 1 + +/datum/action/spell/pointed/void_phase/is_valid_spell(mob/user, atom/target) + // We do the close range check first + if(get_dist(get_turf(owner), get_turf(target)) < min_cast_range) + owner.balloon_alert(owner, "too close!") return FALSE -/obj/effect/proc_holder/spell/pointed/void_phase/cast(list/targets, mob/user) + return ..() + +/datum/action/spell/pointed/void_phase/on_cast(mob/user, atom/target) . = ..() - var/target = targets[1] + var/turf/source_turf = get_turf(user) var/turf/targeted_turf = get_turf(target) - playsound(user,'sound/magic/voidblink.ogg',100) - playsound(targeted_turf,'sound/magic/voidblink.ogg',100) - - new /obj/effect/temp_visual/voidin(user.drop_location()) + new /obj/effect/temp_visual/voidin(source_turf) new /obj/effect/temp_visual/voidout(targeted_turf) - for(var/mob/living/living_mob in range(1, user) - user) - if(IS_HERETIC_OR_MONSTER(living_mob)) + // We handle sounds here so we can disable vary + playsound(source_turf, 'sound/magic/voidblink.ogg', 60, FALSE) + playsound(targeted_turf, 'sound/magic/voidblink.ogg', 60, FALSE) + + for(var/mob/living/living_mob in range(damage_radius, source_turf)) + if(IS_HERETIC_OR_MONSTER(living_mob) || living_mob == user || living_mob.can_block_magic(MAGIC_RESISTANCE)) continue - living_mob.adjustBruteLoss(40) + living_mob.apply_damage(40, BRUTE) - for(var/mob/living/living_mob in range(1, targeted_turf) - user) - if(IS_HERETIC_OR_MONSTER(living_mob)) + for(var/mob/living/living_mob in range(damage_radius, targeted_turf)) + if(IS_HERETIC_OR_MONSTER(living_mob) || living_mob == user || living_mob.can_block_magic(MAGIC_RESISTANCE)) continue - living_mob.adjustBruteLoss(40) + living_mob.apply_damage(40, BRUTE) - do_teleport(user,targeted_turf,TRUE,no_effects = TRUE,channel=TELEPORT_CHANNEL_MAGIC) + do_teleport( + user, + targeted_turf, + precision = 1, + no_effects = TRUE, + channel = TELEPORT_CHANNEL_MAGIC_SELF, + ) /obj/effect/temp_visual/voidin icon = 'icons/effects/96x96.dmi' @@ -48,7 +66,6 @@ duration = 6 pixel_x = -32 pixel_y = -32 - /obj/effect/temp_visual/voidout icon = 'icons/effects/96x96.dmi' icon_state = "void_blink_out" diff --git a/code/modules/antagonists/heretic/magic/void_pull.dm b/code/modules/antagonists/heretic/magic/void_pull.dm index eaf2ba30a48f3..9e98bfcd51879 100644 --- a/code/modules/antagonists/heretic/magic/void_pull.dm +++ b/code/modules/antagonists/heretic/magic/void_pull.dm @@ -1,31 +1,60 @@ -/obj/effect/proc_holder/spell/targeted/void_pull +/datum/action/spell/aoe/void_pull name = "Void Pull" - desc = "Call the void, this pulls all nearby people closer to you and damages people already around you. If they are 4 tiles or closer they are also knocked down and a short stun is applied." - action_icon = 'icons/hud/actions/actions_heretic.dmi' - action_icon_state = "voidpull" - action_background_icon_state = "bg_ecult" + desc = "Calls the void, damaging, knocking down, and stunning people nearby. \ + Distant foes are also pulled closer to you (but not damaged)." + background_icon_state = "bg_ecult" + icon_icon = 'icons/hud/actions/actions_ecult.dmi' + button_icon_state = "voidpull" + sound = 'sound/magic/voidblink.ogg' + + school = SCHOOL_FORBIDDEN + cooldown_time = 40 SECONDS + invocation = "BR'NG F'RTH TH'M T' M'" invocation_type = INVOCATION_WHISPER - requires_heretic_focus = TRUE - clothes_req = FALSE - range = -1 - include_user = TRUE - charge_max = 400 + spell_requirements = NONE + + aoe_radius = 7 + /// The radius of the actual damage circle done before cast + var/damage_radius = 1 + /// The radius of the stun applied to nearby people on cast + var/stun_radius = 4 -/obj/effect/proc_holder/spell/targeted/void_pull/cast(list/targets, mob/user) +// Before the cast, we do some small AOE damage around the caster +/datum/action/spell/aoe/void_pull/pre_cast(mob/user, atom/target) . = ..() - for(var/mob/living/living_mob in range(1, user) - user) - if(IS_HERETIC_OR_MONSTER(living_mob)) + if(. & SPELL_CANCEL_CAST) + return + + new /obj/effect/temp_visual/voidin(get_turf(user)) + + // Before we cast the actual effects, deal AOE damage to anyone adjacent to us + var/list/mob/living/people_near_us = get_things_to_cast_on(user, damage_radius) + for(var/mob/living/nearby_living as anything in people_near_us) + nearby_living.apply_damage(30, BRUTE) + +/datum/action/spell/aoe/void_pull/get_things_to_cast_on(atom/center, radius_override = 0) + var/list/things = list() + for(var/mob/living/nearby_mob in view(radius_override || aoe_radius, center)) + if(nearby_mob == owner || nearby_mob == center) + continue + // Don't grab people who are tucked away or something + if(!isturf(nearby_mob.loc)) continue - living_mob.adjustBruteLoss(30) + if(IS_HERETIC_OR_MONSTER(nearby_mob)) + continue + + things += nearby_mob - playsound(user,'sound/magic/voidblink.ogg',100) - new /obj/effect/temp_visual/voidin(user.drop_location()) - for(var/mob/living/livies in view(7, user) - user) + return things - if(get_dist(user, livies) < 4) - livies.AdjustKnockdown(3 SECONDS) - livies.AdjustParalyzed(0.5 SECONDS) +// For the actual cast, we microstun people nearby and pull them in +/datum/action/spell/aoe/void_pull/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + // If the victim's within the stun radius, they're stunned / knocked down + if(get_dist(victim, caster) < stun_radius) + victim.AdjustKnockdown(3 SECONDS) + victim.AdjustParalyzed(0.5 SECONDS) - for(var/i in 1 to 3) - livies.forceMove(get_step_towards(livies,user)) + // Otherwise, they take a few steps closer + for(var/i in 1 to 3) + victim.forceMove(get_step_towards(victim, caster)) diff --git a/code/modules/antagonists/heretic/structures/carving_knife.dm b/code/modules/antagonists/heretic/structures/carving_knife.dm index 516c3f914a763..9b943602128ad 100644 --- a/code/modules/antagonists/heretic/structures/carving_knife.dm +++ b/code/modules/antagonists/heretic/structures/carving_knife.dm @@ -128,7 +128,7 @@ desc = "Destroys all runes carved by this blade." background_icon_state = "bg_ecult" button_icon_state = "rune_break" - icon_icon = 'icons/hud/actions/actions_heretic.dmi' + icon_icon = 'icons/hud/actions/actions_ecult.dmi' /datum/action/item_action/rune_shatter/New(Target) . = ..() @@ -142,21 +142,17 @@ return ..() -/datum/action/item_action/rune_shatter/IsAvailable() +/datum/action/item_action/rune_shatter/is_available() . = ..() if(!.) return if(!IS_HERETIC_OR_MONSTER(owner)) return FALSE - var/obj/item/melee/rune_carver/target_sword = target + var/obj/item/melee/rune_carver/target_sword = master if(!length(target_sword.current_runes)) return FALSE -/datum/action/item_action/rune_shatter/Trigger(trigger_flags) - . = ..() - if(!.) - return - +/datum/action/item_action/rune_shatter/on_activate(mob/user, atom/target) owner.playsound_local(get_turf(owner), 'sound/magic/blind.ogg', 50, TRUE) var/obj/item/melee/rune_carver/target_sword = target QDEL_LIST(target_sword.current_runes) diff --git a/code/modules/antagonists/morph/morph_stomach.dm b/code/modules/antagonists/morph/morph_stomach.dm index bbe246487e9fe..53ba9fad680bc 100644 --- a/code/modules/antagonists/morph/morph_stomach.dm +++ b/code/modules/antagonists/morph/morph_stomach.dm @@ -185,13 +185,12 @@ /datum/action/innate/morph_stomach/New(our_target) . = ..() - button.name = name if(istype(our_target, /datum/morph_stomach)) morph_stomach = our_target else CRASH("morph_stomach action created with non stomach") -/datum/action/innate/morph_stomach/Activate() +/datum/action/innate/morph_stomach/on_activate() morph_stomach.ui_interact(owner) #undef MORPH_FOOD_HEALING_DECAY_TIME diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm index 984ef90c9fb0d..aa38633d4a0c8 100644 --- a/code/modules/antagonists/revenant/revenant.dm +++ b/code/modules/antagonists/revenant/revenant.dm @@ -74,13 +74,20 @@ /mob/living/simple_animal/revenant/Initialize(mapload) . = ..() // more rev abilities are in 'revenant_abilities.dm' - AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(null)) - AddSpell(new /obj/effect/proc_holder/spell/self/revenant_phase_shift(null)) - AddSpell(new /obj/effect/proc_holder/spell/targeted/telepathy/revenant(null)) - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/defile(null)) - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/overload(null)) - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/blight(null)) - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction(null)) + // Starting spells + var/datum/action/spell/night_vision/revenant/vision = new(src) + vision.Grant(src) + var/datum/action/spell/telepathy/revenant/telepathy = new(src) + telepathy.Grant(src) + // Starting spells that start locked + var/datum/action/spell/aoe/revenant/overload/lights_go_zap = new(src) + lights_go_zap.Grant(src) + var/datum/action/spell/aoe/revenant/defile/windows_go_smash = new(src) + windows_go_smash.Grant(src) + var/datum/action/spell/aoe/revenant/blight/botany_go_mad = new(src) + botany_go_mad.Grant(src) + var/datum/action/spell/aoe/revenant/malfunction/shuttle_go_emag = new(src) + shuttle_go_emag.Grant(src) check_rev_teleport() // they're spawned in non-station for some reason... random_revenant_name() AddComponent(/datum/component/tracking_beacon, "ghost", null, null, TRUE, "#9e4d91", TRUE, TRUE, "#490066") @@ -94,11 +101,12 @@ check_rev_teleport() /mob/living/simple_animal/revenant/proc/check_rev_teleport() - var/obj/effect/proc_holder/spell/self/rev_teleport/revtele = locate() in mob_spell_list + var/datum/action/spell/teleport/area_teleport/wizard/revtele = locate() in actions if(!is_station_level(src.z) && !revtele) // give them an ability to back to the station - AddSpell(new /obj/effect/proc_holder/spell/self/rev_teleport(null)) + revtele = new /datum/action/spell/teleport/area_teleport/wizard + revtele.Grant() else if(is_station_level(src.z) && revtele) // you're back to the station. Remove tele spell. - RemoveSpell(revtele) + revtele.Remove() /mob/living/simple_animal/revenant/Destroy() . = ..() diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm index abf2c51171a6d..c98f7c6eeeab8 100644 --- a/code/modules/antagonists/revenant/revenant_abilities.dm +++ b/code/modules/antagonists/revenant/revenant_abilities.dm @@ -112,7 +112,7 @@ reveal(46) stun(46) target.visible_message("[target] suddenly rises slightly into the air, [target.p_their()] skin turning an ashy gray.") - if(target.anti_magic_check(FALSE, TRUE)) + if(target.can_block_magic(MAGIC_RESISTANCE_HOLY)) to_chat(src, "Something's wrong! [target] seems to be resisting the siphoning, leaving you vulnerable!") target.visible_message("[target] slumps onto the ground.", \ "Violet lights, dancing in your vision, receding--") @@ -151,361 +151,264 @@ // ------------------------------------------- //Toggle night vision: lets the revenant toggle its night vision -/obj/effect/proc_holder/spell/targeted/night_vision/revenant - charge_max = 0 - panel = "Revenant Abilities" - message = "You toggle your night vision." - action_icon = 'icons/hud/actions/actions_revenant.dmi' - action_icon_state = "r_nightvision" - action_background_icon_state = "bg_revenant" - -// Recall to Station: teleport & recall to the station -/obj/effect/proc_holder/spell/self/rev_teleport - name = "Recall to Station" - desc = "Teleport to the station." - charge_max = 0 - panel = "Revenant Abilities" - action_icon = 'icons/hud/actions/actions_revenant.dmi' - action_icon_state = "r_teleport" - action_background_icon_state = "bg_revenant" - clothes_req = FALSE - -/obj/effect/proc_holder/spell/self/rev_teleport/cast(mob/living/simple_animal/revenant/user = usr) - if(!isrevenant(user)) - to_chat(user, "You are not revenant.") - return - if(is_station_level(user.z)) - to_chat(user, "Recalling yourself to the station is only available when you're not in the station.") - return - else - if(user.revealed) - to_chat(user, "Recalling yourself to the station is only available when you're invisible.") - return - - to_chat(user, "You start to concentrate recalling yourself to the station.") - if(do_after(user, 30) && !user.revealed) - if(QDELETED(src)) // it's bad when someone spams this... - return - var/turf/targetturf = get_random_station_turf() - if(!do_teleport(user, targetturf, channel = TELEPORT_CHANNEL_CULT, bypass_area_restriction=TRUE)) - to_chat(user, "You have failed to recall yourself to the station... You should try again.") - else - user.reveal(80) - user.stun(40) +/datum/action/spell/night_vision/revenant + name = "Toggle Darkvision" + background_icon_state = "bg_revenant" + icon_icon = 'icons/hud/actions/actions_revenant.dmi' + button_icon_state = "r_nightvision" + toggle_span = "revennotice" //Transmit: the revemant's only direct way to communicate. Sends a single message silently to a single mob -/obj/effect/proc_holder/spell/targeted/telepathy/revenant +/datum/action/spell/telepathy/revenant name = "Revenant Transmit" - panel = "Revenant Abilities" - action_icon = 'icons/hud/actions/actions_revenant.dmi' - action_icon_state = "r_transmit" - action_background_icon_state = "bg_revenant" - notice = "revennotice" - boldnotice = "revenboldnotice" - holy_check = TRUE - -/obj/effect/proc_holder/spell/targeted/telepathy/revenant/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - for(var/mob/living/M in targets) - if(istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) - to_chat(user, "It appears the target's mind is ironclad! No getting a message in there!") - return - if(M.anti_magic_check(magic_check, holy_check)) //hear no evil - to_chat(user, "Something is blocking your power into their mind!") - return - - - var/msg = stripped_input(usr, "What do you wish to tell [M]?", null, "") - if(!msg) - charge_counter = charge_max - return - if(CHAT_FILTER_CHECK(msg)) - to_chat(user, "Your message contains forbidden words.") - return - msg = user.treat_message_min(msg) - log_directed_talk(user, M, msg, LOG_SAY, "[name]") - - to_chat(user, "You transmit to [M]: [msg]") - to_chat(M, "You hear something haunting... [msg]") - user.create_private_chat_message(message="...[msg]", - message_language = /datum/language/metalanguage, - hearers=list(user, M)) - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) - continue - var/follow_rev = FOLLOW_LINK(ded, user) - var/follow_whispee = FOLLOW_LINK(ded, M) - to_chat(ded, "[follow_rev] [user] [name]: \"[msg]\" to [follow_whispee] [M]") - - -/obj/effect/proc_holder/spell/self/revenant_phase_shift - name = "Phase Shift" - desc = "Shift in and out of your corporeal form" - panel = "Revenant Abilities" - action_icon = 'icons/hud/actions/actions_revenant.dmi' - action_icon_state = "r_phase" - action_background_icon_state = "bg_revenant" - clothes_req = FALSE - charge_max = 0 - -/obj/effect/proc_holder/spell/self/revenant_phase_shift/cast(mob/user = usr) - if(!isrevenant(user)) - return FALSE - var/mob/living/simple_animal/revenant/revenant = user - if(!revenant.castcheck(0)) - return FALSE - // if they're trapped in consecrated tiles, they can get out with this. but they can't hide back on these tiles. - if(revenant.incorporeal_move != INCORPOREAL_MOVE_JAUNT) - var/turf/open/floor/stepTurf = get_turf(user) - if(stepTurf) - var/obj/effect/decal/cleanable/food/salt/salt = locate() in stepTurf - if(salt) - to_chat(user, "[salt] blocks your way to spirit realm!") - // the purpose is just letting not them hide onto salt tiles incorporeally. no need to stun. - return - if(stepTurf.flags_1 & NOJAUNT_1) - to_chat(user, "Some strange aura blocks your way to spirit realm.") - return - if(stepTurf.is_holy()) - to_chat(user, "Holy energies block your way to spirit realm!") - return - revenant.phase_shift() - revenant.orbiting?.end_orbit(revenant) - -/obj/effect/proc_holder/spell/aoe_turf/revenant - clothes_req = 0 - action_icon = 'icons/hud/actions/actions_revenant.dmi' - action_background_icon_state = "bg_revenant" - panel = "Revenant Abilities (Locked)" - name = "Report this to a coder" - var/reveal = 80 //How long it reveals the revenant in deciseconds - var/stun = 20 //How long it stuns the revenant in deciseconds - var/locked = TRUE //revenant needs to pay essence to learn their ability - var/unlock_amount = 100 //How much essence it costs to unlock - var/cast_amount = 50 //How much essence it costs to use - -/obj/effect/proc_holder/spell/aoe_turf/revenant/Initialize(mapload) - . = ..() - update_button_info() + background_icon_state = "bg_revenant" + + telepathy_span = "revennotice" + bold_telepathy_span = "revenboldnotice" + + antimagic_flags = MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND + +/datum/action/spell/aoe/revenant + background_icon_state = "bg_revenant" + icon_icon = 'icons/hud/actions/actions_revenant.dmi' + button_icon_state = "r_default" + antimagic_flags = MAGIC_RESISTANCE_HOLY + spell_requirements = NONE + + /// If it's locked, and needs to be unlocked before use + var/locked = TRUE + /// How much essence it costs to unlock + var/unlock_amount = 100 + /// How much essence it costs to use + var/cast_amount = 50 + + /// How long it reveals the revenant + var/reveal_duration = 8 SECONDS + // How long it stuns the revenant + var/stun_duration = 2 SECONDS -/obj/effect/proc_holder/spell/aoe_turf/revenant/proc/update_button_info() - if(!locked) - action.name = "[initial(name)][cast_amount ? " ([cast_amount]E to cast)" : ""]" +/datum/action/spell/aoe/revenant/New(master) + . = ..() + if(locked) + name = "[initial(name)] ([unlock_amount]SE)" else - action.name = "[initial(name)][unlock_amount ? " ([unlock_amount]SE to learn)" : ""]" - action.UpdateButtonIcon() + name = "[initial(name)] ([cast_amount]E)" + +/datum/action/spell/aoe/revenant/Grant(mob/grant_to) + if (!istype(grant_to, /mob/living/simple_animal/revenant)) + CRASH("Attempted to grant a revenant spell to a non-revenant mob which si not allowed.") + . = ..() -/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr) - if(charge_counter < charge_max) +/datum/action/spell/aoe/revenant/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) return FALSE - if(!isrevenant(user)) // If you're not a revenant, it works anyway. - return TRUE - if(user.inhibited) + if(!istype(owner, /mob/living/simple_animal/revenant)) + stack_trace("[type] was owned by a non-revenant mob, please don't.") return FALSE - if(locked) - if(user.essence_excess <= unlock_amount) - return FALSE - if(user.essence <= cast_amount) + + var/mob/living/simple_animal/revenant/ghost = owner + if(ghost.inhibited) + return FALSE + if(locked && ghost.essence_excess <= unlock_amount) + return FALSE + if(ghost.essence <= cast_amount) return FALSE + return TRUE -/obj/effect/proc_holder/spell/aoe_turf/revenant/proc/attempt_cast(mob/living/simple_animal/revenant/user = usr) - // If you're not a revenant, it works anyway. - if(!isrevenant(user)) - if(locked) - locked = FALSE - panel = "Revenant Abilities" - action.name = "[initial(name)]" - action.UpdateButtonIcon() - return TRUE - - // actual revenant check +/datum/action/spell/aoe/revenant/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/turf/nearby_turf in range(aoe_radius, center)) + things += nearby_turf + + return things + +/datum/action/spell/aoe/revenant/pre_cast(mob/living/simple_animal/revenant/cast_on, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return FALSE + if(locked) - if (!user.unlock(unlock_amount)) - charge_counter = charge_max - return FALSE - to_chat(user, "You have unlocked [initial(name)]!") - panel = "Revenant Abilities" + if(!cast_on.unlock(unlock_amount)) + to_chat(cast_on, ("You don't have enough essence to unlock [initial(name)]!")) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + + name = "[initial(name)] ([cast_amount]E)" + to_chat(cast_on, ("You have unlocked [initial(name)]!")) locked = FALSE - charge_counter = charge_max - update_button_info() - return FALSE - if(!user.castcheck(-cast_amount)) - charge_counter = charge_max - return FALSE - user.reveal(reveal) - user.stun(stun) - if(action) - action.UpdateButtonIcon() - return TRUE + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + + if(!cast_on.castcheck(-cast_amount)) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + +/datum/action/spell/aoe/revenant/post_cast(mob/living/simple_animal/revenant/user, atom/target) + . = ..() + if(reveal_duration > 0 SECONDS) + user.reveal(reveal_duration) + if(stun_duration > 0 SECONDS) + user.stun(stun_duration) //Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people. -/obj/effect/proc_holder/spell/aoe_turf/revenant/overload +/datum/action/spell/aoe/revenant/overload name = "Overload Lights" desc = "Directs a large amount of essence into nearby electrical lights, causing lights to shock those nearby." - charge_max = 200 - range = 5 - stun = 30 + button_icon_state = "overload_lights" + cooldown_time = 20 SECONDS + + aoe_radius = 5 unlock_amount = 25 cast_amount = 40 + stun_duration = 3 SECONDS + + /// The range the shocks from the lights go var/shock_range = 2 + /// The damage the shcoskf rom the lgihts do var/shock_damage = 15 - action_icon_state = "overload_lights" - -/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - if(attempt_cast(user)) - for(var/turf/T in targets) - INVOKE_ASYNC(src, PROC_REF(overload), T, user) - -/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload(turf/T, mob/user) - for(var/obj/machinery/light/L in T) - if(!L.on) - return - L.visible_message("\The [L] suddenly flares brightly and begins to spark!") - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(4, 0, L) - s.start() - new /obj/effect/temp_visual/revenant(get_turf(L)) - addtimer(CALLBACK(src, PROC_REF(overload_shock), L, user), 20) - -/obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload_shock(obj/machinery/light/L, mob/user) - if(!L.on) //wait, wait, don't shock me - return - flick("[L.base_state]2", L) - for(var/mob/living/carbon/human/M in hearers(shock_range, L)) - if(M == user) + +/datum/action/spell/aoe/revenant/overload/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) + for(var/obj/machinery/light/light in victim) + if(!light.on) + continue + + light.visible_message(("[light] suddenly flares brightly and begins to spark!")) + var/datum/effect_system/spark_spread/light_sparks = new /datum/effect_system/spark_spread() + light_sparks.set_up(4, 0, light) + light_sparks.start() + new /obj/effect/temp_visual/revenant(get_turf(light)) + addtimer(CALLBACK(src, PROC_REF(overload_shock), light, caster), 20) + +/datum/action/spell/aoe/revenant/overload/proc/overload_shock(obj/machinery/light/to_shock, mob/living/simple_animal/revenant/caster) + flick("[to_shock.base_state]2", to_shock) + for(var/mob/living/carbon/human/human_mob in view(shock_range, to_shock)) + if(human_mob == caster) continue - L.Beam(M,icon_state="purple_lightning", time = 5) - if(!M.anti_magic_check(FALSE, TRUE)) - M.electrocute_act(shock_damage, L, flags = SHOCK_NOGLOVES) - do_sparks(4, FALSE, M) - playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1) + to_shock.Beam(human_mob, icon_state = "purple_lightning", time = 0.5 SECONDS) + if(!human_mob.can_block_magic(MAGIC_RESISTANCE_HOLY)) + human_mob.electrocute_act(shock_damage, to_shock, flags = SHOCK_NOGLOVES) + + do_sparks(4, FALSE, human_mob) + playsound(human_mob, 'sound/machines/defib_zap.ogg', 50, TRUE, -1) //Defile: Corrupts nearby stuff, unblesses floor tiles. -/obj/effect/proc_holder/spell/aoe_turf/revenant/defile +/datum/action/spell/aoe/revenant/defile name = "Defile" desc = "Twists and corrupts the nearby area as well as dispelling holy auras on floors." - charge_max = 150 - range = 4 - stun = 20 - reveal = 40 + button_icon_state = "defile" + cooldown_time = 15 SECONDS + + aoe_radius = 4 unlock_amount = 10 cast_amount = 30 - action_icon_state = "defile" + reveal_duration = 4 SECONDS + stun_duration = 2 SECONDS -/obj/effect/proc_holder/spell/aoe_turf/revenant/defile/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - if(attempt_cast(user)) - for(var/turf/T in targets) - INVOKE_ASYNC(src, PROC_REF(defile), T) +/datum/action/spell/aoe/revenant/defile/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) + for(var/obj/effect/blessing/blessing in victim) + qdel(blessing) + new /obj/effect/temp_visual/revenant(victim) -/obj/effect/proc_holder/spell/aoe_turf/revenant/defile/proc/defile(turf/T) - for(var/obj/effect/blessing/B in T) - qdel(B) - new /obj/effect/temp_visual/revenant(T) - - if(!isplatingturf(T) && !istype(T, /turf/open/floor/engine/cult) && isfloorturf(T) && prob(15)) - var/turf/open/floor/floor = T + if(!isplatingturf(victim) && !istype(victim, /turf/open/floor/engine/cult) && isfloorturf(victim) && prob(15)) + var/turf/open/floor/floor = victim if(floor.overfloor_placed && floor.floor_tile) new floor.floor_tile(floor) floor.broken = 0 floor.burnt = 0 - floor.make_plating(1) - if(T.type == /turf/closed/wall && prob(15)) - new /obj/effect/temp_visual/revenant(T) - T.AddElement(/datum/element/rust) - if(T.type == /turf/closed/wall/r_wall && prob(10)) - new /obj/effect/temp_visual/revenant(T) - T.AddElement(/datum/element/rust) - for(var/obj/effect/decal/cleanable/food/salt/salt in T) - new /obj/effect/temp_visual/revenant(T) + floor.make_plating(TRUE) + + if(victim.type == /turf/closed/wall && prob(15) && !HAS_TRAIT(victim, TRAIT_RUSTY)) + new /obj/effect/temp_visual/revenant(victim) + victim.AddElement(/datum/element/rust) + if(victim.type == /turf/closed/wall/r_wall && prob(10) && !HAS_TRAIT(victim, TRAIT_RUSTY)) + new /obj/effect/temp_visual/revenant(victim) + victim.AddElement(/datum/element/rust) + for(var/obj/effect/decal/cleanable/food/salt/salt in victim) + new /obj/effect/temp_visual/revenant(victim) qdel(salt) - for(var/obj/structure/closet/closet in T.contents) + for(var/obj/structure/closet/closet in victim.contents) closet.open() - for(var/obj/structure/bodycontainer/corpseholder in T) + for(var/obj/structure/bodycontainer/corpseholder in victim) if(corpseholder.connected.loc == corpseholder) corpseholder.open() - for(var/obj/machinery/dna_scannernew/dna in T) + for(var/obj/machinery/dna_scannernew/dna in victim) dna.open_machine() - for(var/obj/structure/window/window in T) - window.take_damage(rand(30,80)) - if(window && window.fulltile) + for(var/obj/structure/window/window in victim) + window.take_damage(rand(30, 80)) + if(window?.fulltile) new /obj/effect/temp_visual/revenant/cracks(window.loc) - for(var/obj/machinery/light/light in T) + for(var/obj/machinery/light/light in victim) light.flicker(20) //spooky //Malfunction: Makes bad stuff happen to robots and machines. -/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction +/datum/action/spell/aoe/revenant/malfunction name = "Malfunction" desc = "Corrupts and damages nearby machines and mechanical objects." - charge_max = 200 - range = 4 + button_icon_state = "malfunction" + cooldown_time = 20 SECONDS + + aoe_radius = 4 cast_amount = 60 unlock_amount = 125 - action_icon_state = "malfunction" - -//A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you. -/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - if(attempt_cast(user)) - for(var/turf/T in targets) - INVOKE_ASYNC(src, PROC_REF(malfunction), T, user) -/obj/effect/proc_holder/spell/aoe_turf/revenant/malfunction/proc/malfunction(turf/T, mob/user) - for(var/mob/living/simple_animal/bot/bot in T) - if(!bot.emagged) +// A note to future coders: do not replace this with an EMP because it will wreck malf AIs and everyone will hate you. +/datum/action/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) + /* emag code differs too much from us + for(var/mob/living/simple_animal/bot/bot in victim) + if(!(bot.bot_cover_flags & BOT_COVER_EMAGGED)) new /obj/effect/temp_visual/revenant(bot.loc) - bot.locked = FALSE - bot.open = TRUE - bot.use_emag() - for(var/mob/living/carbon/human/human in T) - if(human == user) + bot.bot_cover_flags &= ~BOT_COVER_LOCKED + bot.bot_cover_flags |= BOT_COVER_OPEN + bot.emag_act(caster) + */ + for(var/mob/living/carbon/human/human in victim) + if(human == caster) continue - if(human.anti_magic_check(FALSE, TRUE)) + if(human.can_block_magic(MAGIC_RESISTANCE_HOLY)) continue - to_chat(human, "You feel [pick("your sense of direction flicker out", "a stabbing pain in your head", "your mind fill with static")].") + to_chat(human, ("You feel [pick("your sense of direction flicker out", "a stabbing pain in your head", "your mind fill with static")].")) new /obj/effect/temp_visual/revenant(human.loc) human.emp_act(EMP_HEAVY) - for(var/obj/thing in T) - if(istype(thing, /obj/machinery/power/apc) || istype(thing, /obj/machinery/power/smes)) //Doesn't work on SMES and APCs, to prevent kekkery + for(var/obj/thing in victim) + //Doesn't work on SMES and APCs, to prevent kekkery. + if(istype(thing, /obj/machinery/power/apc) || istype(thing, /obj/machinery/power/smes)) continue if(prob(20)) if(prob(50)) new /obj/effect/temp_visual/revenant(thing.loc) - thing.use_emag(null) - else - if(!istype(thing, /obj/machinery/clonepod)) //I hate everything but mostly the fact there's no better way to do this without just not affecting it at all - thing.emp_act(EMP_HEAVY) - for(var/mob/living/silicon/robot/S in T) //Only works on cyborgs, not AI - playsound(S, 'sound/machines/warning-buzzer.ogg', 50, 1) - new /obj/effect/temp_visual/revenant(S.loc) - S.spark_system.start() - S.emp_act(EMP_HEAVY) + //thing.emag_act(caster) + // Only works on cyborgs, not AI! + for(var/mob/living/silicon/robot/cyborg in victim) + playsound(cyborg, 'sound/machines/warning-buzzer.ogg', 50, TRUE) + new /obj/effect/temp_visual/revenant(cyborg.loc) + cyborg.spark_system.start() + cyborg.emp_act(EMP_HEAVY) //Blight: Infects nearby humans and in general messes living stuff up. -/obj/effect/proc_holder/spell/aoe_turf/revenant/blight +/datum/action/spell/aoe/revenant/blight name = "Blight" desc = "Causes nearby living things to waste away." - charge_max = 200 - range = 3 + button_icon_state = "blight" + cooldown_time = 20 SECONDS + + aoe_radius = 3 cast_amount = 50 unlock_amount = 75 - action_icon_state = "blight" - -/obj/effect/proc_holder/spell/aoe_turf/revenant/blight/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - if(attempt_cast(user)) - for(var/turf/T in targets) - INVOKE_ASYNC(src, PROC_REF(blight), T, user) -/obj/effect/proc_holder/spell/aoe_turf/revenant/blight/proc/blight(turf/T, mob/user) - for(var/mob/living/mob in T) - if(mob == user) +/datum/action/spell/aoe/revenant/blight/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) + for(var/mob/living/mob in victim) + if(mob == caster) continue - if(mob.anti_magic_check(FALSE, TRUE)) + if(mob.can_block_magic(MAGIC_RESISTANCE_HOLY)) + to_chat(caster, ("The spell had no effect on [mob]!")) continue new /obj/effect/temp_visual/revenant(mob.loc) if(iscarbon(mob)) if(ishuman(mob)) var/mob/living/carbon/human/H = mob - if(H.dna?.species) - H.dna.species.handle_hair(H,"#1d2953") //will be reset when blight is cured + //H.set_haircolor("#1d2953", override = TRUE) //will be reset when blight is cured var/blightfound = FALSE for(var/datum/disease/revblight/blight in H.diseases) blightfound = TRUE @@ -513,22 +416,21 @@ blight.stage++ if(!blightfound) H.ForceContractDisease(new /datum/disease/revblight(), FALSE, TRUE) - to_chat(H, "You feel [pick("suddenly sick", "a surge of nausea", "like your skin is wrong")].") + to_chat(H, ("You feel [pick("suddenly sick", "a surge of nausea", "like your skin is wrong")].")) else if(mob.reagents) mob.reagents.add_reagent(/datum/reagent/toxin/plasma, 5) else mob.adjustToxLoss(5) - for(var/obj/structure/spacevine/vine in T) //Fucking with botanists, the ability. + for(var/obj/structure/spacevine/vine in victim) //Fucking with botanists, the ability. vine.add_atom_colour("#823abb", TEMPORARY_COLOUR_PRIORITY) new /obj/effect/temp_visual/revenant(vine.loc) QDEL_IN(vine, 10) - for(var/obj/structure/glowshroom/shroom in T) + for(var/obj/structure/glowshroom/shroom in victim) shroom.add_atom_colour("#823abb", TEMPORARY_COLOUR_PRIORITY) new /obj/effect/temp_visual/revenant(shroom.loc) QDEL_IN(shroom, 10) - for(var/obj/machinery/hydroponics/tray in T) + for(var/obj/machinery/hydroponics/tray in victim) new /obj/effect/temp_visual/revenant(tray.loc) - tray.pestlevel = rand(8, 10) - tray.weedlevel = rand(8, 10) - tray.toxic = rand(45, 55) + tray.pestlevel = (rand(8, 10)) + tray.weedlevel = (rand(8, 10)) diff --git a/code/modules/antagonists/santa/santa.dm b/code/modules/antagonists/santa/santa.dm index 563e89059cb82..43b2408f3de7a 100644 --- a/code/modules/antagonists/santa/santa.dm +++ b/code/modules/antagonists/santa/santa.dm @@ -23,7 +23,8 @@ H.equipOutfit(/datum/outfit/santa) H.dna.update_dna_identity() - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport/santa) + var/datum/action/spell/teleport/area_teleport/wizard/santa/teleport = new(owner) + teleport.Grant(H) /datum/antagonist/santa/proc/give_objective() if(!give_objectives) diff --git a/code/modules/antagonists/slaughter/slaughter.dm b/code/modules/antagonists/slaughter/slaughter.dm index 503cd4d13a7e6..9f01ba710d1ae 100644 --- a/code/modules/antagonists/slaughter/slaughter.dm +++ b/code/modules/antagonists/slaughter/slaughter.dm @@ -1,6 +1,6 @@ //////////////////The Monster -/mob/living/simple_animal/slaughter +/mob/living/simple_animal/hostile/imp/slaughter/ name = "slaughter demon" real_name = "slaughter demon" desc = "A large, menacing creature covered in armored black scales." @@ -50,23 +50,32 @@ loot = list(/obj/effect/decal/cleanable/blood, \ /obj/effect/decal/cleanable/blood/innards, \ /obj/item/organ/heart/demon) + // Keep the people we hug! + var/list/consumed_mobs = list() del_on_death = TRUE + var/crawl_type = /datum/action/spell/jaunt/bloodcrawl/slaughter_demon deathmessage = "screams in anger as it collapses into a puddle of viscera!" discovery_points = 3000 - // Keep the people we hug! - var/list/consumed_mobs = list() - var/revive_eject = FALSE -/mob/living/simple_animal/slaughter/Initialize(mapload) +/mob/living/simple_animal/hostile/imp/slaughter/Initialize(mapload) . = ..() - var/obj/effect/proc_holder/spell/bloodcrawl/bloodspell = new - AddSpell(bloodspell) - if(istype(loc, /obj/effect/dummy/phased_mob/slaughter)) - bloodspell.phased = TRUE + var/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/crawl = new crawl_type(src) + crawl.Grant(src) + RegisterSignal(src, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(on_crawl)) + +/// Whenever we enter or exit blood crawl, reset our bonus and hitstreaks. +/mob/living/simple_animal/hostile/imp/slaughter/proc/on_crawl(datum/source) + SIGNAL_HANDLER -/mob/living/simple_animal/slaughter/Destroy() + // Grant us a speed boost if we're on the mortal plane + if(isturf(loc)) + add_movespeed_modifier(/datum/movespeed_modifier/slaughter) + addtimer(CALLBACK(src, PROC_REF(remove_movespeed_modifier), /datum/movespeed_modifier/slaughter), 6 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) + + +/mob/living/simple_animal/hostile/imp/slaughter/Destroy() var/turf/cur_loc = get_turf(src) playsound(cur_loc, feast_sound, 50, 1, -1) for(var/mob/living/stored_mob in consumed_mobs) @@ -92,24 +101,9 @@ icon_state = "innards" random_icon_states = null -/mob/living/simple_animal/slaughter/phasein() - . = ..() - add_movespeed_modifier(/datum/movespeed_modifier/slaughter) - addtimer(CALLBACK(src, PROC_REF(remove_movespeed_modifier), /datum/movespeed_modifier/slaughter, TRUE), 6 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) -/mob/living/simple_animal/slaughter/bloodcrawl_swallow(var/mob/living/victim) - if(consumed_mobs) - // Keep their corpse so rescue is possible - consumed_mobs += victim - else - // Be safe and just eject the corpse - victim.forceMove(get_turf(victim)) - victim.exit_blood_effect() - victim.visible_message("[victim] falls out of the air, covered in blood, looking highly confused. And dead.") -/mob/living/simple_animal/slaughter/proc/release_friends() - //The loot from killing a slaughter demon - can be consumed to allow the user to blood crawl /obj/item/organ/heart/demon name = "demon heart" @@ -124,33 +118,39 @@ /obj/item/organ/heart/demon/attack(mob/M, mob/living/carbon/user, obj/target) if(M != user) return ..() - user.visible_message("[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!", \ - "An unnatural hunger consumes you. You raise [src] your mouth and devour it!") - playsound(user, 'sound/magic/demon_consume.ogg', 50, 1) - for(var/obj/effect/proc_holder/spell/knownspell in user.mind.spell_list) - if(knownspell.type == /obj/effect/proc_holder/spell/bloodcrawl) - to_chat(user, "...and you don't feel any different.") - qdel(src) - return - user.visible_message("[user]'s eyes flare a deep crimson!", \ - "You feel a strange power seep into your body... you have absorbed the demon's blood-travelling powers!") + user.visible_message(( + "[user] raises [src] to [user.p_their()] mouth and tears into it with [user.p_their()] teeth!"), + ("An unnatural hunger consumes you. You raise [src] your mouth and devour it!"), + ) + playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) + + if(locate(/datum/action/spell/jaunt/bloodcrawl) in user.actions) + to_chat(user, ("...and you don't feel any different.")) + qdel(src) + return + + user.visible_message( + ("[user]'s eyes flare a deep crimson!"), + ("You feel a strange power seep into your body... you have absorbed the demon's blood-travelling powers!"), + ) user.temporarilyRemoveItemFromInventory(src, TRUE) src.Insert(user) //Consuming the heart literally replaces your heart with a demon heart. H A R D C O R E -/obj/item/organ/heart/demon/Insert(mob/living/carbon/M, special = 0, pref_load = FALSE) +/obj/item/organ/internal/heart/demon/Insert(mob/living/carbon/M, special = 0) ..() - if(M.mind) - M.mind.AddSpell(new /obj/effect/proc_holder/spell/bloodcrawl(null)) + // Gives a non-eat-people crawl to the new owner + var/datum/action/spell/jaunt/bloodcrawl/crawl = new(M) + crawl.Grant(M) -/obj/item/organ/heart/demon/Remove(mob/living/carbon/M, special = 0, pref_load = FALSE) +/obj/item/organ/internal/heart/demon/Remove(mob/living/carbon/M, special = 0, pref_load = FALSE) ..() - if(M.mind) - M.mind.RemoveSpell(/obj/effect/proc_holder/spell/bloodcrawl) + var/datum/action/spell/jaunt/bloodcrawl/crawl = locate() in M.actions + qdel(crawl) /obj/item/organ/heart/demon/Stop() return 0 // Always beating. -/mob/living/simple_animal/slaughter/laughter +/mob/living/simple_animal/hostile/imp/slaughter/laughter // The laughter demon! It's everyone's best friend! It just wants to hug // them so much, it wants to hug everyone at once! name = "laughter demon" @@ -171,6 +171,7 @@ deathmessage = "fades out, as all of its friends are released from its \ prison of hugs." loot = list(/mob/living/simple_animal/pet/cat/kitten{name = "Laughter"}) + crawl_type = /datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny playstyle_string = "You are a laughter \ demon, a wonderful creature from another realm. You have a single \ @@ -186,10 +187,9 @@ them; but don't worry! When you die, everyone you hugged will be \ released and fully healed, because in the end it's just a jape, \ sibling!" - revive_eject = TRUE -/mob/living/simple_animal/slaughter/laughter/ex_act(severity) +/mob/living/simple_animal/hostile/imp/slaughter/laughter/ex_act(severity) switch(severity) if(EXPLODE_DEVASTATE) investigate_log("has died from a devastating explosion.", INVESTIGATE_DEATHS) diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm index 9c2fdaf3d134f..7b4802534e60d 100644 --- a/code/modules/antagonists/slaughter/slaughterevent.dm +++ b/code/modules/antagonists/slaughter/slaughterevent.dm @@ -32,15 +32,16 @@ message_admins("No valid spawn locations found, aborting...") return MAP_ERROR - var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter((pick(spawn_locs))) - var/mob/living/simple_animal/slaughter/S = new (holder) - S.holder = holder + var/turf/chosen = pick(spawn_locs) + var/mob/living/simple_animal/hostile/imp/slaughter/S = new(chosen) + new /obj/effect/dummy/phased_mob(chosen, S) + player_mind.transfer_to(S) player_mind.assigned_role = "Slaughter Demon" player_mind.special_role = "Slaughter Demon" player_mind.add_antag_datum(/datum/antagonist/slaughter) - to_chat(S, S.playstyle_string) - to_chat(S, "You are currently not currently in the same plane of existence as the station. Blood Crawl near a blood pool to manifest.") + to_chat(S, ("You are currently not currently in the same plane of existence as the station. \ + Use your Blood Crawl ability near a pool of blood to manifest and wreak havoc.")) SEND_SOUND(S, 'sound/magic/demon_dies.ogg') message_admins("[ADMIN_LOOKUPFLW(S)] has been made into a slaughter demon by an event.") log_game("[key_name(S)] was spawned as a slaughter demon by an event.") diff --git a/code/modules/antagonists/space_dragon/carp_rift.dm b/code/modules/antagonists/space_dragon/carp_rift.dm index 869dd05f33666..78717c72b91a6 100644 --- a/code/modules/antagonists/space_dragon/carp_rift.dm +++ b/code/modules/antagonists/space_dragon/carp_rift.dm @@ -12,7 +12,7 @@ icon_icon = 'icons/hud/actions/actions_space_dragon.dmi' button_icon_state = "carp_rift" -/datum/action/innate/summon_rift/Activate() +/datum/action/innate/summon_rift/on_activate() var/datum/antagonist/space_dragon/dragon = owner.mind?.has_antag_datum(/datum/antagonist/space_dragon) if(!dragon) return diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm index c55a06e15c34f..8fe93b4b59416 100644 --- a/code/modules/antagonists/space_dragon/space_dragon.dm +++ b/code/modules/antagonists/space_dragon/space_dragon.dm @@ -219,15 +219,15 @@ button_icon_state = "wavespeak" check_flags = AB_CHECK_CONSCIOUS -/datum/action/innate/wavespeak/IsAvailable() +/datum/action/innate/wavespeak/is_available() if(!("carp" in owner.faction)) return FALSE return ..() -/datum/action/innate/wavespeak/Activate() +/datum/action/innate/wavespeak/on_activate() // This is filtered, treated, and logged in carp_talk var/input = stripped_input(usr, "Enter wavespeak message.", "Carp Wavespeak", "") - if(!input || !IsAvailable() || !isliving(owner)) + if(!input || !is_available() || !isliving(owner)) return var/mob/living/L = owner L.carp_talk(input) diff --git a/code/modules/antagonists/traitor/backstory/traitor_backstory_ui.dm b/code/modules/antagonists/traitor/backstory/traitor_backstory_ui.dm index 2b9b84019b17b..fa3669124604a 100644 --- a/code/modules/antagonists/traitor/backstory/traitor_backstory_ui.dm +++ b/code/modules/antagonists/traitor/backstory/traitor_backstory_ui.dm @@ -17,12 +17,12 @@ desc = "View and customize your traitor faction, backstory, objectives, codewords, uplink location, \ and objective backstories." button_icon_state = "traitor_objectives" + icon_icon = 'icons/hud/actions/action_generic.dmi' background_icon_state = "bg_agent" /datum/action/antag_info/traitor_menu/New(datum/H) . = ..() name = "Traitor Info and Backstory" - button.name = name /datum/antagonist/traitor/ui_data(mob/user) diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm index 94f2af29bb758..b9dd59f069818 100644 --- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm +++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm @@ -1,5 +1,6 @@ #define DEFAULT_DOOMSDAY_TIMER 4500 #define DOOMSDAY_ANNOUNCE_INTERVAL 600 +#define MALF_ACTION_GROUP "malf" GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( /obj/machinery/field/containment, @@ -22,15 +23,21 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( name = "AI Action" desc = "You aren't entirely sure what this does, but it's very beepy and boopy." background_icon_state = "bg_tech_blue" + button_icon_state = null icon_icon = 'icons/hud/actions/actions_AI.dmi' + cooldown_group = MALF_ACTION_GROUP + check_flags = AB_CHECK_CONSCIOUS /// The owner AI, so we don't have to typecast every time var/mob/living/silicon/ai/owner_AI /// If we have multiple uses of the same power var/uses /// If we automatically use up uses on each activation var/auto_use_uses = TRUE - /// If applicable, the time in deciseconds we have to wait before using any more modules - var/cooldown_period + +/datum/action/innate/ai/New() + ..() + if(initial(uses) > 1) + update_desc() /datum/action/innate/ai/Grant(mob/living/L) . = ..() @@ -40,26 +47,15 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( else owner_AI = owner -/datum/action/innate/ai/IsAvailable() - . = ..() - if(owner_AI && owner_AI.malf_cooldown > world.time) - return - -/datum/action/innate/ai/Trigger() - . = ..() +/datum/action/innate/ai/on_activate(mob/user, atom/target) + SHOULD_CALL_PARENT(TRUE) + ..() if(auto_use_uses) adjust_uses(-1) - if(cooldown_period) - owner_AI.malf_cooldown = world.time + cooldown_period + start_cooldown() /datum/action/innate/ai/proc/update_desc() desc = ("[initial(desc)] There [uses > 1 ? "are" : "is"] [uses] reactivation[uses > 1 ? "s" : ""] remaining.") - button.desc = desc - -/datum/action/innate/ai/New() - ..() - if(initial(uses) > 1) - update_desc() /datum/action/innate/ai/proc/adjust_uses(amt, silent) uses += amt @@ -75,17 +71,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( /datum/action/innate/ai/ranged name = "Ranged AI Action" auto_use_uses = FALSE //This is so we can do the thing and disable/enable freely without having to constantly add uses - var/obj/effect/proc_holder/ranged_ai/linked_ability //The linked proc holder that contains the actual ability code - var/linked_ability_type //The path of our linked ability - -/datum/action/innate/ai/ranged/New() - if(!linked_ability_type) - WARNING("Ranged AI action [name] attempted to spawn without a linked ability!") - qdel(src) //uh oh! - return - linked_ability = new linked_ability_type() - linked_ability.attached_action = src - ..() + requires_target = TRUE + unset_after_click = TRUE /datum/action/innate/ai/ranged/adjust_uses(amt, silent) uses += amt @@ -98,31 +85,6 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( Remove(owner) QDEL_IN(src, 100) //let any active timers on us finish up -/datum/action/innate/ai/ranged/Destroy() - QDEL_NULL(linked_ability) - return ..() - -/datum/action/innate/ai/ranged/Activate() - linked_ability.toggle(owner) - return TRUE - -//The actual ranged proc holder. -/obj/effect/proc_holder/ranged_ai - var/enable_text = "Hello World!" //Appears when the user activates the ability - var/disable_text = "Goodbye Cruel World!" //Context clues! - var/datum/action/innate/ai/ranged/attached_action - -/obj/effect/proc_holder/ranged_ai/Destroy() - attached_action = null - return ..() - -/obj/effect/proc_holder/ranged_ai/proc/toggle(mob/user) - if(active) - remove_ranged_ability(disable_text) - else - add_ranged_ability(user, enable_text) - - //The datum and interface for the malf unlock menu, which lets them choose actions to unlock. /datum/module_picker var/temp @@ -222,7 +184,6 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( var/engaged = 0 var/cost = 5 var/one_purchase = FALSE //If this module can only be purchased once. This always applies to upgrades, even if the variable is set to false. - var/power_type = /datum/action/innate/ai //If the module gives an active ability, use this. Mutually exclusive with upgrade. var/upgrade //If the module gives a passive upgrade, use this. Mutually exclusive with power_type. var/unlock_text = "Hello World!" //Text shown when an ability is unlocked @@ -250,17 +211,19 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( desc = "Activates the Doomsday device. This is not reversible." button_icon_state = "doomsday_device" auto_use_uses = FALSE + var/device_active -/datum/action/innate/ai/nuke_station/Activate() +/datum/action/innate/ai/nuke_station/on_activate(mob/user, atom/target) + . = ..() var/turf/T = get_turf(owner) if(!istype(T) || !is_station_level(T.z)) to_chat(owner, "You cannot activate the Doomsday device while off-station!") return if(alert(owner, "Send arming signal? (true = arm, false = cancel)", "purge_all_life()", "confirm = TRUE;", "confirm = FALSE;") != "confirm = TRUE;") return - if (active) + if (device_active) return //prevent the AI from activating an already active doomsday - active = TRUE + device_active = TRUE set_us_up_the_bomb(owner) /datum/action/innate/ai/nuke_station/proc/set_us_up_the_bomb(mob/living/owner) @@ -450,7 +413,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( button_icon_state = "lockdown" uses = 1 -/datum/action/innate/ai/lockdown/Activate() +/datum/action/innate/ai/lockdown/on_activate(mob/user, atom/target) + . = ..() for(var/obj/machinery/door/D in GLOB.airlocks) if(!is_station_level(D.z)) continue @@ -485,9 +449,10 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( desc = "Detonate all non-cyborg RCDs on the station." button_icon_state = "detonate_rcds" uses = 1 - cooldown_period = 100 + cooldown_time = 10 SECONDS -/datum/action/innate/ai/destroy_rcds/Activate() +/datum/action/innate/ai/destroy_rcds/on_activate(mob/user, atom/target) + . = ..() for(var/I in GLOB.rcd_list) if(!istype(I, /obj/item/construction/rcd/borg)) //Ensures that cyborg RCDs are spared. var/obj/item/construction/rcd/RCD = I @@ -529,7 +494,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( button_icon_state = "break_fire_alarms" uses = 1 -/datum/action/innate/ai/break_fire_alarms/Activate() +/datum/action/innate/ai/break_fire_alarms/on_activate(mob/user, atom/target) + . = ..() for(var/obj/machinery/firealarm/F in GLOB.machines) if(!is_station_level(F.z)) continue @@ -558,7 +524,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( button_icon_state = "break_air_alarms" uses = 1 -/datum/action/innate/ai/break_air_alarms/Activate() +/datum/action/innate/ai/break_air_alarms/on_activate(mob/user, atom/target) + . = ..() for(var/obj/machinery/airalarm/AA in GLOB.machines) if(!is_station_level(AA.z)) continue @@ -567,7 +534,6 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( to_chat(owner, "All air alarm safeties on the station have been overridden. Air alarms may now use the Flood environmental mode.") owner.playsound_local(owner, 'sound/machines/terminal_off.ogg', 50, 0) - //Overload Machine: Allows the AI to overload a machine, detonating it after a delay. Two uses per purchase. /datum/AI_Module/small/overload_machine module_name = "Machine Overload" @@ -583,43 +549,41 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( desc = "Overheats a machine, causing a small explosion after a short time." button_icon_state = "overload_machine" uses = 2 - linked_ability_type = /obj/effect/proc_holder/ranged_ai/overload_machine - -/datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(obj/machinery/M) - if(M && !QDELETED(M)) - var/turf/T = get_turf(M) - message_admins("[ADMIN_LOOKUPFLW(usr)] overloaded [M.name] at [ADMIN_VERBOSEJMP(T)].") - log_game("[key_name(usr)] overloaded [M.name] at [AREACOORD(T)].") - explosion(get_turf(M), 0, 2, 3, 0) - if(M) //to check if the explosion killed it before we try to delete it - qdel(M) - -/obj/effect/proc_holder/ranged_ai/overload_machine - active = FALSE - ranged_mousepointer = 'icons/effects/overload_machine_target.dmi' + ranged_mousepointer = 'icons/effects/mouse_pointers/overload_machine_target.dmi' enable_text = "You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel." disable_text = "You release your hold on the powernet." -/obj/effect/proc_holder/ranged_ai/overload_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) - if(..()) +/datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(mob/living/caller, obj/machinery/to_explode) + if(QDELETED(to_explode)) return - if(ranged_ability_user.incapacitated()) - remove_ranged_ability() - return - if(!istype(target)) - to_chat(ranged_ability_user, "You can only overload machines!") - return - if(is_type_in_typecache(target, GLOB.blacklisted_malf_machines)) - to_chat(ranged_ability_user, "You cannot overload that device!") - return - ranged_ability_user.playsound_local(ranged_ability_user, "sparks", 50, 0) - attached_action.adjust_uses(-1) - target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") - caller.log_message("activated malf module [name]", LOG_GAME) - addtimer(CALLBACK(attached_action, TYPE_PROC_REF(/datum/action/innate/ai/ranged/overload_machine, detonate_machine), target), 50) //kaboom! - remove_ranged_ability("Overcharging machine...") - return TRUE + var/turf/machine_turf = get_turf(to_explode) + message_admins("[ADMIN_LOOKUPFLW(caller)] overloaded [to_explode.name] ([to_explode.type]) at [ADMIN_VERBOSEJMP(machine_turf)].") + log_game("[key_name(caller)] overloaded [to_explode.name] ([to_explode.type]) at [AREACOORD(machine_turf)].") + explosion(to_explode, heavy_impact_range = 2, light_impact_range = 3) + if(!QDELETED(to_explode)) //to check if the explosion killed it before we try to delete it + qdel(to_explode) + +/datum/action/innate/ai/ranged/overload_machine/on_activate(mob/user, atom/target) + . = ..() + if(!istype(target, /obj/machinery)) + to_chat(user, "You can only overload machines!") + return FALSE + var/obj/machinery/clicked_machine = target + if(is_type_in_typecache(clicked_machine, GLOB.blacklisted_malf_machines)) + to_chat(user, "You cannot overload that device!") + return FALSE + + //caller.playsound_local(caller, SFX_SPARKS, 50, 0) + adjust_uses(-1) + if(uses) + desc = "[initial(desc)] It has [uses] use\s remaining." + update_buttons() + + clicked_machine.audible_message(("You hear a loud electrical buzzing sound coming from [clicked_machine]!")) + addtimer(CALLBACK(src, PROC_REF(detonate_machine), user, clicked_machine), 5 SECONDS) //kaboom! + to_chat(user, "Overcharging machine...") + return TRUE //Override Machine: Allows the AI to override a machine, animating it into an angry, living version of itself. /datum/AI_Module/small/override_machine @@ -636,41 +600,43 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( desc = "Animates a targeted machine, causing it to attack anyone nearby." button_icon_state = "override_machine" uses = 4 - linked_ability_type = /obj/effect/proc_holder/ranged_ai/override_machine - -/datum/action/innate/ai/ranged/override_machine/proc/animate_machine(obj/machinery/M) - if(M && !QDELETED(M)) - var/turf/T = get_turf(M) - message_admins("[ADMIN_LOOKUPFLW(owner)] overrided (animated) [M.name] at [ADMIN_VERBOSEJMP(T)].") - log_game("[key_name(owner)] overrided (animated) [M.name] at [AREACOORD(T)].") - new/mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, owner) - -/obj/effect/proc_holder/ranged_ai/override_machine - active = FALSE - ranged_mousepointer = 'icons/effects/override_machine_target.dmi' + ranged_mousepointer = 'icons/effects/mouse_pointers/override_machine_target.dmi' enable_text = "You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel." disable_text = "You release your hold on the powernet." -/obj/effect/proc_holder/ranged_ai/override_machine/InterceptClickOn(mob/living/caller, params, obj/machinery/target) - if(..()) - return - if(ranged_ability_user.incapacitated()) - remove_ranged_ability() - return - if(!istype(target)) - to_chat(ranged_ability_user, "You can only animate machines!") - return - if(!target.can_be_overridden() || is_type_in_typecache(target, GLOB.blacklisted_malf_machines)) - to_chat(ranged_ability_user, "That machine can't be overridden!") - return - ranged_ability_user.playsound_local(ranged_ability_user, 'sound/misc/interference.ogg', 50, 0) - attached_action.adjust_uses(-1) - target.audible_message("You hear a loud electrical buzzing sound coming from [target]!") - caller.log_message("activated malf module [name]", LOG_GAME) - addtimer(CALLBACK(attached_action, TYPE_PROC_REF(/datum/action/innate/ai/ranged/override_machine, animate_machine), target), 50) //kabeep! - remove_ranged_ability("Sending override signal...") +/datum/action/innate/ai/ranged/override_machine/New() + . = ..() + desc = "[desc] It has [uses] use\s remaining." + +/datum/action/innate/ai/ranged/override_machine/on_activate(mob/user, atom/target) + . = ..() + if(user.incapacitated()) + return FALSE + if(!istype(target, /obj/machinery)) + to_chat(user, ("You can only animate machines!")) + return FALSE + var/obj/machinery/clicked_machine = target + if(!clicked_machine.can_be_overridden() || is_type_in_typecache(clicked_machine, GLOB.blacklisted_malf_machines)) + to_chat(user, ("That machine can't be overridden!")) + return FALSE + + user.playsound_local(user, 'sound/misc/interference.ogg', 50, FALSE, use_reverb = FALSE) + adjust_uses(-1) + + if(uses) + desc = "[initial(desc)] It has [uses] use\s remaining." + update_buttons() + + clicked_machine.audible_message(("You hear a loud electrical buzzing sound coming from [clicked_machine]!")) + addtimer(CALLBACK(src, PROC_REF(animate_machine), user, clicked_machine), 5 SECONDS) //kabeep! + to_chat(user, "Sending override signal...") return TRUE +/datum/action/innate/ai/ranged/override_machine/proc/animate_machine(mob/living/caller, obj/machinery/to_animate) + if(QDELETED(to_animate)) + return + + new /mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(to_animate), to_animate, caller, TRUE) //Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting. /datum/AI_Module/large/place_cyborg_transformer @@ -697,7 +663,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( var/image/I = image("icon"='icons/turf/overlays.dmi') LAZYADD(turfOverlays, I) -/datum/action/innate/ai/place_transformer/Activate() +/datum/action/innate/ai/place_transformer/on_activate(mob/user, atom/target) + . = ..() if(!owner_AI.can_place_transformer(src)) return active = TRUE @@ -766,7 +733,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( button_icon_state = "blackout" uses = 3 -/datum/action/innate/ai/blackout/Activate() +/datum/action/innate/ai/blackout/on_activate(mob/user, atom/target) + . = ..() for(var/obj/machinery/power/apc/apc in GLOB.apcs_list) if(prob(30 * apc.overload)) apc.overload_lighting() @@ -794,7 +762,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( button_icon_state = "emergency_lights" uses = 1 -/datum/action/innate/ai/emergency_lights/Activate() +/datum/action/innate/ai/emergency_lights/on_activate(mob/user, atom/target) + . = ..() for(var/obj/machinery/light/L in GLOB.machines) if(is_station_level(L.z)) L.no_emergency = TRUE @@ -821,9 +790,10 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( button_icon_state = "reactivate_cameras" uses = 20 auto_use_uses = FALSE - cooldown_period = 30 + cooldown_time = 3 SECONDS -/datum/action/innate/ai/reactivate_cameras/Activate() +/datum/action/innate/ai/reactivate_cameras/on_activate(mob/user, atom/target) + . = ..() var/fixed_cameras = 0 for(var/V in GLOB.cameranet.cameras) if(!uses) @@ -910,7 +880,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( button_icon_state = "fake_alert" uses = 1 -/datum/action/innate/ai/fake_alert/Activate() +/datum/action/innate/ai/fake_alert/on_activate(mob/user, atom/target) + . = ..() var/list/events_to_chose = list() for(var/datum/round_event_control/E in SSevents.control) if(!E.can_malf_fake_alert) @@ -928,5 +899,6 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list( owner.log_message("activated malf module [name] (TYPE: [chosen_event])", LOG_GAME) return TRUE +#undef MALF_ACTION_GROUP #undef DEFAULT_DOOMSDAY_TIMER #undef DOOMSDAY_ANNOUNCE_INTERVAL diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index 710cdf672cd60..710e31e38c79e 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -488,7 +488,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/rend) while(breakout < 50) var/turf/potential_T = find_safe_turf() if(T.get_virtual_z_level() != potential_T.get_virtual_z_level() || abs(get_dist_euclidian(potential_T,T)) > 50 - breakout) - do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC, teleport_mode = TELEPORT_ALLOW_WIZARD) + do_teleport(user, potential_T, channel = TELEPORT_CHANNEL_MAGIC_SELF, teleport_mode = TELEPORT_ALLOW_WIZARD) T = potential_T break breakout += 1 diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm deleted file mode 100644 index 4f9d06ddf1d0d..0000000000000 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ /dev/null @@ -1,886 +0,0 @@ -/datum/spellbook_entry - var/name = "Entry Name" - - var/spell_type = null - var/desc = "" - var/category = "Offensive" - var/cost = 2 - var/times = 0 - var/refundable = TRUE - var/obj/effect/proc_holder/spell/S = null //Since spellbooks can be used by only one person anyway we can track the actual spell - var/buy_word = "Learn" - var/cooldown - var/clothes_req = FALSE - var/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook - var/list/no_coexistence_typecache //Used so you can't have specific spells together - var/no_random = FALSE // This is awful one to be a part of randomness - i.e.) soul tap - var/disabled = FALSE // Is this item disabled due to having issues? Must provide an issue reference and description of issue. - -/datum/spellbook_entry/New() - ..() - no_coexistence_typecache = typecacheof(no_coexistence_typecache) - -/datum/spellbook_entry/proc/IsAvailable(obj/item/spellbook/book) // For config prefs / gamemode restrictions - these are round applied - return TRUE - -/datum/spellbook_entry/proc/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) // Specific circumstances - if (disabled) - return FALSE - if(book.uses= aspell.level_max) - to_chat(user, "This spell cannot be improved further.") - return FALSE - else - aspell.name = initial(aspell.name) - aspell.spell_level++ - aspell.charge_max = round(initial(aspell.charge_max) - aspell.spell_level * (initial(aspell.charge_max) - aspell.cooldown_min)/ aspell.level_max) - if(aspell.charge_max < aspell.charge_counter) - aspell.charge_counter = aspell.charge_max - var/newname = "ERROR" - switch(aspell.spell_level) - if(1) - to_chat(user, "You have improved [aspell.name] into Efficient [aspell.name].") - newname = "Efficient [aspell.name]" - if(2) - to_chat(user, "You have further improved [aspell.name] into Quickened [aspell.name].") - newname = "Quickened [aspell.name]" - if(3) - to_chat(user, "You have further improved [aspell.name] into Free [aspell.name].") - newname = "Free [aspell.name]" - if(4) - to_chat(user, "You have further improved [aspell.name] into Instant [aspell.name].") - newname = "Instant [aspell.name]" - aspell.name = newname - if(aspell.spell_level >= aspell.level_max) - to_chat(user, "This spell cannot be strengthened any further.") - //we'll need to update the cooldowns for the spellbook - GetInfo() - book.update_static_data(user) // updates "times" var - SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) - return TRUE - //debug handling - if(book.everything_robeless) - SSblackbox.record_feedback("tally", "debug_wizard_spell_learned", 1, name) - S.clothes_req = FALSE // You'd want no cloth req if you learned spells from a debug spellbook - else - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - - //No same spell found - just learn it - user.mind.AddSpell(S) - to_chat(user, "You have learned [S.name].") - return TRUE - -/datum/spellbook_entry/proc/CanRefund(mob/living/carbon/human/user,obj/item/spellbook/book) - if(!refundable) - return FALSE - if(!S) - S = new spell_type() - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - return TRUE - return FALSE - -/datum/spellbook_entry/proc/Refund(mob/living/carbon/human/user,obj/item/spellbook/book) //return point value or -1 for failure - var/area/wizard_station/A = GLOB.areas_by_type[/area/wizard_station] - if(!(user in A.contents)) - to_chat(user, "You can only refund spells at the wizard lair") - return -1 - if(!S) - S = new spell_type() - var/spell_levels = 0 - for(var/obj/effect/proc_holder/spell/aspell in user.mind.spell_list) - if(initial(S.name) == initial(aspell.name)) - spell_levels = aspell.spell_level - user.mind.spell_list.Remove(aspell) - name = initial(name) - qdel(S) - return cost * (spell_levels+1) - return -1 - -/datum/spellbook_entry/proc/GetInfo() - if(!spell_type) - return - if(!S) - S = new spell_type() - if(S.charge_type == "recharge") - cooldown = S.charge_max/10 - if(S.clothes_req) - clothes_req = TRUE - if(!desc) - desc = S.desc - -/datum/spellbook_entry/fireball - name = "Fireball" - desc = "Fires an explosive fireball at a target. Considered a classic among all wizards." - spell_type = /obj/effect/proc_holder/spell/aimed/fireball - -/datum/spellbook_entry/spell_cards - name = "Spell Cards" - desc = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" - spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards - cost = 1 - -/datum/spellbook_entry/rod_form - name = "Rod Form" - desc = "Take on the form of an immovable rod, destroying all in your path. Purchasing this spell multiple times will also increase the rod's damage and travel range." - spell_type = /obj/effect/proc_holder/spell/targeted/rod_form - -/datum/spellbook_entry/magicm - name = "Magic Missile" - desc = "Fires several, slow moving, magic projectiles at nearby targets." - spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - category = "Defensive" - -/datum/spellbook_entry/disintegrate - name = "Disintegrate" - desc = "Charges your hand with an unholy energy that can be used to cause a touched victim to violently explode." - spell_type = /obj/effect/proc_holder/spell/targeted/touch/disintegrate - -/datum/spellbook_entry/disabletech - name = "Disable Tech" - desc = "Disables all weapons, cameras and most other technology in range." - spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/repulse - name = "Repulse" - desc = "Throws everything around the user away." - spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse - category = "Defensive" - -/datum/spellbook_entry/lightningPacket - name = "Lightning bolt! Lightning bolt!" - desc = "Forged from eldrich energies, a packet of pure power, known as a spell packet will appear in your hand, that when thrown will stun the target." - spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket - category = "Defensive" - -/datum/spellbook_entry/timestop - name = "Time Stop" - desc = "Stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen." - spell_type = /obj/effect/proc_holder/spell/aoe_turf/timestop - category = "Defensive" - -/datum/spellbook_entry/smoke - name = "Smoke" - desc = "Spawns a cloud of choking smoke at your location." - spell_type = /obj/effect/proc_holder/spell/targeted/smoke - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blind - name = "Blind" - desc = "Temporarily blinds a single target." - spell_type = /obj/effect/proc_holder/spell/targeted/blind - cost = 1 - -/datum/spellbook_entry/mindswap - name = "Mindswap" - desc = "Allows you to switch bodies with a target next to you. You will both fall asleep when this happens, and it will be quite obvious that you are the target's body if someone watches you do it." - spell_type = /obj/effect/proc_holder/spell/targeted/mind_transfer - category = "Mobility" - -/datum/spellbook_entry/forcewall - name = "Force Wall" - desc = "Create a magical barrier that only you can pass through." - spell_type = /obj/effect/proc_holder/spell/targeted/forcewall - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blink - name = "Blink" - desc = "Randomly teleports you a short distance." - spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - category = "Mobility" - -/datum/spellbook_entry/teleport - name = "Teleport" - desc = "Teleports you to an area of your selection." - spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport - category = "Mobility" - -/datum/spellbook_entry/mutate - name = "Mutate" - desc = "Causes you to turn into a hulk and gain laser vision for a short while." - spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate - -/datum/spellbook_entry/jaunt - name = "Ethereal Jaunt" - desc = "Turns your form ethereal, temporarily making you invisible and able to pass through walls." - spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt - category = "Mobility" - -/datum/spellbook_entry/knock - name = "Knock" - desc = "Opens nearby doors and closets." - spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock - category = "Mobility" - cost = 1 - -/datum/spellbook_entry/fleshtostone - name = "Flesh to Stone" - desc = "Charges your hand with the power to turn victims into inert statues for a long period of time." - spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone - -/datum/spellbook_entry/summonitem - name = "Summon Item" - desc = "Recalls a previously marked item to your hand from anywhere in the universe." - spell_type = /obj/effect/proc_holder/spell/targeted/summonitem - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/lichdom - name = "Bind Soul" - desc = "A dark necromantic pact that can forever bind your soul to an \ - item of your choosing. So long as both your body and the item remain \ - intact and on the same plane you can revive from death, though the time \ - between reincarnations grows steadily with use, along with the weakness \ - that the new skeleton body will experience upon 'birth'. Note that \ - becoming a lich destroys all internal organs except the brain." - spell_type = /obj/effect/proc_holder/spell/targeted/lichdom - category = "Defensive" - cost = 3 - no_random = WIZARD_NORANDOM_WILDAPPRENTICE - -/datum/spellbook_entry/teslablast - name = "Tesla Blast" - desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down." - spell_type = /obj/effect/proc_holder/spell/targeted/tesla - -/datum/spellbook_entry/lightningbolt - name = "Lightning Bolt" - desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down." - spell_type = /obj/effect/proc_holder/spell/aimed/lightningbolt - -/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return TRUE on success - . = ..() - user.flags_1 |= TESLA_IGNORE_1 - -/datum/spellbook_entry/infinite_guns - name = "Lesser Summon Guns" - desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles that deal little damage, but will knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Arcane Barrage." - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - cost = 3 - no_coexistence_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - -/datum/spellbook_entry/arcane_barrage - name = "Arcane Barrage" - desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Lesser Summon Gun." - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - no_coexistence_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - -/datum/spellbook_entry/barnyard - name = "Barnyard Curse" - desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." - spell_type = /obj/effect/proc_holder/spell/targeted/barnyardcurse - -/datum/spellbook_entry/charge - name = "Charge" - desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user." - spell_type = /obj/effect/proc_holder/spell/targeted/charge - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/shapeshift - name = "Wild Shapeshift" - desc = "Take on the shape of another for a time to use their natural abilities. Once you've made your choice it cannot be changed." - spell_type = /obj/effect/proc_holder/spell/targeted/shapeshift - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/tap - name = "Soul Tap" - desc = "Fuel your spells using your own soul!" - spell_type = /obj/effect/proc_holder/spell/self/tap - category = "Assistance" - cost = 1 - no_random = WIZARD_NORANDOM_WILDAPPRENTICE - -/datum/spellbook_entry/spacetime_dist - name = "Spacetime Distortion" - desc = "Entangle the strings of space-time in an area around you, randomizing the layout and making proper movement impossible. The strings vibrate..." - spell_type = /obj/effect/proc_holder/spell/spacetime_dist - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/the_traps - name = "The Traps!" - desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/bees - name = "Lesser Summon Bees" - desc = "This spell magically kicks a transdimensional beehive, instantly summoning a swarm of bees to your location. These bees are NOT friendly to anyone." - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/creature/bee - category = "Defensive" - -/datum/spellbook_entry/item - name = "Buy Item" - refundable = FALSE - buy_word = "Summon" - var/item_path= null - - -/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - new item_path(get_turf(user)) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - return TRUE - -/datum/spellbook_entry/item/staffchange - name = "Staff of Change" - desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." - item_path = /obj/item/gun/magic/staff/change - -/datum/spellbook_entry/item/staffanimation - name = "Staff of Animation" - desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." - item_path = /obj/item/gun/magic/staff/animate - category = "Assistance" - -/datum/spellbook_entry/item/staffchaos - name = "Staff of Chaos" - desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." - item_path = /obj/item/gun/magic/staff/chaos - -/datum/spellbook_entry/item/spellblade - name = "Spellblade" - desc = "A sword capable of firing blasts of energy which rip targets limb from limb." - item_path = /obj/item/gun/magic/staff/spellblade - -/datum/spellbook_entry/item/staffdoor - name = "Staff of Door Creation" - desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." - item_path = /obj/item/gun/magic/staff/door - cost = 1 - category = "Mobility" - -/datum/spellbook_entry/item/staffhealing - name = "Staff of Healing" - desc = "An altruistic staff that can heal the lame and raise the dead." - item_path = /obj/item/gun/magic/staff/healing - cost = 1 - category = "Defensive" - -/datum/spellbook_entry/item/lockerstaff - name = "Staff of the Locker" - desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." - item_path = /obj/item/gun/magic/staff/locker - category = "Defensive" - -/datum/spellbook_entry/item/scryingorb - name = "Scrying Orb" - desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." - item_path = /obj/item/scrying - category = "Defensive" - -/datum/spellbook_entry/item/soulstones - name = "Soulstone Shard Kit" - desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. The spell Artificer allows you to create arcane machines for the captured souls to pilot." - item_path = /obj/item/storage/belt/soulstone/full - category = "Assistance" - -/datum/spellbook_entry/item/soulstones/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . =..() - if(.) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/construct(null)) - return . - -/datum/spellbook_entry/item/necrostone - name = "A Necromantic Stone" - desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." - item_path = /obj/item/necromantic_stone - category = "Assistance" - -/datum/spellbook_entry/item/wands - name = "Wand Assortment" - desc = "A collection of wands that allow for a wide variety of utility. Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." - item_path = /obj/item/storage/belt/wands/full - category = "Defensive" - -/datum/spellbook_entry/item/armor - name = "Mastercrafted Armor Set" - desc = "An artefact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space." - item_path = /obj/item/clothing/suit/space/hardsuit/wizard - category = "Defensive" - -/datum/spellbook_entry/item/armor/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. - new /obj/item/clothing/gloves/color/purple(get_turf(user))//To complete the outfit - new /obj/item/clothing/mask/breath(get_turf(user)) // so the air gets to your mouth. Just an average mask. - new /obj/item/tank/internals/emergency_oxygen/magic_oxygen(get_turf(user)) // so you have something to actually breathe. Near infinite. - -/datum/spellbook_entry/item/contract - name = "Contract of Apprenticeship" - desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." - item_path = /obj/item/antag_spawner/contract - category = "Assistance" - -/datum/spellbook_entry/item/guardian - name = "Guardian Deck" - desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ - It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." - item_path = /obj/item/holoparasite_creator/wizard - category = "Assistance" - -/datum/spellbook_entry/item/bloodbottle - name = "Bottle of Blood" - desc = "A bottle of magically infused blood, the smell of which will attract extradimensional \ - beings when broken. Be careful though, the kinds of creatures summoned by blood magic are \ - indiscriminate in their killing, and you yourself may become a victim." - item_path = /obj/item/antag_spawner/slaughter_demon - limit = 1 - category = "Assistance" - -/datum/spellbook_entry/item/hugbottle - name = "Bottle of Tickles" - desc = "A bottle of magically infused fun, the smell of which will \ - attract adorable extradimensional beings when broken. These beings \ - are similar to slaughter demons, but they do not permamently kill \ - their victims, instead putting them in an extradimensional hugspace, \ - to be released on the demon's death. Chaotic, but not ultimately \ - damaging. The crew's reaction to the other hand could be very \ - destructive." - item_path = /obj/item/antag_spawner/slaughter_demon/laughter - cost = 1 //non-destructive; it's just a jape, sibling! - limit = 1 - category = "Assistance" - -/datum/spellbook_entry/item/mjolnir - name = "Mjolnir" - desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." - item_path = /obj/item/mjolnir - -/datum/spellbook_entry/item/singularity_hammer - name = "Singularity Hammer" - desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact." - item_path = /obj/item/singularityhammer - -/datum/spellbook_entry/item/battlemage - name = "Battlemage Armour" - desc = "An ensorceled suit of armour, protected by a powerful shield. The shield can completely negate sixteen attacks before being permanently depleted." - item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard - limit = 1 - category = "Defensive" - -/datum/spellbook_entry/item/battlemage_charge - name = "Battlemage Armour Charges" - desc = "A powerful defensive rune, it will grant eight additional charges to a suit of battlemage armour." - item_path = /obj/item/wizard_armour_charge - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/item/warpwhistle - name = "Warp Whistle" - desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use." - item_path = /obj/item/warpwhistle - category = "Mobility" - cost = 1 - -//THESE ARE NOT PURCHASABLE SPELLS! They're references to old spells that got removed + shit that sounds stupid but fun so we can painfully lock behind a dimmer component - -/datum/spellbook_entry/challenge - name = "Take the Challenge" - refundable = FALSE - category = "Challenges" - buy_word = "Accept" - -/datum/spellbook_entry/challenge/multiverse - name = "Multiverse Sword" - desc = "The Station gets a multiverse sword to stop you. Can you withstand the hordes of multiverse realities?" - -/datum/spellbook_entry/challenge/antiwizard - name = "Friendly Wizard Scum" - desc = "A \"Friendly\" Wizard will protect the station, and try to kill you. They get a spellbook much like you, but will use it for \"GOOD\"." - -/// How much threat we need to let these rituals happen on dynamic -#define MINIMUM_THREAT_FOR_RITUALS 85 - -/datum/spellbook_entry/summon - name = "Summon Stuff" - category = "Rituals" - refundable = FALSE - buy_word = "Cast" - var/ritual_invocation // This does nothing. This is a flavor to ghosts observing a wizard. - -/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) - return ..() && !times - -/datum/spellbook_entry/summon/proc/say_invocation(mob/living/carbon/human/user) - if(ritual_invocation) - user.say(ritual_invocation, forced = "spell") - -/datum/spellbook_entry/summon/ghosts - name = "Summon Ghosts" - desc = "Spook the crew out by making them see dead people. Be warned, ghosts are capricious and occasionally vindicative, and some will use their incredibly minor abilities to frustrate you." - cost = 0 - ritual_invocation = "ALADAL DESINARI ODORI'IN TUUR'IS OVOR'E POR" - -/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - new /datum/round_event/wizard/ghost() - times++ - to_chat(user, "You have cast summon ghosts!") - playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1) - say_invocation(user) - return TRUE - -/datum/spellbook_entry/summon/guns - name = "Summon Guns" - desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. There is a good chance that they will shoot each other first." - ritual_invocation = "ALADAL DESINARI ODORI'IN DOL'G FLAM OVOR'E POR" - -/datum/spellbook_entry/summon/guns/IsAvailable(obj/item/spellbook/book) - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(book.bypass_lock) - return TRUE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - var/datum/game_mode/dynamic/mode = SSticker.mode - if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE - return !CONFIG_GET(flag/no_summon_guns) - -/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_GUNS, user, 10) - times++ - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon guns!") - say_invocation(user) - return TRUE - -/datum/spellbook_entry/summon/magic - name = "Summon Magic" - desc = "Share the wonders of magic with the crew and show them why they aren't to be trusted with it at the same time." - ritual_invocation = "ALADAL DESINARI ODORI'IN IDO'LEX SPERMITA OVOR'E POR" - -/datum/spellbook_entry/summon/magic/IsAvailable(obj/item/spellbook/book) - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(book.bypass_lock) - return TRUE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - var/datum/game_mode/dynamic/mode = SSticker.mode - if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE - return !CONFIG_GET(flag/no_summon_magic) - -/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - rightandwrong(SUMMON_MAGIC, user, 10) - times++ - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon magic!") - say_invocation(user) - return TRUE - -/datum/spellbook_entry/summon/events - name = "Summon Events" - desc = "Give Murphy's law a little push and replace all events with special wizard ones that will confound and confuse everyone. Multiple castings increase the rate of these events." - ritual_invocation = "ALADAL DESINARI ODORI'IN IDO'LEX MANAG'ROKT OVOR'E POR" - -/datum/spellbook_entry/summon/events/IsAvailable(obj/item/spellbook/book) - if(!SSticker.mode) // In case spellbook is placed on map - return FALSE - if(book.bypass_lock) - return TRUE - if(istype(SSticker.mode, /datum/game_mode/dynamic)) // Disable events on dynamic - var/datum/game_mode/dynamic/mode = SSticker.mode - if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) - return FALSE - return !CONFIG_GET(flag/no_summon_events) - -/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - summonevents() - times++ - playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1) - to_chat(user, "You have cast summon events.") - say_invocation(user) - return TRUE - -/datum/spellbook_entry/summon/events/GetInfo() - if(times>0) - . += "You cast it [times] times.
" - return . - -/datum/spellbook_entry/summon/curse_of_madness - name = "Curse of Madness" - desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." - cost = 4 - ritual_invocation = "ALADAL DESINARI ODORI'IN PORES ENHIDO'LEN MORI MAKA TU" - -/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - times++ - var/message - while(!message) - message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") - curse_of_madness(user, message) - to_chat(user, "You have cast the curse of insanity!") - playsound(user, 'sound/magic/mandswap.ogg', 50, 1) - return TRUE - -/datum/spellbook_entry/summon/wild_magic - name = "Wild Magic Manipulation" - desc = "multiply your remaining spell points by 70%(round down) and expand all of them to Wild Magic Manipulation. \ - You purchase random spells and items upto the spell points you expanded. Spells from this ritual will no longer be refundable even if you learned it manually, but also the book will no longer accept items to refund." - cost = 0 - ritual_invocation = "ALADAL DESINARI ODORI'IN A'EN SPERMITEN G'ATUA H'UN OVORA DUN SPERMITUN" - -/datum/spellbook_entry/summon/wild_magic/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - if(!book.uses) - to_chat(user, "You have no spell points for this ritual.") // You can cast it again as long as you get more spell points somehow - return FALSE - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - book.uses = round(book.uses*WIZARD_WILDMAGIC_SPELLPOINT_MULTIPLIER) // more spell points - book.refuses_refund = TRUE - book.desc = "An unearthly tome that once had a great power." - while(book.uses) - var/datum/spellbook_entry/target = pick(book.entries) - if(istype(target, /datum/spellbook_entry/summon/wild_magic)) - continue // Too lucky to get more spell points, but no. - if(target.CanBuy(user,book)) - if(target.Buy(user,book)) - book.uses -= target.cost - target.refundable = FALSE - say_invocation(user) - return TRUE - - -#undef MINIMUM_THREAT_FOR_RITUALS - -/obj/item/spellbook - name = "spell book" - desc = "An unearthly tome that glows with power." - icon = 'icons/obj/library.dmi' - icon_state ="book" - throw_speed = 2 - throw_range = 5 - w_class = WEIGHT_CLASS_TINY - var/uses = 10 - var/temp = null - var/refuses_refund = FALSE - /// The mind that first used the book. Automatically assigned when a wizard spawns. - var/datum/mind/owner - var/list/entries = list() - var/everything_robeless = FALSE //! if TRUE, all spells you learn become robeless. Ask admin. - var/bypass_lock = FALSE //! bypasses some locked ritual & spell combinations. Ask admin. - -/obj/item/spellbook/examine(mob/user) - . = ..() - if(owner) - . += "There is a small signature on the front cover: \"[owner]\"." - else - . += "It appears to have no author." - -/obj/item/spellbook/Initialize(mapload) - . = ..() - prepare_spells() - -/obj/item/spellbook/attack_self(mob/user) - if(!owner) - if(!user.mind) - return - to_chat(user, "You bind the spellbook to yourself.") - owner = user.mind - return - if(user.mind != owner) - if(user.mind.special_role == "apprentice") - to_chat(user, "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not.") - else - to_chat(user, "The [name] does not recognize you as its owner and refuses to open!") - return - return ..() - -/obj/item/spellbook/attackby(obj/item/O, mob/user, params) - if(refuses_refund) - to_chat(user, "Your book is powerless because of Wild Magic Manipulation ritual. The book doesn't accept the item.") - return - if(istype(O, /obj/item/antag_spawner/contract)) - var/obj/item/antag_spawner/contract/contract = O - if(contract.used) - to_chat(user, "The contract has been used, you can't get your points back now!") - else - to_chat(user, "You feed the contract back into the spellbook, refunding your points.") - uses += 2 - for(var/datum/spellbook_entry/item/contract/CT in entries) - if(!isnull(CT.limit)) - CT.limit++ - qdel(O) - else if(istype(O, /obj/item/antag_spawner/slaughter_demon)) - to_chat(user, "On second thought, maybe summoning a demon is a bad idea. You refund your points.") - if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) - uses += 1 - for(var/datum/spellbook_entry/item/hugbottle/HB in entries) - if(!isnull(HB.limit)) - HB.limit++ - else - uses += 2 - for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) - if(!isnull(BB.limit)) - BB.limit++ - qdel(O) - -/obj/item/spellbook/proc/prepare_spells() - var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - /datum/spellbook_entry/challenge - for(var/type in entry_types) - var/datum/spellbook_entry/possible_entry = new type - if(possible_entry.IsAvailable(src)) - possible_entry.GetInfo() //loads up things for the entry that require checking spell instance. - entries |= possible_entry - else - qdel(possible_entry) - -/obj/item/spellbook/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "Spellbook") - ui.open() - -/obj/item/spellbook/ui_data(mob/user) - var/list/data = list() - data["owner"] = owner - data["points"] = uses - return data - -//This is a MASSIVE amount of data, please be careful if you remove it from static. -/obj/item/spellbook/ui_static_data(mob/user) - var/list/data = list() - var/list/entry_data = list() - for(var/datum/spellbook_entry/entry as anything in entries) - var/list/individual_entry_data = list() - individual_entry_data["name"] = entry.name - individual_entry_data["desc"] = entry.desc - individual_entry_data["ref"] = REF(entry) - individual_entry_data["clothes_req"] = entry.clothes_req - individual_entry_data["cost"] = entry.cost - individual_entry_data["times"] = entry.times - individual_entry_data["cooldown"] = entry.cooldown - individual_entry_data["cat"] = entry.category - individual_entry_data["refundable"] = entry.refundable - individual_entry_data["limit"] = entry.limit - individual_entry_data["buyword"] = entry.buy_word - entry_data += list(individual_entry_data) - data["entries"] = entry_data - return data - -/obj/item/spellbook/ui_act(action, params) - . = ..() - if(.) - return - var/mob/living/carbon/human/wizard = usr - if(!istype(wizard)) - to_chat(wizard, "The book doesn't seem to listen to lower life forms.") - return - - switch(action) - if("purchase") - var/datum/spellbook_entry/entry = locate(params["spellref"]) in entries - if(entry?.CanBuy(wizard,src)) - if(entry.Buy(wizard,src)) - if(entry.limit) - entry.limit-- - uses -= entry.cost - return TRUE - if("refund") - var/datum/spellbook_entry/entry = locate(params["spellref"]) in entries - if(entry?.refundable) - var/result = entry.Refund(wizard,src) - if(result > 0) - if(!isnull(entry.limit)) - entry.limit += result - uses += result - return TRUE - //actions that are only available if you have full spell points - if(uses < initial(uses)) - to_chat(wizard, "You need to have all your spell points to do this!") - return - switch(action) - if("semirandomize") - semirandomize(wizard) - update_static_data(wizard) //update statics! - if("randomize") - randomize(wizard) - update_static_data(wizard) //update statics! - if("purchase_loadout") - wizard_loadout(wizard, params["id"]) - update_static_data(wizard) //update statics! - -/obj/item/spellbook/proc/wizard_loadout(mob/living/carbon/human/wizard, loadout) - var/list/wanted_spell_names - switch(loadout) - if(WIZARD_LOADOUT_CLASSIC) //(Fireball>2, MM>2, Disintegrate>2, Jauntx2>4) = 10 - wanted_spell_names = list("Fireball" = 1, "Magic Missile" = 1, "Disintegrate" = 1, "Ethereal Jaunt" = 2) - if(WIZARD_LOADOUT_MJOLNIR) //(Mjolnir>2, Summon Item>1, Mutate>2, Force Wall>1, Blink>2, Repusle>2) = 10 - wanted_spell_names = list("Mjolnir" = 1, "Summon Item" = 1, "Mutate" = 1, "Force Wall" = 1, "Blink" = 1, "Repulse" = 1) - if(WIZARD_LOADOUT_WIZARMY) //(Soulstones>2, Staff of Change>2, A Necromantic Stone>2, Teleport>2, Ethereal Jaunt>2) = 10 - wanted_spell_names = list("Soulstone Shard Kit" = 1, "Staff of Change" = 1, "A Necromantic Stone" = 1, "Teleport" = 1, "Ethereal Jaunt" = 1) - if(WIZARD_LOADOUT_SOULTAP) //(Soul Tap>1, Disintegrate>2, Flesh to Stone>2, Mindswap>2, Knock>1, Teleport>2) = 10 - wanted_spell_names = list("Soul Tap" = 1, "Disintegrate" = 1, "Flesh to Stone" = 1, "Mindswap" = 1, "Knock" = 1, "Teleport" = 1) - for(var/datum/spellbook_entry/entry as anything in entries) - if(!(entry.name in wanted_spell_names)) - continue - if(entry.CanBuy(wizard,src)) - var/purchase_count = wanted_spell_names[entry.name] - wanted_spell_names -= entry.name - for(var/i in 1 to purchase_count) - entry.Buy(wizard,src) - if(entry.limit) - entry.limit-- - uses -= entry.cost - entry.refundable = FALSE //once you go loading out, you never go back - if(!length(wanted_spell_names)) - break - - if(length(wanted_spell_names)) - stack_trace("Wizard Loadout \"[loadout]\" could not find valid spells to buy in the spellbook. Either you input a name that doesn't exist, or you overspent") - if(uses) - stack_trace("Wizard Loadout \"[loadout]\" does not use 10 wizard spell slots. Stop scamming players out.") - -/obj/item/spellbook/proc/semirandomize(mob/living/carbon/human/wizard) - var/list/needed_cats = list("Offensive", "Mobility") - var/list/shuffled_entries = shuffle(entries) - for(var/i in 1 to 2) - for(var/datum/spellbook_entry/entry as anything in shuffled_entries) - if(!(entry.category in needed_cats)) - continue - if(entry?.CanBuy(wizard,src)) - if(entry.Buy(wizard,src)) - needed_cats -= entry.category //so the next loop doesn't find another offense spell - entry.refundable = FALSE //once you go random, you never go back - if(entry.limit) - entry.limit-- - uses -= entry.cost - break - //we have given two specific category spells to the wizard. the rest are completely random! - randomize(wizard) - -/obj/item/spellbook/proc/randomize(mob/living/carbon/human/wizard) - var/list/entries_copy = entries.Copy() - while(uses > 0) - var/datum/spellbook_entry/entry = pick_n_take(entries_copy) - if(istype(entry, /datum/spellbook_entry/summon/wild_magic)) - continue - if(entry?.CanBuy(wizard,src)) - if(entry.Buy(wizard,src)) - entry.refundable = FALSE //once you go random, you never go back - if(entry.limit) - entry.limit-- - uses -= entry.cost diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm new file mode 100644 index 0000000000000..dde408a6af029 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm @@ -0,0 +1,233 @@ +/** + * ## Spellbook entries + * + * Wizard spellbooks are automatically populated with + * a list of every spellbook entry subtype when they're made. + * + * Wizards can then buy entries from the book to learn magic, + * invoke rituals, or summon items. + */ +/datum/spellbook_entry + /// The name of the entry + var/name + /// The description of the entry + var/desc + /// The type of spell that the entry grants (typepath) + var/datum/action/spell/spell_type + /// What category the entry falls in + var/category + /// How many book charges does the spell take + var/cost = 2 + /// How many times has the spell been purchased. Compared against limit. + var/times = 0 + /// The limit on number of purchases from this entry in a given spellbook. If null, infinite are allowed. + var/limit + /// Is this refundable? + var/refundable = TRUE + /// Flavor. Verb used in saying how the spell is aquired. Ex "[Learn] Fireball" or "[Summon] Ghosts" + var/buy_word = "Learn" + /// The cooldown of the spell + var/cooldown + /// Whether the spell requires wizard garb or not + var/requires_wizard_garb = FALSE + /// Used so you can't have specific spells together + var/list/no_coexistance_typecache + /// Locking the purchase down for whatever reason + var/locked = FALSE +/datum/spellbook_entry/New() + no_coexistance_typecache = typecacheof(no_coexistance_typecache) + + if(ispath(spell_type)) + if(isnull(limit)) + limit = initial(spell_type.spell_max_level) + if(initial(spell_type.spell_requirements) & SPELL_REQUIRES_WIZARD_GARB) + requires_wizard_garb = TRUE + +/** + * Determines if this entry can be purchased from a spellbook + * Used for configs / round related restrictions. + * + * Return FALSE to prevent the entry from being added to wizard spellbooks, TRUE otherwise + */ +/datum/spellbook_entry/proc/can_be_purchased() + if(!name || !desc || !category || locked) // Erroneously set or abstract + return FALSE + return TRUE + +/** + * Checks if the user, with the supplied spellbook, can purchase the given entry. + * + * Arguments + * * user - the mob who's buying the spell + * * book - what book they're buying the spell from + * + * Return TRUE if it can be bought, FALSE otherwise + */ +/datum/spellbook_entry/proc/can_buy(mob/living/carbon/human/user, obj/item/spellbook/book) + if(book.uses < cost) + return FALSE + if(!isnull(limit) && times >= limit) + return FALSE + for(var/spell in user.actions) + if(is_type_in_typecache(spell, no_coexistance_typecache)) + return FALSE + return TRUE + +/** + * Actually buy the entry for the user + * + * Arguments + * * user - the mob who's bought the spell + * * book - what book they've bought the spell from + * + * Return TRUE if the purchase was successful, FALSE otherwise + */ +/datum/spellbook_entry/proc/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/datum/action/spell/existing = locate(spell_type) in user.actions + if(existing) + var/before_name = existing.name + if(!existing.level_spell()) + to_chat(user, ("This spell cannot be improved further!")) + return FALSE + + to_chat(user, ("You have improved [before_name] into [existing.name].")) + name = existing.name + + //we'll need to update the cooldowns for the spellbook + set_spell_info() + log_spellbook("[key_name(user)] improved their knowledge of [initial(existing.name)] to level [existing.spell_level] for [cost] points") + SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[existing.spell_level]")) + log_purchase(user.key) + return TRUE + + //No same spell found - just learn it + var/datum/action/spell/new_spell = new spell_type(user.mind || user) + new_spell.Grant(user) + to_chat(user, ("You have learned [new_spell.name].")) + + log_spellbook("[key_name(user)] learned [new_spell] for [cost] points") + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + log_purchase(user.key) + return TRUE + +/datum/spellbook_entry/proc/log_purchase(key) + if(!islist(GLOB.wizard_spellbook_purchases_by_key[key])) + GLOB.wizard_spellbook_purchases_by_key[key] = list() + + for(var/list/log as anything in GLOB.wizard_spellbook_purchases_by_key[key]) + if(log[LOG_SPELL_TYPE] == type) + log[LOG_SPELL_AMOUNT]++ + return + + var/list/to_log = list( + LOG_SPELL_TYPE = type, + LOG_SPELL_AMOUNT = 1, + ) + GLOB.wizard_spellbook_purchases_by_key[key] += list(to_log) + +/** + * Checks if the user, with the supplied spellbook, can refund the entry + * + * Arguments + * * user - the mob who's refunding the spell + * * book - what book they're refunding the spell from + * + * Return TRUE if it can refunded, FALSE otherwise + */ +/datum/spellbook_entry/proc/can_refund(mob/living/carbon/human/user, obj/item/spellbook/book) + if(!refundable) + return FALSE + if(!book.refunds_allowed) + return FALSE + + for(var/datum/action/spell/other_spell in user.actions) + if(initial(spell_type.name) == initial(other_spell.name)) + return TRUE + + return FALSE + +/** + * Actually refund the entry for the user + * + * Arguments + * * user - the mob who's refunded the spell + * * book - what book they're refunding the spell from + * + * Return -1 on failure, or return the point value of the refund on success + */ +/datum/spellbook_entry/proc/refund_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/area/wizard_station/wizard_home = GLOB.areas_by_type[/area/wizard_station] + if(get_area(user) != wizard_home) + to_chat(user, ("You can only refund spells at the wizard lair!")) + return -1 + + for(var/datum/action/spell/to_refund in user.actions) + if(initial(spell_type.name) != initial(to_refund.name)) + continue + + var/amount_to_refund = to_refund.spell_level * cost + if(amount_to_refund <= 0) + return -1 + + qdel(to_refund) + name = initial(name) + log_spellbook("[key_name(user)] refunded [src] for [amount_to_refund] points") + return amount_to_refund + + return -1 + +/** + * Set any of the spell info saved on our entry + * after something has occured + * + * For example, updating the cooldown after upgrading it + */ +/datum/spellbook_entry/proc/set_spell_info() + if(!spell_type) + return + + cooldown = (initial(spell_type.cooldown_time) / 10) + +/// Item summons, they give you an item. +/datum/spellbook_entry/item + refundable = FALSE + buy_word = "Summon" + /// Typepath of what item we create when purchased + var/obj/item/item_path + +/datum/spellbook_entry/item/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/atom/spawned_path = new item_path(get_turf(user)) + log_spellbook("[key_name(user)] bought [src] for [cost] points") + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + try_equip_item(user, spawned_path) + log_purchase(user.key) + return spawned_path + +/// Attempts to give the item to the buyer on purchase. +/datum/spellbook_entry/item/proc/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) + var/was_put_in_hands = user.put_in_hands(to_equip) + to_chat(user, ("\A [to_equip.name] has been summoned [was_put_in_hands ? "in your hands" : "at your feet"].")) + +/// Ritual, these cause station wide effects and are (pretty much) a blank slate to implement stuff in +/datum/spellbook_entry/summon + category = "Rituals" + limit = 1 + refundable = FALSE + buy_word = "Cast" + +/datum/spellbook_entry/summon/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + log_spellbook("[key_name(user)] cast [src] for [cost] points") + SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) + log_purchase(user.key) + return TRUE + +/// Non-purchasable flavor spells to populate the spell book with, for style. +/datum/spellbook_entry/challenge + name = "Take the Challenge" + category = "Challenges" + refundable = FALSE + buy_word = "Accept" + +// See, non-purchasable. +/datum/spellbook_entry/challenge/can_buy(mob/living/carbon/human/user, obj/item/spellbook/book) + return FALSE diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm new file mode 100644 index 0000000000000..ed9d8c6082b04 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm @@ -0,0 +1,99 @@ +// Wizard spells that assist the caster in some way +/datum/spellbook_entry/summonitem + name = "Summon Item" + desc = "Recalls a previously marked item to your hand from anywhere in the universe." + spell_type = /datum/action/spell/summonitem + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/charge + name = "Charge" + desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user." + spell_type = /datum/action/spell/charge + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/shapeshift + name = "Wild Shapeshift" + desc = "Take on the shape of another for a time to use their natural abilities. Once you've made your choice it cannot be changed." + spell_type = /datum/action/spell/shapeshift/wizard + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/tap + name = "Soul Tap" + desc = "Fuel your spells using your own soul!" + spell_type = /datum/action/spell/tap + category = "Assistance" + cost = 1 + +/datum/spellbook_entry/item/staffanimation + name = "Staff of Animation" + desc = "An arcane staff capable of shooting bolts of eldritch energy which cause inanimate objects to come to life. This magic doesn't affect machines." + item_path = /obj/item/gun/magic/staff/animate + category = "Assistance" + +/datum/spellbook_entry/item/soulstones + name = "Soulstone Shard Kit" + desc = "Soul Stone Shards are ancient tools capable of capturing and harnessing the spirits of the dead and dying. \ + The spell Artificer allows you to create arcane machines for the captured souls to pilot." + item_path = /obj/item/storage/belt/soulstone/full + category = "Assistance" + +/datum/spellbook_entry/item/soulstones/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) + var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE) + to_chat(user, ("\A [to_equip.name] has been summoned [was_equipped ? "on your waist" : "at your feet"].")) + +/datum/spellbook_entry/item/soulstones/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + . =..() + if(!.) + return + + var/datum/action/spell/conjure/construct/bonus_spell = new(user.mind || user) + bonus_spell.Grant(user) + +/datum/spellbook_entry/item/necrostone + name = "A Necromantic Stone" + desc = "A Necromantic stone is able to resurrect three dead individuals as skeletal thralls for you to command." + item_path = /obj/item/necromantic_stone + category = "Assistance" + +/datum/spellbook_entry/item/contract + name = "Contract of Apprenticeship" + desc = "A magical contract binding an apprentice wizard to your service, using it will summon them to your side." + item_path = /obj/item/antag_spawner/contract + category = "Assistance" + refundable = TRUE + +/datum/spellbook_entry/item/guardian + name = "Guardian Deck" + desc = "A deck of guardian tarot cards, capable of binding a personal guardian to your body. There are multiple types of guardian available, but all of them will transfer some amount of damage to you. \ + It would be wise to avoid buying these with anything capable of causing you to swap bodies with others." + item_path = /obj/item/holoparasite_creator/wizard + category = "Assistance" + +/datum/spellbook_entry/item/bloodbottle + name = "Bottle of Blood" + desc = "A bottle of magically infused blood, the smell of which will \ + attract extradimensional beings when broken. Be careful though, \ + the kinds of creatures summoned by blood magic are indiscriminate \ + in their killing, and you yourself may become a victim." + item_path = /obj/item/antag_spawner/slaughter_demon + limit = 3 + category = "Assistance" + refundable = TRUE + +/datum/spellbook_entry/item/hugbottle + name = "Bottle of Tickles" + desc = "A bottle of magically infused fun, the smell of which will \ + attract adorable extradimensional beings when broken. These beings \ + are similar to slaughter demons, but they do not permanently kill \ + their victims, instead putting them in an extradimensional hugspace, \ + to be released on the demon's death. Chaotic, but not ultimately \ + damaging. The crew's reaction to the other hand could be very \ + destructive." + item_path = /obj/item/antag_spawner/slaughter_demon/laughter + cost = 1 //non-destructive; it's just a jape, sibling! + limit = 3 + category = "Assistance" + refundable = TRUE diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/challenges.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/challenges.dm new file mode 100644 index 0000000000000..d200b89758854 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/challenges.dm @@ -0,0 +1,10 @@ +// THESE ARE NOT PURCHASABLE SPELLS! +// They're flavor references to old spells that got removed + +// shit that sounds stupid but fun so we can painfully lock behind a dimmer +/datum/spellbook_entry/challenge/multiverse + name = "Multiverse Sword" + desc = "The Station gets a multiverse sword to stop you. Can you withstand the hordes of multiverse realities?" + +/datum/spellbook_entry/challenge/antiwizard + name = "Friendly Wizard Scum" + desc = "A \"Friendly\" Wizard will protect the station, and try to kill you. They get a spellbook much like you, but will use it for \"GOOD\"." diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm new file mode 100644 index 0000000000000..da1e848a231a9 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm @@ -0,0 +1,143 @@ +// Defensive wizard spells +/datum/spellbook_entry/magicm + name = "Magic Missile" + desc = "Fires several, slow moving, magic projectiles at nearby targets." + spell_type = /datum/action/spell/aoe/magic_missile + category = "Defensive" + +/datum/spellbook_entry/disabletech + name = "Disable Tech" + desc = "Disables all weapons, cameras and most other technology in range." + spell_type = /datum/action/spell/emp/disable_tech + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/repulse + name = "Repulse" + desc = "Throws everything around the user away." + spell_type = /datum/action/spell/aoe/repulse/wizard + category = "Defensive" + +/datum/spellbook_entry/lightning_packet + name = "Thrown Lightning" + desc = "Forged from eldrich energies, a packet of pure power, \ + known as a spell packet will appear in your hand, that when thrown will stun the target." + spell_type = /datum/action/spell/conjure_item/spellpacket + category = "Defensive" + +/datum/spellbook_entry/timestop + name = "Time Stop" + desc = "Stops time for everyone except for you, allowing you to move freely \ + while your enemies and even projectiles are frozen." + spell_type = /datum/action/spell/timestop + category = "Defensive" + +/datum/spellbook_entry/smoke + name = "Smoke" + desc = "Spawns a cloud of choking smoke at your location." + spell_type = /datum/action/spell/smoke + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/forcewall + name = "Force Wall" + desc = "Create a magical barrier that only you can pass through." + spell_type = /datum/action/spell/forcewall + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/lichdom + name = "Bind Soul" + desc = "A dark necromantic pact that can forever bind your soul to an item of your choosing, \ + turning you into an immortal Lich. So long as the item remains intact, you will revive from death, \ + no matter the circumstances. Be wary - with each revival, your body will become weaker, and \ + it will become easier for others to find your item of power." + spell_type = /datum/action/spell/lichdom + category = "Defensive" + +/datum/spellbook_entry/spacetime_dist + name = "Spacetime Distortion" + desc = "Entangle the strings of space-time in an area around you, \ + randomizing the layout and making proper movement impossible. The strings vibrate..." + spell_type = /datum/action/spell/spacetime_dist + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/the_traps + name = "The Traps!" + desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." + spell_type = /datum/action/spell/conjure/the_traps + category = "Defensive" + cost = 1 + +/datum/spellbook_entry/bees + name = "Lesser Summon Bees" + desc = "This spell magically kicks a transdimensional beehive, \ + instantly summoning a swarm of bees to your location. These bees are NOT friendly to anyone." + spell_type = /datum/action/spell/conjure/bee + category = "Defensive" + +//There was supposed to be a cursed duffelbag that eats you but it requires code beyond the scope of this pr + +/datum/spellbook_entry/item/staffhealing + name = "Staff of Healing" + desc = "An altruistic staff that can heal the lame and raise the dead." + item_path = /obj/item/gun/magic/staff/healing + cost = 1 + category = "Defensive" + +/datum/spellbook_entry/item/lockerstaff + name = "Staff of the Locker" + desc = "A staff that shoots lockers. It eats anyone it hits on its way, leaving a welded locker with your victims behind." + item_path = /obj/item/gun/magic/staff/locker + category = "Defensive" + +/datum/spellbook_entry/item/scryingorb + name = "Scrying Orb" + desc = "An incandescent orb of crackling energy. Using it will allow you to release your ghost while alive, allowing you to spy upon the station and talk to the deceased. In addition, buying it will permanently grant you X-ray vision." + item_path = /obj/item/scrying + category = "Defensive" + +/datum/spellbook_entry/item/wands + name = "Wand Assortment" + desc = "A collection of wands that allow for a wide variety of utility. \ + Wands have a limited number of charges, so be conservative with their use. Comes in a handy belt." + item_path = /obj/item/storage/belt/wands/full + category = "Defensive" + +/datum/spellbook_entry/item/wands/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) + var/was_equipped = user.equip_to_slot_if_possible(to_equip, ITEM_SLOT_BELT, disable_warning = TRUE) + to_chat(user, ("\A [to_equip.name] has been summoned [was_equipped ? "on your waist" : "at your feet"].")) + +/datum/spellbook_entry/item/armor + name = "Mastercrafted Armour Set" + desc = "An artefact suit of armour that allows you to cast spells while providing more protection against attacks and the void of space." + item_path = /obj/item/clothing/suit/space/hardsuit/wizard + category = "Defensive" + +/datum/spellbook_entry/item/armor/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. + new /obj/item/clothing/gloves/color/purple(get_turf(user))//To complete the outfit + new /obj/item/clothing/mask/breath(get_turf(user)) // so the air gets to your mouth. Just an average mask. + new /obj/item/tank/internals/emergency_oxygen/magic_oxygen(get_turf(user)) // so you have something to actually breathe. Near infinite. + . = ..() + +/datum/spellbook_entry/item/shielded_armor + name = "Shielded Mastercrafted Armour Set" + desc = "An artefact suit of armour that allows you to cast spells while providing more protection against attacks and the void of space. A shielded variation that requires additional charges to be bought in order to restore it's magical shields" + item_path = /obj/item/clothing/suit/space/hardsuit/shielded/wizard + category = "Defensive" + +/datum/spellbook_entry/item/shielded_armor/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + new /obj/item/clothing/shoes/sandal/magic(get_turf(user)) //In case they've lost them. + new /obj/item/clothing/gloves/color/purple(get_turf(user))//To complete the outfit + new /obj/item/clothing/mask/breath(get_turf(user)) // so the air gets to your mouth. Just an average mask. + new /obj/item/tank/internals/emergency_oxygen/magic_oxygen(get_turf(user)) // so you have something to actually breathe. Near infinite. + . = ..() + +/datum/spellbook_entry/item/battlemage_charge + name = "Battlemage Armour Charges" + desc = "A powerful defensive rune, it will grant eight additional charges to a battlemage shield." + item_path = /obj/item/wizard_armour_charge + category = "Defensive" + cost = 1 diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm new file mode 100644 index 0000000000000..482452e2d8b56 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm @@ -0,0 +1,45 @@ +// Wizard spells that aid mobiilty(or stealth?) +/datum/spellbook_entry/mindswap + name = "Mindswap" + desc = "Allows you to switch bodies with a target next to you. You will both fall asleep when this happens, and it will be quite obvious that you are the target's body if someone watches you do it." + spell_type = /datum/action/spell/pointed/mind_transfer + category = "Mobility" + +/datum/spellbook_entry/knock + name = "Knock" + desc = "Opens nearby doors and closets." + spell_type = /datum/action/spell/aoe/knock + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/blink + name = "Blink" + desc = "Randomly teleports you a short distance." + spell_type = /datum/action/spell/teleport/radius_turf/blink + category = "Mobility" + +/datum/spellbook_entry/teleport + name = "Teleport" + desc = "Teleports you to an area of your selection." + spell_type = /datum/action/spell/teleport/area_teleport/wizard + category = "Mobility" + +/datum/spellbook_entry/jaunt + name = "Ethereal Jaunt" + desc = "Turns your form ethereal, temporarily making you invisible and able to pass through walls." + spell_type = /datum/action/spell/jaunt/ethereal_jaunt + category = "Mobility" + +/datum/spellbook_entry/item/warpwhistle + name = "Warp Whistle" + desc = "A strange whistle that will transport you to a distant safe place on the station. There is a window of vulnerability at the beginning of every use." + item_path = /obj/item/warpwhistle + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/item/staffdoor + name = "Staff of Door Creation" + desc = "A particular staff that can mold solid walls into ornate doors. Useful for getting around in the absence of other transportation. Does not work on glass." + item_path = /obj/item/gun/magic/staff/door + cost = 1 + category = "Mobility" diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm new file mode 100644 index 0000000000000..039082415dd8d --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm @@ -0,0 +1,108 @@ +// Offensive wizard spells +/datum/spellbook_entry/fireball + name = "Fireball" + desc = "Fires an explosive fireball at a target. Considered a classic among all wizards." + spell_type = /datum/action/spell/pointed/projectile/fireball + category = "Offensive" + +/datum/spellbook_entry/spell_cards + name = "Spell Cards" + desc = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" + spell_type = /datum/action/spell/pointed/projectile/spell_cards + category = "Offensive" + +/datum/spellbook_entry/rod_form + name = "Rod Form" + desc = "Take on the form of an immovable rod, destroying all in your path. Purchasing this spell multiple times will also increase the rod's damage and travel range." + spell_type = /datum/action/spell/rod_form + category = "Offensive" + +/datum/spellbook_entry/disintegrate + name = "Smite" + desc = "Charges your hand with an unholy energy that can be used to cause a touched victim to violently explode." + spell_type = /datum/action/spell/touch/smite + category = "Offensive" + +/datum/spellbook_entry/blind + name = "Blind" + desc = "Temporarily blinds a single target." + spell_type = /datum/action/spell/pointed/blind + category = "Offensive" + cost = 1 + +/datum/spellbook_entry/mutate + name = "Mutate" + desc = "Causes you to turn into a hulk and gain laser vision for a short while." + spell_type = /datum/action/spell/apply_mutations/mutate + category = "Offensive" + +/datum/spellbook_entry/fleshtostone + name = "Flesh to Stone" + desc = "Charges your hand with the power to turn victims into inert statues for a long period of time." + spell_type = /datum/action/spell/touch/flesh_to_stone + category = "Offensive" + +/datum/spellbook_entry/teslablast + name = "Tesla Blast" + desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down." + spell_type = /datum/action/spell/tesla + category = "Offensive" + +/datum/spellbook_entry/lightningbolt + name = "Lightning Bolt" + desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down." + spell_type = /datum/action/spell/pointed/projectile/lightningbolt + category = "Offensive" + cost = 1 + +/datum/spellbook_entry/infinite_guns + name = "Lesser Summon Guns" + desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles that deal little damage, but will knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Arcane Barrage." + spell_type = /datum/action/spell/conjure_item/infinite_guns/gun + category = "Offensive" + cost = 3 + no_coexistance_typecache = list(/datum/action/spell/conjure_item/infinite_guns/arcane_barrage) + +/datum/spellbook_entry/arcane_barrage + name = "Arcane Barrage" + desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Lesser Summon Gun." + spell_type = /datum/action/spell/conjure_item/infinite_guns/arcane_barrage + category = "Offensive" + cost = 3 + no_coexistance_typecache = list(/datum/action/spell/conjure_item/infinite_guns/gun) + +/datum/spellbook_entry/barnyard + name = "Barnyard Curse" + desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." + spell_type = /datum/action/spell/pointed/barnyardcurse + category = "Offensive" + +/datum/spellbook_entry/item/staffchaos + name = "Staff of Chaos" + desc = "A caprious tool that can fire all sorts of magic without any rhyme or reason. Using it on people you care about is not recommended." + item_path = /obj/item/gun/magic/staff/chaos + category = "Offensive" + +/datum/spellbook_entry/item/staffchange + name = "Staff of Change" + desc = "An artefact that spits bolts of coruscating energy which cause the target's very form to reshape itself." + item_path = /obj/item/gun/magic/staff/change + category = "Offensive" + +/datum/spellbook_entry/item/mjolnir + name = "Mjolnir" + desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power." + item_path = /obj/item/mjolnir + category = "Offensive" + +/datum/spellbook_entry/item/singularity_hammer + name = "Singularity Hammer" + desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact." + item_path = /obj/item/singularityhammer + category = "Offensive" + +/datum/spellbook_entry/item/spellblade + name = "Spellblade" + desc = "A sword capable of firing blasts of energy which rip targets limb from limb." + item_path = /obj/item/gun/magic/staff/spellblade + category = "Offensive" diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm new file mode 100644 index 0000000000000..dfbf547cd3d0d --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm @@ -0,0 +1,64 @@ +// Ritual spells which affect the station at large +/// How much threat we need to let these rituals happen on dynamic +#define MINIMUM_THREAT_FOR_RITUALS 100 + +/datum/spellbook_entry/summon/guns + name = "Summon Guns" + desc = "Nothing could possibly go wrong with arming a crew of lunatics just itching for an excuse to kill you. \ + There is a good chance that they will shoot each other first." + +/datum/spellbook_entry/summon/guns/can_be_purchased() + // Also must be config enabled + return !CONFIG_GET(flag/no_summon_guns) + +/datum/spellbook_entry/summon/guns/buy_spell(mob/living/carbon/human/user,obj/item/spellbook/book) + give_guns(user) + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + return ..() + +/datum/spellbook_entry/summon/magic + name = "Summon Magic" + desc = "Share the wonders of magic with the crew and show them \ + why they aren't to be trusted with it at the same time." + +/datum/spellbook_entry/summon/magic/can_be_purchased() + // Also must be config enabled + return !CONFIG_GET(flag/no_summon_magic) + +/datum/spellbook_entry/summon/magic/buy_spell(mob/living/carbon/human/user,obj/item/spellbook/book) + give_magic(user, 10) + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + return ..() + +/datum/spellbook_entry/summon/events + name = "Summon Events" + desc = "Give Murphy's law a little push and replace all events with \ + special wizard ones that will confound and confuse everyone. \ + Multiple castings increase the rate of these events." + cost = 2 + limit = 5 // Each purchase can intensify it. + +/datum/spellbook_entry/summon/events/can_be_purchased() + // Also, must be config enabled + return !CONFIG_GET(flag/no_summon_events) + +/datum/spellbook_entry/summon/events/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + summonevents(user) + playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, TRUE) + return ..() + +/datum/spellbook_entry/summon/curse_of_madness + name = "Curse of Madness" + desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance." + cost = 4 + locked = TRUE + +/datum/spellbook_entry/summon/curse_of_madness/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + var/message = tgui_input_text(user, "Whisper a secret truth to drive your victims to madness", "Whispers of Madness") + if(!message) + return FALSE + curse_of_madness(user, message) + playsound(user, 'sound/magic/mandswap.ogg', 50, TRUE) + return ..() + +#undef MINIMUM_THREAT_FOR_RITUALS diff --git a/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm b/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm new file mode 100644 index 0000000000000..59c67c85517bc --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm @@ -0,0 +1,333 @@ +/obj/item/spellbook + name = "spell book" + desc = "An unearthly tome that glows with power." + icon = 'icons/obj/library.dmi' + icon_state ="book" + worn_icon_state = "book" + throw_speed = 2 + throw_range = 5 + w_class = WEIGHT_CLASS_TINY + + /// The number of book charges we have to buy spells + var/uses = 10 + /// The bonus that you get from going semi-random. + var/semi_random_bonus = 2 + /// The bonus that you get from going full random. + var/full_random_bonus = 5 + /// Determines if this spellbook can refund anything. + var/refunds_allowed = TRUE + /// The mind that first used the book. Automatically assigned when a wizard spawns. + var/datum/mind/owner + /// A list to all spellbook entries within + var/list/entries = list() + +/obj/item/spellbook/Initialize(mapload) + . = ..() + prepare_spells() + RegisterSignal(src, COMSIG_ITEM_MAGICALLY_CHARGED, PROC_REF(on_magic_charge)) + +/obj/item/spellbook/Destroy(force) + owner = null + entries.Cut() + return ..() + +/** + * Signal proc for [COMSIG_ITEM_MAGICALLY_CHARGED] + * + * Has no effect on charge, but gives a funny message to people who think they're clever. + */ +/obj/item/spellbook/proc/on_magic_charge(datum/source, datum/action/spell/spell, mob/living/caster) + SIGNAL_HANDLER + + var/static/list/clever_girl = list( + "NICE TRY BUT NO!", + "CLEVER BUT NOT CLEVER ENOUGH!", + "SUCH FLAGRANT CHEESING IS WHY WE ACCEPTED YOUR APPLICATION!", + "CUTE! VERY CUTE!", + "YOU DIDN'T THINK IT'D BE THAT EASY, DID YOU?", + ) + + to_chat(caster, ("Glowing red letters appear on the front cover...")) + to_chat(caster, ("[pick(clever_girl)]")) + + return COMPONENT_ITEM_BURNT_OUT + +/obj/item/spellbook/examine(mob/user) + . = ..() + if(owner) + . += {"There is a small signature on the front cover: "[owner]"."} + else + . += "It appears to have no author." + +/obj/item/spellbook/attack_self(mob/user) + if(!owner) + if(!user.mind) + return + to_chat(user, ("You bind [src] to yourself.")) + owner = user.mind + return + + if(user.mind != owner) + if(user.mind?.special_role == ROLE_WIZARD_APPRENTICE) + to_chat(user, ("If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not.")) + else + to_chat(user, ("[src] does not recognize you as its owner and refuses to open!")) + return + + return ..() + +/obj/item/spellbook/attackby(obj/item/O, mob/user, params) + // This can be generalized in the future, but for now it stays + if(istype(O, /obj/item/antag_spawner/contract)) + var/datum/spellbook_entry/item/contract/contract_entry = locate() in entries + if(!istype(contract_entry)) + to_chat(user, ("[src] doesn't seem to want to refund [O].")) + return + if(!contract_entry.can_refund(user, src)) + to_chat(user, ("You can't refund [src].")) + return + var/obj/item/antag_spawner/contract/contract = O + if(contract.used) + to_chat(user, ("The contract has been used, you can't get your points back now!")) + return + + to_chat(user, ("You feed the contract back into the spellbook, refunding your points.")) + uses += contract_entry.cost + contract_entry.times-- + qdel(O) + + else if(istype(O, /obj/item/antag_spawner/slaughter_demon/laughter)) + var/datum/spellbook_entry/item/hugbottle/demon_entry = locate() in entries + if(!istype(demon_entry)) + to_chat(user, ("[src] doesn't seem to want to refund [O].")) + return + if(!demon_entry.can_refund(user, src)) + to_chat(user, ("You can't refund [O].")) + return + + to_chat(user, ("On second thought, maybe summoning a demon isn't a funny idea. You refund your points.")) + uses += demon_entry.cost + demon_entry.times-- + qdel(O) + + else if(istype(O, /obj/item/antag_spawner/slaughter_demon)) + var/datum/spellbook_entry/item/bloodbottle/demon_entry = locate() in entries + if(!istype(demon_entry)) + to_chat(user, ("[src] doesn't seem to want to refund [O].")) + return + if(!demon_entry.can_refund(user, src)) + to_chat(user, ("You can't refund [O].")) + return + + to_chat(user, ("On second thought, maybe summoning a demon is a bad idea. You refund your points.")) + uses += demon_entry.cost + demon_entry.times-- + qdel(O) + + return ..() + +/// Instantiates our list of spellbook entries. +/obj/item/spellbook/proc/prepare_spells() + var/entry_types = subtypesof(/datum/spellbook_entry) + for(var/type in entry_types) + var/datum/spellbook_entry/possible_entry = new type() + if(!possible_entry.can_be_purchased()) + qdel(possible_entry) + continue + + possible_entry.set_spell_info() //loads up things for the entry that require checking spell instance. + entries |= possible_entry + +/obj/item/spellbook/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Spellbook") + ui.open() + +/obj/item/spellbook/ui_data(mob/user) + var/list/data = list() + data["owner"] = owner + data["points"] = uses + data["semi_random_bonus"] = initial(uses) + semi_random_bonus + data["full_random_bonus"] = initial(uses) + full_random_bonus + return data + +//This is a MASSIVE amount of data, please be careful if you remove it from static. +/obj/item/spellbook/ui_static_data(mob/user) + var/list/data = list() + // Collect all info from each intry. + var/list/entry_data = list() + for(var/datum/spellbook_entry/entry as anything in entries) + var/list/individual_entry_data = list() + individual_entry_data["name"] = entry.name + individual_entry_data["desc"] = entry.desc + individual_entry_data["ref"] = REF(entry) + individual_entry_data["requires_wizard_garb"] = entry.requires_wizard_garb + individual_entry_data["cost"] = entry.cost + individual_entry_data["times"] = entry.times + individual_entry_data["cooldown"] = entry.cooldown + individual_entry_data["cat"] = entry.category + individual_entry_data["refundable"] = entry.refundable + individual_entry_data["limit"] = entry.limit + individual_entry_data["buyword"] = entry.buy_word + entry_data += list(individual_entry_data) + + data["entries"] = entry_data + return data + +/obj/item/spellbook/ui_act(action, params) + . = ..() + if(.) + return + var/mob/living/carbon/human/wizard = usr + if(!istype(wizard)) + to_chat(wizard, ("The book doesn't seem to listen to lower life forms.")) + return FALSE + + // Actions that are always available + switch(action) + if("purchase") + var/datum/spellbook_entry/entry = locate(params["spellref"]) in entries + return purchase_entry(entry, wizard) + + if("refund") + var/datum/spellbook_entry/entry = locate(params["spellref"]) in entries + if(!istype(entry)) + CRASH("[type] had an invalid ref to a spell passed in refund.") + if(!entry.can_refund(wizard, src)) + return FALSE + var/result = entry.refund_spell(wizard, src) + if(result <= 0) + return FALSE + + entry.times = 0 + uses += result + return TRUE + + if(uses < initial(uses)) + to_chat(wizard, ("You need to have all your spell points to do this!")) + return FALSE + + // Actions that are only available if you have full spell points + switch(action) + if("semirandomize") + semirandomize(wizard, semi_random_bonus) + return TRUE + + if("randomize") + randomize(wizard, full_random_bonus) + return TRUE + + if("purchase_loadout") + wizard_loadout(wizard, params["id"]) + return TRUE + +/// Attempts to purchased the passed entry [to_buy] for [user]. +/obj/item/spellbook/proc/purchase_entry(datum/spellbook_entry/to_buy, mob/living/carbon/human/user) + if(!istype(to_buy)) + CRASH("Spellbook attempted to buy an invalid entry. Got: [to_buy ? "[to_buy] ([to_buy.type])" : "null"]") + if(!to_buy.can_buy(user, src)) + return FALSE + if(!to_buy.buy_spell(user, src)) + return FALSE + + to_buy.times++ + uses -= to_buy.cost + return TRUE + +/// Purchases a wizard loadout [loadout] for [wizard]. +/obj/item/spellbook/proc/wizard_loadout(mob/living/carbon/human/wizard, loadout) + var/list/wanted_spells + switch(loadout) + if(WIZARD_LOADOUT_CLASSIC) //(Fireball>2, MM>2, Smite>2, Jauntx2>4) = 10 + wanted_spells = list( + /datum/spellbook_entry/fireball = 1, + /datum/spellbook_entry/magicm = 1, + /datum/spellbook_entry/disintegrate = 1, + /datum/spellbook_entry/jaunt = 2, + ) + if(WIZARD_LOADOUT_MJOLNIR) //(Mjolnir>2, Summon Itemx1>1, Mutate>2, Force Wall>1, Blink>2, tesla>2) = 10 + wanted_spells = list( + /datum/spellbook_entry/item/mjolnir = 1, + /datum/spellbook_entry/summonitem = 1, + /datum/spellbook_entry/mutate = 1, + /datum/spellbook_entry/forcewall = 1, + /datum/spellbook_entry/blink = 1, + /datum/spellbook_entry/teslablast = 1, + ) + if(WIZARD_LOADOUT_WIZARMY) //(Soulstones>2, Staff of Change>2, A Necromantic Stone>2, Teleport>2, Ethereal Jaunt>2) = 10 + wanted_spells = list( + /datum/spellbook_entry/item/soulstones = 1, + /datum/spellbook_entry/item/staffchange = 1, + /datum/spellbook_entry/item/necrostone = 1, + /datum/spellbook_entry/teleport = 1, + /datum/spellbook_entry/jaunt = 1, + ) + if(WIZARD_LOADOUT_SOULTAP) //(Soul Tap>1, Smite>2, Flesh to Stone>2, Mindswap>2, Knock>1, Teleport>2) = 10 + wanted_spells = list( + /datum/spellbook_entry/tap = 1, + /datum/spellbook_entry/disintegrate = 1, + /datum/spellbook_entry/fleshtostone = 1, + /datum/spellbook_entry/mindswap = 1, + /datum/spellbook_entry/knock = 1, + /datum/spellbook_entry/teleport = 1, + ) + + if(!length(wanted_spells)) + stack_trace("Wizard Loadout \"[loadout]\" did not find a loadout that existed.") + return + + for(var/entry in wanted_spells) + if(!ispath(entry, /datum/spellbook_entry)) + stack_trace("Wizard Loadout \"[loadout]\" had an non-spellbook_entry type in its wanted spells list. ([entry])") + continue + + var/datum/spellbook_entry/to_buy = locate(entry) in entries + if(!istype(to_buy)) + stack_trace("Wizard Loadout \"[loadout]\" had an invalid entry in its wanted spells list. ([entry])") + continue + + for(var/i in 1 to wanted_spells[entry]) + if(!purchase_entry(to_buy, wizard)) + stack_trace("Wizard Loadout \"[loadout]\" was unable to buy a spell for [wizard]. ([entry])") + message_admins("Wizard [wizard] purchased Loadout \"[loadout]\" but was unable to purchase one of the entries ([to_buy]) for some reason.") + break + + refunds_allowed = FALSE + + if(uses > 0) + stack_trace("Wizard Loadout \"[loadout]\" does not use 10 wizard spell slots (used: [initial(uses) - uses]). Stop scamming players out.") + +/// Purchases a semi-random wizard loadout for [wizard] +/// If passed a number [bonus_to_give], the wizard is given additional uses on their spellbook, used in randomization. +/obj/item/spellbook/proc/semirandomize(mob/living/carbon/human/wizard, bonus_to_give = 0) + var/list/needed_cats = list("Offensive", "Mobility") + var/list/shuffled_entries = shuffle(entries) + for(var/i in 1 to 2) + for(var/datum/spellbook_entry/entry as anything in shuffled_entries) + if(!(entry.category in needed_cats)) + continue + if(!purchase_entry(entry, wizard)) + continue + needed_cats -= entry.category //so the next loop doesn't find another offense spell + break + + refunds_allowed = FALSE + //we have given two specific category spells to the wizard. the rest are completely random! + randomize(wizard, bonus_to_give = bonus_to_give) + +/// Purchases a fully random wizard loadout for [wizard], with a point bonus [bonus_to_give]. +/// If passed a number [bonus_to_give], the wizard is given additional uses on their spellbook, used in randomization. +/obj/item/spellbook/proc/randomize(mob/living/carbon/human/wizard, bonus_to_give = 0) + var/list/entries_copy = entries.Copy() + uses += bonus_to_give + while(uses > 0 && length(entries_copy)) + var/datum/spellbook_entry/entry = pick(entries_copy) + if(!purchase_entry(entry, wizard)) + continue + entries_copy -= entry + refunds_allowed = FALSE + +/obj/item/spellbook/debug + name = "Debug Spellbook" + uses = 4096 //enough for everything you may need diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm index 94e9f8e46d02a..847edc5b6a9ec 100644 --- a/code/modules/antagonists/wizard/wizard.dm +++ b/code/modules/antagonists/wizard/wizard.dm @@ -1,3 +1,6 @@ +/// Global assoc list. [ckey] = [spellbook entry type] +GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key) + /datum/antagonist/wizard name = "Space Wizard" roundend_category = "wizards/witches" @@ -130,8 +133,11 @@ log_objective(owner, hijack_objective.explanation_text) /datum/antagonist/wizard/on_removal() - unregister() - owner.RemoveAllSpells() // TODO keep track which spells are wizard spells which innate stuff + // Currently removes all spells regardless of innate or not. Could be improved. + for(var/datum/action/spell/spell in owner.current.actions) + if(spell.owner == owner) + qdel(spell) + owner.current.actions -= spell return ..() /datum/antagonist/wizard/proc/equip_wizard() @@ -216,53 +222,56 @@ . = ..() if(!owner) return - var/mob/living/carbon/human/H = owner.current - if(!istype(H)) + if(!ishuman(owner.current)) return + var/list/spells_to_grant = list() + var/list/items_to_grant = list() switch(school) if(APPRENTICE_DESTRUCTION) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/aimed/fireball(null)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned powerful, destructive spells. You are able to cast magic missile and fireball.") + spells_to_grant = list( + /datum/action/spell/aoe/magic_missile, + /datum/action/spell/pointed/projectile/fireball, + ) + to_chat(owner, ("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned powerful, \ + destructive spells. You are able to cast magic missile and fireball.")) + if(APPRENTICE_BLUESPACE) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(null)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned reality bending mobility spells. You are able to cast teleport and ethereal jaunt.") + spells_to_grant = list( + /datum/action/spell/teleport/area_teleport/wizard, + /datum/action/spell/jaunt/ethereal_jaunt, + ) + to_chat(owner, ("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned reality-bending \ + mobility spells. You are able to cast teleport and ethereal jaunt.")) + if(APPRENTICE_HEALING) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/charge(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/forcewall(null)) - H.put_in_hands(new /obj/item/gun/magic/staff/healing(H)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned livesaving survival spells. You are able to cast charge and forcewall.") + spells_to_grant = list( + /datum/action/spell/charge, + /datum/action/spell/forcewall, + ) + items_to_grant = list( + /obj/item/gun/magic/staff/healing, + ) + to_chat(owner, ("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned life-saving \ + survival spells. You are able to cast charge and forcewall, and have a staff of healing.")) if(APPRENTICE_ROBELESS) - owner.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/mind_transfer(null)) - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned stealthy, robeless spells. You are able to cast knock and mindswap.") - if(APPRENTICE_WILDMAGIC) - var/static/list/spell_entry - if(!spell_entry) - spell_entry = list() - for(var/datum/spellbook_entry/each_entry as() in subtypesof(/datum/spellbook_entry)-typesof(/datum/spellbook_entry/item)-typesof(/datum/spellbook_entry/summon)) - spell_entry += new each_entry - - var/spells_left = 2 - while(spells_left) - var/failsafe = FALSE - var/datum/spellbook_entry/chosen_spell = pick(spell_entry) - if(chosen_spell.no_random) - continue - for(var/obj/effect/proc_holder/spell/my_spell in owner.spell_list) - if(chosen_spell.spell_type == my_spell.type) // You don't learn the same spell - failsafe = TRUE - break - if(is_type_in_typecache(my_spell.type, chosen_spell.no_coexistence_typecache)) // You don't learn a spell that isn't compatible with another - failsafe = TRUE - break - if(failsafe) - continue - var/obj/effect/proc_holder/spell/new_spell = chosen_spell.spell_type - owner.AddSpell(new new_spell(null)) - spells_left-- - to_chat(owner, "Your service has not gone unrewarded, however. Studying under [master.current.real_name], you have learned special spells that aren't available to standard apprentices.") + spells_to_grant = list( + /datum/action/spell/aoe/knock, + /datum/action/spell/pointed/mind_transfer, + ) + to_chat(owner, ("Your service has not gone unrewarded, however. \ + Studying under [master.current.real_name], you have learned stealthy, \ + robeless spells. You are able to cast knock and mindswap.")) + + for(var/spell_type in spells_to_grant) + var/datum/action/spell/new_spell = new spell_type(owner) + new_spell.Grant(owner.current) + + for(var/item_type in items_to_grant) + var/obj/item/new_item = new item_type(owner.current) + owner.current.put_in_hands(new_item) /datum/antagonist/wizard/apprentice/create_objectives() var/datum/objective/protect/new_objective = new /datum/objective/protect @@ -301,9 +310,12 @@ H.equip_to_slot_or_del(new master_mob.back.type, ITEM_SLOT_BACK) //Operation: Fuck off and scare people - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/area_teleport/teleport(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink(null)) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt(null)) + var/datum/action/spell/jaunt/ethereal_jaunt/jaunt = new(owner) + jaunt.Grant(H) + var/datum/action/spell/teleport/area_teleport/wizard/teleport = new(owner) + teleport.Grant(H) + var/datum/action/spell/teleport/radius_turf/blink/blink = new(owner) + blink.Grant(H) /datum/antagonist/wizard/proc/update_wiz_icons_added(mob/living/wiz,join = TRUE) var/datum/atom_hud/antag/wizhud = GLOB.huds[ANTAG_HUD_WIZ] @@ -315,6 +327,34 @@ wizhud.leave_hud(wiz) set_antag_hud(wiz, null) + +/datum/antagonist/wizard/academy + name = "Academy Teacher" + outfit_type = /datum/outfit/wizard + move_to_lair = FALSE + +/datum/antagonist/wizard/academy/equip_wizard() + . = ..() + if(!isliving(owner.current)) + return + var/mob/living/living_current = owner.current + + var/datum/action/spell/jaunt/ethereal_jaunt/jaunt = new(owner) + jaunt.Grant(living_current) + var/datum/action/spell/aoe/magic_missile/missile = new(owner) + missile.Grant(living_current) + var/datum/action/spell/pointed/projectile/fireball/fireball = new(owner) + fireball.Grant(living_current) + + var/obj/item/implant/exile/exiled = new /obj/item/implant/exile(living_current) + exiled.implant(living_current) + +/datum/antagonist/wizard/academy/create_objectives() + var/datum/objective/new_objective = new("Protect Wizard Academy from the intruders") + new_objective.owner = owner + objectives += new_objective + log_objective(owner, new_objective.explanation_text) + //Solo wizard report /datum/antagonist/wizard/roundend_report() var/list/parts = list() @@ -336,12 +376,18 @@ else parts += "The wizard has failed!" - if(owner.spell_list.len>0) - parts += "[owner.name] used the following spells: " - var/list/spell_names = list() - for(var/obj/effect/proc_holder/spell/S in owner.spell_list) - spell_names += S.name - parts += spell_names.Join(", ") + var/list/purchases = list() + for(var/list/log as anything in GLOB.wizard_spellbook_purchases_by_key[owner.key]) + var/datum/spellbook_entry/bought = log[LOG_SPELL_TYPE] + var/amount = log[LOG_SPELL_AMOUNT] + + purchases += "[amount > 1 ? "[amount]x ":""][initial(bought.name)]" + + if(length(purchases)) + parts += ("[owner.name] used the following spells:") + parts += purchases.Join(", ") + else + parts += ("[owner.name] didn't buy any spells!") return parts.Join("
") diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 20dfbd188c3ea..338019285d08f 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -79,12 +79,6 @@ var/atom/movable/screen/click_catcher/void - //These two vars are used to make a special mouse cursor, with a unique icon for clicking - /// Mouse icon while not clicking - var/mouse_up_icon = null - /// Mouse icon while clicking - var/mouse_down_icon = null - var/ip_intel = "Disabled" /// Datum that controls the displaying and hiding of tooltips @@ -135,3 +129,21 @@ /// client/eye is immediately changed, and it makes a lot of errors to track eye change var/datum/weakref/eye_weakref + +///Autoclick list of two elements, first being the clicked thing, second being the parameters. + var/list/atom/selected_target[2] + ///Used in MouseDrag to preserve the original mouse click parameters + var/mouseParams = "" + ///Used in MouseDrag to preserve the last mouse-entered location. Weakref + var/datum/weakref/mouse_location_ref = null + ///Used in MouseDrag to preserve the last mouse-entered object. Weakref + var/datum/weakref/mouse_object_ref + //Middle-mouse-button clicked object control for aimbot exploit detection. Weakref + var/datum/weakref/middle_drag_atom_ref + + ///used to make a special mouse cursor, this one for mouse up icon + var/mouse_up_icon = null + ///used to make a special mouse cursor, this one for mouse up icon + var/mouse_down_icon = null + ///used to override the mouse cursor so it doesnt get reset + var/mouse_override_icon = null diff --git a/code/modules/client/preferences/README.md b/code/modules/client/preferences/README.md index 9c40e105abd35..3d47011e2fdc0 100644 --- a/code/modules/client/preferences/README.md +++ b/code/modules/client/preferences/README.md @@ -497,7 +497,7 @@ Middleware can hijack actions by specifying `action_delegations`: ) /datum/preference_middleware/congratulations/proc/congratulate_me(list/params, mob/user) - to_chat(user, span_notice("Wow, you did a great job learning about middleware!")) + to_chat(user, ("Wow, you did a great job learning about middleware!")) return TRUE ``` diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index d5a06748e8673..25779fe69d360 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -5,10 +5,7 @@ icon_icon = 'icons/hud/actions/actions_items.dmi' button_icon_state = "random" -/datum/action/item_action/chameleon/drone/randomise/Trigger() - if(!IsAvailable()) - return - +/datum/action/item_action/chameleon/drone/randomise/on_activate(mob/user, atom/target) // Damn our lack of abstract interfeces if (istype(target, /obj/item/clothing/head/chameleon/drone)) var/obj/item/clothing/head/chameleon/drone/X = target @@ -16,26 +13,21 @@ if (istype(target, /obj/item/clothing/mask/chameleon/drone)) var/obj/item/clothing/mask/chameleon/drone/Z = target Z.chameleon_action.random_look(owner) - - return 1 - + return TRUE /datum/action/item_action/chameleon/drone/togglehatmask name = "Toggle Headgear Mode" icon_icon = 'icons/hud/actions/actions_silicon.dmi' -/datum/action/item_action/chameleon/drone/togglehatmask/New() +/datum/action/item_action/chameleon/drone/togglehatmask/New(master) ..() - if (istype(target, /obj/item/clothing/head/chameleon/drone)) + if (istype(master, /obj/item/clothing/head/chameleon/drone)) button_icon_state = "drone_camogear_helm" - if (istype(target, /obj/item/clothing/mask/chameleon/drone)) + if (istype(master, /obj/item/clothing/mask/chameleon/drone)) button_icon_state = "drone_camogear_mask" -/datum/action/item_action/chameleon/drone/togglehatmask/Trigger() - if(!IsAvailable()) - return - +/datum/action/item_action/chameleon/drone/togglehatmask/on_activate(mob/user, atom/target) // No point making the code more complicated if no non-drone // is ever going to use one of these @@ -65,12 +57,12 @@ qdel(old_headgear) // where is `ITEM_SLOT_HEAD` defined? WHO KNOWS D.equip_to_slot(new_headgear, ITEM_SLOT_HEAD) - return 1 - + return TRUE /datum/action/chameleon_outfit name = "Select Chameleon Outfit" button_icon_state = "chameleon_outfit" + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_HANDS_BLOCKED var/list/outfit_options //By default, this list is shared between all instances. It is not static because if it were, subtypes would not be able to have their own. If you ever want to edit it, copy it first. /datum/action/chameleon_outfit/New() @@ -88,14 +80,14 @@ sortTim(standard_outfit_options, GLOBAL_PROC_REF(cmp_text_asc)) outfit_options = standard_outfit_options -/datum/action/chameleon_outfit/Trigger() - return select_outfit(owner) +/datum/action/chameleon_outfit/on_activate(mob/user, atom/target) + return select_outfit(user) /datum/action/chameleon_outfit/proc/select_outfit(mob/user) - if(!user || !IsAvailable()) + if(!user || !is_available()) return FALSE var/selected = input("Select outfit to change into", "Chameleon Outfit") as null|anything in outfit_options - if(!IsAvailable() || QDELETED(src) || QDELETED(user)) + if(!is_available() || QDELETED(src) || QDELETED(user)) return FALSE var/outfit_type = outfit_options[selected] if(!outfit_type) @@ -105,8 +97,8 @@ var/obj/item/card/id/syndicate/chamel_card // this is awful but this hardcoding is better than adding `obj/proc/get_chameleon_variable()` for every chalemon item for(var/datum/action/item_action/chameleon/change/A in user.chameleon_item_actions) - if(istype(A.target, /obj/item/modular_computer)) - var/obj/item/modular_computer/comp = A.target + if(istype(UNLINT(A.master), /obj/item/modular_computer)) + var/obj/item/modular_computer/comp = UNLINT(A.master) if(istype(comp.GetID(), /obj/item/card/id/syndicate)) chamel_card = comp.GetID() @@ -187,10 +179,8 @@ ..() /datum/action/item_action/chameleon/change/proc/initialize_disguises() - if(button) - button.name = "Change [chameleon_name] Appearance" - - chameleon_blacklist |= typecacheof(target.type) + name = "Change [chameleon_name] Appearance" + chameleon_blacklist |= typecacheof(master.type) for(var/V in typesof(chameleon_type)) if(ispath(V) && ispath(V, /obj/item)) var/obj/item/I = V @@ -233,14 +223,14 @@ return update_item(picked_item, emp=emp, item_holder=user) - var/obj/item/thing = target + var/obj/item/thing = master thing.update_slot_icon() - UpdateButtonIcon() + update_buttons() /datum/action/item_action/chameleon/change/proc/update_item(obj/item/picked_item, emp=FALSE, mob/item_holder=null) var/keepname = FALSE - if(isitem(target)) - var/obj/item/clothing/I = target + if(isitem(master)) + var/obj/item/clothing/I = master I.worn_icon = initial(picked_item.worn_icon) I.lefthand_file = initial(picked_item.lefthand_file) I.righthand_file = initial(picked_item.righthand_file) @@ -259,11 +249,11 @@ var/obj/item/clothing/PCL = picked_item CL.flags_cover = initial(PCL.flags_cover) if(initial(picked_item.greyscale_config) && initial(picked_item.greyscale_colors)) - target.icon = SSgreyscale.GetColoredIconByType(initial(picked_item.greyscale_config), initial(picked_item.greyscale_colors)) + I.icon = SSgreyscale.GetColoredIconByType(initial(picked_item.greyscale_config), initial(picked_item.greyscale_colors)) else - target.icon = initial(picked_item.icon) + I.icon = initial(picked_item.icon) if(isidcard(I) && ispath(picked_item, /obj/item/card/id)) - var/obj/item/card/id/ID = target + var/obj/item/card/id/ID = master var/obj/item/card/id/ID_from = picked_item ID.hud_state = initial(ID_from.hud_state) if(!emp) @@ -281,7 +271,7 @@ ID.update_label() // we're going to find a PDA that this ID card is inserted into, then force-update PDA - var/atom/current_holder = target.loc + var/atom/current_holder = master if(istype(current_holder, /obj/item/computer_hardware/card_slot)) current_holder = current_holder.loc if(istype(current_holder, /obj/item/modular_computer)) @@ -292,8 +282,8 @@ comp.update_id_display() update_mob_hud(item_holder) - if(istype(target, /obj/item/modular_computer)) - var/obj/item/modular_computer/comp = target + if(istype(master, /obj/item/modular_computer)) + var/obj/item/modular_computer/comp = master var/obj/item/card/id/id = comp.GetID() if(id) comp.saved_identification = id.registered_name @@ -302,15 +292,15 @@ keepname = TRUE // do not change PDA name unnecesarily update_mob_hud(item_holder) if(!keepname) - target.name = initial(picked_item.name) - target.desc = initial(picked_item.desc) - target.icon_state = initial(picked_item.icon_state) + name = initial(picked_item.name) + //desc = initial(picked_item.desc) + //icon_state = initial(picked_item.icon_state) /datum/action/item_action/chameleon/change/proc/update_mob_hud(atom/card_holder) // we're going to find a human, and store human ref to 'card_holder' by checking loc multiple time. if(!ishuman(card_holder)) if(!card_holder) - card_holder = target.loc + card_holder = master if(istype(card_holder, /obj/item/storage/wallet)) card_holder = card_holder.loc // this should be human if(istype(card_holder, /obj/item/computer_hardware/card_slot)) @@ -322,10 +312,7 @@ var/mob/living/carbon/human/card_holding_human = card_holder card_holding_human.sec_hud_set_ID() -/datum/action/item_action/chameleon/change/Trigger() - if(!IsAvailable()) - return - +/datum/action/item_action/chameleon/change/on_activate(mob/user, atom/target) select_look(owner) return 1 @@ -408,6 +395,7 @@ chameleon_action.chameleon_name = "Jumpsuit" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/under, /obj/item/clothing/under/color, /obj/item/clothing/under/rank, /obj/item/clothing/under/changeling), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/under/chameleon/emp_act(severity) . = ..() @@ -465,6 +453,7 @@ chameleon_action.chameleon_name = "Suit" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/suit/armor/abductor, /obj/item/clothing/suit/changeling), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/suit/chameleon/emp_act(severity) . = ..() @@ -519,6 +508,7 @@ chameleon_action.chameleon_name = "Glasses" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/glasses/chameleon/emp_act(severity) . = ..() @@ -582,6 +572,7 @@ chameleon_action.chameleon_name = "Gloves" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/clothing/gloves, /obj/item/clothing/gloves/color, /obj/item/clothing/gloves/changeling), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/gloves/chameleon/emp_act(severity) . = ..() @@ -663,6 +654,7 @@ chameleon_action.chameleon_name = "Hat" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/head/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/head/chameleon/emp_act(severity) . = ..() @@ -724,9 +716,9 @@ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) chameleon_action.random_look() var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src) - togglehatmask_action.UpdateButtonIcon() + togglehatmask_action.update_buttons() var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.UpdateButtonIcon() + randomise_action.update_buttons() /datum/action/item_action/chameleon/tongue_change name = "Tongue Change" @@ -748,8 +740,8 @@ temporary_list[tongue_name] = found_item tongue_list = sort_list(temporary_list) -/datum/action/item_action/chameleon/tongue_change/Trigger() - if(!IsAvailable() || !isitem(target)) +/datum/action/item_action/chameleon/tongue_change/on_activate(mob/user, atom/target) + if(!isitem(target)) return FALSE var/obj/item/clothing/mask/target_mask = target var/obj/item/organ/tongue/picked_tongue @@ -799,6 +791,7 @@ chameleon_action.chameleon_name = "Mask" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/mask/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) tongue_action = new(src) if(!tongue_action.tongue_list) tongue_action.generate_tongue_list() @@ -862,9 +855,9 @@ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) chameleon_action.random_look() var/datum/action/item_action/chameleon/drone/togglehatmask/togglehatmask_action = new(src) - togglehatmask_action.UpdateButtonIcon() + togglehatmask_action.update_buttons() var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.UpdateButtonIcon() + randomise_action.update_buttons() /obj/item/clothing/mask/chameleon/drone/attack_self(mob/user) to_chat(user, "[src] does not have a voice changer.") @@ -900,6 +893,7 @@ chameleon_action.chameleon_name = "Shoes" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/shoes/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/shoes/chameleon/emp_act(severity) . = ..() @@ -941,6 +935,7 @@ chameleon_action.chameleon_type = /obj/item/storage/backpack chameleon_action.chameleon_name = "Backpack" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/storage/backpack/chameleon/emp_act(severity) . = ..() @@ -980,6 +975,7 @@ chameleon_action.chameleon_type = /obj/item/storage/belt chameleon_action.chameleon_name = "Belt" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/storage/belt/chameleon/ComponentInitialize() . = ..() @@ -1022,6 +1018,7 @@ chameleon_action.chameleon_type = /obj/item/radio/headset chameleon_action.chameleon_name = "Headset" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/radio/headset/chameleon/emp_act(severity) . = ..() @@ -1066,6 +1063,7 @@ chameleon_action.chameleon_name = "tablet" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/modular_computer/tablet/pda/heads), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/modular_computer/tablet/pda/chameleon/emp_act(severity) . = ..() @@ -1102,6 +1100,7 @@ chameleon_action.chameleon_type = /obj/item/stamp chameleon_action.chameleon_name = "Stamp" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/stamp/chameleon/broken/Initialize(mapload) . = ..() @@ -1144,6 +1143,7 @@ chameleon_action.chameleon_type = /obj/item/clothing/neck chameleon_action.chameleon_name = "Neck Accessory" chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/neck/chameleon/emp_act(severity) . = ..() diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index 7f999cb1c9f09..4784d1b6106e9 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -65,7 +65,7 @@ var/obj/item/food/clothing/moth_snack /obj/item/clothing/Initialize(mapload) - if(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE)) + if(clothing_flags & VOICEBOX_TOGGLABLE) actions_types += /datum/action/item_action/toggle_voice_box . = ..() if(ispath(pocket_storage_component_path)) diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index a73b48b71a3e2..6814b8993d112 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -498,6 +498,7 @@ chameleon_action.chameleon_name = "Glasses" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/glasses/thermal/syndi/emp_act(severity) . = ..() @@ -562,10 +563,35 @@ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE resistance_flags = LAVA_PROOF | FIRE_PROOF vision_correction = 1 // why should the eye of a god have bad vision? + //var/datum/action/scan/scan_ability /obj/item/clothing/glasses/godeye/Initialize(mapload) . = ..() - ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) + //scan_ability = new(src) + +/obj/item/clothing/glasses/godeye/Destroy() + //QDEL_NULL(scan_ability) + return ..() + + +/obj/item/clothing/glasses/godeye/equipped(mob/living/user, slot) + . = ..() + if(ishuman(user) && slot == ITEM_SLOT_EYES) + ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) + pain(user) + //scan_ability.Grant(user) + +/obj/item/clothing/glasses/godeye/dropped(mob/living/user) + . = ..() + // Behead someone, their "glasses" drop on the floor + // and thus, the god eye should no longer be sticky + REMOVE_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) + //scan_ability.Remove(user) + +/obj/item/clothing/glasses/godeye/proc/pain(mob/living/victim) + to_chat(victim, ("You experience blinding pain, as [src] burrows into your skull.")) + victim.emote("scream") + victim.flash_act() /obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) if(istype(W, src) && W != src && W.loc == user) @@ -580,7 +606,46 @@ to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") qdel(src) ..() - +/* +/datum/action/scan Given that the eye did nuffin previously I am leaving this bit of code in in case someone wants to change that + name = "Scan" + desc = "Scan an enemy, to get their location and stagger them, increasing their time between attacks." + background_icon_state = "bg_clock" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "scan" + + requires_target = TRUE + cooldown_time = 45 SECONDS + ranged_mousepointer = 'icons/effects/mouse_pointers/scan_target.dmi' + +/datum/action/scan/is_available() + return ..() && isliving(owner) + +/datum/action/scan/on_activate(atom/scanned) + start_cooldown(15 SECONDS) + + if(owner.stat != CONSCIOUS) + return FALSE + if(!isliving(scanned) || scanned == owner) + owner.balloon_alert(owner, "invalid scanned!") + return FALSE + + var/mob/living/living_owner = owner + var/mob/living/living_scanned = scanned + living_scanned.apply_status_effect(/datum/status_effect/stagger) + var/datum/status_effect/agent_pinpointer/scan_pinpointer = living_owner.apply_status_effect(/datum/status_effect/agent_pinpointer/scan) + living_scanned.Jitter(100 SECONDS) + to_chat(living_scanned, span_warning("You've been staggered!")) + living_scanned.add_filter("scan", 2, list("type" = "outline", "color" = COLOR_YELLOW, "size" = 1)) + addtimer(CALLBACK(living_scanned, TYPE_PROC_REF(/atom, remove_filter), "scan"), 30 SECONDS) + + owner.playsound_local(get_turf(owner), 'sound/magic/smoke.ogg', 50, TRUE) + owner.balloon_alert(owner, "[living_scanned] scanned") + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, balloon_alert), owner, "scan recharged"), cooldown_time) + + start_cooldown() + return TRUE +*/ /obj/item/clothing/glasses/AltClick(mob/user) if(!user.canUseTopic(src, BE_CLOSE)) return diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 743fe43f953c1..6fc1a06994fb8 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -237,6 +237,7 @@ chameleon_action.chameleon_name = "Glasses" chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/clothing/glasses/hud/security/chameleon/emp_act(severity) . = ..() @@ -340,6 +341,9 @@ var/datum/atom_hud/H = GLOB.huds[hud_type] H.add_hud_to(user) +/datum/action/item_action/switch_hud + name = "Switch HUD" + /obj/item/clothing/glasses/hud/toggle/thermal name = "thermal HUD scanner" desc = "Thermal imaging HUD in the shape of glasses." diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm index 8b4cb3ca851ec..a93e7118f5c12 100644 --- a/code/modules/clothing/gloves/miscellaneous.dm +++ b/code/modules/clothing/gloves/miscellaneous.dm @@ -163,8 +163,8 @@ /datum/action/item_action/artifact_pincher_mode name = "Toggle Safety" -/datum/action/item_action/artifact_pincher_mode/Trigger() +/datum/action/item_action/artifact_pincher_mode/on_activate(mob/user, atom/target) var/obj/item/clothing/gloves/artifact_pinchers/pinchy = target if(istype(pinchy)) pinchy.safety = !pinchy.safety - button.icon_state = (pinchy.safety ? "template_active" : "template") + button_icon_state = (pinchy.safety ? "template_active" : "template") diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 8376cba81b934..48705343643d6 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -142,6 +142,13 @@ if(user.canUseTopic(src, BE_CLOSE)) toggle_welding_screen(user) +/obj/item/clothing/head/utility/hardhat/welding/ui_action_click(mob/user, actiontype) + if(istype(actiontype, /datum/action/item_action/toggle_welding_screen)) + toggle_welding_screen(user) + return + + return ..() + /obj/item/clothing/head/utility/hardhat/welding/proc/toggle_welding_screen(mob/living/user) if(weldingvisortoggle(user)) playsound(src, 'sound/mecha/mechmove03.ogg', 50, TRUE) //Visors don't just come from nothing diff --git a/code/modules/clothing/head/tinfoilhat.dm b/code/modules/clothing/head/tinfoilhat.dm index c38d794b93217..ebc2f2a825d56 100644 --- a/code/modules/clothing/head/tinfoilhat.dm +++ b/code/modules/clothing/head/tinfoilhat.dm @@ -104,4 +104,4 @@ for(var/X in actions) var/datum/action/A=X - A.UpdateButtonIcon() + A.update_buttons() diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 242f21c780d8f..f8f9143b2a73a 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -116,7 +116,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.update_buttons() to_chat(user, "Your Clown Mask has now morphed into [choice], all praise the Honkmother!") return 1 @@ -167,7 +167,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.update_buttons() to_chat(user, "Your Mime Mask has now morphed into [choice]!") return 1 @@ -249,7 +249,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.update_buttons() to_chat(M, "The Tiki Mask has now changed into the [choice] Mask!") return 1 diff --git a/code/modules/clothing/masks/hailer.dm b/code/modules/clothing/masks/hailer.dm index bd1317c8d3d74..13b97980d1e76 100644 --- a/code/modules/clothing/masks/hailer.dm +++ b/code/modules/clothing/masks/hailer.dm @@ -214,3 +214,7 @@ playsound(src.loc, "sound/voice/complionator/[phrase_sound].ogg", 100, 0, 4) cooldown = world.time cooldown_special = world.time + +/datum/action/item_action/halt + name = "HALT!" + diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index d472999cf14df..4a0f3c000bb5e 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -95,7 +95,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.update_buttons() to_chat(user, "Your emotion mask has now morphed into [choice]!") return 1 @@ -291,3 +291,10 @@ message = replacetextEx(message,regex(capitalize(key),"g"), "[capitalize(value)]") message = replacetextEx(message,regex(key,"g"), "[value]") speech_args[SPEECH_MESSAGE] = trim(message) + +GLOBAL_LIST_INIT(cursed_animal_masks, list( + /obj/item/clothing/mask/pig/cursed, + /obj/item/clothing/mask/frog/cursed, + /obj/item/clothing/mask/cowmask/cursed, + /obj/item/clothing/mask/horsehead/cursed, + )) diff --git a/code/modules/clothing/outfits/standard.dm b/code/modules/clothing/outfits/standard.dm index c215d0e44b4a2..b7114da5aeb98 100644 --- a/code/modules/clothing/outfits/standard.dm +++ b/code/modules/clothing/outfits/standard.dm @@ -328,14 +328,6 @@ back = /obj/item/storage/backpack backpack_contents = list(/obj/item/storage/box=1) -/datum/outfit/wizard/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE) - if(visualsOnly) - return - - var/obj/item/spellbook/S = locate() in H.held_items - if(S) - S.owner = H.mind - /datum/outfit/wizard/apprentice name = "Wizard Apprentice" r_hand = null diff --git a/code/modules/clothing/shoes/taeclowndo.dm b/code/modules/clothing/shoes/taeclowndo.dm index 2938cc9a9ddc7..a2d363f6b402a 100644 --- a/code/modules/clothing/shoes/taeclowndo.dm +++ b/code/modules/clothing/shoes/taeclowndo.dm @@ -1,9 +1,9 @@ /obj/item/clothing/shoes/clown_shoes/taeclowndo var/list/spelltypes = list ( - /obj/effect/proc_holder/spell/targeted/conjure_item/summon_pie, - /obj/effect/proc_holder/spell/aimed/banana_peel, - /obj/effect/proc_holder/spell/targeted/touch/megahonk, - /obj/effect/proc_holder/spell/targeted/touch/bspie, + /datum/action/spell/conjure_item/summon_pie, + /datum/action/spell/pointed/banana_peel, + /datum/action/spell/touch/megahonk, + /datum/action/spell/touch/bspie, ) var/list/spells = list() @@ -16,11 +16,8 @@ if(slot == ITEM_SLOT_FEET) spells = new for(var/spell in spelltypes) - var/obj/effect/proc_holder/spell/S = new spell - spells += S - S.charge_counter = 0 - S.start_recharge() - H.mind?.AddSpell(S) + var/datum/action/spell/S = new spell + S.Grant(H) /obj/item/clothing/shoes/clown_shoes/taeclowndo/dropped(mob/user) ..() @@ -29,6 +26,6 @@ var/mob/living/carbon/human/H = user if(H.get_item_by_slot(ITEM_SLOT_FEET) == src) for(var/spell in spells) - var/obj/effect/proc_holder/spell/S = spell - H.mind?.spell_list.Remove(S) + var/datum/action/spell/S = spell + S.Remove(H) qdel(S) diff --git a/code/modules/clothing/spacesuits/_spacesuits.dm b/code/modules/clothing/spacesuits/_spacesuits.dm index 82a0bb5ecd852..5bc6dfae93d48 100644 --- a/code/modules/clothing/spacesuits/_spacesuits.dm +++ b/code/modules/clothing/spacesuits/_spacesuits.dm @@ -97,26 +97,25 @@ // Space Suit temperature regulation and power usage /obj/item/clothing/suit/space/process() - var/mob/living/carbon/human/user = src.loc - if(!user || !ishuman(user) || !(user.wear_suit == src)) + var/mob/living/carbon/human/user = loc + if(!user || !ishuman(user) || user.wear_suit != src) return // Do nothing if thermal regulators are off if(!thermal_on) return - // If we got here, thermal regulators are on. If there's no cell, turn them - // off + // If we got here, thermal regulators are on. If there's no cell, turn them off if(!cell) - toggle_spacesuit() + toggle_spacesuit(user) update_hud_icon(user) return // cell.use will return FALSE if charge is lower than THERMAL_REGULATOR_COST if(!cell.use(THERMAL_REGULATOR_COST)) - toggle_spacesuit() + toggle_spacesuit(user) update_hud_icon(user) - to_chat(user, "The thermal regulator cuts off as [cell] runs out of charge.") + to_chat(user, ("The thermal regulator cuts off as [cell] runs out of charge.")) return // If we got here, it means thermals are on, the cell is in and the cell has @@ -220,20 +219,24 @@ to_chat(user, "You [cell_cover_open ? "open" : "close"] the cell cover on \the [src].") /// Toggle the space suit's thermal regulator status -/obj/item/clothing/suit/space/proc/toggle_spacesuit() +/obj/item/clothing/suit/space/proc/toggle_spacesuit(mob/toggler) // If we're turning thermal protection on, check for valid cell and for enough // charge that cell. If it's too low, we shouldn't bother with setting the // thermal protection value and should just return out early. - var/mob/living/carbon/human/user = src.loc - if(!thermal_on && !(cell && cell.charge >= THERMAL_REGULATOR_COST)) - to_chat(user, "The thermal regulator on \the [src] has no charge.") + if(!thermal_on && (!cell || cell.charge < THERMAL_REGULATOR_COST)) + if(toggler) + to_chat(toggler, ("The thermal regulator on [src] has no charge.")) return thermal_on = !thermal_on min_cold_protection_temperature = thermal_on ? SPACE_SUIT_MIN_TEMP_PROTECT : SPACE_SUIT_MIN_TEMP_PROTECT_OFF - if(user) - to_chat(user, "You turn [thermal_on ? "on" : "off"] \the [src]'s thermal regulator.") - SEND_SIGNAL(src, COMSIG_SUIT_SPACE_TOGGLE) + if(toggler) + to_chat(toggler, ("You turn [thermal_on ? "on" : "off"] [src]'s thermal regulator.")) + + update_action_buttons() + +/obj/item/clothing/suit/space/ui_action_click(mob/user, actiontype) + toggle_spacesuit(user) // let emags override the temperature settings /obj/item/clothing/suit/space/on_emag(mob/user) diff --git a/code/modules/clothing/spacesuits/chronosuit.dm b/code/modules/clothing/spacesuits/chronosuit.dm index baa5c3651f7b7..0ca60ceef520a 100644 --- a/code/modules/clothing/spacesuits/chronosuit.dm +++ b/code/modules/clothing/spacesuits/chronosuit.dm @@ -48,7 +48,6 @@ /obj/item/clothing/suit/space/chronos/Initialize(mapload) teleport_now.chronosuit = src - teleport_now.target = src return ..() /obj/item/clothing/suit/space/chronos/proc/new_camera(mob/user) @@ -107,7 +106,7 @@ camera.remove_target_ui() camera.forceMove(user) user.reset_perspective(camera) - teleport_now.UpdateButtonIcon() + teleport_now.update_buttons() /obj/item/clothing/suit/space/chronos/proc/chronowalk(atom/location) var/mob/living/carbon/human/user = src.loc @@ -121,7 +120,7 @@ if(camera) camera.remove_target_ui() - teleport_now.UpdateButtonIcon() + teleport_now.update_buttons() var/list/nonsafe_slots = list(ITEM_SLOT_BELT, ITEM_SLOT_BACK) var/list/exposed = list() @@ -340,10 +339,10 @@ CREATION_TEST_IGNORE_SUBTYPES(/atom/movable/screen/chronos_target) chronosuit = null return ..() -/datum/action/innate/chrono_teleport/IsAvailable() +/datum/action/innate/chrono_teleport/is_available() return (chronosuit && chronosuit.activated && chronosuit.camera && !chronosuit.teleporting) -/datum/action/innate/chrono_teleport/Activate() - if(IsAvailable()) +/datum/action/innate/chrono_teleport/on_activate() + if(is_available()) if(chronosuit.camera) chronosuit.chronowalk(chronosuit.camera) diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index 9f7d6ac1b56fa..b74f0a1a653d8 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -775,7 +775,7 @@ syndieHelmet.update_icon() for(var/X in syndieHelmet.actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.update_buttons() //Update the icon_state first icon_state = "hardsuit[syndieHelmet.on]-[syndieHelmet.hardsuit_type]" update_icon() @@ -900,6 +900,7 @@ resistance_flags = FIRE_PROOF | ACID_PROOF //No longer shall our kind be foiled by lone chemists with spray bottles! armor_type = /datum/armor/hardsuit_wizard heat_protection = HEAD //Uncomment to enable firesuit protection + clothing_flags = CASTING_CLOTHES max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT @@ -923,6 +924,7 @@ item_state = "wiz_hardsuit" w_class = WEIGHT_CLASS_NORMAL resistance_flags = FIRE_PROOF | ACID_PROOF + clothing_flags = CASTING_CLOTHES armor_type = /datum/armor/hardsuit_wizard allowed = list(/obj/item/teleportation_scroll, /obj/item/tank/internals) heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS //Uncomment to enable firesuit protection @@ -949,8 +951,15 @@ /obj/item/clothing/suit/space/hardsuit/wizard/ComponentInitialize() . = ..() AddComponent(/datum/component/anti_artifact, INFINITY, FALSE, 100) - AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, FALSE, INFINITY, FALSE) + AddComponent(/datum/component/anti_magic, INNATE_TRAIT, MAGIC_RESISTANCE) +/obj/item/clothing/suit/space/hardsuit/wizard/equipped(mob/user, slot) + ADD_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, TRAIT_ANTIMAGIC_NO_SELFBLOCK) + . = ..() + +/obj/item/clothing/suit/space/hardsuit/wizard/dropped(mob/user, slot) + REMOVE_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, TRAIT_ANTIMAGIC_NO_SELFBLOCK) + . = ..() //Medical hardsuit /obj/item/clothing/head/helmet/space/hardsuit/medical @@ -1386,7 +1395,6 @@ /// The icon for the shield var/shield_icon = "shield-old" - /datum/armor/hardsuit_shielded melee = 30 bullet = 15 @@ -1405,9 +1413,6 @@ if(!allowed) allowed = GLOB.advanced_hardsuit_allowed -/obj/item/clothing/suit/space/hardsuit/shielded/setup_shielding() - AddComponent(/datum/component/shielded, max_integrity = shield_integrity, recharge_start_delay = recharge_delay, charge_increment_delay = recharge_rate, shield_icon = shield_icon) - /obj/item/clothing/head/helmet/space/hardsuit/shielded resistance_flags = FIRE_PROOF | ACID_PROOF diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index fa735a1f228ce..f5f0818a2effd 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -487,7 +487,7 @@ Contains: /obj/item/clothing/suit/space/hardsuit/ert/paranormal/Initialize(mapload) . = ..() - AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, TRUE) + AddComponent(/datum/component/anti_magic, INNATE_TRAIT, (MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) //Lavaland suits diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index 519b0d0d1339b..b473517fd72c8 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -70,7 +70,7 @@ var/visor_state = "enviro_visor" var/lamp_functional = TRUE var/obj/item/clothing/head/attached_hat - actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen/plasmaman) + actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT flags_cover = HEADCOVERSMOUTH|HEADCOVERSEYES @@ -107,6 +107,13 @@ else . += "A hat can be placed on the helmet." +/obj/item/clothing/head/helmet/space/plasmaman/ui_action_click(mob/user, action) + if(istype(action, /datum/action/item_action/toggle_welding_screen)) + toggle_welding_screen(user) + return + + return ..() + /obj/item/clothing/head/helmet/space/plasmaman/proc/toggle_welding_screen(mob/living/user) if(!weldingvisortoggle(user)) return @@ -174,7 +181,7 @@ //The icon's may look differently due to overlays being applied asynchronously for(var/X in actions) var/datum/action/A=X - A.UpdateButtonIcon() + A.update_buttons() /obj/item/clothing/head/helmet/space/plasmaman/worn_overlays(mutable_appearance/standing, isinhands = FALSE, icon_file, item_layer, atom/origin) . = ..() diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index 1d59a7a2484c5..96a2392f32374 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -9,7 +9,7 @@ armor_type = /datum/armor/head_wizard strip_delay = 50 equip_delay_other = 50 - clothing_flags = SNUG_FIT | THICKMATERIAL + clothing_flags = SNUG_FIT | THICKMATERIAL | CASTING_CLOTHES resistance_flags = FIRE_PROOF | ACID_PROOF dog_fashion = /datum/dog_fashion/head/blue_wizard @@ -91,7 +91,7 @@ strip_delay = 50 equip_delay_other = 50 resistance_flags = FIRE_PROOF | ACID_PROOF - clothing_flags = THICKMATERIAL + clothing_flags = THICKMATERIAL | CASTING_CLOTHES /datum/armor/suit_wizrobe @@ -240,8 +240,24 @@ helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard armor_type = /datum/armor/shielded_wizard slowdown = 0 + clothing_flags = CASTING_CLOTHES resistance_flags = FIRE_PROOF | ACID_PROOF +/obj/item/clothing/suit/space/hardsuit/shielded/wizard/ComponentInitialize() + . = ..() + AddComponent(/datum/component/anti_artifact, INFINITY, FALSE, 100) + AddComponent(/datum/component/anti_magic, INNATE_TRAIT, MAGIC_RESISTANCE) + +/obj/item/clothing/suit/space/hardsuit/shielded/wizard/equipped(mob/user, slot) + ADD_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, TRAIT_ANTIMAGIC_NO_SELFBLOCK) + . = ..() + +/obj/item/clothing/suit/space/hardsuit/shielded/wizard/dropped(mob/user, slot) + REMOVE_TRAIT(user, TRAIT_ANTIMAGIC_NO_SELFBLOCK, TRAIT_ANTIMAGIC_NO_SELFBLOCK) + . = ..() + +/obj/item/clothing/suit/space/hardsuit/shielded/wizard/setup_shielding() + AddComponent(/datum/component/shielded, max_integrity = 600, charge_recovery = 0 SECONDS, charge_increment_delay = 1 SECONDS, shield_icon = "shield-red") /datum/armor/shielded_wizard melee = 30 @@ -256,9 +272,6 @@ stamina = 70 bleed = 70 -/obj/item/clothing/suit/space/hardsuit/shielded/wizard/setup_shielding() - AddComponent(/datum/component/shielded, max_integrity = 600, charge_recovery = 0 SECONDS, charge_increment_delay = 1 SECONDS, shield_icon = "shield-red") - /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard name = "battlemage helmet" desc = "A suitably impressive helmet.." @@ -266,6 +279,7 @@ item_state = "battlemage" min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT + clothing_flags = CASTING_CLOTHES armor_type = /datum/armor/shielded_wizard actions_types = null //No inbuilt light resistance_flags = FIRE_PROOF | ACID_PROOF diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index f3724968c8c13..5de883149249e 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -24,7 +24,7 @@ /datum/action/item_action/displayDetectiveScanResults name = "Display Forensic Scanner Results" -/datum/action/item_action/displayDetectiveScanResults/Trigger() +/datum/action/item_action/displayDetectiveScanResults/on_activate(mob/user, atom/target) var/obj/item/detective_scanner/scanner = target if(istype(scanner)) scanner.displayDetectiveScanResults(usr) diff --git a/code/modules/events/spider_infestation.dm b/code/modules/events/spider_infestation.dm index a2715faac6365..f1d002a37c535 100644 --- a/code/modules/events/spider_infestation.dm +++ b/code/modules/events/spider_infestation.dm @@ -54,7 +54,7 @@ spider_antag.set_spider_team(spider_team) if(fed) spooder.fed += 3 // Give our spiders some friends to help them get started - spooder.lay_eggs.UpdateButtonIcon() + spooder.lay_eggs.update_buttons() fed-- spawncount-- message_admins("[ADMIN_LOOKUPFLW(spooder)] has been made into a spider by an event.") diff --git a/code/modules/events/wizard/aid.dm b/code/modules/events/wizard/aid.dm index 8525674e07a69..fd881d703295c 100644 --- a/code/modules/events/wizard/aid.dm +++ b/code/modules/events/wizard/aid.dm @@ -1,5 +1,5 @@ -//in this file: Various events that directly aid the wizard. This is the "lets entice the wizard to use summon events!" file. - +// Various events that directly aid the wizard. +// This is the "lets entice the wizard to use summon events!" file. /datum/round_event_control/wizard/robelesscasting //EI NUDTH! name = "Robeless Casting" weight = 2 @@ -9,16 +9,19 @@ /datum/round_event/wizard/robelesscasting/start() - for(var/i in GLOB.mob_living_list) //Hey if a corgi has magic missle he should get the same benifit as anyone - var/mob/living/L = i - if(L.mind && L.mind.spell_list.len != 0) - var/spell_improved = FALSE - for(var/obj/effect/proc_holder/spell/S in L.mind.spell_list) - if(S.clothes_req) - S.clothes_req = 0 - spell_improved = TRUE - if(spell_improved) - to_chat(L, "You suddenly feel like you never needed those garish robes in the first place...") + // Hey, if a corgi has magic missle, he should get the same benefit as anyone + for(var/mob/living/caster as anything in GLOB.mob_living_list) + if(!length(caster.actions)) + continue + + var/spell_improved = FALSE + for(var/datum/action/spell/spell in caster.actions) + if(spell.spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + spell.spell_requirements &= ~SPELL_REQUIRES_WIZARD_GARB + spell_improved = TRUE + + if(spell_improved) + to_chat(caster, ("You suddenly feel like you never needed those garish robes in the first place...")) //--// @@ -30,29 +33,16 @@ earliest_start = 0 MINUTES /datum/round_event/wizard/improvedcasting/start() - for(var/i in GLOB.mob_living_list) - var/mob/living/L = i - if(L.mind && L.mind.spell_list.len != 0) - for(var/obj/effect/proc_holder/spell/S in L.mind.spell_list) - S.name = initial(S.name) - S.spell_level++ - if(S.spell_level >= 6 || S.charge_max <= 0) //Badmin checks, these should never be a problem in normal play - continue - if(S.level_max <= 0) - continue - S.charge_max = round(initial(S.charge_max) - S.spell_level * (initial(S.charge_max) - S.cooldown_min) / S.level_max) - if(S.charge_max < S.charge_counter) - S.charge_counter = S.charge_max - switch(S.spell_level) - if(1) - S.name = "Efficient [S.name]" - if(2) - S.name = "Quickened [S.name]" - if(3) - S.name = "Free [S.name]" - if(4) - S.name = "Instant [S.name]" - if(5) - S.name = "Ludicrous [S.name]" - - to_chat(L, "You suddenly feel more competent with your casting!") + for(var/mob/living/caster as anything in GLOB.mob_living_list) + if(!length(caster.actions)) + continue + + var/upgraded_a_spell = FALSE + for(var/datum/action/spell/spell in caster.actions) + // If improved casting has already boosted this spell further beyond, go no further + if(spell.spell_level >= spell.spell_max_level + 1) + continue + upgraded_a_spell = spell.level_spell(TRUE) + + if(upgraded_a_spell) + to_chat(caster, ("You suddenly feel more competent with your casting!")) diff --git a/code/modules/events/wizard/shuffle.dm b/code/modules/events/wizard/shuffle.dm index c1185144a7e8c..71ad94582ea10 100644 --- a/code/modules/events/wizard/shuffle.dm +++ b/code/modules/events/wizard/shuffle.dm @@ -79,28 +79,29 @@ earliest_start = 0 MINUTES /datum/round_event/wizard/shuffleminds/start() - var/list/mobs = list() + var/list/mobs_to_swap = list() - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(H.stat || !H.mind || iswizard(H)) + for(var/mob/living/carbon/human/alive_human in GLOB.alive_mob_list) + if(alive_human.stat != CONSCIOUS || !alive_human.mind || IS_WIZARD(alive_human)) continue //the wizard(s) are spared on this one - if(istype(H.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat) || H.anti_magic_check()) - continue - mobs += H + mobs_to_swap += alive_human - if(!mobs) + if(!length(mobs_to_swap)) return - shuffle_inplace(mobs) + mobs_to_swap = shuffle(mobs_to_swap) - var/obj/effect/proc_holder/spell/targeted/mind_transfer/swapper = new /obj/effect/proc_holder/spell/targeted/mind_transfer - while(mobs.len > 1) - var/mob/living/carbon/human/H = pick(mobs) - mobs -= H - swapper.cast(list(H), mobs[mobs.len], 1) - mobs -= mobs[mobs.len] + var/datum/action/spell/pointed/mind_transfer/swapper = new() - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(0, H.loc) + while(mobs_to_swap.len > 1) + var/mob/living/swap_to = pick_n_take(mobs_to_swap) + var/mob/living/swap_from = pick_n_take(mobs_to_swap) + + swapper.swap_minds(swap_to, swap_from) + + qdel(swapper) + + for(var/mob/living/carbon/human/alive_human in GLOB.alive_mob_list) + var/datum/effect_system/smoke_spread/smoke = new() + smoke.set_up(0, alive_human.loc) smoke.start() diff --git a/code/modules/hydroponics/grown/melon.dm b/code/modules/hydroponics/grown/melon.dm index 24652aed6380a..d51c0488f3aec 100644 --- a/code/modules/hydroponics/grown/melon.dm +++ b/code/modules/hydroponics/grown/melon.dm @@ -97,7 +97,12 @@ var/uses = 1 if(seed) uses = round(seed.potency / 20) - AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, TRUE, uses, TRUE, CALLBACK(src, PROC_REF(block_magic)), CALLBACK(src, PROC_REF(expire))) //deliver us from evil o melon god + AddComponent(/datum/component/anti_magic, \ + _source = src, \ + antimagic_flags = (MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY),\ + charges = uses, \ + drain_antimagic = CALLBACK(src, PROC_REF(block_magic)),\ + expiration = CALLBACK(src, PROC_REF(expire))) //deliver us from evil o melon god /obj/item/food/grown/holymelon/proc/block_magic(mob/user, major) if(major) diff --git a/code/modules/instruments/items.dm b/code/modules/instruments/items.dm index abd24a3418d21..c45fe6aecf662 100644 --- a/code/modules/instruments/items.dm +++ b/code/modules/instruments/items.dm @@ -222,6 +222,17 @@ ..() UnregisterSignal(M, COMSIG_MOB_SAY) +/datum/action/item_action/instrument + name = "Use Instrument" + desc = "Use the instrument specified" + +/datum/action/item_action/instrument/on_activate(mob/user, atom/target) + if(istype(target, /obj/item/instrument)) + var/obj/item/instrument/I = target + I.interact(usr) + return + return ..() + /obj/item/instrument/bikehorn name = "gilded bike horn" desc = "An exquisitely decorated bike horn, capable of honking in a variety of notes." diff --git a/code/modules/jobs/job_mail.dm b/code/modules/jobs/job_mail.dm index 9a5d816947ad4..2c9eac2f8d7e1 100644 --- a/code/modules/jobs/job_mail.dm +++ b/code/modules/jobs/job_mail.dm @@ -320,7 +320,7 @@ /obj/item/reagent_containers/cup/glass/bottle/bottleofnothing = 10, /obj/item/book/mimery = 2, //when you thought it could get worse... - /obj/item/book/granter/spell/mimery_blockade = 1, + /obj/item/book/granter/action/spell/mime ) //PSYCHOLOGIST / PSYCHIATRIST GIMMICK diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm index 4f9a5c62911a7..05426324e0861 100644 --- a/code/modules/jobs/job_types/mime.dm +++ b/code/modules/jobs/job_types/mime.dm @@ -64,8 +64,10 @@ if(visualsOnly) return + // Start our mime out with a vow of silence and the ability to break (or make) it if(H.mind) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak(null)) + var/datum/action/spell/vow_of_silence/vow = new(H.mind) + vow.Grant(H) H.mind.miming = 1 /obj/item/book/mimery @@ -73,31 +75,56 @@ desc = "A primer on basic pantomime." icon_state ="bookmime" -/obj/item/book/mimery/attack_self(mob/user,) - user.set_machine(src) - var/dat = "Guide to Dank Mimery
" - dat += "Teaches one of three classic pantomime routines, allowing a practiced mime to conjure invisible objects into corporeal existence.
" - dat += "Once you have mastered your routine, this book will have no more to say to you.
" - dat += "
" - dat += "Invisible Wall
" - dat += "Invisible Chair
" - dat += "Invisible Box
" - user << browse(dat, "window=book") - -/obj/item/book/mimery/Topic(href, href_list) - ..() - if (usr.stat != CONSCIOUS || HAS_TRAIT(usr, TRAIT_HANDS_BLOCKED) || src.loc != usr) - return - if (!ishuman(usr)) +/obj/item/book/mimery/attack_self(mob/user) + . = ..() + if(.) return - var/mob/living/carbon/human/H = usr - if(H.is_holding(src) && H.mind) - H.set_machine(src) - if (href_list["invisible_wall"]) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall(null)) - if (href_list["invisible_chair"]) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair(null)) - if (href_list["invisible_box"]) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box(null)) - to_chat(usr, "The book disappears into thin air.") - qdel(src) + + var/list/spell_icons = list( + "Invisible Wall" = image(icon = 'icons/hud/actions/actions_mime.dmi', icon_state = "invisible_wall"), + "Invisible Chair" = image(icon = 'icons/hud/actions/actions_mime.dmi', icon_state = "invisible_chair"), + "Invisible Box" = image(icon = 'icons/hud/actions/actions_mime.dmi', icon_state = "invisible_box") + ) + var/picked_spell = show_radial_menu(user, src, spell_icons, custom_check = CALLBACK(src, PROC_REF(check_menu), user), radius = 36, require_near = TRUE) + var/datum/action/spell/picked_spell_type + switch(picked_spell) + if("Invisible Wall") + picked_spell_type = /datum/action/spell/conjure/invisible_wall + + if("Invisible Chair") + picked_spell_type = /datum/action/spell/conjure/invisible_chair + + if("Invisible Box") + picked_spell_type = /datum/action/spell/conjure_item/invisible_box + + if(ispath(picked_spell_type)) + // Gives the user a vow ability too, if they don't already have one + var/datum/action/spell/vow_of_silence/vow = locate() in user.actions + if(!vow && user.mind) + vow = new(user.mind) + vow.Grant(user) + + picked_spell_type = new picked_spell_type(user.mind || user) + picked_spell_type.Grant(user) + + to_chat(user, ("The book disappears into thin air.")) + qdel(src) + + return TRUE + +/** + * Checks if we are allowed to interact with a radial menu + * + * Arguments: + * * user The human mob interacting with the menu + */ +/obj/item/book/mimery/proc/check_menu(mob/living/carbon/human/user) + if(!istype(user)) + return FALSE + if(!user.is_holding(src)) + return FALSE + if(user.incapacitated()) + return FALSE + if(!user.mind) + return FALSE + return TRUE diff --git a/code/modules/mining/aux_base_camera.dm b/code/modules/mining/aux_base_camera.dm index 61bab3f29a8c5..7ebf5aedb91de 100644 --- a/code/modules/mining/aux_base_camera.dm +++ b/code/modules/mining/aux_base_camera.dm @@ -35,13 +35,13 @@ circuit = /obj/item/circuitboard/computer/base_construction off_action = new/datum/action/innate/camera_off/base_construction jump_action = null - var/datum/action/innate/aux_base/switch_mode/switch_mode_action = new //Action for switching the RCD's build modes - var/datum/action/innate/aux_base/build/build_action = new //Action for using the RCD - var/datum/action/innate/aux_base/airlock_type/airlock_mode_action = new //Action for setting the airlock type - var/datum/action/innate/aux_base/window_type/window_action = new //Action for setting the window type - var/datum/action/innate/aux_base/place_fan/fan_action = new //Action for spawning fans + var/datum/action/innate/aux_base/switch_mode/switch_mode_action //Action for switching the RCD's build modes + var/datum/action/innate/aux_base/build/build_action //Action for using the RCD + var/datum/action/innate/aux_base/airlock_type/airlock_mode_action //Action for setting the airlock type + var/datum/action/innate/aux_base/window_type/window_action //Action for setting the window type + var/datum/action/innate/aux_base/place_fan/fan_action //Action for spawning fans var/fans_remaining = 0 //Number of fans in stock. - var/datum/action/innate/aux_base/install_turret/turret_action = new //Action for spawning turrets + var/datum/action/innate/aux_base/install_turret/turret_action //Action for spawning turrets var/turret_stock = 0 //Turrets in stock var/obj/machinery/computer/auxillary_base/found_aux_console //Tracker for the Aux base console, so the eye can always find it. @@ -53,6 +53,12 @@ /obj/machinery/computer/camera_advanced/base_construction/Initialize(mapload) . = ..() RCD = new(src) + switch_mode_action = new(src) + build_action = new(src) + airlock_mode_action = new(src) + window_action = new(src) + fan_action = new(src) + turret_action = new(src) /obj/machinery/computer/camera_advanced/base_construction/Initialize(mapload) . = ..() @@ -93,32 +99,26 @@ ..() if(switch_mode_action) - switch_mode_action.target = src switch_mode_action.Grant(user) actions += switch_mode_action if(build_action) - build_action.target = src build_action.Grant(user) actions += build_action if(airlock_mode_action) - airlock_mode_action.target = src airlock_mode_action.Grant(user) actions += airlock_mode_action if(window_action) - window_action.target = src window_action.Grant(user) actions += window_action if(fan_action) - fan_action.target = src fan_action.Grant(user) actions += fan_action if(turret_action) - turret_action.target = src turret_action.Grant(user) actions += turret_action @@ -129,17 +129,18 @@ eyeobj.invisibility = INVISIBILITY_MAXIMUM //Hide the eye when not in use. /datum/action/innate/aux_base //Parent aux base action + button_icon_state = null icon_icon = 'icons/hud/actions/actions_construction.dmi' var/mob/living/C //Mob using the action var/mob/camera/ai_eye/remote/base_construction/remote_eye //Console's eye mob var/obj/machinery/computer/camera_advanced/base_construction/B //Console itself -/datum/action/innate/aux_base/Activate() - if(!target) +/datum/action/innate/aux_base/on_activate() + if(!master) return TRUE C = owner remote_eye = C.remote_control - B = target + B = master if(!B.RCD) //The console must always have an RCD. B.RCD = new /obj/item/construction/rcd/internal(src) //If the RCD is lost somehow, make a new (empty) one! @@ -167,7 +168,7 @@ name = "Build" button_icon_state = "build" -/datum/action/innate/aux_base/build/Activate() +/datum/action/innate/aux_base/build/on_activate() if(..()) return @@ -190,7 +191,7 @@ name = "Switch Mode" button_icon_state = "builder_mode" -/datum/action/innate/aux_base/switch_mode/Activate() +/datum/action/innate/aux_base/switch_mode/on_activate() if(..()) return @@ -203,7 +204,7 @@ name = "Select Airlock Type" button_icon_state = "airlock_select" -/datum/action/innate/aux_base/airlock_type/Activate() +/datum/action/innate/aux_base/airlock_type/on_activate() if(..()) return @@ -214,7 +215,7 @@ name = "Select Window Glass" button_icon_state = "window_select" -/datum/action/innate/aux_base/window_type/Activate() +/datum/action/innate/aux_base/window_type/on_activate() if(..()) return B.RCD.toggle_window_glass() @@ -223,7 +224,7 @@ name = "Place Tiny Fan" button_icon_state = "build_fan" -/datum/action/innate/aux_base/place_fan/Activate() +/datum/action/innate/aux_base/place_fan/on_activate() if(..()) return @@ -249,7 +250,7 @@ name = "Install Plasma Anti-Wildlife Turret" button_icon_state = "build_turret" -/datum/action/innate/aux_base/install_turret/Activate() +/datum/action/innate/aux_base/install_turret/on_activate() if(..()) return diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index 105deac6bf761..c20eb2224af7d 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -152,7 +152,7 @@ spawn(1) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.update_buttons() item_state = "crusher[wielded]" //destablizing force diff --git a/code/modules/mining/equipment/survival_pod.dm b/code/modules/mining/equipment/survival_pod.dm index 8083967f78af0..f7ae66f4b8482 100644 --- a/code/modules/mining/equipment/survival_pod.dm +++ b/code/modules/mining/equipment/survival_pod.dm @@ -372,7 +372,6 @@ /obj/item/gun/magic/wand/fireball, /obj/item/stack/sheet/telecrystal/twenty, /obj/item/nuke_core, - /obj/item/phylactery, /obj/item/banhammer) /obj/item/fakeartefact/Initialize(mapload) diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index c2c8abb594648..4a23cc7205572 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -49,7 +49,7 @@ /obj/item/reagent_containers/cup/bottle/necropolis_seed = 5, /obj/item/borg/upgrade/modkit/lifesteal = 5, /obj/item/shared_storage/red = 5, - /obj/item/staff/storm = 5 + /obj/item/staff/storm = 5, ) if(..()) @@ -183,7 +183,7 @@ name = "Memento Mori" desc = "Bind your life to the pendant." -/datum/action/item_action/hands_free/memento_mori/Trigger() +/datum/action/item_action/hands_free/memento_mori/on_activate(mob/user, atom/target) var/obj/item/clothing/neck/necklace/memento_mori/MM = target if(!MM.active_owner) if(ishuman(owner)) @@ -359,7 +359,7 @@ user.forceMove(get_turf(link_holder)) qdel(link_holder) return - do_teleport(link_holder, get_turf(linked), no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC) + do_teleport(link_holder, get_turf(linked), no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC_SELF) sleep(2.5) if(QDELETED(user)) qdel(link_holder) @@ -469,7 +469,7 @@ /obj/item/immortality_talisman/Initialize(mapload) . = ..() - AddComponent(/datum/component/anti_magic, INNATE_TRAIT, TRUE, TRUE) + AddComponent(/datum/component/anti_magic, INNATE_TRAIT, (MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY)) /datum/action/item_action/immortality name = "Immortality" @@ -642,7 +642,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/shared_storage/blue) C.emote("scream") if(holycheck) to_chat(C, "You feel blessed!") - C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, _magic = FALSE, _holy = TRUE) + C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, MAGIC_RESISTANCE_HOLY) ..() @@ -847,6 +847,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/shared_storage/blue) new /obj/item/dragons_blood(src) new /obj/item/clothing/suit/hooded/cloak/drake(src) //Drake armor crafted only by Ashwalkers now, but still available as drop for miners new /obj/item/crusher_trophy/tail_spike(src) + //new /obj/item/book/granter/action/spell/sacredflame(src) It's supposed to drop from the dragon but idk if you guys want it like that tell me in the review code // Ghost Sword - left in for other references and admin shenanigans @@ -1268,8 +1269,10 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/shared_storage/blue) /obj/item/hierophant_club/ui_action_click(mob/user, action) if(istype(action, /datum/action/item_action/toggle_unfriendly_fire)) //toggle friendly fire... + var/datum/action/toggle = action friendly_fire_check = !friendly_fire_check - to_chat(user, "You toggle friendly fire [friendly_fire_check ? "off":"on"]!") + toggle.update_buttons() + to_chat(user, "You toggle friendly fire [friendly_fire_check ? "on":"off"]!") return if(timer > world.time) return @@ -1384,7 +1387,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/shared_storage/blue) sleep(2) if(!M) return - do_teleport(M, turf_to_teleport_to, no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC) + do_teleport(M, turf_to_teleport_to, no_effects = TRUE, channel = TELEPORT_CHANNEL_MAGIC_SELF) sleep(1) if(!M) return diff --git a/code/modules/mining/minebot.dm b/code/modules/mining/minebot.dm index 22541a75e7d9d..f4f9d541af5c1 100644 --- a/code/modules/mining/minebot.dm +++ b/code/modules/mining/minebot.dm @@ -496,6 +496,7 @@ // Used when a player's in control of a minebot. /datum/action/innate/minedrone + button_icon_state = null check_flags = AB_CHECK_CONSCIOUS icon_icon = 'icons/hud/actions/actions_mecha.dmi' background_icon_state = "bg_default" @@ -506,7 +507,7 @@ icon_icon = 'icons/obj/clothing/glasses.dmi' button_icon_state = "trayson-" -/datum/action/innate/minedrone/toggle_meson_vision/Activate() +/datum/action/innate/minedrone/toggle_meson_vision/on_activate() var/mob/living/simple_animal/hostile/mining_drone/user = owner if(user.sight & SEE_TURFS) user.sight &= ~SEE_TURFS @@ -520,37 +521,37 @@ button_icon_state = "trayson-meson" user.sync_lighting_plane_alpha() to_chat(user, "You toggle your meson vision [(user.sight & SEE_TURFS) ? "on" : "off"].") - UpdateButtonIcon() + update_buttons() /// Toggles a minebot's inbuilt light. /datum/action/innate/minedrone/toggle_light name = "Toggle Light" button_icon_state = "mech_lights_off" -/datum/action/innate/minedrone/toggle_light/Activate() +/datum/action/innate/minedrone/toggle_light/on_activate() var/mob/living/simple_animal/hostile/mining_drone/user = owner user.set_light_on(!user.light_on) to_chat(user, "You toggle your light [user.light_on ? "on" : "off"].") button_icon_state = "mech_lights_[user.light_on ? "on" : "off"]" - UpdateButtonIcon() + update_buttons() /// Toggles the minebot's mode from combat to mining mode, effectively switching between the minebot's plasma cutter and PKA. /datum/action/innate/minedrone/toggle_mode name = "Toggle Mode" button_icon_state = "mech_zoom_off" -/datum/action/innate/minedrone/toggle_mode/Activate() +/datum/action/innate/minedrone/toggle_mode/on_activate() var/mob/living/simple_animal/hostile/mining_drone/user = owner user.toggle_mode() button_icon_state = "mech_zoom_[user.mode == MODE_COMBAT ? "on" : "off"]" - UpdateButtonIcon() + update_buttons() /// Allows a minebot to manually dump its own ore. /datum/action/innate/minedrone/dump_ore name = "Dump Ore" button_icon_state = "mech_eject" -/datum/action/innate/minedrone/dump_ore/Activate() +/datum/action/innate/minedrone/dump_ore/on_activate() var/mob/living/simple_animal/hostile/mining_drone/user = owner user.drop_ore() @@ -559,12 +560,12 @@ name = "Toggle Mining Scanner" button_icon_state = "mech_cycle_equip_off" -/datum/action/innate/minedrone/toggle_scanner/Activate() +/datum/action/innate/minedrone/toggle_scanner/on_activate() var/mob/living/simple_animal/hostile/mining_drone/user = owner user.stored_scanner.toggle_on() to_chat(user, "You toggle your mining scanner [user.stored_scanner.on ? "on" : "off"].") button_icon_state = "mech_cycle_equip_[user.stored_scanner.on ? "on" : "off"]" - UpdateButtonIcon() + update_buttons() /**********************Minebot Upgrades**********************/ // Similar to PKA upgrades, except for minebots. Each upgrade can only be installed once and is stored in the minebot when installed. diff --git a/code/modules/mob/living/bloodcrawl.dm b/code/modules/mob/living/bloodcrawl.dm deleted file mode 100644 index b2e9d1b3e081e..0000000000000 --- a/code/modules/mob/living/bloodcrawl.dm +++ /dev/null @@ -1,179 +0,0 @@ -/obj/effect/dummy/phased_mob/slaughter //Can't use the wizard one, blocked by jaunt/slow - name = "water" - icon = 'icons/effects/effects.dmi' - icon_state = "nothing" - var/canmove = 1 - density = FALSE - anchored = TRUE - invisibility = 60 - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/dummy/phased_mob/slaughter/relaymove(mob/living/user, direction) - forceMove(get_step(src,direction)) - -/obj/effect/dummy/phased_mob/slaughter/ex_act() - return - -/obj/effect/dummy/phased_mob/slaughter/bullet_act() - return BULLET_ACT_FORCE_PIERCE - -/obj/effect/dummy/phased_mob/slaughter/singularity_act() - return - - - -/mob/living/proc/phaseout(obj/effect/decal/cleanable/B) - if(iscarbon(src)) - var/mob/living/carbon/C = src - for(var/obj/item/I in C.held_items) - //TODO make it toggleable to either forcedrop the items, or deny - //entry when holding them - // literally only an option for carbons though - to_chat(C, "You may not hold items while blood crawling!") - return 0 - var/obj/item/bloodcrawl/B1 = new(C) - var/obj/item/bloodcrawl/B2 = new(C) - B1.icon_state = "bloodhand_left" - B2.icon_state = "bloodhand_right" - C.put_in_hands(B1) - C.put_in_hands(B2) - C.regenerate_icons() - src.notransform = TRUE - spawn(0) - bloodpool_sink(B) - src.notransform = FALSE - return 1 - -/mob/living/proc/bloodpool_sink(obj/effect/decal/cleanable/B) - var/turf/mobloc = get_turf(src.loc) - - src.visible_message("[src] sinks into the pool of blood!") - playsound(get_turf(src), 'sound/magic/enter_blood.ogg', 50, 1, -1) - // Extinguish, unbuckle, stop being pulled, set our location into the - // dummy object - var/obj/effect/dummy/phased_mob/slaughter/holder = new /obj/effect/dummy/phased_mob/slaughter(mobloc) - src.ExtinguishMob() - - // Keep a reference to whatever we're pulling, because forceMove() - // makes us stop pulling - var/pullee = src.pulling - - src.holder = holder - src.forceMove(holder) - - // if we're not pulling anyone, or we can't eat anyone - if(!pullee || src.bloodcrawl != BLOODCRAWL_EAT) - return - - // if the thing we're pulling isn't alive - if (!isliving(pullee) || ismegafauna(pullee)) - return - - var/mob/living/victim = pullee - var/kidnapped = FALSE - - if(victim.stat == CONSCIOUS) - src.visible_message("[victim] kicks free of the blood pool just before entering it!", null, "You hear splashing and struggling.") - else if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/consumable/ethanol/demonsblood, needs_metabolizing = TRUE)) - visible_message("Something prevents [victim] from entering the pool!", "A strange force is blocking [victim] from entering!", "You hear a splash and a thud.") - else - victim.forceMove(src) - victim.emote("scream") - src.visible_message("[src] drags [victim] into the pool of blood!", null, "You hear a splash.") - kidnapped = TRUE - - if(kidnapped) - var/success = bloodcrawl_consume(victim) - if(!success) - to_chat(src, "You happily devour... nothing? Your meal vanished at some point!") - return 1 - -/mob/living/proc/bloodcrawl_consume(mob/living/victim) - to_chat(src, "You begin to feast on [victim]. You can not move while you are doing this.") - - var/sound - if(istype(src, /mob/living/simple_animal/slaughter)) - var/mob/living/simple_animal/slaughter/SD = src - sound = SD.feast_sound - else - sound = 'sound/magic/demon_consume.ogg' - - for(var/i in 1 to 3) - playsound(get_turf(src),sound, 50, 1) - sleep(30) - - if(!victim) - return FALSE - - if(victim.reagents && victim.reagents.has_reagent(/datum/reagent/consumable/ethanol/devilskiss, needs_metabolizing = TRUE)) - to_chat(src, "AAH! THEIR FLESH! IT BURNS!") - adjustBruteLoss(25) //I can't use adjustHealth() here because bloodcrawl affects /mob/living and adjustHealth() only affects simple mobs - var/found_bloodpool = FALSE - for(var/obj/effect/decal/cleanable/target in range(1,get_turf(victim))) - if(target.can_bloodcrawl_in()) - victim.forceMove(get_turf(target)) - victim.visible_message("[target] violently expels [victim]!") - victim.exit_blood_effect(target) - found_bloodpool = TRUE - - if(!found_bloodpool) - // Fuck it, just eject them, thanks to some split second cleaning - victim.forceMove(get_turf(victim)) - victim.visible_message("[victim] appears from nowhere, covered in blood!") - victim.exit_blood_effect() - return TRUE - - to_chat(src, "You devour [victim]. Your health is fully restored.") - src.revive(full_heal = 1) - - victim.apply_damage(200, BRUTE) - if(victim.stat != DEAD) - victim.investigate_log("has been killed by being consumed by a slaugter demon.", INVESTIGATE_DEATHS) - victim.death() - bloodcrawl_swallow(victim) - return TRUE - -/mob/living/proc/bloodcrawl_swallow(var/mob/living/victim) - qdel(victim) - -/obj/item/bloodcrawl - name = "blood crawl" - desc = "You are unable to hold anything while in this form." - icon = 'icons/effects/blood.dmi' - item_flags = ABSTRACT | DROPDEL - -/obj/item/bloodcrawl/Initialize(mapload) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/mob/living/proc/exit_blood_effect(obj/effect/decal/cleanable/B) - playsound(get_turf(src), 'sound/magic/exit_blood.ogg', 50, 1, -1) - //Makes the mob have the color of the blood pool it came out of - var/newcolor = rgb(149, 10, 10) - if(istype(B, /obj/effect/decal/cleanable/xenoblood)) - newcolor = rgb(43, 186, 0) - add_atom_colour(newcolor, TEMPORARY_COLOUR_PRIORITY) - // but only for a few seconds - addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, remove_atom_colour), TEMPORARY_COLOUR_PRIORITY, newcolor), 6 SECONDS) - -/mob/living/proc/phasein(obj/effect/decal/cleanable/B) - if(src.notransform) - to_chat(src, "Finish eating first!") - return 0 - B.visible_message("[B] starts to bubble...") - if(!do_after(src, 20, target = B)) - return - if(!B) - return - forceMove(B.loc) - src.client.set_eye(src) - src.visible_message("[src] rises out of the pool of blood!") - exit_blood_effect(B) - if(iscarbon(src)) - var/mob/living/carbon/C = src - for(var/obj/item/bloodcrawl/BC in C) - BC.flags_1 = null - qdel(BC) - qdel(src.holder) - src.holder = null - return 1 diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm index ef41226772bf9..400d43ce86227 100644 --- a/code/modules/mob/living/brain/brain.dm +++ b/code/modules/mob/living/brain/brain.dm @@ -86,8 +86,6 @@ var/obj/vehicle/sealed/mecha/M = container.mecha if(M.mouse_pointer) client.mouse_pointer_icon = M.mouse_pointer - if (client && ranged_ability && ranged_ability.ranged_mousepointer) - client.mouse_pointer_icon = ranged_ability.ranged_mousepointer /mob/living/brain/proc/get_traumas() . = list() diff --git a/code/modules/mob/living/carbon/alien/alien.dm b/code/modules/mob/living/carbon/alien/alien.dm index 594fb65d2a303..0d4ce3fc86e4f 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -110,8 +110,10 @@ Des: Removes all infected images from the alien. return TRUE /mob/living/carbon/alien/proc/alien_evolve(mob/living/carbon/alien/new_xeno) - to_chat(src, "You begin to evolve!") - visible_message("[src] begins to twist and contort!") + visible_message( + ("[src] begins to twist and contort!"), + ("You begin to evolve!"), + ) new_xeno.setDir(dir) if(!alien_name_regex.Find(name)) new_xeno.name = name diff --git a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm index 3eb399dbc9d92..c1992233fe8b8 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm @@ -1,327 +1,389 @@ /*NOTES: These are general powers. Specific powers are stored under the appropriate alien creature type. */ - /*Alien spit now works like a taser shot. It won't home in on the target but will act the same once it does hit. Doesn't work on other aliens/AI.*/ -/obj/effect/proc_holder/alien +/datum/action/alien name = "Alien Power" - panel = "Alien" - base_action = /datum/action/spell_action/alien - action_icon = 'icons/hud/actions/actions_xeno.dmi' - action_background_icon_state = "bg_alien" + background_icon_state = "bg_alien" + icon_icon = 'icons/hud/actions/actions_xeno.dmi' + button_icon_state = null + check_flags = AB_CHECK_CONSCIOUS + /// How much plasma this action uses. var/plasma_cost = 0 - var/check_turf = FALSE -/obj/effect/proc_holder/alien/Click() - if(!iscarbon(usr)) +/datum/action/alien/is_available() + . = ..() + if(!.) + return FALSE + if(!iscarbon(owner)) return FALSE - var/mob/living/carbon/user = usr - if(cost_check(check_turf,user)) - if(fire(user) && user) // Second check to prevent runtimes when evolving - user.adjustPlasma(-plasma_cost) + var/mob/living/carbon/carbon_owner = owner + if(carbon_owner.getPlasma() < plasma_cost) + return FALSE + return TRUE -/obj/effect/proc_holder/alien/on_gain(mob/living/carbon/user) - return +/datum/action/alien/pre_activate(mob/user, atom/target) + // Parent calls Activate(), so if parent returns TRUE, + // it means the activation happened successfuly by this point + . = ..() + if(!.) + return FALSE + // Xeno actions like "evolve" may result in our action (or our alien) being deleted + // In that case, we can just exit now as a "success" + if(QDELETED(src) || QDELETED(owner)) + return TRUE -/obj/effect/proc_holder/alien/on_lose(mob/living/carbon/user) - return + var/mob/living/carbon/carbon_owner = owner + carbon_owner.adjustPlasma(-plasma_cost) + // It'd be really annoying if click-to-fire actions stayed active, + // even if our plasma amount went under the required amount. + if(requires_target && carbon_owner.getPlasma() < plasma_cost) + unset_click_ability(owner, refund_cooldown = FALSE) -/obj/effect/proc_holder/alien/fire(mob/living/carbon/user) return TRUE -/obj/effect/proc_holder/alien/get_panel_text() +/datum/action/alien/update_stat_status(list/stat) + stat[STAT_STATUS] = GENERATE_STAT_TEXT("PLASMA - [plasma_cost]") + +/datum/action/alien/make_structure + /// The type of structure the action makes on use + var/obj/structure/made_structure_type + +/datum/action/alien/make_structure/is_available() . = ..() - if(plasma_cost > 0) - return "[plasma_cost]" + if(!.) + return FALSE + if(!isturf(owner.loc) || isspaceturf(owner.loc)) + return FALSE + + return TRUE -/obj/effect/proc_holder/alien/proc/cost_check(check_turf = FALSE, mob/living/carbon/user, silent = FALSE) - if(user.stat) - if(!silent) - to_chat(user, "You must be conscious to do this.") +/datum/action/alien/make_structure/pre_activate(mob/user, atom/target) + if(!check_for_duplicate()) return FALSE - if(user.getPlasma() < plasma_cost) - if(!silent) - to_chat(user, "Not enough plasma stored.") + + if(!check_for_vents()) return FALSE - if(check_turf && (!isturf(user.loc) || isspaceturf(user.loc))) - if(!silent) - to_chat(user, "Bad place for a garden!") + + return ..() + +/datum/action/alien/make_structure/on_activate(mob/user, atom/target) + new made_structure_type(owner.loc) + return TRUE + +/// Checks if there's a duplicate structure in the owner's turf +/datum/action/alien/make_structure/proc/check_for_duplicate() + var/obj/structure/existing_thing = locate(made_structure_type) in owner.loc + if(existing_thing) + to_chat(owner, ("There is already \a [existing_thing] here!")) return FALSE + return TRUE -/obj/effect/proc_holder/alien/proc/check_vent_block(mob/living/user) - var/obj/machinery/atmospherics/components/unary/atmos_thing = locate() in user.loc +/// Checks if there's an atmos machine (vent) in the owner's turf +/datum/action/alien/make_structure/proc/check_for_vents() + var/obj/machinery/atmospherics/components/unary/atmos_thing = locate() in owner.loc if(atmos_thing) - var/rusure = alert(user, "Laying eggs and shaping resin here would block access to [atmos_thing]. Do you want to continue?", "Blocking Atmospheric Component", "Yes", "No") - if(rusure != "Yes") + var/are_you_sure = tgui_alert(owner, "Laying eggs and shaping resin here would block access to [atmos_thing]. Do you want to continue?", "Blocking Atmospheric Component", list("Yes", "No")) + if(are_you_sure != "Yes") return FALSE + if(QDELETED(src) || QDELETED(owner) || !check_for_duplicate()) + return FALSE + return TRUE -/obj/effect/proc_holder/alien/plant +/datum/action/alien/make_structure/plant_weeds name = "Plant Weeds" - desc = "Alien weeds spread resin which heals any alien. Costs 50 Plasma." + desc = "Plants some alien weeds." + button_icon_state = "alien_plant" plasma_cost = 50 - check_turf = TRUE - action_icon_state = "alien_plant" + made_structure_type = /obj/structure/alien/weeds/node -/obj/effect/proc_holder/alien/plant/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/weeds/node) in get_turf(user)) - to_chat(user, "There's already a weed node here.") - return FALSE - user.visible_message("[user] has planted some alien weeds!") - new/obj/structure/alien/weeds/node(get_turf(user)) - return TRUE +/datum/action/alien/make_structure/plant_weeds/on_activate(mob/user, atom/target) + owner.visible_message(("[owner] plants some alien weeds!")) + return ..() -/obj/effect/proc_holder/alien/whisper +/datum/action/alien/whisper name = "Whisper" - desc = "Whisper to someone through the hivemind. Costs 10 Plasma." + desc = "Whisper to someone." + button_icon_state = "alien_whisper" plasma_cost = 10 - action_icon_state = "alien_whisper" - -/obj/effect/proc_holder/alien/whisper/fire(mob/living/carbon/user) - var/list/options = list() - for(var/mob/living/L in oview(user)) - options += L - var/mob/living/M = input("Select who to whisper to:","Whisper to?",null) as null|mob in sort_names(options) - if(!M) + +/datum/action/alien/whisper/on_activate(mob/user, atom/target) + var/list/possible_recipients = list() + for(var/mob/living/recipient in oview(owner)) + possible_recipients += recipient + + if(!length(possible_recipients)) + to_chat(owner, ("There's no one around to whisper to.")) return FALSE - var/msg = stripped_input(usr, "Message:", "Alien Whisper") - if(!msg) + + var/mob/living/chosen_recipient = tgui_input_list(owner, "Select whisper recipient", "Whisper", sort_names(possible_recipients)) + if(!chosen_recipient) return FALSE - if(CHAT_FILTER_CHECK(msg)) - to_chat(usr, "Your message contains forbidden words.") + + var/to_whisper = tgui_input_text(owner, title = "Alien Whisper") + if(QDELETED(chosen_recipient) || QDELETED(src) || QDELETED(owner) || !is_available() || !to_whisper) return FALSE - msg = user.treat_message_min(msg) - log_directed_talk(user, M, msg, LOG_SAY, tag="alien whisper") - to_chat(M, "You hear a strange, alien voice in your head.[msg]") - to_chat(user, "You said: \"[msg]\" to [M]") - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) + if(chosen_recipient.can_block_magic()) + to_chat(owner, ("As you reach into [chosen_recipient]'s mind, you are stopped by a mental blockage. It seems you've been foiled.")) + return FALSE + + log_directed_talk(owner, chosen_recipient, to_whisper, LOG_SAY, tag = "alien whisper") + to_chat(chosen_recipient, "[("You hear a strange, alien voice in your head...")][to_whisper]") + to_chat(owner, ("You said: \"[to_whisper]\" to [chosen_recipient]")) + for(var/mob/dead_mob as anything in GLOB.dead_mob_list) + if(!isobserver(dead_mob)) continue - var/follow_link_user = FOLLOW_LINK(ded, user) - var/follow_link_whispee = FOLLOW_LINK(ded, M) - to_chat(ded, "[follow_link_user] [user] Alien Whisper --> [follow_link_whispee] [M] [msg]") + var/follow_link_user = FOLLOW_LINK(dead_mob, owner) + var/follow_link_whispee = FOLLOW_LINK(dead_mob, chosen_recipient) + to_chat(dead_mob, "[follow_link_user] [("[owner]")] [("Alien Whisper --> ")] [follow_link_whispee] [("[chosen_recipient]")] [("[to_whisper]")]") + return TRUE -/obj/effect/proc_holder/alien/transfer +/datum/action/alien/transfer name = "Transfer Plasma" desc = "Transfer Plasma to another alien." - action_icon_state = "alien_transfer" + plasma_cost = 0 + button_icon_state = "alien_transfer" -/obj/effect/proc_holder/alien/transfer/fire(mob/living/carbon/user) +/datum/action/alien/transfer/on_activate(mob/user, atom/target) + var/mob/living/carbon/carbon_owner = owner var/list/mob/living/carbon/aliens_around = list() - for(var/mob/living/carbon/A in oview(user)) - if(A.getorgan(/obj/item/organ/alien/plasmavessel)) - aliens_around.Add(A) - var/mob/living/carbon/M = input("Select who to transfer to:","Transfer plasma to?",null) as mob in sort_names(aliens_around) - if(!M) - return 0 - var/amount = input("Amount:", "Transfer Plasma to [M]") as num - amount = min(abs(round(amount)), user.getPlasma()) - if(!amount) + for(var/mob/living/carbon/alien in view(owner)) + if(alien.getPlasma() == -1 || alien == owner) + continue + aliens_around += alien + + if(!length(aliens_around)) + to_chat(owner, ("There are no other aliens around.")) + return FALSE + + var/mob/living/carbon/donation_target = tgui_input_list(owner, "Target to transfer to", "Plasma Donation", sort_names(aliens_around)) + if(!donation_target) return FALSE - if(!user.Adjacent(M)) - to_chat(user, "You need to be closer!") + var/amount = tgui_input_number(owner, "Amount", "Transfer Plasma to [donation_target]", max_value = carbon_owner.getPlasma()) + if(QDELETED(donation_target) || QDELETED(src) || QDELETED(owner) || !is_available() || isnull(amount) || amount <= 0) return FALSE - M.adjustPlasma(amount) - user.adjustPlasma(-amount) - to_chat(M, "[user] has transferred [amount] plasma to you.") - to_chat(user, "You transfer [amount] plasma to [M]") + if(get_dist(owner, donation_target) > 1) + to_chat(owner, ("You need to be closer!")) + return FALSE + + donation_target.adjustPlasma(amount) + carbon_owner.adjustPlasma(-amount) + + to_chat(donation_target, ("[owner] has transferred [amount] plasma to you.")) + to_chat(owner, ("You transfer [amount] plasma to [donation_target].")) return TRUE -/obj/effect/proc_holder/alien/acid +/datum/action/alien/acid + requires_target = TRUE + unset_after_click = FALSE + +/datum/action/alien/acid/corrosion name = "Corrosive Acid" - desc = "Drench an object in acid, destroying it over time. Costs 200 Plasma." + desc = "Drench an object in acid, destroying it over time." + button_icon_state = "alien_acid" plasma_cost = 200 - action_icon_state = "alien_acid" - -/obj/effect/proc_holder/alien/acid/on_gain(mob/living/carbon/user) - user.add_verb(/mob/living/carbon/proc/corrosive_acid) -/obj/effect/proc_holder/alien/acid/on_lose(mob/living/carbon/user) - user.remove_verb(/mob/living/carbon/proc/corrosive_acid) +/datum/action/alien/acid/corrosion/set_click_ability(mob/on_who) + . = ..() + if(!.) + return -/obj/effect/proc_holder/alien/acid/proc/corrode(atom/target,mob/living/carbon/user = usr) - if(target in oview(1,user)) - if(target.acid_act(200, 100)) - user.visible_message("[user] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!") - return 1 - else - to_chat(user, "You cannot dissolve this object.") + to_chat(on_who, ("You prepare to vomit acid. Click a target to acid it!")) + on_who.update_icons() +/datum/action/alien/acid/corrosion/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return - return 0 - else - to_chat(src, "[target] is too far away.") - return 0 + if(refund_cooldown) + to_chat(on_who, ("You empty your corrosive acid glands.")) + on_who.update_icons() +/datum/action/alien/acid/corrosion/pre_activate(mob/user, atom/target) + if(get_dist(owner, target) > 1) + return FALSE -/obj/effect/proc_holder/alien/acid/fire(mob/living/carbon/alien/user) - var/O = input("Select what to dissolve:","Dissolve",null) as obj|turf in oview(1,user) - if(!O || user.incapacitated()) - return 0 - else - return corrode(O,user) + return ..() -/mob/living/carbon/proc/corrosive_acid(O as obj|turf in oview(1)) // right click menu verb ugh - set name = "Corrosive Acid" +/datum/action/alien/acid/corrosion/on_activate(mob/user, atom/target) + if(!target.acid_act(200, 1000)) + to_chat(owner, ("You cannot dissolve this object.")) + return FALSE - if(!iscarbon(usr)) - return - var/mob/living/carbon/user = usr - var/obj/effect/proc_holder/alien/acid/A = locate() in user.abilities - if(!A) - return - if(user.getPlasma() > A.plasma_cost && A.corrode(O)) - user.adjustPlasma(-A.plasma_cost) + owner.visible_message( + ("[owner] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!"), + ("You vomit globs of acid over [target]. It begins to sizzle and melt."), + ) + return TRUE -/obj/effect/proc_holder/alien/neurotoxin +/datum/action/alien/acid/neurotoxin name = "Spit Neurotoxin" - desc = "Activates your Neurotoxin glands. You can shoot paralyzing shots. Each shot costs 50 Plasma." - action_icon_state = "alien_neurotoxin_0" - active = FALSE - -/obj/effect/proc_holder/alien/neurotoxin/fire(mob/living/carbon/user) - var/message - if(active) - message = "You empty your neurotoxin gland." - remove_ranged_ability(message) - else - message = "You prepare your neurotoxin gland. Left-click to fire at a target!" - add_ranged_ability(user, message, TRUE) + desc = "Spits neurotoxin at someone, paralyzing them for a short time." + button_icon_state = "alien_neurotoxin_0" + plasma_cost = 50 -/obj/effect/proc_holder/alien/neurotoxin/update_icon() - action.button_icon_state = "alien_neurotoxin_[active]" - action.UpdateButtonIcon() +/datum/action/alien/acid/neurotoxin/is_available() + return ..() && isturf(owner.loc) -/obj/effect/proc_holder/alien/neurotoxin/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - var/p_cost = 30 - if(!iscarbon(ranged_ability_user) || ranged_ability_user.stat) - remove_ranged_ability() +/datum/action/alien/acid/neurotoxin/set_click_ability(mob/on_who) + . = ..() + if(!.) return - var/mob/living/carbon/user = ranged_ability_user + to_chat(on_who, ("You prepare your neurotoxin gland. Left-click to fire at a target!")) - if(user.getPlasma() < p_cost) - to_chat(user, "You need at least [p_cost] plasma to spit.") - remove_ranged_ability() + button_icon_state = "alien_neurotoxin_1" + update_buttons() + on_who.update_icons() + +/datum/action/alien/acid/neurotoxin/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) return - var/turf/T = user.loc - var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction - if(!isturf(U) || !isturf(T)) + if(refund_cooldown) + to_chat(on_who, ("You empty your neurotoxin gland.")) + + button_icon_state = "alien_neurotoxin_0" + update_buttons() + on_who.update_icons() + +/datum/action/alien/acid/neurotoxin/InterceptClickOn(mob/living/caller, params, atom/target) + . = ..() + if(!.) + unset_click_ability(caller, refund_cooldown = FALSE) return FALSE - user.visible_message("[user] spits neurotoxin!", "You spit neurotoxin.") - var/obj/projectile/bullet/neurotoxin/A = new /obj/projectile/bullet/neurotoxin(user.loc) - A.preparePixelProjectile(target, user, params) - A.firer = user - A.fire() - user.newtonian_move(get_dir(U, T)) - user.adjustPlasma(-p_cost) + // We do this in InterceptClickOn() instead of Activate() + // because we use the click parameters for aiming the projectile + // (or something like that) + var/turf/user_turf = caller.loc + var/turf/target_turf = get_step(caller, target.dir) // Get the tile infront of the move, based on their direction + if(!isturf(target_turf)) + return FALSE + var/modifiers = params2list(params) + caller.visible_message( + ("[caller] spits neurotoxin!"), + ("You spit neurotoxin."), + ) + var/obj/projectile/bullet/neurotoxin/neurotoxin = new /obj/projectile/bullet/neurotoxin(caller.loc) + neurotoxin.preparePixelProjectile(target, caller, modifiers) + neurotoxin.firer = caller + neurotoxin.fire() + caller.newtonian_move(get_dir(target_turf, user_turf)) return TRUE -/obj/effect/proc_holder/alien/neurotoxin/on_lose(mob/living/carbon/user) - remove_ranged_ability() - -/obj/effect/proc_holder/alien/neurotoxin/add_ranged_ability(mob/living/user,msg,forced) - . = ..() - if(isalienadult(user)) - var/mob/living/carbon/alien/humanoid/A = user - A.drooling = TRUE - A.update_icons() - -/obj/effect/proc_holder/alien/neurotoxin/remove_ranged_ability(msg) - if(isalienadult(ranged_ability_user)) - var/mob/living/carbon/alien/humanoid/A = ranged_ability_user - A.drooling = FALSE - A.update_icons() - return ..() +// Has to return TRUE, otherwise is skipped. +/datum/action/alien/acid/neurotoxin/on_activate(mob/user, atom/target) + return TRUE -/obj/effect/proc_holder/alien/resin +/datum/action/alien/make_structure/resin name = "Secrete Resin" - desc = "Secrete tough malleable resin. Costs 55 Plasma." + desc = "Secrete tough malleable resin." + button_icon_state = "alien_resin" plasma_cost = 55 - check_turf = TRUE - var/list/structures = list( + /// A list of all structures we can make. + var/static/list/structures = list( "resin wall" = /obj/structure/alien/resin/wall, "resin membrane" = /obj/structure/alien/resin/membrane, - "resin nest" = /obj/structure/bed/nest) + "resin nest" = /obj/structure/bed/nest, + ) + +// Snowflake to check for multiple types of alien resin structures +/datum/action/alien/make_structure/resin/check_for_duplicate() + for(var/blocker_name in structures) + var/obj/structure/blocker_type = structures[blocker_name] + if(locate(blocker_type) in owner.loc) + to_chat(owner, ("There is already a resin structure there!")) + return FALSE - action_icon_state = "alien_resin" + return TRUE -/obj/effect/proc_holder/alien/resin/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/resin) in user.loc) - to_chat(user, "There is already a resin structure there.") +/datum/action/alien/make_structure/resin/on_activate(mob/user, atom/target) + var/choice = tgui_input_list(owner, "Select a shape to build", "Resin building", structures) + if(isnull(choice) || QDELETED(src) || QDELETED(owner) || !check_for_duplicate() || !is_available()) return FALSE - if(!check_vent_block(user)) + var/obj/structure/choice_path = structures[choice] + if(!ispath(choice_path)) return FALSE - var/choice = input("Choose what you wish to shape.","Resin building") as null|anything in structures - if(!choice) - return FALSE - if(!cost_check(check_turf,user)) - return FALSE - user.visible_message("[user] vomits up a thick purple substance and begins to shape it.", "You shape a [choice].") + owner.visible_message( + ("[owner] vomits up a thick purple substance and begins to shape it."), + ("You shape a [choice] out of resin."), + ) - choice = structures[choice] - new choice(get_turf(user)) + new choice_path(owner.loc) return TRUE -/obj/effect/proc_holder/alien/sneak +/datum/action/alien/sneak name = "Sneak" desc = "Blend into the shadows to stalk your prey." - active = 0 - action_icon_state = "alien_sneak" - -/obj/effect/proc_holder/alien/sneak/fire(mob/living/carbon/alien/humanoid/user) - if(!active) - user.alpha = 75 //Still easy to see in lit areas with bright tiles, almost invisible on resin. - user.sneaking = TRUE - active = 1 - to_chat(user, "You blend into the shadows.") + button_icon_state = "alien_sneak" + /// The alpha we go to when sneaking. + var/sneak_alpha = 75 + +/datum/action/alien/sneak/Remove(mob/living/remove_from) + if(HAS_TRAIT(remove_from, TRAIT_ALIEN_SNEAK)) + remove_from.alpha = initial(remove_from.alpha) + REMOVE_TRAIT(remove_from, TRAIT_ALIEN_SNEAK, name) + + return ..() + +/datum/action/alien/sneak/on_activate(mob/user, atom/target) + if(HAS_TRAIT(owner, TRAIT_ALIEN_SNEAK)) + // It's safest to go to the initial alpha of the mob. + // Otherwise we get permanent invisbility exploits. + owner.alpha = initial(owner.alpha) + to_chat(owner, ("You reveal yourself!")) + REMOVE_TRAIT(owner, TRAIT_ALIEN_SNEAK, name) + else - user.alpha = initial(user.alpha) - user.sneaking = FALSE - active = 0 - to_chat(user, "You reveal yourself!") + owner.alpha = sneak_alpha + to_chat(owner, ("You blend into the shadows...")) + ADD_TRAIT(owner, TRAIT_ALIEN_SNEAK, name) + return TRUE +/// Gets the plasma level of this carbon's plasma vessel, or -1 if they don't have one /mob/living/carbon/proc/getPlasma() var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) if(!vessel) - return FALSE - return vessel.storedPlasma - + return -1 + return vessel.stored_plasma +/// Adjusts the plasma level of the carbon's plasma vessel if they have one /mob/living/carbon/proc/adjustPlasma(amount) var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) if(!vessel) return FALSE - vessel.storedPlasma = max(vessel.storedPlasma + amount,0) - vessel.storedPlasma = min(vessel.storedPlasma, vessel.max_plasma) //upper limit of max_plasma, lower limit of 0 - for(var/X in abilities) - var/obj/effect/proc_holder/alien/APH = X - if(APH.has_action) - APH.action.UpdateButtonIcon() + vessel.stored_plasma = max(vessel.stored_plasma + amount,0) + vessel.stored_plasma = min(vessel.stored_plasma, vessel.max_plasma) //upper limit of max_plasma, lower limit of 0 + for(var/datum/action/alien/ability in actions) + ability.update_buttons() return TRUE /mob/living/carbon/alien/adjustPlasma(amount) . = ..() updatePlasmaDisplay() -/mob/living/carbon/proc/usePlasma(amount) - if(getPlasma() >= amount) - adjustPlasma(-amount) - return TRUE +//For alien evolution/promotion/queen finder procs. Checks for an active alien of that type +/proc/get_alien_type(alienpath) + for(var/mob/living/carbon/alien/humanoid/A in GLOB.alive_mob_list) + if(!istype(A, alienpath)) + continue + if(!A.key || A.stat == DEAD) //Only living aliens with a ckey are valid. + continue + return A return FALSE diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm index 2c64eac9a6b9d..65ea2be229ac5 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm @@ -6,8 +6,9 @@ icon_state = "aliend" /mob/living/carbon/alien/humanoid/drone/Initialize(mapload) - AddAbility(new/obj/effect/proc_holder/alien/evolve(null)) - . = ..() + var/datum/action/alien/evolve_to_praetorian/evolution = new(src) + evolution.Grant(src) + return ..() /mob/living/carbon/alien/humanoid/drone/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel/large @@ -15,28 +16,35 @@ internal_organs += new /obj/item/organ/alien/acid return ..() -/obj/effect/proc_holder/alien/evolve +/datum/action/alien/evolve_to_praetorian name = "Evolve to Praetorian" desc = "Praetorian" + button_icon_state = "alien_evolve_drone" plasma_cost = 500 - action_icon_state = "alien_evolve_drone" -/obj/effect/proc_holder/alien/evolve/fire(mob/living/carbon/alien/humanoid/user) - var/obj/item/organ/alien/hivenode/node = user.getorgan(/obj/item/organ/alien/hivenode) - if(!node) //Players are Murphy's Law. We may not expect there to ever be a living xeno with no hivenode, but they _WILL_ make it happen. - to_chat(user, "Without the hivemind, you can't possibly hold the responsibility of leadership!") +/datum/action/alien/evolve_to_praetorian/is_available() + . = ..() + if(!.) return FALSE - if(node.recent_queen_death) - to_chat(user, "Your thoughts are still too scattered to take up the position of leadership.") + + if(!isturf(owner.loc)) return FALSE - if(!isturf(user.loc)) - to_chat(user, "You can't evolve here!") + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal)) return FALSE - if(!get_alien_type_in_hive(/mob/living/carbon/alien/humanoid/royal)) - var/mob/living/carbon/alien/humanoid/royal/praetorian/new_xeno = new(user.loc) - user.alien_evolve(new_xeno) - return TRUE - else - to_chat(user, "We already have a living royal!") + + var/mob/living/carbon/alien/humanoid/royal/evolver = owner + var/obj/item/organ/alien/hivenode/node = evolver.getorgan(/obj/item/organ/alien/hivenode) + // Players are Murphy's Law. We may not expect + // there to ever be a living xeno with no hivenode, + // but they _WILL_ make it happen. + if(!node || node.recent_queen_death) return FALSE + + return TRUE + +/datum/action/alien/evolve_to_praetorian/on_activate(mob/user, atom/target) + var/mob/living/carbon/alien/humanoid/evolver = owner + var/mob/living/carbon/alien/humanoid/royal/praetorian/new_xeno = new(owner.loc) + evolver.alien_evolve(new_xeno) + return TRUE diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm index be2e538500a93..f90e09b3c1ba2 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm @@ -7,9 +7,11 @@ /mob/living/carbon/alien/humanoid/royal/praetorian/Initialize(mapload) real_name = name - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/xeno(src)) - AddAbility(new /obj/effect/proc_holder/alien/royal/praetorian/evolve()) - . = ..() + var/datum/action/spell/aoe/repulse/xeno/tail_whip = new(src) + tail_whip.Grant(src) + var/datum/action/alien/evolve_to_queen/evolution = new(src) + evolution.Grant(src) + return ..() /mob/living/carbon/alien/humanoid/royal/praetorian/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel/large @@ -18,24 +20,32 @@ internal_organs += new /obj/item/organ/alien/neurotoxin return ..() -/obj/effect/proc_holder/alien/royal/praetorian/evolve +/datum/action/alien/evolve_to_queen name = "Evolve" - desc = "Produce an internal egg sac capable of spawning children. Only one queen can exist at a time. Costs 500 Plasma." + desc = "Produce an internal egg sac capable of spawning children. Only one queen can exist at a time." + button_icon_state = "alien_evolve_praetorian" plasma_cost = 500 - action_icon_state = "alien_evolve_praetorian" -/obj/effect/proc_holder/alien/royal/praetorian/evolve/fire(mob/living/carbon/alien/humanoid/user) - var/obj/item/organ/alien/hivenode/node = user.getorgan(/obj/item/organ/alien/hivenode) - if(!node) //Just in case this particular Praetorian gets violated and kept by the RD as a replacement for Lamarr. - to_chat(user, "Without the hivemind, you would be unfit to rule as queen!") +/datum/action/alien/evolve_to_queen/is_available() + . = ..() + if(!.) return FALSE - if(node.recent_queen_death) - to_chat(user, "You are still too burdened with guilt to evolve into a queen.") + + if(!isturf(owner.loc)) return FALSE - if(!get_alien_type_in_hive(/mob/living/carbon/alien/humanoid/royal/queen)) - var/mob/living/carbon/alien/humanoid/royal/queen/new_xeno = new(user.loc) - user.alien_evolve(new_xeno) - return TRUE - else - to_chat(user, "We already have an alive queen.") + + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/queen)) return FALSE + + var/mob/living/carbon/alien/humanoid/royal/evolver = owner + var/obj/item/organ/alien/hivenode/node = evolver.getorgan(/obj/item/organ/alien/hivenode) + if(!node || node.recent_queen_death) + return FALSE + + return TRUE + +/datum/action/alien/evolve_to_queen/on_activate(mob/user, atom/target) + var/mob/living/carbon/alien/humanoid/royal/evolver = owner + var/mob/living/carbon/alien/humanoid/royal/queen/new_queen = new(owner.loc) + evolver.alien_evolve(new_queen) + return TRUE diff --git a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm index 73a33ead26da4..28ec13577982c 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm @@ -6,8 +6,10 @@ icon_state = "aliens" /mob/living/carbon/alien/humanoid/sentinel/Initialize(mapload) - AddAbility(new /obj/effect/proc_holder/alien/sneak) - . = ..() + var/datum/action/alien/sneak/sneaky_beaky = new(src) + sneaky_beaky.Grant(src) + return ..() + /mob/living/carbon/alien/humanoid/sentinel/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index 19796e27868b0..9bfc44443ecbe 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -19,8 +19,6 @@ var/alt_icon = 'icons/mob/alienleap.dmi' //used to switch between the two alien icon files. var/leap_on_click = FALSE COOLDOWN_DECLARE(pounce_cooldown) - var/sneaking = FALSE //For sneaky-sneaky mode and appropriate slowdown - var/drooling = FALSE //For Neurotoxic spit overlays GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( /datum/strippable_item/hand/left, @@ -60,6 +58,6 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( return FALSE /mob/living/carbon/alien/humanoid/check_breath(datum/gas_mixture/breath) - if(breath && breath.total_moles() > 0 && !sneaking) - playsound(get_turf(src), pick('sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg'), 50, 0, -5) + if(breath?.total_moles() > 0 && !HAS_TRAIT(src, TRAIT_ALIEN_SNEAK)) + playsound(get_turf(src), pick('sound/voice/lowHiss2.ogg', 'sound/voice/lowHiss3.ogg', 'sound/voice/lowHiss4.ogg'), 50, FALSE, -5) return ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index db3d5e6dc2cb3..805fde66755f5 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -40,7 +40,6 @@ maxHealth = 400 health = 400 icon_state = "alienq" - var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/queen() /mob/living/carbon/alien/humanoid/royal/queen/Initialize(mapload) RegisterSignal(src, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(set_countdown)) @@ -58,9 +57,12 @@ real_name = src.name - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/repulse/xeno(src)) - AddAbility(new/obj/effect/proc_holder/alien/royal/queen/promote()) + var/datum/action/spell/aoe/repulse/xeno/tail_whip = new(src) + tail_whip.Grant(src) + var/datum/action/small_sprite/queen/smallsprite = new(src) smallsprite.Grant(src) + var/datum/action/alien/promote/promotion = new(src) + promotion.Grant(src) return ..() /mob/living/carbon/alien/humanoid/royal/queen/create_internal_organs() @@ -69,7 +71,7 @@ internal_organs += new /obj/item/organ/alien/acid internal_organs += new /obj/item/organ/alien/neurotoxin internal_organs += new /obj/item/organ/alien/eggsac - ..() + return ..() /mob/living/carbon/alien/humanoid/royal/queen/proc/set_countdown() SIGNAL_HANDLER @@ -100,87 +102,109 @@ ..() //Queen verbs -/obj/effect/proc_holder/alien/lay_egg +/datum/action/alien/make_structure/lay_egg name = "Lay Egg" - desc = "Lay an egg to produce huggers to impregnate prey with. Costs 75 Plasma." + desc = "Lay an egg to produce huggers to impregnate prey with." + button_icon_state = "alien_egg" plasma_cost = 75 - check_turf = TRUE - action_icon_state = "alien_egg" + made_structure_type = /obj/structure/alien/egg + +/datum/action/alien/make_structure/lay_egg/on_activate(mob/user, atom/target) + . = ..() + owner.visible_message(("[owner] lays an egg!")) + +//Button to let queen choose her praetorian. +/datum/action/alien/promote + name = "Create Royal Parasite" + desc = "Produce a royal parasite to grant one of your children the honor of being your Praetorian." + button_icon_state = "alien_queen_promote" + /// The promotion only takes plasma when completed, not on activation. + var/promotion_plasma_cost = 500 + +/datum/action/alien/promote/update_stat_status(list/stat) + stat[STAT_STATUS] = GENERATE_STAT_TEXT("PLASMA - [promotion_plasma_cost]") -/obj/effect/proc_holder/alien/lay_egg/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/egg) in get_turf(user)) - to_chat(user, "There's already an egg here.") +/datum/action/alien/promote/is_available() + . = ..() + if(!.) + return FALSE + + var/mob/living/carbon/carbon_owner = owner + if(carbon_owner.getPlasma() < promotion_plasma_cost) return FALSE - if(!check_vent_block(user)) + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian)) return FALSE - user.visible_message("[user] has laid an egg!") - new /obj/structure/alien/egg(user.loc) return TRUE -//Button to let queen choose her praetorian. -/obj/effect/proc_holder/alien/royal/queen/promote - name = "Create Royal Parasite" - desc = "Produce a royal parasite to grant one of your children the honor of being your Praetorian. Costs 500 Plasma." - plasma_cost = 500 //Plasma cost used on promotion, not spawning the parasite. - - action_icon_state = "alien_queen_promote" - -/obj/effect/proc_holder/alien/royal/queen/promote/fire(mob/living/carbon/alien/user) - var/obj/item/queenpromote/prom - if(get_alien_type_in_hive(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, "You already have a Praetorian!") - return 0 - else - for(prom in user) - to_chat(user, "You discard [prom].") - qdel(prom) - return 0 - - prom = new (user.loc) - if(!user.put_in_active_hand(prom, 1)) - to_chat(user, "You must empty your hands before preparing the parasite.") - return 0 - else //Just in case telling the player only once is not enough! - to_chat(user, "Use the royal parasite on one of your children to promote her to Praetorian!") - return 0 - -/obj/item/queenpromote +/datum/action/alien/promote/on_activate(mob/user, atom/target) + var/obj/item/queen_promotion/existing_promotion = locate() in owner.held_items + if(existing_promotion) + to_chat(owner, ("You discard [existing_promotion].")) + owner.temporarilyRemoveItemFromInventory(existing_promotion) + qdel(existing_promotion) + return TRUE + + if(!owner.get_empty_held_indexes()) + to_chat(owner, ("You must have an empty hand before preparing the parasite.")) + return FALSE + + var/obj/item/queen_promotion/new_promotion = new(owner.loc) + if(!owner.put_in_hands(new_promotion, del_on_fail = TRUE)) + to_chat(owner, ("You fail to prepare a parasite.")) + return FALSE + + to_chat(owner, ("Use [new_promotion] on one of your children to promote her to a Praetorian!")) + return TRUE + +/obj/item/queen_promotion name = "\improper royal parasite" desc = "Inject this into one of your grown children to promote her to a Praetorian!" icon_state = "alien_medal" - item_flags = ABSTRACT | DROPDEL + item_flags = NOBLUDGEON | ABSTRACT | DROPDEL icon = 'icons/mob/alien.dmi' -/obj/item/queenpromote/Initialize(mapload) +/obj/item/queen_promotion/attack(mob/living/to_promote, mob/living/carbon/alien/humanoid/queen) . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) + if(.) + return + + var/datum/action/alien/promote/promotion = locate() in queen.actions + if(!promotion) + CRASH("[type] was created and handled by a mob ([queen]) that didn't have a promotion action associated.") -/obj/item/queenpromote/attack(mob/living/M, mob/living/carbon/alien/humanoid/user) - if(!isalienadult(M) || isalienroyal(M)) - to_chat(user, "You may only use this with your adult, non-royal children!") + if(!isalienadult(to_promote) || isalienroyal(to_promote)) + to_chat(queen, ("You may only use this with your adult, non-royal children!")) return - if(get_alien_type_in_hive(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, "You already have a Praetorian!") + + if(!promotion.is_available()) + to_chat(queen, ("You cannot promote a child right now!")) return - var/mob/living/carbon/alien/humanoid/A = M - if(A.stat == CONSCIOUS && A.mind && A.key) - if(!user.usePlasma(500)) - to_chat(user, "You must have 500 plasma stored to use this!") - return - - to_chat(A, "The queen has granted you a promotion to Praetorian!") - user.visible_message("[A] begins to expand, twist and contort!") - var/mob/living/carbon/alien/humanoid/royal/praetorian/new_prae = new (A.loc) - A.mind.transfer_to(new_prae) - qdel(A) - qdel(src) + if(to_promote.stat != CONSCIOUS || !to_promote.mind || !to_promote.key) return - else - to_chat(user, "This child must be alert and responsive to become a Praetorian!") -/obj/item/queenpromote/attack_self(mob/user) - to_chat(user, "You discard [src].") + queen.adjustPlasma(-promotion.promotion_plasma_cost) + + to_chat(queen, ("You have promoted [to_promote] to a Praetorian!")) + to_promote.visible_message( + ("[to_promote] begins to expand, twist and contort!"), + ("The queen has granted you a promotion to Praetorian!"), + ) + + var/mob/living/carbon/alien/humanoid/royal/praetorian/new_prae = new(to_promote.loc) + to_promote.mind.transfer_to(new_prae) + + qdel(to_promote) qdel(src) + return TRUE + +/obj/item/queen_promotion/attack_self(mob/user) + to_chat(user, ("You discard [src].")) + qdel(src) + +/obj/item/queen_promotion/dropped(mob/user, silent) + if(!silent) + to_chat(user, ("You discard [src].")) + return ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm index 42264f2534440..44dd2f22b2879 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/update_icons.dm @@ -4,7 +4,7 @@ for(var/I in overlays_standing) add_overlay(I) - var/asleep = IsSleeping() + var/are_we_drooling = istype(click_intercept, /datum/action/alien/acid) if(stat == DEAD) //If we mostly took damage from fire if(getFireLoss() > 125) @@ -12,7 +12,7 @@ else icon_state = "alien[caste]_dead" - else if((stat == UNCONSCIOUS && !asleep) || stat == HARD_CRIT || stat == SOFT_CRIT || IsParalyzed()) + else if((stat == UNCONSCIOUS && !IsSleeping()) || stat == HARD_CRIT || stat == SOFT_CRIT || IsParalyzed()) icon_state = "alien[caste]_unconscious" else if(leap_on_click) icon_state = "alien[caste]_pounce" @@ -21,11 +21,11 @@ icon_state = "alien[caste]_sleep" else if(mob_size == MOB_SIZE_LARGE) icon_state = "alien[caste]" - if(drooling) + if(are_we_drooling) add_overlay("alienspit_[caste]") else icon_state = "alien[caste]" - if(drooling) + if(are_we_drooling) add_overlay("alienspit") if(leaping) diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index 2702d366724b5..d31d00a154e45 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -33,16 +33,19 @@ //This is fine right now, if we're adding organ specific damage this needs to be updated /mob/living/carbon/alien/larva/Initialize(mapload) - - AddAbility(new/obj/effect/proc_holder/alien/hide(null)) - AddAbility(new/obj/effect/proc_holder/alien/larva_evolve(null)) - . = ..() + var/datum/action/alien/larva_evolve/evolution = new(src) + evolution.Grant(src) + var/datum/action/alien/hide/hide = new(src) + hide.Grant(src) + return ..() /mob/living/carbon/alien/larva/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel/small/tiny ..() //This needs to be fixed +// This comment is 12 years old I hope it's fixed by now +// 14 years old idk if it's fixed /mob/living/carbon/alien/larva/get_stat_tab_status() var/list/tab_data = ..() tab_data["Progress"] = GENERATE_STAT_TEXT("[amount_grown]/[max_grown]") diff --git a/code/modules/mob/living/carbon/alien/larva/powers.dm b/code/modules/mob/living/carbon/alien/larva/powers.dm index fd9b734910674..55f8d06802c74 100644 --- a/code/modules/mob/living/carbon/alien/larva/powers.dm +++ b/code/modules/mob/living/carbon/alien/larva/powers.dm @@ -1,67 +1,103 @@ -/obj/effect/proc_holder/alien/hide +/datum/action/alien/hide name = "Hide" - desc = "Allows aliens to hide beneath tables or certain items. Toggled on or off." + desc = "Allows you to hide beneath tables and certain objects." + button_icon_state = "alien_hide" plasma_cost = 0 + /// The layer we are on while hiding + var/hide_layer = ABOVE_NORMAL_TURF_LAYER - action_icon_state = "alien_hide" +/datum/action/alien/hide/on_activate(mob/user, atom/target) + if(owner.layer == hide_layer) + owner.layer = initial(owner.layer) + owner.visible_message( + ("[owner] slowly peeks up from the ground..."), + ("You stop hiding."), + ) -/obj/effect/proc_holder/alien/hide/fire(mob/living/carbon/alien/user) - if(user.stat != CONSCIOUS) - return - - if (user.layer != ABOVE_NORMAL_TURF_LAYER) - user.layer = ABOVE_NORMAL_TURF_LAYER - user.visible_message("[user] scurries to the ground!", \ - "You are now hiding.") else - user.layer = MOB_LAYER - user.visible_message("[user] slowly peeks up from the ground...", \ - "You stop hiding.") - return 1 + owner.layer = hide_layer + owner.visible_message( + ("[owner] scurries to the ground!"), + ("You are now hiding."), + ) + return TRUE -/obj/effect/proc_holder/alien/larva_evolve +/datum/action/alien/larva_evolve name = "Evolve" desc = "Evolve into a higher alien caste." + button_icon_state = "alien_evolve_larva" plasma_cost = 0 - action_icon_state = "alien_evolve_larva" +/datum/action/alien/larva_evolve/is_available() + . = ..() + if(!.) + return FALSE + if(!islarva(owner)) + return FALSE -/obj/effect/proc_holder/alien/larva_evolve/fire(mob/living/carbon/alien/user) - if(!islarva(user)) - return - var/mob/living/carbon/alien/larva/L = user + var/mob/living/carbon/alien/larva/larva = owner + if(larva.handcuffed || larva.legcuffed) // Cuffing larvas ? Eh ? + return FALSE + if(larva.amount_grown < larva.max_grown) + return FALSE + if(larva.movement_type & VENTCRAWLING) + return FALSE + + return TRUE + +/datum/action/alien/larva_evolve/on_activate(mob/user, atom/target) + var/mob/living/carbon/alien/larva/larva = owner + var/static/list/caste_options + if(!caste_options) + caste_options = list() + + // This can probably be genericized in the future. + var/mob/hunter_path = /mob/living/carbon/alien/humanoid/hunter + var/datum/radial_menu_choice/hunter = new() + hunter.name = "Hunter" + hunter.image = image(icon = initial(hunter_path.icon), icon_state = initial(hunter_path.icon_state)) + hunter.info = ("Hunters are the most agile caste, tasked with hunting for hosts. \ + They are faster than a human and can even pounce, but are not much tougher than a drone.") - if(L.handcuffed || L.legcuffed) // Cuffing larvas ? Eh ? - to_chat(user, "You cannot evolve when you are cuffed.") + caste_options["Hunter"] = hunter + + var/mob/sentinel_path = /mob/living/carbon/alien/humanoid/sentinel + var/datum/radial_menu_choice/sentinel = new() + sentinel.name = "Sentinel" + sentinel.image = image(icon = initial(sentinel_path.icon), icon_state = initial(sentinel_path.icon_state)) + sentinel.info = ("Sentinels are tasked with protecting the hive. \ + With their ranged spit, invisibility, and high health, they make formidable guardians \ + and acceptable secondhand hunters.") + + caste_options["Sentinel"] = sentinel + + var/mob/drone_path = /mob/living/carbon/alien/humanoid/drone + var/datum/radial_menu_choice/drone = new() + drone.name = "Drone" + drone.image = image(icon = initial(drone_path.icon), icon_state = initial(drone_path.icon_state)) + drone.info = ("Drones are the weakest and slowest of the castes, \ + but can grow into a praetorian and then queen if no queen exists, \ + and are vital to maintaining a hive with their resin secretion abilities.") + + caste_options["Drone"] = drone + + var/alien_caste = show_radial_menu(owner, owner, caste_options, radius = 38, require_near = TRUE, tooltips = TRUE) + if(QDELETED(src) || QDELETED(owner) || !is_available() || !alien_caste) return - if(L.movement_type & VENTCRAWLING) - to_chat(user, "You cannot evolve while in a vent.") + if(alien_caste == null) return + var/mob/living/carbon/alien/humanoid/new_xeno + switch(alien_caste) + if("Hunter") + new_xeno = new /mob/living/carbon/alien/humanoid/hunter(larva.loc) + if("Sentinel") + new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(larva.loc) + if("Drone") + new_xeno = new /mob/living/carbon/alien/humanoid/drone(larva.loc) + else + CRASH("Alien evolve was given an invalid / incorrect alien cast type. Got: [alien_caste]") - if(L.amount_grown >= L.max_grown) //TODO ~Carn - to_chat(L, "You are growing into a beautiful alien! It is time to choose a caste.") - to_chat(L, "There are three to choose from:") - to_chat(L, "Hunters are the most agile caste, tasked with hunting for hosts. They are faster than a human and can even pounce, but are not much tougher than a drone.") - to_chat(L, "Sentinels are tasked with protecting the hive. With their ranged spit, invisibility, and high health, they make formidable guardians and acceptable secondhand hunters.") - to_chat(L, "Drones are the weakest and slowest of the castes, but can grow into a praetorian and then queen if no queen exists, and are vital to maintaining a hive with their resin secretion abilities.") - var/alien_caste = alert(L, "Please choose which alien caste you shall belong to.",,"Hunter","Sentinel","Drone") - - if(user.incapacitated()) //something happened to us while we were choosing. - return - - var/mob/living/carbon/alien/humanoid/new_xeno - switch(alien_caste) - if("Hunter") - new_xeno = new /mob/living/carbon/alien/humanoid/hunter(L.loc) - if("Sentinel") - new_xeno = new /mob/living/carbon/alien/humanoid/sentinel(L.loc) - if("Drone") - new_xeno = new /mob/living/carbon/alien/humanoid/drone(L.loc) - - L.alien_evolve(new_xeno) - return 0 - else - to_chat(user, "You are not fully grown.") - return 0 + larva.alien_evolve(new_xeno) + return TRUE diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index a67bc637986aa..697d599ab100a 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -2,29 +2,6 @@ icon_state = "acid" visual = FALSE food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/toxin/acid = 10) - var/list/alien_powers = list() - -/obj/item/organ/alien/Initialize(mapload) - . = ..() - for(var/A in alien_powers) - if(ispath(A)) - alien_powers -= A - alien_powers += new A(src) - -/obj/item/organ/alien/Destroy() - QDEL_LIST(alien_powers) - return ..() - -/obj/item/organ/alien/Insert(mob/living/carbon/M, special = 0, pref_load = FALSE) - . = ..() - for(var/obj/effect/proc_holder/alien/P in alien_powers) - M.AddAbility(P) - -/obj/item/organ/alien/Remove(mob/living/carbon/M, special = 0, pref_load = FALSE) - for(var/obj/effect/proc_holder/alien/P in alien_powers) - M.RemoveAbility(P) - return ..() - /obj/item/organ/alien/plasmavessel name = "plasma vessel" @@ -32,10 +9,14 @@ w_class = WEIGHT_CLASS_NORMAL zone = BODY_ZONE_CHEST slot = "plasmavessel" - alien_powers = list(/obj/effect/proc_holder/alien/plant, /obj/effect/proc_holder/alien/transfer) + actions_types = list( + /datum/action/alien/make_structure/plant_weeds, + /datum/action/alien/transfer, + ) food_reagents = list(/datum/reagent/consumable/nutriment = 5, /datum/reagent/toxin/plasma = 10) - var/storedPlasma = 100 + /// The current amount of stored plasma. + var/stored_plasma = 100 var/max_plasma = 250 var/heal_rate = 5 var/plasma_rate = 10 @@ -44,7 +25,7 @@ name = "large plasma vessel" icon_state = "plasma_large" w_class = WEIGHT_CLASS_BULKY - storedPlasma = 200 + stored_plasma = 200 max_plasma = 500 plasma_rate = 15 @@ -55,7 +36,7 @@ name = "small plasma vessel" icon_state = "plasma_small" w_class = WEIGHT_CLASS_SMALL - storedPlasma = 100 + stored_plasma = 100 max_plasma = 150 plasma_rate = 5 @@ -64,7 +45,7 @@ icon_state = "plasma_tiny" w_class = WEIGHT_CLASS_TINY max_plasma = 100 - alien_powers = list(/obj/effect/proc_holder/alien/transfer) + actions_types = list(/datum/action/alien/transfer) /obj/item/organ/alien/plasmavessel/on_life() //If there are alien weeds on the ground then heal if needed or give some plasma @@ -105,7 +86,7 @@ zone = BODY_ZONE_HEAD slot = "hivenode" w_class = WEIGHT_CLASS_TINY - alien_powers = list(/obj/effect/proc_holder/alien/whisper) + actions_types = list(/datum/action/alien/whisper) var/recent_queen_death = 0 //Indicates if the queen died recently, aliens are heavily weakened while this is active. /obj/item/organ/alien/hivenode/Insert(mob/living/carbon/M, special = 0, pref_load = FALSE) @@ -158,7 +139,7 @@ icon_state = "stomach-x" zone = BODY_ZONE_PRECISE_MOUTH slot = "resinspinner" - alien_powers = list(/obj/effect/proc_holder/alien/resin) + actions_types = list(/datum/action/alien/make_structure/resin) /obj/item/organ/alien/acid @@ -166,7 +147,7 @@ icon_state = "acid" zone = BODY_ZONE_PRECISE_MOUTH slot = "acidgland" - alien_powers = list(/obj/effect/proc_holder/alien/acid) + actions_types = list(/datum/action/alien/acid/corrosion) /obj/item/organ/alien/neurotoxin @@ -174,7 +155,7 @@ icon_state = "neurotox" zone = BODY_ZONE_PRECISE_MOUTH slot = "neurotoxingland" - alien_powers = list(/obj/effect/proc_holder/alien/neurotoxin) + actions_types = list(/datum/action/alien/acid/neurotoxin) /obj/item/organ/alien/eggsac @@ -183,4 +164,4 @@ zone = BODY_ZONE_PRECISE_GROIN slot = "eggsac" w_class = WEIGHT_CLASS_BULKY - alien_powers = list(/obj/effect/proc_holder/alien/lay_egg) + actions_types = list(/datum/action/alien/make_structure/lay_egg) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index fd7e668c4103e..8f0de91504f41 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -205,7 +205,7 @@ CREATION_TEST_IGNORE_SELF(/mob/living/carbon) /mob/living/carbon/on_fall() . = ..() - loc.handle_fall(src)//it's loc so it doesn't call the mob's handle_fall which does nothing + loc?.handle_fall(src)//it's loc so it doesn't call the mob's handle_fall which does nothing /mob/living/carbon/is_muzzled() return(istype(src.wear_mask, /obj/item/clothing/mask/muzzle)) @@ -400,15 +400,11 @@ CREATION_TEST_IGNORE_SELF(/mob/living/carbon) var/list/tab_data = ..() var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) if(vessel) - tab_data["Plasma Stored"] = GENERATE_STAT_TEXT("[vessel.storedPlasma]/[vessel.max_plasma]") + tab_data += "Plasma Stored: [vessel.stored_plasma]/[vessel.max_plasma]" if(locate(/obj/item/assembly/health) in src) tab_data["Health"] = GENERATE_STAT_TEXT("[health]") return tab_data -/mob/living/carbon/Stat() - ..() - add_abilities_to_panel() - /mob/living/carbon/attack_ui(slot) if(!has_hand_for_held_index(active_hand_index)) return 0 diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index 179c44f5bc71f..e8fc5ab00915e 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -40,6 +40,45 @@ return s_store return null +/mob/living/carbon/human/get_slot_by_item(obj/item/looking_for) + if(looking_for == belt) + return ITEM_SLOT_BELT + + if(looking_for == wear_id) + return ITEM_SLOT_ID + + if(looking_for == ears) + return ITEM_SLOT_EARS + + if(looking_for == glasses) + return ITEM_SLOT_EYES + + if(looking_for == gloves) + return ITEM_SLOT_GLOVES + + if(looking_for == head) + return ITEM_SLOT_HEAD + + if(looking_for == shoes) + return ITEM_SLOT_FEET + + if(looking_for == wear_suit) + return ITEM_SLOT_OCLOTHING + + if(looking_for == w_uniform) + return ITEM_SLOT_ICLOTHING + + if(looking_for == r_store) + return ITEM_SLOT_RPOCKET + + if(looking_for == l_store) + return ITEM_SLOT_LPOCKET + + if(looking_for == s_store) + return ITEM_SLOT_SUITSTORE + + return ..() + /mob/living/carbon/human/proc/get_all_slots() . = get_head_slots() | get_body_slots() diff --git a/code/modules/mob/living/carbon/human/species_types/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm index 90696ecee5f1c..ffc6ad3638db3 100644 --- a/code/modules/mob/living/carbon/human/species_types/IPC.dm +++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm @@ -107,7 +107,7 @@ icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "drone_vision" -/datum/action/innate/change_screen/Activate() +/datum/action/innate/change_screen/on_activate() var/screen_choice = input(usr, "Which screen do you want to use?", "Screen Change") as null | anything in GLOB.ipc_screens_list var/color_choice = input(usr, "Which color do you want your screen to be?", "Color Change") as null | color if(!screen_choice) diff --git a/code/modules/mob/living/carbon/human/species_types/diona.dm b/code/modules/mob/living/carbon/human/species_types/diona.dm index d77a46f069d4c..0ff8729d43c41 100644 --- a/code/modules/mob/living/carbon/human/species_types/diona.dm +++ b/code/modules/mob/living/carbon/human/species_types/diona.dm @@ -61,12 +61,12 @@ if(H.stat != CONSCIOUS) H.remove_status_effect(STATUS_EFFECT_PLANTHEALING) if((H.health <= H.crit_threshold)) //Shit, we're dying! Scatter! - split_ability.Trigger(TRUE) + split_ability.split(FALSE, H) if(H.nutrition > NUTRITION_LEVEL_WELL_FED && !informed_nymph) informed_nymph = TRUE to_chat(H, "You feel sufficiently satiated to allow a nymph to split off from your gestalt!") if(partition_ability) - partition_ability.UpdateButtonIcon() + partition_ability.update_buttons() if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) var/light_amount = 0 //how much light there is in the place, affects receiving nutrition and healing @@ -88,7 +88,7 @@ /datum/species/diona/spec_updatehealth(mob/living/carbon/human/H) var/mob/living/simple_animal/hostile/retaliate/nymph/drone = drone_ref?.resolve() if(H.stat != CONSCIOUS && !H.mind && drone) //If the home body is not fully conscious, they dont have a mind and have a drone - drone.switch_ability.Trigger(H) //Bring them home. + drone.switch_ability.trigger() //Bring them home. /datum/species/diona/handle_mutations_and_radiation(mob/living/carbon/human/H) . = FALSE @@ -125,7 +125,7 @@ if(gibbed) QDEL_NULL(H) return - split_ability.Trigger(TRUE) + split_ability.split(gibbed, H) /datum/species/diona/spec_gib(no_brain, no_organs, no_bodyparts, mob/living/carbon/human/H) H.unequip_everything() @@ -180,18 +180,13 @@ background_icon_state = "bg_default" icon_icon = 'icons/hud/actions/actions_spells.dmi' button_icon_state = "split" + check_flags = AB_CHECK_DEAD var/Activated = FALSE -/datum/action/diona/split/Trigger(special) - . = ..() - var/mob/living/carbon/human/user = owner - if(!isdiona(user)) - return FALSE - if(special) - startSplitting(FALSE, user) //This runs when you are dead. - return TRUE - if(user.incapacitated(IGNORE_RESTRAINTS)) //Are we incapacitated right now? - return FALSE +/datum/action/diona/split/is_available() + return ..() && isdiona(owner) + +/datum/action/diona/split/on_activate(mob/user, atom/target) if(tgui_alert(usr, "Are we sure we wish to devolve ourselves and split into separated nymphs?",,list("Yes", "No")) != "Yes") return FALSE if(do_after(user, 8 SECONDS, user, hidden = TRUE)) @@ -251,22 +246,20 @@ background_icon_state = "bg_default" icon_icon = 'icons/hud/actions/actions_spells.dmi' button_icon_state = "grow" - var/ability_partition_cooldown + cooldown_time = 5 MINUTES + var/ability_partition_cooldow -/datum/action/diona/partition/Trigger(special) - . = ..() - if(!IsAvailable()) - return +/datum/action/diona/partition/on_activate(mob/user, atom/target) var/mob/living/carbon/human/H = owner - ability_partition_cooldown = world.time + 5 MINUTES + start_cooldown() H.nutrition = NUTRITION_LEVEL_STARVING playsound(H, 'sound/creatures/venus_trap_death.ogg', 25, 1) new /mob/living/simple_animal/hostile/retaliate/nymph(H.loc) -/datum/action/diona/partition/IsAvailable() +/datum/action/diona/partition/is_available() if(..()) var/mob/living/carbon/human/H = owner - if(H.nutrition >= NUTRITION_LEVEL_WELL_FED && (ability_partition_cooldown <= world.time)) + if(H.nutrition >= NUTRITION_LEVEL_WELL_FED) return TRUE return FALSE diff --git a/code/modules/mob/living/carbon/human/species_types/dullahan.dm b/code/modules/mob/living/carbon/human/species_types/dullahan.dm index dfb68c3d0aaa0..5fdcbc468e1f0 100644 --- a/code/modules/mob/living/carbon/human/species_types/dullahan.dm +++ b/code/modules/mob/living/carbon/human/species_types/dullahan.dm @@ -114,7 +114,7 @@ // As it's the first time there's a client in our mob, we can finally update its vision to place it in the head instead! var/datum/action/item_action/organ_action/dullahan/eyes_toggle_perspective_action = locate() in eyes?.actions - eyes_toggle_perspective_action?.Trigger() + eyes_toggle_perspective_action?.trigger() owner_first_client_connection_handled = TRUE @@ -192,8 +192,7 @@ name = "Toggle Perspective" desc = "Switch between seeing normally from your head, or blindly from your body." -/datum/action/item_action/organ_action/dullahan/Trigger() - . = ..() +/datum/action/item_action/organ_action/dullahan/on_activate(mob/user, atom/target) var/obj/item/organ/eyes/dullahan/dullahan_eyes = target dullahan_eyes.tint = dullahan_eyes.tint ? NONE : INFINITY diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index 5c55063e583b6..4a60a5c1c52fe 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -95,7 +95,7 @@ /datum/species/golem/adamantine/on_species_gain(mob/living/carbon/C, datum/species/old_species) ..() - C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, _magic = TRUE, _holy = FALSE) + C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, MAGIC_RESISTANCE) /datum/species/golem/adamantine/on_species_loss(mob/living/carbon/C) for (var/datum/component/anti_magic/anti_magic in C.GetComponents(/datum/component/anti_magic)) @@ -153,8 +153,9 @@ desc = "Set yourself aflame, bringing yourself closer to exploding!" check_flags = AB_CHECK_CONSCIOUS button_icon_state = "sacredflame" + icon_icon = 'icons/hud/actions/actions_spells.dmi' -/datum/action/innate/ignite/Activate() +/datum/action/innate/ignite/on_activate() if(ishuman(owner)) var/mob/living/carbon/human/H = owner if(H.fire_stacks) @@ -198,7 +199,7 @@ /datum/species/golem/silver/on_species_gain(mob/living/carbon/C, datum/species/old_species) ..() - C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, _magic = FALSE, _holy = TRUE) + C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, MAGIC_RESISTANCE_HOLY) /datum/species/golem/silver/on_species_loss(mob/living/carbon/C) for (var/datum/component/anti_magic/anti_magic in C.GetComponents(/datum/component/anti_magic)) @@ -514,13 +515,13 @@ var/cooldown = 150 var/last_teleport = 0 -/datum/action/innate/unstable_teleport/IsAvailable() +/datum/action/innate/unstable_teleport/is_available() if(..()) if(world.time > last_teleport + cooldown) return 1 return 0 -/datum/action/innate/unstable_teleport/Activate() +/datum/action/innate/unstable_teleport/on_activate() var/mob/living/carbon/human/H = owner H.visible_message("[H] starts vibrating!", "You start charging your bluespace core...") playsound(get_turf(H), 'sound/weapons/flash.ogg', 25, 1) @@ -534,9 +535,9 @@ spark_system.start() do_teleport(H, get_turf(H), 12, asoundin = 'sound/weapons/emitter2.ogg', channel = TELEPORT_CHANNEL_BLUESPACE) last_teleport = world.time - UpdateButtonIcon() //action icon looks unavailable + update_buttons() //action icon looks unavailable //action icon looks available again - addtimer(CALLBACK(src, PROC_REF(UpdateButtonIcon)), cooldown + 5) + addtimer(CALLBACK(src, PROC_REF(update_buttons)), cooldown + 5) //honk @@ -639,9 +640,12 @@ random_eligible = FALSE //Zesko claims runic golems break the game inherent_factions = list("cult") species_language_holder = /datum/language_holder/golem/runic - var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/golem/phase_shift - var/obj/effect/proc_holder/spell/targeted/abyssal_gaze/abyssal_gaze - var/obj/effect/proc_holder/spell/targeted/dominate/dominate + /// A ref to our jaunt spell that we get on species gain. + var/datum/action/spell/jaunt/ethereal_jaunt/shift/golem/jaunt + /// A ref to our gaze spell that we get on species gain. + var/datum/action/spell/pointed/abyssal_gaze/abyssal_gaze + /// A ref to our dominate spell that we get on species gain. + var/datum/action/spell/pointed/dominate/dominate species_chest = /obj/item/bodypart/chest/golem/cult species_head = /obj/item/bodypart/head/golem/cult @@ -656,29 +660,30 @@ var/golem_name = "[edgy_first_name] [edgy_last_name]" return golem_name -/datum/species/golem/runic/on_species_gain(mob/living/carbon/C, datum/species/old_species) +/datum/species/golem/runic/on_species_gain(mob/living/carbon/grant_to, datum/species/old_species) . = ..() - phase_shift = new - phase_shift.charge_counter = 0 - phase_shift.start_recharge() - C.AddSpell(phase_shift) - abyssal_gaze = new - abyssal_gaze.charge_counter = 0 - abyssal_gaze.start_recharge() - C.AddSpell(abyssal_gaze) - dominate = new - dominate.charge_counter = 0 - dominate.start_recharge() - C.AddSpell(dominate) + // Create our species specific spells here. + // Note we link them to the mob, not the mind, + // so they're not moved around on mindswaps + jaunt = new(grant_to) + jaunt.start_cooldown() + jaunt.Grant(grant_to) + + abyssal_gaze = new(grant_to) + abyssal_gaze.start_cooldown() + abyssal_gaze.Grant(grant_to) + + dominate = new(grant_to) + dominate.start_cooldown() + dominate.Grant(grant_to) /datum/species/golem/runic/on_species_loss(mob/living/carbon/C) - . = ..() - if(phase_shift) - C.RemoveSpell(phase_shift) - if(abyssal_gaze) - C.RemoveSpell(abyssal_gaze) - if(dominate) - C.RemoveSpell(dominate) + // Aaand cleanup our species specific spells. + // No free rides. + QDEL_NULL(jaunt) + QDEL_NULL(abyssal_gaze) + QDEL_NULL(dominate) + return ..() /datum/species/golem/runic/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) if(istype(chem, /datum/reagent/water/holywater)) @@ -776,7 +781,7 @@ /datum/species/golem/cloth/on_species_gain(mob/living/carbon/C, datum/species/old_species) ..() - C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, _magic = FALSE, _holy = TRUE) + C.AddComponent(/datum/component/anti_magic, SPECIES_TRAIT, MAGIC_RESISTANCE_HOLY) /datum/species/golem/cloth/on_species_loss(mob/living/carbon/C) for (var/datum/component/anti_magic/anti_magic in C.GetComponents(/datum/component/anti_magic)) @@ -1153,7 +1158,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/cloth_pile) var/last_use var/snas_chance = 3 -/datum/action/innate/bonechill/Activate() +/datum/action/innate/bonechill/on_activate() if(world.time < last_use + cooldown) to_chat(owner, "You aren't ready yet to rattle your bones again.") return @@ -1190,8 +1195,10 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/cloth_pile) species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES,NOTRANSSTING) //no mutcolors, no eye sprites inherent_traits = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER) - var/obj/effect/proc_holder/spell/targeted/conjure_item/snowball/ball - var/obj/effect/proc_holder/spell/aimed/cryo/cryo + /// A ref to our "throw snowball" spell we get on species gain. + var/datum/action/spell/conjure_item/snowball/snowball + /// A ref to our cryobeam spell we get on species gain. + var/datum/action/spell/pointed/projectile/cryo/cryo species_chest = /obj/item/bodypart/chest/golem/snow species_head = /obj/item/bodypart/head/golem/snow @@ -1209,33 +1216,23 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/cloth_pile) new /obj/item/food/grown/carrot(get_turf(H)) qdel(H) -/datum/species/golem/snow/on_species_gain(mob/living/carbon/C, datum/species/old_species) +/datum/species/golem/snow/on_species_gain(mob/living/carbon/grant_to, datum/species/old_species) . = ..() - C.weather_immunities |= "snow" - ball = new - ball.charge_counter = 0 - ball.start_recharge() - C.AddSpell(ball) - cryo = new - cryo.charge_counter = 0 - cryo.start_recharge() - C.AddSpell(cryo) - -/datum/species/golem/snow/on_species_loss(mob/living/carbon/C) - . = ..() - C.weather_immunities -= "snow" - if(ball) - C.RemoveSpell(ball) - if(cryo) - C.RemoveSpell(cryo) - -/obj/effect/proc_holder/spell/targeted/conjure_item/snowball - name = "Snowball" - desc = "Concentrates cryokinetic forces to create snowballs, useful for throwing at people." - item_type = /obj/item/toy/snowball - charge_max = 15 - action_icon = 'icons/obj/toy.dmi' - action_icon_state = "snowball" + ADD_TRAIT(grant_to, TRAIT_SNOWSTORM_IMMUNE, SPECIES_TRAIT) + + snowball = new(grant_to) + snowball.start_cooldown() + snowball.Grant(grant_to) + + cryo = new(grant_to) + cryo.start_cooldown() + cryo.Grant(grant_to) + +/datum/species/golem/snow/on_species_loss(mob/living/carbon/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_SNOWSTORM_IMMUNE, SPECIES_TRAIT) + QDEL_NULL(snowball) + QDEL_NULL(cryo) + return ..() /datum/species/golem/capitalist name = "Capitalist Golem" @@ -1259,14 +1256,15 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/cloth_pile) C.revive(full_heal = TRUE) SEND_SOUND(C, sound('sound/misc/capitialism.ogg')) - C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) + var/datum/action/spell/aoe/knock/K = new /datum/action/spell/aoe/knock + K.Grant(C) RegisterSignal(C, COMSIG_MOB_SAY, PROC_REF(handle_speech)) /datum/species/golem/capitalist/on_species_loss(mob/living/carbon/C) . = ..() UnregisterSignal(C, COMSIG_MOB_SAY) - for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) - C.RemoveSpell(spell) + var/datum/action/spell/aoe/knock/K = new /datum/action/spell/aoe/knock + K.Remove(C) /datum/species/golem/capitalist/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) ..() @@ -1302,13 +1300,14 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/cloth_pile) C.revive(full_heal = TRUE) SEND_SOUND(C, sound('sound/misc/Russian_Anthem_chorus.ogg')) - C.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock ()) + var/datum/action/spell/aoe/knock/K = new /datum/action/spell/aoe/knock + K.Grant(C) RegisterSignal(C, COMSIG_MOB_SAY, PROC_REF(handle_speech)) /datum/species/golem/soviet/on_species_loss(mob/living/carbon/C) . = ..() - for(var/obj/effect/proc_holder/spell/aoe_turf/knock/spell in C.mob_spell_list) - C.RemoveSpell(spell) + var/datum/action/spell/aoe/knock/K = new /datum/action/spell/aoe/knock + K.Remove(C) UnregisterSignal(C, COMSIG_MOB_SAY, PROC_REF(handle_speech)) /datum/species/golem/soviet/spec_unarmedattacked(mob/living/carbon/human/user, mob/living/carbon/human/target) diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 6eed45779e6b4..b47715c8b6099 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -83,14 +83,14 @@ icon_icon = 'icons/hud/actions/actions_slime.dmi' background_icon_state = "bg_alien" -/datum/action/innate/split_body/IsAvailable() +/datum/action/innate/split_body/is_available() if(..()) var/mob/living/carbon/human/H = owner if(H.blood_volume >= BLOOD_VOLUME_SLIME_SPLIT) return 1 return 0 -/datum/action/innate/split_body/Activate() +/datum/action/innate/split_body/on_activate() var/mob/living/carbon/human/H = owner if(!isslimeperson(H)) return @@ -164,7 +164,7 @@ icon_icon = 'icons/hud/actions/actions_slime.dmi' background_icon_state = "bg_alien" -/datum/action/innate/swap_body/Activate() +/datum/action/innate/swap_body/on_activate() if(!isslimeperson(owner)) to_chat(owner, "You are not a slimeperson.") Remove(owner) @@ -340,9 +340,9 @@ /datum/species/oozeling/luminescent/proc/update_slime_actions() integrate_extract.update_name() - integrate_extract.UpdateButtonIcon() - extract_minor.UpdateButtonIcon() - extract_major.UpdateButtonIcon() + integrate_extract.update_buttons() + extract_minor.update_buttons() + extract_major.update_buttons() /datum/species/oozeling/luminescent/on_species_loss(mob/living/carbon/C) . = ..() @@ -369,7 +369,7 @@ background_icon_state = "bg_alien" /datum/action/innate/integrate_extract/proc/update_name() - var/datum/species/oozeling/luminescent/species = target + var/datum/species/oozeling/luminescent/species = owner if(!species || !species.current_extract) name = "Integrate Extract" desc = "Eat a slime extract to use its properties." @@ -377,23 +377,23 @@ name = "Eject Extract" desc = "Eject your current slime extract." -/datum/action/innate/integrate_extract/UpdateButtonIcon(status_only, force) - var/datum/species/oozeling/luminescent/species = target +/datum/action/innate/integrate_extract/update_buttons(status_only, force) + var/datum/species/oozeling/luminescent/species = owner if(!species || !species.current_extract) button_icon_state = "slimeconsume" else button_icon_state = "slimeeject" ..() -/datum/action/innate/integrate_extract/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force) +/datum/action/innate/integrate_extract/apply_icon(atom/movable/screen/movable/action_button/current_button, force) ..(current_button, TRUE) - var/datum/species/oozeling/luminescent/species = target + var/datum/species/oozeling/luminescent/species = owner if(species?.current_extract) current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) -/datum/action/innate/integrate_extract/Activate() +/datum/action/innate/integrate_extract/on_activate() var/mob/living/carbon/human/H = owner - var/datum/species/oozeling/luminescent/species = target + var/datum/species/oozeling/luminescent/species = owner if(!is_species(H, /datum/species/oozeling/luminescent) || !species) return CHECK_DNA_AND_SPECIES(H) @@ -430,21 +430,21 @@ background_icon_state = "bg_alien" var/activation_type = SLIME_ACTIVATE_MINOR -/datum/action/innate/use_extract/IsAvailable() +/datum/action/innate/use_extract/is_available() if(..()) - var/datum/species/oozeling/luminescent/species = target + var/datum/species/oozeling/luminescent/species = owner if(species && species.current_extract && (world.time > species.extract_cooldown)) return TRUE return FALSE -/datum/action/innate/use_extract/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force) +/datum/action/innate/use_extract/apply_icon(atom/movable/screen/movable/action_button/current_button, force) ..(current_button, TRUE) var/mob/living/carbon/human/H = owner var/datum/species/oozeling/luminescent/species = H.dna.species if(species && species.current_extract) current_button.add_overlay(mutable_appearance(species.current_extract.icon, species.current_extract.icon_state)) -/datum/action/innate/use_extract/Activate() +/datum/action/innate/use_extract/on_activate() var/mob/living/carbon/human/H = owner CHECK_DNA_AND_SPECIES(H) var/datum/species/oozeling/luminescent/species = H.dna.species @@ -816,7 +816,7 @@ GLOBAL_LIST_EMPTY(slime_links_by_mind) icon_icon = 'icons/hud/actions/actions_slime.dmi' background_icon_state = "bg_alien" -/datum/action/innate/linked_speech/Activate() +/datum/action/innate/linked_speech/on_activate() var/mob/living/living_owner = owner if(!living_owner || !istype(living_owner) || living_owner.stat == DEAD) return @@ -835,7 +835,7 @@ GLOBAL_LIST_EMPTY(slime_links_by_mind) icon_icon = 'icons/hud/actions/actions_slime.dmi' background_icon_state = "bg_alien" -/datum/action/innate/project_thought/Activate() +/datum/action/innate/project_thought/on_activate() var/mob/living/carbon/human/human_owner = owner if(!isstargazer(human_owner)) return @@ -878,7 +878,7 @@ GLOBAL_LIST_EMPTY(slime_links_by_mind) icon_icon = 'icons/hud/actions/actions_slime.dmi' background_icon_state = "bg_alien" -/datum/action/innate/link_minds/Activate() +/datum/action/innate/link_minds/on_activate() var/mob/living/carbon/human/human_owner = owner if(!isstargazer(human_owner)) return @@ -918,7 +918,7 @@ GLOBAL_LIST_EMPTY(slime_links_by_mind) icon_icon = 'icons/hud/actions/actions_slime.dmi' background_icon_state = "bg_alien" -/datum/action/innate/unlink_minds/Activate() +/datum/action/innate/unlink_minds/on_activate() var/mob/living/carbon/human/human_owner = owner if(!isstargazer(human_owner)) return diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 7b32986dea02a..0951e68c3e145 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -20,7 +20,7 @@ meat = /obj/item/food/meat/slab/human/mutant/lizard skinned_type = /obj/item/stack/sheet/animalhide/lizard exotic_bloodtype = "L" - inert_mutation = FIREBREATH + //inert_mutation = FIREBREATH deathsound = 'sound/voice/lizard/deathsound.ogg' species_language_holder = /datum/language_holder/lizard digitigrade_customization = DIGITIGRADE_OPTIONAL diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 6b2683571c83c..889fc2fdf6dab 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -77,7 +77,7 @@ /datum/species/moth/spec_life(mob/living/carbon/human/H) if(cocoon_action) - cocoon_action.UpdateButtonIcon() + cocoon_action.update_buttons() /datum/action/innate/cocoon name = "Cocoon" @@ -86,7 +86,7 @@ button_icon_state = "wrap_0" icon_icon = 'icons/hud/actions/actions_animal.dmi' -/datum/action/innate/cocoon/Activate() +/datum/action/innate/cocoon/on_activate() var/mob/living/carbon/H = owner var/obj/item/organ/wingcheck = H.getorgan(/obj/item/organ/wings/moth) if(!wingcheck) //This is to stop easy organ farms @@ -122,7 +122,7 @@ else to_chat(H, "You need to hold still in order to weave a cocoon!") -/datum/action/innate/cocoon/IsAvailable() +/datum/action/innate/cocoon/is_available() if(..()) var/mob/living/carbon/human/H = owner if(HAS_TRAIT(H, TRAIT_MOTH_BURNT)) diff --git a/code/modules/mob/living/carbon/human/species_types/oozelings.dm b/code/modules/mob/living/carbon/human/species_types/oozelings.dm index a3aaaeefebfee..4491382821d61 100644 --- a/code/modules/mob/living/carbon/human/species_types/oozelings.dm +++ b/code/modules/mob/living/carbon/human/species_types/oozelings.dm @@ -19,7 +19,7 @@ changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT species_language_holder = /datum/language_holder/oozeling swimming_component = /datum/component/swimming/dissolve - inert_mutation = ACIDOOZE + //inert_mutation = ACIDOOZE species_chest = /obj/item/bodypart/chest/oozeling species_head = /obj/item/bodypart/head/oozeling @@ -99,7 +99,7 @@ if(H.blood_volume < BLOOD_VOLUME_OKAY) Cannibalize_Body(H) if(regenerate_limbs) - regenerate_limbs.UpdateButtonIcon() + regenerate_limbs.update_buttons() /datum/species/oozeling/proc/Cannibalize_Body(mob/living/carbon/human/H) var/list/limbs_to_consume = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_R_LEG, BODY_ZONE_L_LEG) - H.get_missing_limbs() @@ -126,7 +126,7 @@ icon_icon = 'icons/hud/actions/actions_slime.dmi' background_icon_state = "bg_alien" -/datum/action/innate/regenerate_limbs/IsAvailable() +/datum/action/innate/regenerate_limbs/is_available() if(..()) var/mob/living/carbon/human/H = owner var/list/limbs_to_heal = H.get_missing_limbs() @@ -134,7 +134,7 @@ return TRUE return FALSE -/datum/action/innate/regenerate_limbs/Activate() +/datum/action/innate/regenerate_limbs/on_activate() var/mob/living/carbon/human/H = owner var/list/limbs_to_heal = H.get_missing_limbs() if(!LAZYLEN(limbs_to_heal)) diff --git a/code/modules/mob/living/carbon/human/species_types/psyphoza.dm b/code/modules/mob/living/carbon/human/species_types/psyphoza.dm index 5c60a5e976f13..480ade6dde569 100644 --- a/code/modules/mob/living/carbon/human/species_types/psyphoza.dm +++ b/code/modules/mob/living/carbon/human/species_types/psyphoza.dm @@ -64,7 +64,7 @@ /datum/species/psyphoza/primary_species_action() . = ..() - PH?.Trigger() + PH?.trigger() /datum/species/psyphoza/get_species_description() return "Psyphoza are a species of extra-sensory lesser-sensory \ @@ -193,14 +193,8 @@ ///Start auto timer addtimer(CALLBACK(src, PROC_REF(auto_sense)), auto_cooldown) -/datum/action/item_action/organ_action/psychic_highlight/IsAvailable() - if(has_cooldown_timer) - return FALSE - return ..() - -/datum/action/item_action/organ_action/psychic_highlight/Trigger() - . = ..() - if(has_cooldown_timer || !owner || !check_head()) +/datum/action/item_action/organ_action/psychic_highlight/on_activate(mob/user, atom/target) + if(!owner || !check_head()) return //Reveal larger area of sense dim_overlay() @@ -209,14 +203,8 @@ if(BS) for(var/mob/living/L in urange(9, owner, 1)) BS.highlight_object(L, "mob", L.dir) - has_cooldown_timer = TRUE - UpdateButtonIcon() - addtimer(CALLBACK(src, PROC_REF(finish_cooldown)), cooldown + sense_time) - -/datum/action/item_action/organ_action/psychic_highlight/UpdateButtonIcon(status_only = FALSE, force = FALSE) - . = ..() - if(!IsAvailable()) - button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0) //Overwrite this line from the original to support my fucked up use + update_buttons() + addtimer(CALLBACK(src, PROC_REF(finish_cooldown)), cooldown + sense_time) //Overwrite this line from the original to support my fucked up use /datum/action/item_action/organ_action/psychic_highlight/proc/remove() owner?.clear_fullscreen("psychic_highlight") @@ -233,12 +221,11 @@ /datum/action/item_action/organ_action/psychic_highlight/proc/auto_sense() if(auto_sense) - Trigger() + trigger() addtimer(CALLBACK(src, PROC_REF(auto_sense)), auto_cooldown) /datum/action/item_action/organ_action/psychic_highlight/proc/finish_cooldown() - has_cooldown_timer = FALSE - UpdateButtonIcon() + update_buttons() //Allows user to see images through walls - mostly for if this action is added to something without xray /datum/action/item_action/organ_action/psychic_highlight/proc/toggle_eyes_fowards() @@ -432,8 +419,7 @@ qdel(src) -/datum/action/change_psychic_visual/Trigger() - . = ..() +/datum/action/change_psychic_visual/on_activate(mob/user, atom/target) if(!psychic_overlay) psychic_overlay = locate(/atom/movable/screen/fullscreen/blind/psychic_highlight) in owner?.client?.screen psychic_overlay?.cycle_visuals() @@ -461,12 +447,11 @@ qdel(src) -/datum/action/change_psychic_auto/Trigger() - . = ..() +/datum/action/change_psychic_auto/on_activate(mob/user, atom/target) psychic_action?.auto_sense = !psychic_action?.auto_sense - UpdateButtonIcon() + update_buttons() -/datum/action/change_psychic_auto/IsAvailable() +/datum/action/change_psychic_auto/is_available() . = ..() if(psychic_action?.auto_sense) return FALSE @@ -497,8 +482,7 @@ qdel(src) -/datum/action/change_psychic_texture/Trigger() - . = ..() +/datum/action/change_psychic_texture/on_activate(mob/user, atom/target) psychic_overlay = psychic_overlay || owner?.screens["psychic_highlight"] psychic_overlay?.cycle_textures() blind_overlay = blind_overlay || owner?.screens["blind"] diff --git a/code/modules/mob/living/carbon/human/species_types/pumpkin_man.dm b/code/modules/mob/living/carbon/human/species_types/pumpkin_man.dm index fc44046ead173..991750647e565 100644 --- a/code/modules/mob/living/carbon/human/species_types/pumpkin_man.dm +++ b/code/modules/mob/living/carbon/human/species_types/pumpkin_man.dm @@ -161,8 +161,7 @@ return generate_candy() -/datum/action/item_action/organ_action/pumpkin_head_candy/Trigger() - . = ..() +/datum/action/item_action/organ_action/pumpkin_head_candy/on_activate(mob/user, atom/target) if(!iscarbon(owner)) return var/mob/living/carbon/H = owner diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index b32ddee29e193..6facbc719df88 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -127,21 +127,20 @@ name = "tumorous mass" desc = "A fleshy growth that was dug out of the skull of a Nightmare." icon_state = "brain-x-d" - var/obj/effect/proc_holder/spell/targeted/shadowwalk/shadowwalk + var/datum/action/spell/jaunt/shadow_walk/shadowwalk /obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = 0, pref_load = FALSE) ..() if(M.dna.species.id != "nightmare") M.set_species(/datum/species/shadow/nightmare) visible_message("[M] thrashes as [src] takes root in [M.p_their()] body!") - var/obj/effect/proc_holder/spell/targeted/shadowwalk/SW = new - M.AddSpell(SW) - shadowwalk = SW + shadowwalk = new /datum/action/spell/jaunt/shadow_walk + shadowwalk.Grant(M) /obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = 0, pref_load = FALSE) if(shadowwalk) - M.RemoveSpell(shadowwalk) + shadowwalk.Remove(M) ..() diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index e9e06773bef24..c6030cce31cf8 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -14,7 +14,7 @@ examine_limb_id = SPECIES_HUMAN skinned_type = /obj/item/stack/sheet/animalhide/human var/info_text = "You are a Vampire. You will slowly but constantly lose blood if outside of a coffin. If inside a coffin, you will slowly heal. You may gain more blood by grabbing a live victim and using your drain ability." - var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/batform //attached to the datum itself to avoid cloning memes, and other duplicates + var/datum/action/spell/shapeshift/bat/batform //attached to the datum itself to avoid cloning memes, and other duplicates /datum/species/vampire/check_roundstart_eligible() if(SSevents.holidays && SSevents.holidays[HALLOWEEN]) @@ -28,12 +28,12 @@ C.update_body(0) if(isnull(batform)) batform = new - C.AddSpell(batform) + batform.Grant(C) /datum/species/vampire/on_species_loss(mob/living/carbon/C) . = ..() if(!isnull(batform)) - C.RemoveSpell(batform) + batform.Remove(C) QDEL_NULL(batform) /datum/species/vampire/spec_life(mob/living/carbon/human/C) @@ -139,8 +139,7 @@ name = "Drain Victim" desc = "Leech blood from any carbon victim you are passively grabbing." -/datum/action/item_action/organ_action/vampire/Trigger() - . = ..() +/datum/action/item_action/organ_action/vampire/on_activate(mob/user, atom/target) if(iscarbon(owner)) var/mob/living/carbon/H = owner var/obj/item/organ/tongue/vampire/V = target @@ -159,7 +158,7 @@ to_chat(H, "[victim] doesn't have blood!") return V.drain_cooldown = world.time + 30 - if(victim.anti_magic_check(FALSE, TRUE, FALSE)) + if(victim.can_block_magic(FALSE, TRUE, FALSE)) to_chat(victim, "[H] tries to bite you, but stops before touching you!") to_chat(H, "[victim] is blessed! You stop just in time to avoid catching fire.") return @@ -190,16 +189,14 @@ name = "Check Blood Level" desc = "Check how much blood you have remaining." -/datum/action/item_action/organ_action/vampire_heart/Trigger() - . = ..() +/datum/action/item_action/organ_action/vampire_heart/on_activate(mob/user, atom/target) if(iscarbon(owner)) var/mob/living/carbon/H = owner to_chat(H, "Current blood level: [H.blood_volume]/[BLOOD_VOLUME_MAXIMUM].") -/obj/effect/proc_holder/spell/targeted/shapeshift/bat +/datum/action/spell/shapeshift/bat name = "Bat Form" desc = "Take on the shape a space bat." invocation = "Squeak!" - charge_max = 50 - cooldown_min = 50 - shapeshift_type = /mob/living/simple_animal/hostile/retaliate/bat/vampire + cooldown_time = 5 SECONDS + possible_shapes = list(/mob/living/simple_animal/hostile/retaliate/bat/vampire) diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index f1d918e47dec2..27220e1aff4de 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -12,7 +12,31 @@ return handcuffed if(ITEM_SLOT_LEGCUFFED) return legcuffed - return null + return ..() + +/mob/living/carbon/get_slot_by_item(obj/item/looking_for) + if(looking_for == back) + return ITEM_SLOT_BACK + + if(back && (looking_for in back)) + return ITEM_SLOT_BACKPACK + + if(looking_for == wear_mask) + return ITEM_SLOT_MASK + + if(looking_for == wear_neck) + return ITEM_SLOT_NECK + + if(looking_for == head) + return ITEM_SLOT_HEAD + + if(looking_for == handcuffed) + return ITEM_SLOT_HANDCUFFED + + if(looking_for == legcuffed) + return ITEM_SLOT_LEGCUFFED + + return ..() /mob/living/carbon/proc/equip_in_one_of_slots(obj/item/I, list/slots, qdel_on_fail = TRUE) for(var/slot in slots) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index d44cc72c47cd7..53db4c1827fc5 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -46,8 +46,6 @@ qdel(S) else S.be_replaced() - if(ranged_ability) - ranged_ability.remove_ranged_ability(src) if(buckled) buckled.unbuckle_mob(src,force=1) @@ -657,10 +655,6 @@ clear_alert("not_enough_oxy") reload_fullscreen() . = 1 - if(mind) - for(var/S in mind.spell_list) - var/obj/effect/proc_holder/spell/spell = S - spell.updateButtonIcon() /* * Heals up the [target] to up to [heal_to] of the main damage types. @@ -1189,7 +1183,7 @@ apply_effect((amount*RAD_MOB_COEFFICIENT)/max(1, (radiation**2)*RAD_OVERDOSE_REDUCTION), EFFECT_IRRADIATE, blocked) -/mob/living/anti_magic_check(magic = TRUE, holy = FALSE, major = TRUE, self = FALSE) +/mob/living/can_block_magic(casted_magic_flags) . = ..() if(.) return @@ -1276,22 +1270,6 @@ /mob/living/proc/on_fall() return -/mob/living/proc/AddAbility(obj/effect/proc_holder/A) - abilities.Add(A) - A.on_gain(src) - if(A.has_action) - A.action.Grant(src) - -/mob/living/proc/RemoveAbility(obj/effect/proc_holder/A) - abilities.Remove(A) - A.on_lose(src) - if(A.action) - A.action.Remove(src) - -/mob/living/proc/add_abilities_to_panel() - for(var/obj/effect/proc_holder/A in abilities) - statpanel("[A.panel]",A.get_panel_text(),A) - /mob/living/lingcheck() if(mind) var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) @@ -1497,11 +1475,6 @@ else clear_fullscreen("remote_view", 0) -/mob/living/update_mouse_pointer() - ..() - if (client && ranged_ability && ranged_ability.ranged_mousepointer) - client.mouse_pointer_icon = ranged_ability.ranged_mousepointer - /mob/living/vv_edit_var(var_name, var_value) switch(var_name) if (NAMEOF(src, maxHealth)) diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index c7b849c0c4b69..5fac1e9a8c671 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -107,7 +107,6 @@ var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added var/blood_volume = 0 //how much blood the mob has - var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override var/see_override = 0 //0 for no override, sets see_invisible = see_override in silicon & carbon life process via update_sight() @@ -129,8 +128,6 @@ var/last_words //used for database logging - var/list/obj/effect/proc_holder/abilities = list() - var/can_be_held = FALSE //whether this can be picked up and held. var/worn_slot_flags = NONE //if it can be held, can it be equipped to any slots? (think pAI's on head) diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index a38d479c67ed0..cccc7a36f262d 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -19,9 +19,6 @@ if(ventcrawler) to_chat(src, "You can ventcrawl! Use alt+click on vents to quickly travel about the station.") - if(ranged_ability) - ranged_ability.add_ranged_ability(src, "You currently have [ranged_ability] active!") - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) if(changeling) changeling.regain_powers() diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 78fcdc3b789c2..39a8f0a61a768 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -431,5 +431,19 @@ GLOBAL_LIST_INIT(department_radio_keys, list( else . = ..() -/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) +/** + * Living level whisper. + * + * Living mobs which whisper have their message only appear to people very close. + * + * message - the message to display + * bubble_type - the type of speech bubble that shows up when they speak (currently does nothing) + * spans - a list of spans to apply around the message + * sanitize - whether we sanitize the message + * language - typepath language to force them to speak / whisper in + * ignore_spam - whether we ignore the spam filter + * forced - string source of what forced this speech to happen, also bypasses spam filter / mutes if supplied + * filterproof - whether we ignore the word filter + */ +/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language, ignore_spam = FALSE, forced, filterproof) say("#[message]", bubble_type, spans, sanitize, language, ignore_spam, forced) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 98e45e0be3878..14316aff9254b 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -57,7 +57,6 @@ var/control_disabled = FALSE // Set to TRUE to stop AI from interacting via Click() var/malfhacking = FALSE // More or less a copy of the above var, so that malf AIs can hack and still get new cyborgs -- NeoFite - var/malf_cooldown = 0 //Cooldown var for malf modules, stores a worldtime + cooldown var/obj/machinery/power/apc/malfhack var/explosive = FALSE //does the AI explode when it dies? @@ -1017,7 +1016,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/silicon/ai) icon_icon = 'icons/hud/actions/actions_AI.dmi' button_icon_state = "ai_shell" -/datum/action/innate/deploy_shell/Trigger() +/datum/action/innate/deploy_shell/on_activate(mob/user, atom/target) var/mob/living/silicon/ai/AI = owner if(!AI) return @@ -1030,7 +1029,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/silicon/ai) button_icon_state = "ai_last_shell" var/mob/living/silicon/robot/last_used_shell -/datum/action/innate/deploy_last_shell/Trigger() +/datum/action/innate/deploy_last_shell/on_activate(mob/user, atom/target) if(!owner) return if(last_used_shell) @@ -1039,7 +1038,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/silicon/ai) else Remove(owner) //If the last shell is blown, destroy it. -/mob/living/silicon/ai/proc/disconnect_shell() +/mob/living/silicon/ai/proc/disconnect_shell(trigger_flags) if(deployed_shell) //Forcibly call back AI in event of things such as damage, EMP or power loss. to_chat(src, "Your remote connection has been reset!") deployed_shell.undeploy() diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index f85e0619bdeca..4dd164318b874 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -254,9 +254,10 @@ /datum/action/innate/pai name = "PAI Action" icon_icon = 'icons/hud/actions/actions_silicon.dmi' + button_icon_state = null var/mob/living/silicon/pai/P -/datum/action/innate/pai/Trigger() +/datum/action/innate/pai/on_activate(mob/user, atom/target) if(!ispAI(owner)) return 0 P = owner @@ -266,8 +267,7 @@ button_icon_state = "pai" background_icon_state = "bg_tech" -/datum/action/innate/pai/software/Trigger() - ..() +/datum/action/innate/pai/software/on_activate(mob/user, atom/target) P.ui_act() /datum/action/innate/pai/shell @@ -275,8 +275,7 @@ button_icon_state = "pai_holoform" background_icon_state = "bg_tech" -/datum/action/innate/pai/shell/Trigger() - ..() +/datum/action/innate/pai/shell/on_activate(mob/user, atom/target) if(P.holoform) P.fold_in(0) else @@ -287,8 +286,7 @@ button_icon_state = "pai_chassis" background_icon_state = "bg_tech" -/datum/action/innate/pai/chassis/Trigger() - ..() +/datum/action/innate/pai/chassis/on_activate(mob/user, atom/target) P.choose_chassis() /datum/action/innate/pai/rest @@ -296,8 +294,7 @@ button_icon_state = "pai_rest" background_icon_state = "bg_tech" -/datum/action/innate/pai/rest/Trigger() - ..() +/datum/action/innate/pai/rest/on_activate(mob/user, atom/target) P.toggle_resting() /datum/action/innate/pai/light @@ -306,8 +303,7 @@ button_icon_state = "emp" background_icon_state = "bg_tech" -/datum/action/innate/pai/light/Trigger() - ..() +/datum/action/innate/pai/light/on_activate(mob/user, atom/target) P.toggle_integrated_light() /mob/living/silicon/pai/Process_Spacemove(movement_dir = 0) diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 659ad5d21bac9..6136eaaf8c281 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -1197,9 +1197,7 @@ icon_icon = 'icons/hud/actions/actions_AI.dmi' button_icon_state = "ai_core" -/datum/action/innate/undeployment/Trigger() - if(!..()) - return FALSE +/datum/action/innate/undeployment/on_activate(mob/user, atom/target) var/mob/living/silicon/robot/R = owner R.undeploy() diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index e63ceaa7f6182..d8cf1c805d5d0 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -44,8 +44,6 @@ var/seeking = FALSE var/can_repair_constructs = FALSE var/can_repair_self = FALSE - var/runetype - var/datum/action/innate/cult/create_rune/our_rune /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue var/theme = THEME_CULT chat_color = "#FF6262" @@ -57,29 +55,25 @@ /mob/living/simple_animal/hostile/construct/Initialize(mapload) . = ..() - update_health_hud() - var/spellnum = 1 + ADD_TRAIT(src, TRAIT_HEALS_FROM_CULT_PYLONS, INNATE_TRAIT) for(var/spell in construct_spells) - var/the_spell = new spell(null) - AddSpell(the_spell) - var/obj/effect/proc_holder/spell/S = mob_spell_list[spellnum] - var/pos = 2+spellnum*31 + var/datum/action/new_spell = new spell(src) + new_spell.Grant(src) + + var/spellnum = 1 + for(var/datum/action/spell as anything in actions) + if(!(type in construct_spells)) + continue + + var/pos = 2 + spellnum * 31 if(construct_spells.len >= 4) - pos -= 31*(construct_spells.len - 4) - S.action.button.screen_loc = "6:[pos],4:-2" - S.action.button.moved = "6:[pos],4:-2" + pos -= 31 * (construct_spells.len - 4) + spell.default_button_position = "6:[pos],4:-2" // Set the default position to this random position spellnum++ - if(runetype) - our_rune = new runetype(src) - our_rune.Grant(src) - var/pos = 2+spellnum*31 - our_rune.button.screen_loc = "6:[pos],4:-2" - our_rune.button.moved = "6:[pos],4:-2" - add_overlay("glow_[icon_state]_[theme]") - -/mob/living/simple_animal/hostile/construct/Destroy() - QDEL_NULL(our_rune) - return ..() + update_action_buttons() + + if(icon_state) + add_overlay("glow_[icon_state]_[theme]") /mob/living/simple_animal/hostile/construct/Login() . = ..() @@ -157,10 +151,10 @@ mob_size = MOB_SIZE_LARGE force_threshold = 10 construct_spells = list( - /obj/effect/proc_holder/spell/targeted/forcewall/cult, - /obj/effect/proc_holder/spell/targeted/projectile/dumbfire/juggernaut - ) - runetype = /datum/action/innate/cult/create_rune/wall + /datum/action/spell/forcewall/cult, + /datum/action/spell/basic_projectile/juggernaut, + /datum/action/innate/cult/create_rune/wall, + ) playstyle_string = "You are a Juggernaut. Though slow, your shell can withstand heavy punishment, \ create shield walls, rip apart enemies and walls alike, and even deflect energy weapons." @@ -223,13 +217,18 @@ attack_verb_continuous = "slashes" attack_verb_simple = "slash" attack_sound = 'sound/weapons/bladeslice.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) - runetype = /datum/action/innate/cult/create_rune/tele - playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, can phase through walls, and your attacks will lower the cooldown on phasing." - - var/attack_refund = 10 //1 second per attack - var/crit_refund = 50 //5 seconds when putting a target into critical - var/kill_refund = 250 //full refund on kills + construct_spells = list( + /datum/action/spell/jaunt/ethereal_jaunt/shift, + /datum/action/innate/cult/create_rune/tele, + ) + playstyle_string = "You are a Wraith. Though relatively fragile, you are fast, deadly, \ + can phase through walls, and your attacks will lower the cooldown on phasing." + + // Accomplishing various things gives you a refund on jaunt, to jump in and out. + /// The seconds refunded per attack + var/attack_refund = 1 SECONDS + /// The seconds refunded when putting a target into critical + var/crit_refund = 5 SECONDS /mob/living/simple_animal/hostile/construct/wraith/AttackingTarget() //refund jaunt cooldown when attacking living targets var/prev_stat @@ -239,17 +238,24 @@ . = ..() - if(. && isnum_safe(prev_stat)) + if(. && isnum(prev_stat)) + var/datum/action/spell/jaunt/ethereal_jaunt/shift/jaunt = locate() in actions + if(!jaunt) + return var/mob/living/L = target - var/refund = 0 - if(QDELETED(L) || (L.stat == DEAD && prev_stat != DEAD)) //they're dead, you killed them - refund += kill_refund - else if(HAS_TRAIT(L, TRAIT_CRITICAL_CONDITION) && prev_stat == CONSCIOUS) //you knocked them into critical - refund += crit_refund + var/total_refund = 0 SECONDS + // they're dead, and you killed them - full refund + if(QDELETED(L) || (L.stat == DEAD && prev_stat != DEAD)) + total_refund += jaunt.cooldown_time + // you knocked them into critical + else if(HAS_TRAIT(L, TRAIT_CRITICAL_CONDITION) && prev_stat == CONSCIOUS) + total_refund += crit_refund + if(L.stat != DEAD && prev_stat != DEAD) - refund += attack_refund - for(var/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/S in mob_spell_list) - S.charge_counter = min(S.charge_counter + refund, S.charge_max) + total_refund += attack_refund + + jaunt.reduce_cooldown(total_refund) + jaunt.update_buttons() /mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things AIStatus = AI_ON @@ -257,13 +263,19 @@ //////////////////////////Wraith-alts//////////////////////////// /mob/living/simple_animal/hostile/construct/wraith/angelic theme = THEME_HOLY - construct_spells = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/angelic) + construct_spells = list( + /datum/action/spell/jaunt/ethereal_jaunt/shift/angelic, + /datum/action/innate/cult/create_rune/tele, + ) loot = list(/obj/item/ectoplasm/angelic) chat_color = "#AED2FF" /mob/living/simple_animal/hostile/construct/wraith/mystic theme = THEME_WIZARD - construct_spells = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/mystic) + construct_spells = list( + /datum/action/spell/jaunt/ethereal_jaunt/shift/mystic, + /datum/action/innate/cult/create_rune/tele, + ) loot = list(/obj/item/ectoplasm/mystic) /mob/living/simple_animal/hostile/construct/wraith/noncult @@ -288,17 +300,17 @@ environment_smash = ENVIRONMENT_SMASH_WALLS attack_sound = 'sound/weapons/punch2.ogg' construct_spells = list( - /obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser - ) - playstyle_string = "You are an Artificer. You are incredibly weak and fragile, but you are able to construct fortifications, \ - - use magic missile, repair allied constructs, shades, and yourself (by clicking on them), \ - and, most important of all, create new constructs by producing soulstones to capture souls, \ - and shells to place those soulstones into." + /datum/action/spell/conjure/cult_floor, + /datum/action/spell/conjure/cult_wall, + /datum/action/spell/conjure/soulstone, + /datum/action/spell/conjure/construct/lesser, + /datum/action/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) + playstyle_string = "You are an Artificer. You are incredibly weak and fragile, \ + but you are able to construct fortifications, use magic missile, and repair allied constructs, shades, \ + and yourself (by clicking on them). Additionally, and most important of all, you can create new constructs \ + by producing soulstones to capture souls, and shells to place those soulstones into." can_repair_constructs = TRUE can_repair_self = TRUE @@ -351,30 +363,33 @@ chat_color = "#AED2FF" loot = list(/obj/item/ectoplasm/angelic) construct_spells = list( - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/purified, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser - ) + /datum/action/spell/conjure/soulstone/purified, + /datum/action/spell/conjure/construct/lesser, + /datum/action/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) /mob/living/simple_animal/hostile/construct/artificer/mystic theme = THEME_WIZARD loot = list(/obj/item/ectoplasm/mystic) construct_spells = list( - /obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/mystic, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser - ) + /datum/action/spell/conjure/cult_floor, + /datum/action/spell/conjure/cult_wall, + /datum/action/spell/conjure/soulstone/mystic, + /datum/action/spell/conjure/construct/lesser, + /datum/action/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) /mob/living/simple_animal/hostile/construct/artificer/noncult construct_spells = list( - /obj/effect/proc_holder/spell/aoe_turf/conjure/wall, - /obj/effect/proc_holder/spell/aoe_turf/conjure/floor, - /obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser - ) + /datum/action/spell/conjure/cult_floor, + /datum/action/spell/conjure/cult_wall, + /datum/action/spell/conjure/soulstone/noncult, + /datum/action/spell/conjure/construct/lesser, + /datum/action/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) /////////////////////////////Harvester///////////////////////// /mob/living/simple_animal/hostile/construct/harvester @@ -390,8 +405,10 @@ attack_verb_continuous = "butchers" attack_verb_simple = "butcher" attack_sound = 'sound/weapons/bladeslice.ogg' - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/area_conversion, - /obj/effect/proc_holder/spell/targeted/forcewall/cult) + construct_spells = list( + /datum/action/spell/aoe/area_conversion, + /datum/action/spell/forcewall/cult, + ) playstyle_string = "You are a Harvester. You are incapable of directly killing humans, but your attacks will remove their limbs: \ Bring those who still cling to this world of illusion back to the Geometer so they may know Truth. Your form and any you are pulling can pass through runed walls effortlessly." can_repair_constructs = TRUE @@ -439,7 +456,7 @@ . = ..() var/datum/action/innate/seek_prey/seek = new() seek.Grant(src) - seek.Activate() + seek.trigger() /////////////////////////////Proteon///////////////////////// /mob/living/simple_animal/hostile/construct/proteon @@ -470,6 +487,7 @@ background_icon_state = "bg_demon" buttontooltipstyle = "cult" button_icon_state = "cult_mark" + icon_icon = 'icons/hud/actions/actions_cult.dmi' var/tracking = FALSE var/mob/living/simple_animal/hostile/construct/the_construct @@ -478,7 +496,7 @@ the_construct = C ..() -/datum/action/innate/seek_master/Activate() +/datum/action/innate/seek_master/on_activate() var/datum/antagonist/cult/C = owner.mind.has_antag_datum(/datum/antagonist/cult) if(!C) return @@ -510,7 +528,7 @@ buttontooltipstyle = "cult" button_icon_state = "cult_mark" -/datum/action/innate/seek_prey/Activate() +/datum/action/innate/seek_prey/on_activate() if(GLOB.cult_narsie == null) return var/mob/living/simple_animal/hostile/construct/harvester/the_construct = owner diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm index fe819868a7832..d2df8cac69360 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm @@ -42,6 +42,12 @@ return internal_storage return ..() +/mob/living/simple_animal/drone/get_slot_by_item(obj/item/looking_for) + if(internal_storage == looking_for) + return ITEM_SLOT_DEX_STORAGE + if(head == looking_for) + return ITEM_SLOT_HEAD + return ..() /mob/living/simple_animal/drone/equip_to_slot(obj/item/I, slot) if(!slot) diff --git a/code/modules/mob/living/simple_animal/heretic_monsters.dm b/code/modules/mob/living/simple_animal/heretic_monsters.dm index 86f2056b70cc2..bc99134f3e605 100644 --- a/code/modules/mob/living/simple_animal/heretic_monsters.dm +++ b/code/modules/mob/living/simple_animal/heretic_monsters.dm @@ -31,20 +31,13 @@ faction = list(FACTION_HERETIC) simple_mob_flags = SILENCE_RANGED_MESSAGE /// Innate spells that are added when a beast is created. - var/list/spells_to_add + var/list/actions_to_add /mob/living/simple_animal/hostile/heretic_summon/Initialize(mapload) . = ..() - add_spells() - -/** - * Add_spells - * - * Goes through spells_to_add and adds each spell to the mind. - */ -/mob/living/simple_animal/hostile/heretic_summon/proc/add_spells() - for(var/spell in spells_to_add) - AddSpell(new spell()) + for(var/spell in actions_to_add) + var/datum/action/spell/new_spell = new spell(src) + new_spell.Grant(src) /mob/living/simple_animal/hostile/heretic_summon/raw_prophet name = "Raw Prophet" @@ -57,11 +50,10 @@ maxHealth = 50 health = 50 sight = SEE_MOBS|SEE_OBJS|SEE_TURFS - spells_to_add = list( - /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long, - /obj/effect/proc_holder/spell/pointed/manse_link, - /obj/effect/proc_holder/spell/targeted/telepathy/eldritch, - /obj/effect/proc_holder/spell/targeted/blind/eldritch, + actions_to_add = list( + /datum/action/spell/jaunt/ethereal_jaunt/ash/long, + /datum/action/spell/telepathy/eldritch, + /datum/action/spell/pointed/blind/eldritch, ) /// A assoc list of [mob/living ref] to [datum/action ref] - all the mobs linked to our mansus network. @@ -72,7 +64,6 @@ linked_mobs = list() var/datum/action/innate/hereticmob/change_sight_range/C = new() C.Grant(src) - link_mob(src) /mob/living/simple_animal/hostile/heretic_summon/raw_prophet/Login() . = ..() @@ -85,7 +76,7 @@ button_icon_state = "binoculars" background_icon_state = "bg_ecult" -/datum/action/innate/hereticmob/change_sight_range/Activate() +/datum/action/innate/hereticmob/change_sight_range/on_activate() var/list/views = list() for(var/i in 1 to 10) views |= i @@ -95,57 +86,6 @@ else usr.client.view_size.setTo(10) -/** - * Link [linked_mob] to our mansus link, if possible. - * Creates a mansus speech action and grants it to the linked mob, - * storing it in our linked_mobs list. - */ -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/proc/link_mob(mob/living/mob_linked) - if(QDELETED(mob_linked) || mob_linked.stat == DEAD) - return FALSE - if(HAS_TRAIT(mob_linked, TRAIT_MINDSHIELD)) //mindshield implant, no dice - return FALSE - if(mob_linked.anti_magic_check(FALSE, FALSE, TRUE, 0)) - return FALSE - if(linked_mobs[mob_linked]) - return FALSE - - to_chat(mob_linked, "You feel something new enter your sphere of mind... \ - You hear whispers of people far away, screeches of horror and a huming of welcome to [src]'s Mansus Link.") - - var/datum/action/innate/mansus_speech/action = new(src) - linked_mobs[mob_linked] = action - action.Grant(mob_linked) - - RegisterSignals(mob_linked, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, SIGNAL_ADDTRAIT(TRAIT_MINDSHIELD)), PROC_REF(unlink_mob)) - - return TRUE - -/** - * Signal proc that handles removing mobs from our mansus link. - * - * Remove the [mob_linked] from our list of linked mobs, and delete the associated action. - */ -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/proc/unlink_mob(mob/living/mob_linked) - SIGNAL_HANDLER - - if(QDELETED(linked_mobs[mob_linked])) - return - UnregisterSignal(mob_linked, list(COMSIG_MOB_DEATH, COMSIG_PARENT_QDELETING, SIGNAL_ADDTRAIT(TRAIT_MINDSHIELD))) - var/datum/action/innate/mansus_speech/action = linked_mobs[mob_linked] - action.Remove(mob_linked) - qdel(action) - - to_chat(mob_linked, "Your mind shatters as [src]'s Mansus Link leaves your mind.") - INVOKE_ASYNC(mob_linked, TYPE_PROC_REF(/mob, emote), "scream") - mob_linked.AdjustParalyzed(0.5 SECONDS) //micro stun - - linked_mobs -= mob_linked - -/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/Destroy() - for(var/linked_mob in linked_mobs) - unlink_mob(linked_mob) - return ..() // What if we took a linked list... But made it a mob? /// The "Terror of the Night" / Armsy, a large worm made of multiple bodyparts that occupies multiple tiles @@ -170,7 +110,7 @@ ranged_cooldown_time = 5 ranged = TRUE rapid = 1 - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/worm_contract) + actions_to_add = list(/datum/action/spell/worm_contract) ///Previous segment in the chain var/mob/living/simple_animal/hostile/heretic_summon/armsy/back ///Next segment in the chain @@ -387,9 +327,9 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/simple_animal/hostile/heretic_summon/a health = 75 melee_damage = 20 sight = SEE_TURFS - spells_to_add = list( - /obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small, - /obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave/short, + actions_to_add = list( + /datum/action/spell/aoe/rust_conversion/small, + /datum/action/spell/basic_projectile/rust_wave/short, ) /mob/living/simple_animal/hostile/heretic_summon/rust_spirit/setDir(newdir) @@ -425,10 +365,10 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/simple_animal/hostile/heretic_summon/a health = 75 melee_damage = 20 sight = SEE_TURFS - spells_to_add = list( - /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash, - /obj/effect/proc_holder/spell/pointed/cleave, - /obj/effect/proc_holder/spell/targeted/fire_sworn, + actions_to_add = list( + /datum/action/spell/jaunt/ethereal_jaunt/ash, + /datum/action/spell/pointed/cleave, + /datum/action/spell/fire_sworn, ) /mob/living/simple_animal/hostile/heretic_summon/stalker @@ -442,8 +382,8 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/simple_animal/hostile/heretic_summon/a health = 150 melee_damage = 20 sight = SEE_MOBS - spells_to_add = list( - /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash, - /obj/effect/proc_holder/spell/targeted/shapeshift/eldritch, - /obj/effect/proc_holder/spell/targeted/emplosion/eldritch, + actions_to_add = list( + /datum/action/spell/jaunt/ethereal_jaunt/ash, + /datum/action/spell/shapeshift/eldritch, + /datum/action/spell/emp/eldritch, ) diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm index 8eb1c7212184e..5be2d685fb7af 100644 --- a/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm +++ b/code/modules/mob/living/simple_animal/hostile/bosses/boss.dm @@ -53,31 +53,22 @@ var/needs_target = TRUE //Does the boss need to have a target? (Only matters for the AI) var/say_when_triggered = "" //What does the boss Say() when the ability triggers? -/datum/action/boss/Trigger() - . = ..() - if(.) - if(!istype(boss, boss_type)) - return 0 - if(!boss.atb) +/datum/action/boss/on_activate(mob/user, atom/target) + if(!istype(boss, boss_type)) + return 0 + if(!boss.atb) + return 0 + if(boss.atb.points < boss_cost) + return 0 + if(!boss.client) + if(needs_target && !boss.target) return 0 - if(boss.atb.points < boss_cost) + if(boss) + if(say_when_triggered) + boss.say(say_when_triggered, forced = "boss action") + if(!boss.atb.spend(boss_cost)) return 0 - if(!boss.client) - if(needs_target && !boss.target) - return 0 - if(boss) - if(say_when_triggered) - boss.say(say_when_triggered, forced = "boss action") - if(!boss.atb.spend(boss_cost)) - return 0 - -//Example: -/* -/datum/action/boss/selfgib/Trigger() - if(..()) - boss.gib() -*/ - + return TRUE //Designed for boss mobs only /datum/boss_active_timed_battle @@ -129,7 +120,7 @@ abilities = shuffle(abilities) for(var/ab in abilities) var/datum/action/boss/AB = ab - if(prob(AB.usage_probability) && AB.Trigger()) + if(prob(AB.usage_probability) && AB.trigger()) break diff --git a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm index 07f57b4f47829..24325806ddd9a 100644 --- a/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm +++ b/code/modules/mob/living/simple_animal/hostile/bosses/paperwizard.dm @@ -38,8 +38,8 @@ say_when_triggered = "Rise, my creations! Jump off your pages and into this realm!" var/static/summoned_minions = 0 -/datum/action/boss/wizard_summon_minions/Trigger() - if(summoned_minions <= 6 && ..()) +/datum/action/boss/wizard_summon_minions/on_activate(mob/user, atom/target) + if(summoned_minions <= 6) var/list/minions = list( /mob/living/simple_animal/hostile/stickman, /mob/living/simple_animal/hostile/stickman, @@ -64,29 +64,28 @@ boss_type = /mob/living/simple_animal/hostile/boss/paper_wizard say_when_triggered = "" -/datum/action/boss/wizard_mimic/Trigger() - if(..()) - var/mob/living/target - if(!boss.client) //AI's target - target = boss.target - else //random mob - var/list/threats = boss.PossibleThreats() - if(threats.len) - target = pick(threats) - if(target) - var/mob/living/simple_animal/hostile/boss/paper_wizard/wiz = boss - var/directions = GLOB.cardinals.Copy() - for(var/i in 1 to 3) - var/mob/living/simple_animal/hostile/boss/paper_wizard/copy/C = new (get_step(target,pick_n_take(directions))) - wiz.copies += C - C.original = wiz - C.say("My craft defines me, you could even say it IS me!") - wiz.say("My craft defines me, you could even say it IS me!") - wiz.forceMove(get_step(target,pick_n_take(directions))) - wiz.minimum_distance = 1 //so he doesn't run away and ruin everything - wiz.retreat_distance = 0 - else - boss.atb.refund(boss_cost) +/datum/action/boss/wizard_mimic/on_activate(mob/user, atom/target) + var/mob/living/mimic_target + if(!boss.client) //AI's target + mimic_target = boss.target + else //random mob + var/list/threats = boss.PossibleThreats() + if(threats.len) + mimic_target = pick(threats) + if(mimic_target) + var/mob/living/simple_animal/hostile/boss/paper_wizard/wiz = boss + var/directions = GLOB.cardinals.Copy() + for(var/i in 1 to 3) + var/mob/living/simple_animal/hostile/boss/paper_wizard/copy/C = new (get_step(mimic_target,pick_n_take(directions))) + wiz.copies += C + C.original = wiz + C.say("My craft defines me, you could even say it IS me!") + wiz.say("My craft defines me, you could even say it IS me!") + wiz.forceMove(get_step(mimic_target,pick_n_take(directions))) + wiz.minimum_distance = 1 //so he doesn't run away and ruin everything + wiz.retreat_distance = 0 + else + boss.atb.refund(boss_cost) /mob/living/simple_animal/hostile/boss/paper_wizard/copy desc = "'Tis a ruse!" diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index bb6896759e69c..3bde1cdd73a13 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -3,6 +3,7 @@ #define LAYING_EGGS 2 #define MOVING_TO_TARGET 3 #define SPINNING_COCOON 4 +#define INTERACTION_SPIDER_KEY "spider_key" /mob/living/simple_animal/hostile/poison mobchatspan = "researchdirector" @@ -55,8 +56,7 @@ footstep_type = FOOTSTEP_MOB_CLAW sentience_type = SENTIENCE_OTHER // not eligible for sentience potions var/busy = SPIDER_IDLE // What a spider's doing - var/datum/action/innate/spider/lay_web/lay_web // Web action - var/obj/effect/proc_holder/spider/wrap/lesser/lesserwrap // Wrap action + //var/obj/effect/proc_holder/spider/wrap/lesser/lesserwrap // Wrap action var/web_speed = 1 // How quickly a spider lays down webs (percentage) var/mob/master // The spider's master, used by sentience var/onweb_speed @@ -68,7 +68,9 @@ var/enriched_fed = 0 var/datum/action/innate/spider/lay_eggs/lay_eggs //the ability to lay eggs, granted to broodmothers var/datum/team/spiders/spider_team = null //utilized by AI controlled broodmothers to pass antag team info onto their eggs without a mind - + var/datum/action/innate/spider/lay_web/webbing + var/datum/action/wrap/wrap + var/datum/action/innate/spider/comm/comm atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) minbodytemp = 0 discovery_points = 1000 @@ -76,10 +78,10 @@ /mob/living/simple_animal/hostile/poison/giant_spider/Initialize(mapload) . = ..() - lay_web = new - lay_web.Grant(src) - lesserwrap = new - AddAbility(lesserwrap) + webbing = new(src) + webbing.Grant(src) + wrap = new(src) + wrap.Grant(src) /mob/living/simple_animal/hostile/poison/giant_spider/mind_initialize() . = ..() @@ -91,8 +93,7 @@ mind.add_antag_datum(spooder, spider_team) /mob/living/simple_animal/hostile/poison/giant_spider/Destroy() - RemoveAbility(lesserwrap) - QDEL_NULL(lay_web) + webbing.Remove() GLOB.spidermobs -= src return ..() @@ -143,7 +144,7 @@ if(!busy) var/obj/structure/spider/stickyweb/W = locate() in get_turf(src) if(!W) - lay_web.Activate() + webbing.trigger() else var/list/can_see = view(10, src) for(var/obj/O in can_see) @@ -199,7 +200,7 @@ else fed++ //it is not a humanoid, but still has nourishment if(lay_eggs) - lay_eggs.UpdateButtonIcon(TRUE) + lay_eggs.update_buttons(TRUE) visible_message("[src] sticks a proboscis into [L] and sucks a viscous substance out.","You suck the nutriment out of [L], feeding you enough to lay a cluster of eggs.") else to_chat(src, "[L] cannot sate your hunger!") @@ -236,22 +237,6 @@ stop_automated_movement = FALSE SSmove_manager.stop_looping(src) -// Net casters are the balanced generalist of the spider family: Moderate stats all around, and a ranged knockdown to assist others -/mob/living/simple_animal/hostile/poison/giant_spider/netcaster - name = "net caster" - obj_damage = 35 - speed = 0.5 - onweb_speed = 0 - var/obj/effect/proc_holder/spider/throw_web/spidernet - -/mob/living/simple_animal/hostile/poison/giant_spider/netcaster/Initialize(mapload) - . = ..() - spidernet = new - AddAbility(spidernet) - -/mob/living/simple_animal/hostile/poison/giant_spider/netcaster/Destroy() - . = ..() - RemoveAbility(spidernet) // Nurses heal other spiders and maintain the core of the nest. /mob/living/simple_animal/hostile/poison/giant_spider/nurse @@ -270,6 +255,7 @@ web_speed = 0.33 ///The health HUD applied to the mob. var/health_hud = DATA_HUD_MEDICAL_ADVANCED + var/datum/action/innate/spider/set_directive/set_directive /mob/living/simple_animal/hostile/poison/giant_spider/nurse/Initialize(mapload) . = ..() @@ -278,7 +264,7 @@ // Allows nurses to heal other spiders if they're adjacent /mob/living/simple_animal/hostile/poison/giant_spider/nurse/AttackingTarget() - if(is_busy) + if(DOING_INTERACTION(src, INTERACTION_SPIDER_KEY)) return var/mob/target_mob = target if(!istype(target_mob)) @@ -293,12 +279,15 @@ visible_message("[src] begins wrapping their wounds.","You begin wrapping your wounds.") else visible_message("[src] begins wrapping the wounds of [hurt_spider].","You begin wrapping the wounds of [hurt_spider].") - is_busy = TRUE - if(do_after(src, 20, target = hurt_spider)) - hurt_spider.heal_overall_damage(20) - new /obj/effect/temp_visual/heal(get_turf(hurt_spider), "#80F5FF") - visible_message("[src] wraps the wounds of [hurt_spider].","You wrap the wounds of [hurt_spider].") - is_busy = FALSE + if(!do_after(src, 2 SECONDS, target = hurt_spider)) + return + + hurt_spider.heal_overall_damage(20, 20) + new /obj/effect/temp_visual/heal(get_turf(hurt_spider), "#80F5FF") + visible_message( + ("[src] wraps the wounds of [hurt_spider]."), + ("You wrap the wounds of [hurt_spider]."), + ) //Handles AI nurse healing when spiders are idle /mob/living/simple_animal/hostile/poison/giant_spider/nurse/handle_automated_movement() @@ -338,27 +327,22 @@ /obj/item/food/spiderleg = 8, /obj/item/food/spidereggs = 4 ) - var/obj/effect/proc_holder/spider/wrap/wrap var/datum/action/innate/spider/set_directive/set_directive /// Allows the spider to use spider comms var/datum/action/innate/spider/comm/letmetalkpls /mob/living/simple_animal/hostile/poison/giant_spider/broodmother/Initialize(mapload) . = ..() - RemoveAbility(lesserwrap) - wrap = new - AddAbility(wrap) lay_eggs = new lay_eggs.Grant(src) - set_directive = new - set_directive.Grant(src) letmetalkpls = new letmetalkpls.Grant(src) + set_directive = new + set_directive.Grant(src) /mob/living/simple_animal/hostile/poison/giant_spider/broodmother/Destroy() - RemoveAbility(wrap) + wrap.Remove() QDEL_NULL(lay_eggs) - QDEL_NULL(set_directive) QDEL_NULL(letmetalkpls) return ..() @@ -374,8 +358,8 @@ busy = MOVING_TO_TARGET Goto(C, move_to_delay) addtimer(CALLBACK(src, PROC_REF(GiveUp), C), 20 SECONDS) - if(prob(10) && lay_eggs.IsAvailable()) //so eggs aren't always placed immediately and directly by corpses - lay_eggs.Activate() + if(prob(10) && lay_eggs.is_available()) //so eggs aren't always placed immediately and directly by corpses + lay_eggs.trigger() ..() // Hunters are the most independent of the spiders, not relying on web and having a bit more damage and venom at the cost of health. @@ -466,6 +450,7 @@ /datum/action/innate/spider icon_icon = 'icons/hud/actions/actions_animal.dmi' + button_icon_state = null background_icon_state = "bg_alien" check_flags = AB_CHECK_CONSCIOUS @@ -474,7 +459,7 @@ desc = "Spin a web to slow down potential prey." button_icon_state = "lay_web" -/datum/action/innate/spider/lay_web/Activate() +/datum/action/innate/spider/lay_web/on_activate() if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) return var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner @@ -506,7 +491,7 @@ desc = "Use your massive size to prevent others from passing by you." button_icon_state = "block" -/datum/action/innate/spider/block/Activate() +/datum/action/innate/spider/block/on_activate() if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) return if(owner.a_intent == INTENT_HELP) @@ -516,135 +501,136 @@ owner.a_intent = INTENT_HELP owner.visible_message("[owner] loosens up and allows others to pass again.","You are no longer blocking others from passing around you.") -/obj/effect/proc_holder/spider/Click() - if(!istype(usr, /mob/living/simple_animal/hostile/poison/giant_spider)) - return TRUE - var/mob/living/simple_animal/hostile/poison/giant_spider/user = usr - activate(user) - return TRUE - -/obj/effect/proc_holder/spider/on_lose(mob/living/carbon/user) - remove_ranged_ability() +/datum/action/innate/spider/lay_web/is_available() + . = ..() + if(!.) + return FALSE -/obj/effect/proc_holder/spider/proc/activate(mob/living/user) - return TRUE + if(DOING_INTERACTION(owner, INTERACTION_SPIDER_KEY)) + return FALSE + if(!isspider(owner)) + return FALSE -/obj/effect/proc_holder/spider/wrap - name = "Wrap" - panel = "Spider" - desc = "Wrap something or someone in a cocoon. If it's a living being, you'll also consume them, allowing you to lay eggs." - ranged_mousepointer = 'icons/effects/wrap_target.dmi' - action_icon = 'icons/hud/actions/actions_animal.dmi' - action_icon_state = "wrap_0" - action_background_icon_state = "bg_alien" + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner + var/obj/structure/spider/stickyweb/web = locate() in get_turf(spider) + if(web && (istype(web, /obj/structure/spider/stickyweb))) + to_chat(owner, ("There's already a web here!")) + return FALSE -/obj/effect/proc_holder/spider/wrap/lesser - desc = "Wrap loose objects in a cocoon of silk to prevent them from being used" + if(!isturf(spider.loc)) + return FALSE -/obj/effect/proc_holder/spider/wrap/update_icon() - action.button_icon_state = "wrap_[active]" - action.UpdateButtonIcon() + return TRUE -/obj/effect/proc_holder/spider/wrap/activate(mob/living/user) - var/message - if(active) - message = "You no longer prepare to wrap something in a cocoon." - remove_ranged_ability(message) +/datum/action/innate/spider/lay_web/on_activate() + var/turf/spider_turf = get_turf(owner) + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner + var/obj/structure/spider/stickyweb/web = locate() in spider_turf + if(web) + spider.visible_message( + ("[spider] begins to pack more webbing onto the web."), + ("You begin to seal the web."), + ) else - message = "You prepare to wrap something in a cocoon. Left-click your target to start wrapping!" - add_ranged_ability(user, message, TRUE) - return TRUE + spider.visible_message( + ("[spider] begins to secrete a sticky substance."), + ("You begin to lay a web."), + ) -/obj/effect/proc_holder/spider/wrap/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - if(ranged_ability_user.incapacitated() || !istype(ranged_ability_user, /mob/living/simple_animal/hostile/poison/giant_spider)) - remove_ranged_ability() - return + spider.stop_automated_movement = TRUE - var/mob/living/simple_animal/hostile/poison/giant_spider/user = ranged_ability_user + if(do_after(spider, 4 SECONDS * spider.web_speed, target = spider_turf)) + if(spider.loc == spider_turf) + new /obj/structure/spider/stickyweb(spider_turf) - if(user.Adjacent(target) && (ismob(target) || isobj(target))) - var/atom/movable/target_atom = target - if(target_atom.anchored) - return - user.cocoon_target = target_atom - INVOKE_ASYNC(user, TYPE_PROC_REF(/mob/living/simple_animal/hostile/poison/giant_spider, cocoon)) - remove_ranged_ability() - return TRUE + spider.stop_automated_movement = FALSE -/obj/effect/proc_holder/spider/throw_web - name = "Throw web" - panel = "Spider" - desc = "Throw a sticky web at potential prey to immobilize them temporarily" - ranged_mousepointer = 'icons/effects/throwweb_target.dmi' - action_icon = 'icons/hud/actions/actions_animal.dmi' - action_icon_state = "throw_web_0" - action_background_icon_state = "bg_alien" +/datum/action/wrap + name = "Wrap" + desc = "Wrap something or someone in a cocoon. If it's a human or similar species, \ + you'll also consume them, allowing you to lay enriched eggs." + background_icon_state = "bg_alien" + icon_icon = 'icons/hud/actions/actions_animal.dmi' + button_icon_state = "wrap_0" + check_flags = AB_CHECK_CONSCIOUS + requires_target = TRUE + ranged_mousepointer = 'icons/effects/mouse_pointers/wrap_target.dmi' + /// The time it takes to wrap something. + var/wrap_time = 5 SECONDS -/obj/effect/proc_holder/spider/throw_web/activate(mob/living/user) - var/message - if(active) - message = "You discard the webbing." - remove_ranged_ability(message) - return - if(!istype(user, /mob/living/simple_animal/hostile/poison/giant_spider)) +/datum/action/wrap/is_available() + . = ..() + if(!.) + return FALSE + if(owner.incapacitated()) + return FALSE + if(DOING_INTERACTION(owner, INTERACTION_SPIDER_KEY)) + return FALSE + return TRUE + +/datum/action/wrap/set_click_ability(mob/on_who) + . = ..() + if(!.) return - var/mob/living/simple_animal/hostile/poison/giant_spider/spider = user - if(spider.busy != SPINNING_WEB) - spider.busy = SPINNING_WEB - spider.visible_message("[spider] begins to secrete a sticky substance.","You begin to prepare a net from webbing.") - spider.stop_automated_movement = TRUE - if(do_after(spider, 40 * spider.web_speed, spider)) - message = "You ready the completed net with your forelimbs. Left-click to throw it at a target!" - add_ranged_ability(user, message, TRUE) - spider.busy = SPIDER_IDLE - spider.stop_automated_movement = FALSE - else - to_chat(spider, "You're already spinning a web!") -/obj/effect/proc_holder/spider/throw_web/update_icon() - action.button_icon_state = "throw_web_[active]" - action.UpdateButtonIcon() + to_chat(on_who, ("You prepare to wrap something in a cocoon. Left-click your target to start wrapping!")) + button_icon_state = "wrap_1" + update_buttons() -/obj/effect/proc_holder/spider/throw_web/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) +/datum/action/wrap/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) return - var/turf/T = ranged_ability_user.loc - var/turf/U = get_step(ranged_ability_user, ranged_ability_user.dir) - if(!isturf(U) || !isturf(T)) + if(refund_cooldown) + to_chat(on_who, ("You no longer prepare to wrap something in a cocoon.")) + button_icon_state = "wrap_0" + update_buttons() + +/datum/action/wrap/on_activate(mob/user, atom/target) + if(!owner.Adjacent(target)) + owner.balloon_alert(owner, "must be closer!") + return FALSE + + if(!ismob(target) && !isobj(target)) return FALSE - ranged_ability_user.visible_message("[ranged_ability_user] throws a web!", "You throw the web!") - var/obj/projectile/bullet/spidernet/A = new /obj/projectile/bullet/spidernet(ranged_ability_user.loc) - A.preparePixelProjectile(target, ranged_ability_user, params) - A.firer = ranged_ability_user - A.fire() - ranged_ability_user.newtonian_move(get_dir(U, T)) - remove_ranged_ability() //have to spin another net before you can use it again + if(target == owner) + return FALSE + + if(isspider(target)) + owner.balloon_alert(owner, "can't wrap spiders!") + return FALSE + var/atom/movable/target_movable = target + if(target_movable.anchored) + return FALSE + + start_cooldown(wrap_time) + INVOKE_ASYNC(src, PROC_REF(cocoon), target) return TRUE -// Laying eggs -// If a spider eats a human, they can lay eggs that can hatch into special variants of the base spiders -// Otherwise, it's just basic spiders. +/datum/action/wrap/proc/cocoon(atom/movable/to_wrap) + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner + spider.cocoon_target = to_wrap + spider.cocoon() + /datum/action/innate/spider/lay_eggs name = "Lay Eggs" desc = "Lay a cluster of eggs, which will soon grow into more spiders. You must have a directive set and wrap a living being to do this." button_icon_state = "lay_eggs" -/datum/action/innate/spider/lay_eggs/IsAvailable() - if(..()) - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother)) - return FALSE - var/mob/living/simple_animal/hostile/poison/giant_spider/broodmother/S = owner - var/datum/antagonist/spider/spider_antag = S.mind?.has_antag_datum(/datum/antagonist/spider) - if((S.fed || S.enriched_fed) && (spider_antag?.spider_team.directive || !S.ckey)) - return TRUE +/datum/action/innate/spider/lay_eggs/is_available() + . = ..() + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother)) return FALSE + var/mob/living/simple_animal/hostile/poison/giant_spider/broodmother/S = owner + var/datum/antagonist/spider/spider_antag = S.mind?.has_antag_datum(/datum/antagonist/spider) + if((S.fed || S.enriched_fed) && (spider_antag?.spider_team.directive || !S.ckey)) + return TRUE + return FALSE -/datum/action/innate/spider/lay_eggs/Activate() +/datum/action/innate/spider/lay_eggs/on_activate() if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother)) return var/mob/living/simple_animal/hostile/poison/giant_spider/broodmother/spider = owner @@ -682,37 +668,12 @@ spider.spider_team = spiders //lets make sure her potentially sentient children are all on the same team new_cluster.spider_team = spider.spider_team new_cluster.faction = spider.faction.Copy() - UpdateButtonIcon(TRUE) + update_buttons() spider.busy = SPIDER_IDLE spider.stop_automated_movement = FALSE -// Directive command, for giving children orders -// The set directive is placed in the notes of every child spider, and said child gets the objective when they log into the mob -/datum/action/innate/spider/set_directive - name = "Set Directive" - desc = "Set a directive for your children to follow." - button_icon_state = "directive" -/datum/action/innate/spider/set_directive/IsAvailable() - if(..()) - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother)) - return FALSE - return TRUE -/datum/action/innate/spider/set_directive/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother)) - return - if(!owner.mind) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/broodmother/S = owner - var/datum/antagonist/spider/spider_antag = S.mind.has_antag_datum(/datum/antagonist/spider) - if(!spider_antag) - spider_antag = S.mind.add_antag_datum(/datum/antagonist/spider) - var/new_directive = stripped_input(S, "Enter the new directive", "Create directive") - if(new_directive) - spider_antag.spider_team.update_directives(new_directive) - log_game("[key_name(owner)][spider_antag.spider_team.master ? " (master: [spider_antag.spider_team.master]" : ""] set its directive to: '[new_directive]'.") - S.lay_eggs.UpdateButtonIcon(TRUE) // Spider command ability for broodmothers /datum/action/innate/spider/comm @@ -720,12 +681,12 @@ desc = "Send a command to all living spiders." button_icon_state = "command" -/datum/action/innate/spider/comm/IsAvailable() +/datum/action/innate/spider/comm/is_available() return ..() && istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother) -/datum/action/innate/spider/comm/Trigger() +/datum/action/innate/spider/comm/on_activate(mob/user, atom/target) var/input = stripped_input(owner, "Input a command for your children to follow.", "Command", "") - if(QDELETED(src) || !input || !IsAvailable()) + if(QDELETED(src) || !input || !is_available()) return FALSE spider_command(owner, input) return TRUE @@ -741,14 +702,14 @@ var/datum/antagonist/spider/spider_antag = user.mind?.has_antag_datum(/datum/antagonist/spider) if(!spider_antag) return - for(var/mob/living/simple_animal/hostile/poison/giant_spider/M in GLOB.spidermobs) - var/datum/antagonist/spider/target_spider_antag = M.mind?.has_antag_datum(/datum/antagonist/spider) + for(var/mob/living/simple_animal/hostile/poison/giant_spider/spider as anything in GLOB.spidermobs) + var/datum/antagonist/spider/target_spider_antag = spider.mind?.has_antag_datum(/datum/antagonist/spider) if(spider_antag?.spider_team == target_spider_antag?.spider_team) - to_chat(M, my_message) + to_chat(spider, my_message) for(var/M in GLOB.dead_mob_list) var/link = FOLLOW_LINK(M, user) to_chat(M, "[link] [my_message]") - usr.log_talk(message, LOG_SAY, tag="spider command") + user.log_talk(message, LOG_SAY, tag = "spider command") // Temperature damage // Flat 10 brute if they're out of safe temperature, making them vulnerable to fire or spacing @@ -762,8 +723,91 @@ else clear_alert("temp") + +// Net casters are the balanced generalist of the spider family: Moderate stats all around, and a ranged knockdown to assist others +/mob/living/simple_animal/hostile/poison/giant_spider/netcaster + name = "net caster" + obj_damage = 35 + speed = 0.5 + onweb_speed = 0 + var/datum/action/spell/basic_projectile/throw_web/spidernet + +/mob/living/simple_animal/hostile/poison/giant_spider/netcaster/Initialize(mapload) + . = ..() + spidernet = new + spidernet.Grant() + +/mob/living/simple_animal/hostile/poison/giant_spider/netcaster/Destroy() + . = ..() + spidernet.Remove() + +/datum/action/spell/basic_projectile/throw_web + name = "Throw web" + desc = "Throw a sticky web at potential prey to immobilize them temporarily" + ranged_mousepointer = 'icons/effects/throwweb_target.dmi' + icon_icon = 'icons/hud/actions/actions_animal.dmi' + button_icon_state = "throw_web_0" + background_icon_state = "bg_alien" + cooldown_time = 2 SECONDS + projectile_range = 20 // Proc holder had no range :shrug: + projectile_type = /obj/projectile/bullet/spidernet + +/datum/action/spell/basic_projectile/throw_web/can_cast_spell(feedback) + . = ..() + var/mob/living/user = owner + var/message + if(!istype(user, /mob/living/simple_animal/hostile/poison/giant_spider)) + return FALSE + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = user + if(spider.busy != SPINNING_WEB) + spider.busy = SPINNING_WEB + spider.visible_message("[spider] begins to secrete a sticky substance.","You begin to prepare a net from webbing.") + spider.stop_automated_movement = TRUE + if(do_after(spider, 40 * spider.web_speed, spider)) + message = "You ready the completed net with your forelimbs. Left-click to throw it at a target!" + to_chat(spider, message) + return TRUE + spider.busy = SPIDER_IDLE + spider.stop_automated_movement = FALSE + else + to_chat(spider, "You're already spinning a web!") + return FALSE + + +// Directive command, for giving children orders +// The set directive is placed in the notes of every child spider, and said child gets the objective when they log into the mob +/datum/action/innate/spider/set_directive + name = "Set Directive" + desc = "Set a directive for your children to follow." + button_icon_state = "directive" + + +/datum/action/innate/spider/set_directive/is_available() + if(..()) + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother)) + return FALSE + return TRUE + + +/datum/action/innate/spider/set_directive/on_activate() + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/broodmother)) + return + if(!owner.mind) + return + var/mob/living/simple_animal/hostile/poison/giant_spider/broodmother/S = owner + var/datum/antagonist/spider/spider_antag = S.mind.has_antag_datum(/datum/antagonist/spider) + if(!spider_antag) + spider_antag = S.mind.add_antag_datum(/datum/antagonist/spider) + var/new_directive = stripped_input(S, "Enter the new directive", "Create directive") + if(new_directive) + spider_antag.spider_team.update_directives(new_directive) + log_game("[key_name(owner)][spider_antag.spider_team.master ? " (master: [spider_antag.spider_team.master]" : ""] set its directive to: '[new_directive]'.") + S.lay_eggs.update_buttons() + + #undef SPIDER_IDLE #undef SPINNING_WEB #undef LAYING_EGGS #undef MOVING_TO_TARGET #undef SPINNING_COCOON +#undef INTERACTION_SPIDER_KEY diff --git a/code/modules/mob/living/simple_animal/hostile/goose.dm b/code/modules/mob/living/simple_animal/hostile/goose.dm index 5701f4712fb73..3f4a3044725e3 100644 --- a/code/modules/mob/living/simple_animal/hostile/goose.dm +++ b/code/modules/mob/living/simple_animal/hostile/goose.dm @@ -57,7 +57,7 @@ var/vomiting = FALSE var/vomitCoefficient = 1 var/vomitTimeBonus = 0 - var/datum/action/cooldown/vomit/goosevomit + var/datum/action/vomit/goosevomit /mob/living/simple_animal/hostile/retaliate/goose/vomit/Initialize(mapload) . = ..() @@ -178,18 +178,17 @@ if (tasty) feed(tasty) -/datum/action/cooldown/vomit +/datum/action/vomit name = "Vomit" check_flags = AB_CHECK_CONSCIOUS button_icon_state = "vomit" icon_icon = 'icons/mob/animal.dmi' cooldown_time = 250 -/datum/action/cooldown/vomit/Trigger() - if(!..()) - return FALSE - if(!istype(owner, /mob/living/simple_animal/hostile/retaliate/goose/vomit)) - return FALSE +/datum/action/vomit/is_available() + return ..() && istype(owner, /mob/living/simple_animal/hostile/retaliate/goose/vomit) + +/datum/action/vomit/on_activate(mob/user, atom/target) var/mob/living/simple_animal/hostile/retaliate/goose/vomit/vomit = owner if(!vomit.vomiting) vomit.vomit_prestart(vomit.vomitTimeBonus + 25) diff --git a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm index fd6d4521dba22..ad42e0b1d8c42 100644 --- a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm +++ b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm @@ -91,7 +91,7 @@ search_objects = 0 if(LAZYACCESSASSOC(mecha.occupant_actions, src, /datum/action/vehicle/sealed/mecha/mech_defense_mode) && !mecha.defense_mode) var/datum/action/action = mecha.occupant_actions[src][/datum/action/vehicle/sealed/mecha/mech_defense_mode] - action.Trigger(TRUE) + action.trigger() /mob/living/simple_animal/hostile/syndicate/mecha_pilot/proc/exit_mecha(obj/vehicle/sealed/mecha/M) if(!M) @@ -225,23 +225,23 @@ if(threat_count >= threat_use_mecha_smoke && prob(smoke_chance)) if(LAZYACCESSASSOC(mecha.occupant_actions, src, /datum/action/vehicle/sealed/mecha/mech_smoke) && !mecha.smoke_charges) var/datum/action/action = mecha.occupant_actions[src][/datum/action/vehicle/sealed/mecha/mech_smoke] - action.Trigger() + action.trigger() //Heavy damage - Defense Power or Retreat if(mecha.get_integrity() < mecha.max_integrity*0.25) if(prob(defense_mode_chance)) if(LAZYACCESSASSOC(mecha.occupant_actions, src, /datum/action/vehicle/sealed/mecha/mech_defense_mode) && !mecha.defense_mode) var/datum/action/action = mecha.occupant_actions[src][/datum/action/vehicle/sealed/mecha/mech_defense_mode] - action.Trigger(TRUE) - addtimer(CALLBACK(action, TYPE_PROC_REF(/datum/action/vehicle/sealed/mecha/mech_defense_mode, Trigger), FALSE), 100) //10 seconds of defense, then toggle off + action.trigger() + addtimer(CALLBACK(action, TYPE_PROC_REF(/datum/action/vehicle/sealed/mecha/mech_defense_mode, trigger), FALSE), 100) //10 seconds of defense, then toggle off else if(prob(retreat_chance)) //Speed boost if possible if(LAZYACCESSASSOC(mecha.occupant_actions, src, /datum/action/vehicle/sealed/mecha/mech_overload_mode) && !mecha.leg_overload_mode) var/datum/action/action = mecha.occupant_actions[src][/datum/action/vehicle/sealed/mecha/mech_overload_mode] mecha.leg_overload_mode = FALSE - action.Trigger(TRUE) - addtimer(CALLBACK(action, TYPE_PROC_REF(/datum/action/vehicle/sealed/mecha/mech_overload_mode, Trigger), FALSE), 100) //10 seconds of speeeeed, then toggle off + action.trigger() + addtimer(CALLBACK(action, TYPE_PROC_REF(/datum/action/vehicle/sealed/mecha/mech_overload_mode, trigger), FALSE), 100) //10 seconds of speeeeed, then toggle off retreat_distance = 50 addtimer(VARSET_CALLBACK(src, retreat_distance, 0), 10 SECONDS) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm index 8670e2bcc3139..88f6e725145b4 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/bubblegum.dm @@ -93,7 +93,7 @@ Difficulty: Hard /datum/action/innate/megafauna_attack/hallucination_surround name = "Surround Target" icon_icon = 'icons/turf/walls/wall.dmi' - button_icon_state = "wall" + button_icon_state = "wall-0" chosen_message = "You are now surrounding the target you click on with hallucinations." chosen_attack_num = 3 diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 225bf40f36e9c..c9752554ca9f6 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -841,8 +841,8 @@ GLOBAL_DATUM(blackbox, /obj/machinery/smartfridge/black_box) ADD_TRAIT(L, TRAIT_MUTE, STASIS_MUTE) L.status_flags |= GODMODE L.mind.transfer_to(holder_animal) - var/obj/effect/proc_holder/spell/targeted/exit_possession/P = new /obj/effect/proc_holder/spell/targeted/exit_possession - holder_animal.mind.AddSpell(P) + var/datum/action/exit_possession/P = new /datum/action/exit_possession + P.Grant(holder_animal) holder_animal.remove_verb(/mob/living/verb/pulled) /obj/structure/closet/stasis/dump_contents(kill = TRUE) @@ -852,8 +852,9 @@ GLOBAL_DATUM(blackbox, /obj/machinery/smartfridge/black_box) L.status_flags &= ~GODMODE L.notransform = 0 if(holder_animal) + var/datum/action/exit_possession/P = new /datum/action/exit_possession + P.Remove(holder_animal) holder_animal.mind.transfer_to(L) - L.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/exit_possession) if(kill || !isanimal(loc)) L.investigate_log("has died from [src].", INVESTIGATE_DEATHS) L.death(FALSE) @@ -865,32 +866,23 @@ GLOBAL_DATUM(blackbox, /obj/machinery/smartfridge/black_box) /obj/structure/closet/stasis/ex_act() return -/obj/effect/proc_holder/spell/targeted/exit_possession +/datum/action/exit_possession name = "Exit Possession" - desc = "Exits the body you are possessing." - charge_max = 60 - clothes_req = 0 - invocation_type = INVOCATION_NONE - max_targets = 1 - range = -1 - include_user = TRUE - selection_type = "view" - action_icon = 'icons/hud/actions/actions_spells.dmi' - action_icon_state = "exit_possession" - sound = null - -/obj/effect/proc_holder/spell/targeted/exit_possession/cast(list/targets, mob/user = usr) - if(!isfloorturf(user.loc)) - return - var/datum/mind/target_mind = user.mind - for(var/i in user) - if(istype(i, /obj/structure/closet/stasis)) - var/obj/structure/closet/stasis/S = i - S.dump_contents(0) - qdel(S) - break - user.gib() - target_mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/exit_possession) + desc = "Exits the body you are possessing. They will explode violently when this occurs." + icon_icon = 'icons/hud/actions/actions_spells.dmi' + button_icon_state = "exit_possession" + +/datum/action/exit_possession/is_available() + return ..() && isfloorturf(owner.loc) + +/datum/action/exit_possession/on_activate(mob/user, atom/target) + var/obj/structure/closet/stasis/stasis = locate() in owner + if(!stasis) + CRASH("[type] did not find a stasis closet thing in the owner.") + + stasis.dump_contents(FALSE) + qdel(stasis) + qdel(src) #undef ACTIVATE_TOUCH diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm index 185f6b2307c1b..c00f74ea1a5d6 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/megafauna.dm @@ -159,7 +159,7 @@ return FALSE return ..() -/datum/action/innate/megafauna_attack/Activate() +/datum/action/innate/megafauna_attack/on_activate() var/mob/living/simple_animal/hostile/megafauna/fauna = owner fauna.chosen_attack = chosen_attack_num to_chat(fauna, chosen_message) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm index c47bfa1a84049..358316c1f48ac 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/elite.dm @@ -67,7 +67,7 @@ While using this makes the system rely on OnFire, it still gives options for tim /datum/action/innate/elite_attack name = "Elite Attack" icon_icon = 'icons/hud/actions/actions_elites.dmi' - button_icon_state = "" + button_icon_state = null background_icon_state = "bg_default" var/chosen_message var/chosen_attack_num = 0 @@ -77,7 +77,7 @@ While using this makes the system rely on OnFire, it still gives options for tim return ..() return FALSE -/datum/action/innate/elite_attack/Activate() +/datum/action/innate/elite_attack/on_activate() var/mob/living/simple_animal/hostile/asteroid/elite/elite_owner = owner elite_owner.chosen_attack = chosen_attack_num to_chat(elite_owner, chosen_message) diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/nymph.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/nymph.dm index 19d11f929cbcb..3ff29da646c66 100644 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/nymph.dm +++ b/code/modules/mob/living/simple_animal/hostile/retaliate/nymph.dm @@ -101,7 +101,7 @@ evolve_ability.Remove(src) if(is_drone) if(mind) - switch_ability.Trigger(drone_parent, TRUE) //If we have someone conscious in the drone, throw them out. + switch_ability.on_activate(src, null) //If we have someone conscious in the drone, throw them out. switch_ability.Remove(src) return ..(gibbed,death_msg) @@ -264,27 +264,23 @@ background_icon_state = "bg_default" icon_icon = 'icons/hud/actions/actions_spells.dmi' button_icon_state = "grow" + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED -/datum/action/nymph/evolve/Trigger() - . = ..() - var/mob/living/simple_animal/hostile/retaliate/nymph/user = owner - if(!isnymph(user)) - return - if(user.is_drone) - to_chat(user, "You can't grow up as a drone!") +/datum/action/nymph/evolve/on_activate(mob/user, atom/target) + var/mob/living/simple_animal/hostile/retaliate/nymph/nymph = owner + if(!isnymph(nymph)) return - if(user.movement_type & VENTCRAWLING) - to_chat(user, "You cannot evolve while in a vent.") + if(nymph.is_drone) + to_chat(nymph, "You can't grow up as a drone!") return - if(user.stat != CONSCIOUS) + if(nymph.movement_type & VENTCRAWLING) + to_chat(nymph, "You cannot evolve while in a vent.") return - if(user.amount_grown >= user.max_grown) - if(user.incapacitated()) //something happened to us while we were choosing. - return - playsound(user, 'sound/creatures/venus_trap_death.ogg', 25, 1) - user.evolve() + if(nymph.amount_grown >= nymph.max_grown) + playsound(nymph, 'sound/creatures/venus_trap_death.ogg', 25, 1) + nymph.evolve() else - to_chat(user, "You are not ready to grow up by yourself.") + to_chat(nymph, "You are not ready to grow up by yourself.") return FALSE /datum/action/nymph/SwitchFrom @@ -294,23 +290,25 @@ icon_icon = 'icons/hud/actions/actions_spells.dmi' button_icon_state = "return" -/datum/action/nymph/SwitchFrom/Trigger(drone_parent, forced) - . = ..() - var/mob/living/simple_animal/hostile/retaliate/nymph/user = owner - var/mob/living/carbon/human/drone_diona = user.drone_parent - if(forced) - SwitchFrom(user, drone_parent) - if(!isnymph(user)) - return - if(user.movement_type & VENTCRAWLING) - to_chat(user, "You cannot switch while in a vent.") - return +/datum/action/nymph/SwitchFrom/pre_activate(mob/user, atom/target) + var/mob/living/simple_animal/hostile/retaliate/nymph/nymph = owner + var/mob/living/carbon/human/drone_diona = nymph.drone_parent + if(!isnymph(nymph)) + return FALSE + if(nymph.movement_type & VENTCRAWLING) + to_chat(nymph, "You cannot switch while in a vent.") + return FALSE if(QDELETED(drone_diona)) // FUCK SOMETHING HAPPENED TO THE MAIN DIONA, ABORT ABORT ABORT - user.is_drone = FALSE //We're not a drone anymore!!!! Panic! - to_chat(user, "You feel like your gestalt is gone! Something must have gone wrong...") - user.switch_ability.Remove(user) - return - SwitchFrom(user, drone_parent) + nymph.is_drone = FALSE //We're not a drone anymore!!!! Panic! + to_chat(nymph, "You feel like your gestalt is gone! Something must have gone wrong...") + nymph.switch_ability.Remove(nymph) + return FALSE + . = ..() + +/datum/action/nymph/SwitchFrom/on_activate(mob/user, atom/target) + var/mob/living/simple_animal/hostile/retaliate/nymph/nymph = owner + var/mob/living/carbon/human/drone_diona = nymph.drone_parent + SwitchFrom(nymph, drone_diona) /datum/action/nymph/SwitchFrom/proc/SwitchFrom(mob/living/simple_animal/hostile/retaliate/nymph/user, mob/living/carbon/M) var/datum/mind/C = user.mind diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index a7165dc4e4b17..da0c27339bf14 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -74,7 +74,7 @@ /// Whether space dragon is swallowing a body currently var/is_swallowing = FALSE /// The cooldown ability to use wing gust - var/datum/action/cooldown/gust_attack/gust + var/datum/action/gust_attack/gust /// The ability to make your sprite smaller var/datum/action/small_sprite/space_dragon/small_sprite /// The color of the space dragon. @@ -458,7 +458,7 @@ var/link = FOLLOW_LINK(S, src) to_chat(S, "[link] [rendered]") -/datum/action/cooldown/gust_attack +/datum/action/gust_attack name = "Gust Attack" desc = "Use your wings to knock back foes with gusts of air, pushing them away and stunning them. Using this too often will leave you vulnerable for longer periods of time." background_icon_state = "bg_default" @@ -466,9 +466,10 @@ button_icon_state = "gust_attack" cooldown_time = 5 SECONDS // the ability takes up around 2-3 seconds -/datum/action/cooldown/gust_attack/Trigger() - if(!..() || !istype(owner, /mob/living/simple_animal/hostile/space_dragon)) - return FALSE +/datum/action/gust_attack/is_available() + return ..() && istype(owner, /mob/living/simple_animal/hostile/space_dragon) + +/datum/action/gust_attack/on_activate(mob/user, atom/target) var/mob/living/simple_animal/hostile/space_dragon/S = owner if(S.using_special) return FALSE @@ -476,7 +477,7 @@ S.icon_state = "spacedragon_gust" S.update_dragon_overlay() S.useGust(TRUE) - StartCooldown() + start_cooldown() return TRUE #undef DARKNESS_THRESHOLD diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm index 2b6f7b538ccc5..b46a87ccae239 100644 --- a/code/modules/mob/living/simple_animal/hostile/statue.dm +++ b/code/modules/mob/living/simple_animal/hostile/statue.dm @@ -62,9 +62,12 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/simple_animal/hostile/statue) /mob/living/simple_animal/hostile/statue/Initialize(mapload, var/mob/living/creator) . = ..() // Give spells - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/flicker_lights) - AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/blindness) - AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision) + var/datum/action/spell/aoe/flicker_lights/flicker = new(src) + flicker.Grant(src) + var/datum/action/spell/aoe/blindness/blind = new(src) + blind.Grant(src) + var/datum/action/spell/night_vision/night_vision = new(src) + night_vision.Grant(src) // Set creator if(creator) @@ -164,68 +167,55 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/living/simple_animal/hostile/statue) . = ..() return . - creator +/mob/living/simple_animal/hostile/netherworld/statue/sentience_act() + faction -= "neutral" + // Statue powers // Flicker lights -/obj/effect/proc_holder/spell/aoe_turf/flicker_lights +/datum/action/spell/aoe/flicker_lights name = "Flicker Lights" desc = "You will trigger a large amount of lights around you to flicker." - charge_max = 300 - clothes_req = 0 - range = 14 + cooldown_time = 30 SECONDS + spell_requirements = NONE + aoe_radius = 14 + +/datum/action/spell/aoe/flicker_lights/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/obj/machinery/light/nearby_light in range(aoe_radius, center)) + if(!nearby_light.on) + continue -/obj/effect/proc_holder/spell/aoe_turf/flicker_lights/cast(list/targets,mob/user = usr) - for(var/turf/T in targets) - for(var/obj/machinery/light/L in T) - L.flicker() - return + things += nearby_light + + return things + +/datum/action/spell/aoe/flicker_lights/cast_on_thing_in_aoe(obj/machinery/light/victim, atom/caster) + victim.flicker() //Blind AOE -/obj/effect/proc_holder/spell/aoe_turf/blindness +/datum/action/spell/aoe/blindness name = "Blindness" desc = "Your prey will be momentarily blind for you to advance on them." - message = "You glare your eyes." - charge_max = 600 - clothes_req = 0 - range = 10 - -/obj/effect/proc_holder/spell/aoe_turf/blindness/cast(list/targets,mob/user = usr) - for(var/mob/living/L in GLOB.alive_mob_list) - var/turf/T = get_turf(L.loc) - if(T && (T in targets)) - L.adjust_blindness(4) - return - -//Toggle Night Vision -/obj/effect/proc_holder/spell/targeted/night_vision - name = "Toggle Nightvision \[ON\]" - desc = "Toggle your nightvision mode." - - charge_max = 10 - clothes_req = 0 - - message = "You toggle your night vision!" - range = -1 - include_user = 1 - -/obj/effect/proc_holder/spell/targeted/night_vision/cast(list/targets, mob/user = usr) - for(var/mob/living/target in targets) - switch(target.lighting_alpha) - if (LIGHTING_PLANE_ALPHA_VISIBLE) - target.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - name = "Toggle Nightvision \[More]" - if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) - target.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - name = "Toggle Nightvision \[Full]" - if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) - target.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE - name = "Toggle Nightvision \[OFF]" - else - target.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE - name = "Toggle Nightvision \[ON]" - target.update_sight() - -/mob/living/simple_animal/hostile/statue/sentience_act() - faction -= "neutral" + cooldown_time = 1 MINUTES + spell_requirements = NONE + aoe_radius = 14 + +/datum/action/spell/aoe/blindness/on_cast(mob/user, atom/target) + user.visible_message("[user] glares their eyes.") + return ..() + +/datum/action/spell/aoe/blindness/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/mob/living/nearby_mob in range(aoe_radius, center)) + if(nearby_mob == owner || nearby_mob == center) + continue + + things += nearby_mob + + return things + +/datum/action/spell/aoe/blindness/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + victim.set_blindness(4) diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm index b8699820ef2c4..faa158d415a78 100644 --- a/code/modules/mob/living/simple_animal/hostile/wizard.dm +++ b/code/modules/mob/living/simple_animal/hostile/wizard.dm @@ -20,57 +20,60 @@ unsuitable_atmos_damage = 15 faction = list(FACTION_WIZARD) status_flags = CANPUSH - + footstep_type = FOOTSTEP_MOB_SHOE retreat_distance = 3 //out of fireball range minimum_distance = 3 del_on_death = TRUE loot = list(/obj/effect/mob_spawn/human/corpse/wizard, /obj/item/staff) - var/obj/effect/proc_holder/spell/aimed/fireball/fireball = null - var/obj/effect/proc_holder/spell/targeted/turf_teleport/blink/blink = null - var/obj/effect/proc_holder/spell/targeted/projectile/magic_missile/mm = null + var/datum/action/spell/pointed/projectile/fireball/fireball + var/datum/action/spell/teleport/radius_turf/blink/blink + var/datum/action/spell/aoe/magic_missile/magic_missile var/next_cast = 0 - footstep_type = FOOTSTEP_MOB_SHOE discovery_points = 3000 /mob/living/simple_animal/hostile/wizard/Initialize(mapload) . = ..() - fireball = new /obj/effect/proc_holder/spell/aimed/fireball - fireball.clothes_req = 0 - fireball.human_req = 0 - fireball.player_lock = 0 - AddSpell(fireball) - implants += new /obj/item/implant/exile(src) + var/obj/item/implant/exile/exiled = new /obj/item/implant/exile(src) + exiled.implant(src) + + fireball = new(src) + fireball.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + fireball.Grant(src) - mm = new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - mm.clothes_req = 0 - mm.human_req = 0 - mm.player_lock = 0 - AddSpell(mm) + magic_missile = new(src) + magic_missile.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + magic_missile.Grant(src) - blink = new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - blink.clothes_req = 0 - blink.human_req = 0 - blink.player_lock = 0 + blink = new(src) + blink.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) blink.outer_tele_radius = 3 - AddSpell(blink) + blink.Grant(src) + +/mob/living/simple_animal/hostile/wizard/Destroy() + QDEL_NULL(fireball) + QDEL_NULL(magic_missile) + QDEL_NULL(blink) + return ..() /mob/living/simple_animal/hostile/wizard/handle_automated_action() . = ..() if(target && next_cast < world.time) - if((get_dir(src,target) in list(SOUTH,EAST,WEST,NORTH)) && fireball.cast_check(0,src)) //Lined up for fireball - src.setDir(get_dir(src,target)) - fireball.perform(list(target), user = src) - next_cast = world.time + 10 //One spell per second - return . - if(mm.cast_check(0,src)) - mm.choose_targets(src) - next_cast = world.time + 10 - return . - if(blink.cast_check(0,src)) //Spam Blink when you can - blink.choose_targets(src) - next_cast = world.time + 10 - return . + if((get_dir(src, target) in list(SOUTH, EAST, WEST, NORTH)) && fireball.can_cast_spell(feedback = FALSE)) + setDir(get_dir(src, target)) + fireball.pre_activate(src, target) + next_cast = world.time + 1 SECONDS + return + + if(magic_missile.is_available()) + magic_missile.pre_activate(src, target) + next_cast = world.time + 1 SECONDS + return + + if(blink.is_available()) // Spam Blink when you can + blink.pre_activate(src, src) + next_cast = world.time + 1 SECONDS + return diff --git a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm index 05037f4860349..7a691efd56237 100644 --- a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm +++ b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm @@ -50,7 +50,7 @@ if(!wumbo) inflate_cooldown = max((inflate_cooldown - 1), 0) if(target && AIStatus == AI_ON) - E.Activate() + E.trigger() ..() /mob/living/simple_animal/hostile/asteroid/fugu/adjustHealth(amount, updating_health = TRUE, forced = FALSE) @@ -60,9 +60,10 @@ /mob/living/simple_animal/hostile/asteroid/fugu/Aggro() ..() - E.Activate() + E.trigger() /datum/action/innate/fugu + button_icon_state = null icon_icon = 'icons/hud/actions/actions_animal.dmi' /datum/action/innate/fugu/expand @@ -70,7 +71,7 @@ desc = "Temporarily increases your size, and makes you significantly more dangerous and tough! Do not bully the fugu!" button_icon_state = "expand" -/datum/action/innate/fugu/expand/Activate() +/datum/action/innate/fugu/expand/on_activate() var/mob/living/simple_animal/hostile/asteroid/fugu/F = owner if(F.wumbo) to_chat(F, "YOU'RE ALREADY WUMBO!") diff --git a/code/modules/mob/living/simple_animal/slime/powers.dm b/code/modules/mob/living/simple_animal/slime/powers.dm index 588289cb1767a..c2173ddd4fe92 100644 --- a/code/modules/mob/living/simple_animal/slime/powers.dm +++ b/code/modules/mob/living/simple_animal/slime/powers.dm @@ -4,10 +4,11 @@ /datum/action/innate/slime check_flags = AB_CHECK_CONSCIOUS icon_icon = 'icons/hud/actions/actions_slime.dmi' + button_icon_state = null background_icon_state = "bg_alien" var/needs_growth = NO_GROWTH_NEEDED -/datum/action/innate/slime/IsAvailable() +/datum/action/innate/slime/is_available() if(..()) var/mob/living/simple_animal/slime/S = owner if(needs_growth == GROWTH_NEEDED) @@ -40,7 +41,7 @@ button_icon_state = "slimeeat" -/datum/action/innate/slime/feed/Activate() +/datum/action/innate/slime/feed/on_activate() var/mob/living/simple_animal/slime/S = owner S.Feed() @@ -155,7 +156,7 @@ button_icon_state = "slimegrow" needs_growth = GROWTH_NEEDED -/datum/action/innate/slime/evolve/Activate() +/datum/action/innate/slime/evolve/on_activate() var/mob/living/simple_animal/slime/S = owner S.Evolve() if(S.is_adult) @@ -221,7 +222,7 @@ button_icon_state = "slimesplit" needs_growth = GROWTH_NEEDED -/datum/action/innate/slime/reproduce/Activate() +/datum/action/innate/slime/reproduce/on_activate() var/mob/living/simple_animal/slime/S = owner S.Reproduce() diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index e1a0aa58098e2..c4d79fe65506a 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -132,6 +132,10 @@ AddElement(/datum/element/weather_listener, /datum/weather/ash_storm, ZTRAIT_ASHSTORM, GLOB.ash_storm_sounds) + // Set mouse pointer + client.mouse_override_icon = null + update_mouse_pointer() + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_LOGGED_IN, src) return TRUE diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 290e2fa7ded2f..252a48a3c5672 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -17,6 +17,10 @@ for (var/datum/component/comp in GetComponents(/datum/component/moved_relay)) qdel(comp) - clear_important_client_contents(client) + // Unset the click abilities when logging out + for (var/datum/action/action in actions) + if (click_intercept == action) + action.unset_click_ability(src) + clear_important_client_contents(client) return TRUE diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 0b04e5ecbd70c..35b794b1457db 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -45,13 +45,6 @@ ghostize() if(mind?.current == src) //Let's just be safe yeah? This will occasionally be cleared, but not always. Can't do it with ghostize without changing behavior mind.set_current(null) - QDEL_LIST(mob_spell_list) - for(var/datum/action/A as() in actions) - if(istype(A.target, /obj/effect/proc_holder)) - A.Remove(src) // Mind's spells' actions should only be removed - else - qdel(A) // Other actions can be safely deleted - actions.Cut() return ..() /** @@ -342,7 +335,6 @@ /mob/proc/get_slot_by_item(obj/item/looking_for) if(looking_for in held_items) return ITEM_SLOT_HANDS - return null ///Is the mob incapacitated @@ -821,24 +813,21 @@ /mob/proc/is_muzzled() return FALSE -/** - * Convert a list of spells into a displyable list for the statpanel - * - * Shows charge and other important info - */ -/mob/proc/get_spell_stat_data(list/spells, current_tab) - var/list/stat_data = list() - for(var/obj/effect/proc_holder/spell/S in spells) - if(S.can_be_cast_by(src) && current_tab == S.panel) - client.stat_update_mode = STAT_MEDIUM_UPDATE - switch(S.charge_type) - if("recharge") - stat_data["[S.name]"] = GENERATE_STAT_TEXT("[S.charge_counter/10.0]/[S.charge_max/10]") - if("charges") - stat_data["[S.name]"] = GENERATE_STAT_TEXT("[S.charge_counter]/[S.charge_max]") - if("holdervar") - stat_data["[S.name]"] = GENERATE_STAT_TEXT("[S.holder_var_type] [S.holder_var_amount]") - return stat_data +/datum/action/proc/get_stat_label() + var/label = "" + var/time_left = max(next_use_time - world.time, 0) + if (cooldown_time) + if(istype(src, /datum/action/spell)) + var/datum/action/spell/spell = src + label = "Spell Level: [spell.spell_level]/[spell.spell_max_level], Spell Cooldown: [(spell.cooldown_time/10)] Seconds, Can be cast in [(time_left/10)]" + else + label = "Action Cooldown: [(cooldown_time/10)] Seconds, Can be cast in [(time_left/10)]" + else + label = "Activate" + return label + +/datum/action/proc/update_stat_status(list/stat) + return null #define MOB_FACE_DIRECTION_DELAY 1 @@ -916,39 +905,90 @@ ghost.notify_cloning(message, sound, source, flashwindow) return ghost -///Add a spell to the mobs spell list -/mob/proc/AddSpell(obj/effect/proc_holder/spell/S) - // HACK: Preferences menu creates one of every selectable species. - // Some species, like vampires, create spells when they're made. - // The "action" is created when those spells Initialize. - // Preferences menu can create these assets at *any* time, primarily before - // the atoms SS initializes. - // That means "action" won't exist. - if (isnull(S.action)) - return - mob_spell_list += S - S.action.Grant(src) +/** + * Checks to see if the mob can block magic + * + * args: + * * casted_magic_flags (optional) A bitfield with the types of magic resistance being checked (see flags at: /datum/component/anti_magic) + * * charge_cost (optional) The cost of charge to block a spell that will be subtracted from the protection used +**/ +/mob/proc/can_block_magic(casted_magic_flags = MAGIC_RESISTANCE, charge_cost = 1) + if(casted_magic_flags == NONE) // magic with the NONE flag is immune to blocking + return FALSE -///Remove a spell from the mobs spell list -/mob/proc/RemoveSpell(obj/effect/proc_holder/spell/spell) - if(!spell) - return - for(var/X in mob_spell_list) - var/obj/effect/proc_holder/spell/S = X - if(istype(S, spell)) - mob_spell_list -= S - qdel(S) - -///Return any anti magic atom on this mob that matches the magic type -/mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, major = TRUE, self = FALSE) - if(!magic && !holy) - return - var/list/protection_sources = list() - if(SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, src, magic, holy, major, self, protection_sources) & COMPONENT_BLOCK_MAGIC) - if(protection_sources.len) - return pick(protection_sources) - else - return src + // A list of all things which are providing anti-magic to us + var/list/antimagic_sources = list() + var/is_magic_blocked = FALSE + + if(SEND_SIGNAL(src, COMSIG_MOB_RECEIVE_MAGIC, casted_magic_flags, charge_cost, antimagic_sources) & COMPONENT_MAGIC_BLOCKED) + is_magic_blocked = TRUE + if(HAS_TRAIT(src, TRAIT_ANTIMAGIC)) + is_magic_blocked = TRUE + if((casted_magic_flags & MAGIC_RESISTANCE_HOLY) && HAS_TRAIT(src, TRAIT_HOLY)) + is_magic_blocked = TRUE + + if(is_magic_blocked && charge_cost > 0 && !HAS_TRAIT(src, TRAIT_RECENTLY_BLOCKED_MAGIC)) + on_block_magic_effects(casted_magic_flags, antimagic_sources) + + return is_magic_blocked + +/// Called whenever a magic effect with a charge cost is blocked and we haven't recently blocked magic. +/mob/proc/on_block_magic_effects(magic_flags, list/antimagic_sources) + return + +/mob/proc/antimagic_trait_handler() + REMOVE_TRAIT(src, TRAIT_RECENTLY_BLOCKED_MAGIC, MAGIC_TRAIT) + +/mob/living/on_block_magic_effects(magic_flags, list/antimagic_sources) + ADD_TRAIT(src, TRAIT_RECENTLY_BLOCKED_MAGIC, MAGIC_TRAIT) + addtimer(CALLBACK(src, PROC_REF(antimagic_trait_handler)), 6 SECONDS) + var/mutable_appearance/antimagic_effect + var/antimagic_color + var/atom/antimagic_source = length(antimagic_sources) ? pick(antimagic_sources) : src + + if(magic_flags & MAGIC_RESISTANCE) + visible_message( + "[src] pulses red as [ismob(antimagic_source) ? p_they() : antimagic_source] absorbs magic energy!", + "An intense magical aura pulses around [ismob(antimagic_source) ? "you" : antimagic_source] as it dissipates into the air!", + ) + antimagic_effect = mutable_appearance('icons/effects/effects.dmi', "shield-red", MOB_SHIELD_LAYER) + antimagic_color = LIGHT_COLOR_BLOOD_MAGIC + playsound(src, 'sound/magic/magic_block.ogg', 50, TRUE) + + else if(magic_flags & MAGIC_RESISTANCE_HOLY) + visible_message( + ("[src] starts to glow as [ismob(antimagic_source) ? p_they() : antimagic_source] emits a halo of light!"), + ("A feeling of warmth washes over [ismob(antimagic_source) ? "you" : antimagic_source] as rays of light surround your body and protect you!"), + ) + antimagic_effect = mutable_appearance('icons/mob/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER) + antimagic_color = LIGHT_COLOR_HOLY_MAGIC + playsound(src, 'sound/magic/magic_block_holy.ogg', 50, TRUE) + + else if(magic_flags & MAGIC_RESISTANCE_MIND) + visible_message( + ("[src] forehead shines as [ismob(antimagic_source) ? p_they() : antimagic_source] repulses magic from their mind!"), + ("A feeling of cold splashes on [ismob(antimagic_source) ? "you" : antimagic_source] as your forehead reflects magic usering your mind!"), + ) + antimagic_effect = mutable_appearance('icons/mob/effects/genetics.dmi', "telekinesishead", MOB_SHIELD_LAYER) + antimagic_color = LIGHT_COLOR_DARK_BLUE + playsound(src, 'sound/magic/magic_block_mind.ogg', 50, TRUE) + + mob_light(range = 2, color = antimagic_color, duration = 5 SECONDS) + add_overlay(antimagic_effect) + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, cut_overlay), antimagic_effect), 5 SECONDS) + +/** + * Checks to see if the mob can cast normal magic spells. + * + * args: + * * magic_flags (optional) A bitfield with the type of magic being cast (see flags at: /datum/component/anti_magic) +**/ +/mob/proc/can_cast_magic(magic_flags = MAGIC_RESISTANCE) + if(magic_flags == NONE) // magic with the NONE flag can always be cast + return TRUE + + var/restrict_magic_flags = SEND_SIGNAL(src, COMSIG_MOB_RESTRICT_MAGIC, magic_flags) + return restrict_magic_flags == NONE ///Return any anti artifact atom on this mob /mob/proc/anti_artifact_check(self = FALSE) @@ -1143,20 +1183,20 @@ bloom = client.prefs.read_preference(/datum/preference/numeric/bloom) * (ADDITIVE_LIGHTING_PLANE_ALPHA_MAX / 100) LA.alpha = lighting_alpha * (bloom / 255) + + ///Update the mouse pointer of the attached client in this mob /mob/proc/update_mouse_pointer() - if (!client) + if(!client) return - client.mouse_pointer_icon = initial(client.mouse_pointer_icon) - if (ismecha(loc)) - var/obj/vehicle/sealed/mecha/M = loc - if(M.mouse_pointer) - client.mouse_pointer_icon = M.mouse_pointer - else if (istype(loc, /obj/vehicle/sealed)) + if(client.mouse_pointer_icon != initial(client.mouse_pointer_icon))//only send changes to the client if theyre needed + client.mouse_pointer_icon = initial(client.mouse_pointer_icon) + if(istype(loc, /obj/vehicle/sealed)) var/obj/vehicle/sealed/E = loc if(E.mouse_pointer) client.mouse_pointer_icon = E.mouse_pointer - + if(client.mouse_override_icon) + client.mouse_pointer_icon = client.mouse_override_icon /// This mob can read /mob/proc/is_literate() diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index b72f36346b73e..1c8da518bf46f 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -41,8 +41,9 @@ CREATION_TEST_IGNORE_SELF(/mob) var/cached_multiplicative_actions_slowdown /// List of action hud items the user has var/list/datum/action/actions = list() - /// A special action? No idea why this lives here - var/list/datum/action/chameleon_item_actions + /// A list of chameleon actions we have specifically + /// This can be unified with the actions list + var/list/datum/action/item_action/chameleon/chameleon_item_actions /// Whether a mob is alive or dead. TODO: Move this to living - Nodrak (2019, still here) var/stat = CONSCIOUS @@ -165,14 +166,6 @@ CREATION_TEST_IGNORE_SELF(/mob) /// Can this mob enter shuttles var/move_on_shuttle = 1 - /** - * construct spells and mime spells. - * - * Spells that do not transfer from one mob to another and can not be lost in mindswap. - * obviously do not live in the mind - */ - var/list/mob_spell_list = list() - /// bitflags defining which status effects can be inflicted (replaces canknockdown, canstun, etc) var/status_flags = CANSTUN|CANKNOCKDOWN|CANUNCONSCIOUS|CANPUSH diff --git a/code/modules/mob/mob_stat.dm b/code/modules/mob/mob_stat.dm index 8f75fde9dc687..987f03d9da361 100644 --- a/code/modules/mob/mob_stat.dm +++ b/code/modules/mob/mob_stat.dm @@ -20,6 +20,8 @@ ? "Turf" \ : "Other" +#define STAT_TAB_ACTIONS "Actions" + /client var/stat_update_mode = STAT_FAST_UPDATE var/stat_update_time = 0 @@ -93,15 +95,20 @@ "message" = message.message ) tab_data["messages"] += list(msg) + if (STAT_TAB_ACTIONS) + for(var/datum/action/action in actions) + tab_data["[action.name]"] = list( + text = action.get_stat_label(), + type = STAT_BUTTON, + action = "do_action", + params = list("ref" = REF(action)) + ) else // ===== NON CONSTANT TABS (Tab names which can change) ===== // ===== LISTEDS TURFS ===== if(listed_turf && sanitize(listed_turf.name) == selected_tab) // Check if we can actually see the turf listed_turf.render_stat_information(client, tab_data) - if(mind) - tab_data += get_spell_stat_data(mind.spell_list, selected_tab) - tab_data += get_spell_stat_data(mob_spell_list, selected_tab) if(requires_holder && !client.holder) message_admins("[ckey] attempted to access the [selected_tab] tab without sufficient rights.") log_admin("[ckey] attempted to access the [selected_tab] tab without sufficient rights.") @@ -320,13 +327,9 @@ listed_turf = null else tabs |= sanitize(listed_turf.name) - //Add spells - var/list/spells = mob_spell_list - if(mind) - spells = mind.spell_list - for(var/obj/effect/proc_holder/spell/S in spells) - if(S.can_be_cast_by(src)) - tabs |= S.panel + //Spells we have + if (length(actions)) + tabs += STAT_TAB_ACTIONS //Holder stat tabs if(client.holder) tabs |= "MC" @@ -462,6 +465,11 @@ client.battle_royale() if ("votetoleave") client.vote_to_leave() + if ("do_action") + var/datum/action/action = locate(params["ref"]) in actions + if (!action) + return + action.trigger() /* * Sets the current stat tab selected. @@ -514,3 +522,4 @@ #undef MAX_ICONS_PER_TILE #undef STAT_PANEL_TAG +#undef STAT_TAB_ACTIONS diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 6205706f1cefc..5510f7a851d0c 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -20,8 +20,14 @@ return whisper(message) -///whisper a message -/mob/proc/whisper(message, datum/language/language=null) +/** + * Whisper a message. + * + * Basic level implementation just speaks the message, nothing else. + */ +/mob/proc/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language, ignore_spam = FALSE, forced, filterproof) + if(!message) + return say(message, language) //only living mobs actually whisper, everything else just talks ///The me emote verb diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 2bd2eae94883f..bfc0c1c3ca0f0 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -93,8 +93,6 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar var/init_ringtone = "beep" /// If the device starts with its ringer on var/init_ringer_on = TRUE - /// The action for enabling/disabling the flashlight - var/datum/action/item_action/toggle_computer_light/light_action /// Stored pAI card var/obj/item/paicard/stored_pai_card /// If the device is capable of storing a pAI @@ -118,7 +116,7 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar idle_threads = list() update_id_display() if(has_light) - light_action = new(src) + add_item_action(/datum/action/item_action/toggle_computer_light) update_icon() add_messenger() @@ -153,17 +151,16 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar if(istype(stored_pai_card)) qdel(stored_pai_card) remove_pai() - if(istype(light_action)) - QDEL_NULL(light_action) physical = null remove_messenger() return ..() /obj/item/modular_computer/ui_action_click(mob/user, actiontype) - if(istype(actiontype, light_action)) + if(istype(actiontype, /datum/action/item_action/toggle_computer_light)) toggle_flashlight() - else - ..() + return + + return ..() /// From [/datum/newscaster/feed_network/proc/save_photo] /obj/item/modular_computer/proc/save_photo(icon/photo) @@ -575,8 +572,7 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar set_light_on(!light_on) update_icon() // Show the light_on overlay on top of the action button icon - if(light_action?.owner) - light_action.UpdateButtonIcon(force = TRUE) + update_action_buttons(force = TRUE) //force it because we added an overlay, not changed its icon return TRUE /** diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm index be62b73c4a85f..fb6bb60cbfbc3 100644 --- a/code/modules/paperwork/contract.dm +++ b/code/modules/paperwork/contract.dm @@ -47,3 +47,5 @@ M.visible_message("[user] beats [M] over the head with [src]!", \ "[user] beats [M] over the head with [src]!") return ..() + + diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 325c26c405cea..dea021157e96a 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -59,7 +59,7 @@ var/mob/living/carbon/C = hit_atom if(C.can_catch_item(TRUE)) var/datum/action/innate/origami/origami_action = locate() in C.actions - if(origami_action?.active) //if they're a master of origami and have the ability turned on, force throwmode on so they'll automatically catch the plane. + if(origami_action?.is_active()) //if they're a master of origami and have the ability turned on, force throwmode on so they'll automatically catch the plane. C.throw_mode_on(THROW_MODE_TOGGLE) if(..() || !ishuman(hit_atom))//if the plane is caught or it hits a nonhuman diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index a5e743d4e929d..df43ea9009c1b 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -439,8 +439,9 @@ name = "Switch to Manual Firing" desc = "The emitter will only fire on your command and at your designated target" button_icon_state = "mech_zoom_on" + icon_icon = 'icons/hud/actions/actions_mecha.dmi' -/datum/action/innate/proto_emitter/firing/Activate() +/datum/action/innate/proto_emitter/firing/on_activate() if(proto_emitter.manual) playsound(proto_emitter,'sound/mecha/mechmove01.ogg', 50, TRUE) proto_emitter.manual = FALSE @@ -450,7 +451,7 @@ for(var/obj/item/item in buckled_mob.held_items) if(istype(item, /obj/item/turret_control)) qdel(item) - UpdateButtonIcon() + update_buttons() return playsound(proto_emitter,'sound/mecha/mechmove01.ogg', 50, TRUE) name = "Switch to Automatic Firing" @@ -467,7 +468,7 @@ else //Entries in the list should only ever be items or null, so if it's not an item, we can assume it's an empty hand var/obj/item/turret_control/turret_control = new /obj/item/turret_control() buckled_mob.put_in_hands(turret_control) - UpdateButtonIcon() + update_buttons() /obj/item/turret_control diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 2a0a69d0fde97..68f7911c1f299 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -680,10 +680,10 @@ button_icon_state = "sniper_zoom" var/obj/item/gun/gun = null -/datum/action/toggle_scope_zoom/Trigger() +/datum/action/toggle_scope_zoom/on_activate(mob/user, atom/target) gun.zoom(owner, owner.dir) -/datum/action/toggle_scope_zoom/IsAvailable() +/datum/action/toggle_scope_zoom/is_available() . = ..() if(!. && gun) gun.zoom(owner, owner.dir, FALSE) diff --git a/code/modules/projectiles/guns/magic.dm b/code/modules/projectiles/guns/magic.dm index 1e1c36ed1a78f..6b95eaee9b3c2 100644 --- a/code/modules/projectiles/guns/magic.dm +++ b/code/modules/projectiles/guns/magic.dm @@ -9,7 +9,7 @@ fire_sound = 'sound/weapons/emitter.ogg' flags_1 = CONDUCT_1 w_class = WEIGHT_CLASS_HUGE - var/checks_antimagic = TRUE + var/antimagic_flags = MAGIC_RESISTANCE var/max_charges = 6 var/charges = 0 var/recharge_rate = 8 @@ -43,7 +43,7 @@ return else no_den_usage = 0 - if(checks_antimagic && user.anti_magic_check(TRUE, FALSE, major = FALSE, self = TRUE)) + if(!user.can_cast_magic(antimagic_flags)) add_fingerprint(user) to_chat(user, "Something is interfering with [src].") return diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 685c7398a0589..9ae10aa41ec35 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -42,6 +42,8 @@ var/ignore_source_check = FALSE /// We are flagged PHASING temporarily to not stop moving when we Bump something but want to keep going anyways. var/temporary_unstoppable_movement = FALSE + /// We ignore mobs with these factions. + var/list/ignored_factions /** PROJECTILE PIERCING * WARNING: @@ -481,6 +483,10 @@ var/mob/M = firer if((target == firer) || ((target == firer.loc) && ismecha(firer.loc)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))) return FALSE + if(ignored_factions?.len && ismob(target) && !direct_target) + var/mob/target_mob = target + if(faction_check(target_mob.faction, ignored_factions)) + return FALSE if(target.density || cross_failed) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc. return TRUE if(!isliving(target)) diff --git a/code/modules/projectiles/projectile/bullets/special.dm b/code/modules/projectiles/projectile/bullets/special.dm index 6da5eb1a233c1..70fa6b614666d 100644 --- a/code/modules/projectiles/projectile/bullets/special.dm +++ b/code/modules/projectiles/projectile/bullets/special.dm @@ -18,6 +18,13 @@ . = ..() SpinAnimation() +/obj/projectile/bullet/honker/on_hit(mob/target, blocked, pierce_hit) + . = ..() + var/mob/M = target + if(istype(M)) + if(M.can_block_magic()) + return BULLET_ACT_BLOCK + // Mime /obj/projectile/bullet/mime diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 1556116350ace..42507677e7cc3 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -7,6 +7,19 @@ armour_penetration = 100 armor_flag = NONE martial_arts_no_deflect = TRUE + /// determines what type of antimagic can block the spell projectile + var/antimagic_flags = MAGIC_RESISTANCE + /// determines the drain cost on the antimagic item + var/antimagic_charge_cost = 1 + +/obj/projectile/magic/prehit_pierce(atom/target) + . = ..() + + if(isliving(target)) + var/mob/living/victim = target + if(victim.can_block_magic(antimagic_flags)) + visible_message(("[src] fizzles on contact with [victim]!")) + return PROJECTILE_DELETE_WITHOUT_HITTING /obj/projectile/magic/death name = "bolt of death" @@ -17,7 +30,7 @@ . = ..() if(ismob(target)) var/mob/M = target - if(M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) M.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK M.death(0) @@ -35,7 +48,7 @@ if(isliving(target)) if(target.ishellbound()) return BULLET_ACT_BLOCK - if(target.anti_magic_check()) + if(target.can_block_magic(antimagic_flags)) target.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK if(iscarbon(target)) @@ -62,7 +75,7 @@ . = ..() if(ismob(target)) var/mob/M = target - if(M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) M.visible_message("[src] fizzles on contact with [target]!") return BULLET_ACT_BLOCK var/teleammount = 0 @@ -89,7 +102,7 @@ . = ..() if(ismob(target)) var/mob/M = target - if(M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) M.visible_message("[src] fizzles on contact with [target]!") return BULLET_ACT_BLOCK if(isturf(target)) @@ -145,7 +158,7 @@ . = ..() if(ismob(change)) var/mob/M = change - if(M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) M.visible_message("[src] fizzles on contact with [M]!") qdel(src) return BULLET_ACT_BLOCK @@ -358,7 +371,7 @@ /obj/projectile/magic/spellblade/on_hit(target) if(ismob(target)) var/mob/M = target - if(M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) M.visible_message("[src] vanishes on contact with [target]!") qdel(src) return BULLET_ACT_BLOCK @@ -376,7 +389,7 @@ /obj/projectile/magic/arcane_barrage/on_hit(target) if(ismob(target)) var/mob/M = target - if(M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) M.visible_message("[src] vanishes on contact with [target]!") qdel(src) return @@ -397,7 +410,7 @@ . = ..() if(isliving(A) && locker_suck) var/mob/living/M = A - if(M.anti_magic_check()) // no this doesn't check if ..() returned to phase through do I care no it's magic ain't gotta explain shit + if(M.can_block_magic(antimagic_flags)) // no this doesn't check if ..() returned to phase through do I care no it's magic ain't gotta explain shit M.visible_message("[src] vanishes on contact with [A]!") return PROJECTILE_DELETE_WITHOUT_HITTING if(M.incorporeal_move || M.mob_size > MOB_SIZE_HUMAN || LAZYLEN(contents)>=5) @@ -475,7 +488,7 @@ . = ..() if(isliving(target)) var/mob/living/L = target - if(L.anti_magic_check()) + if(L.can_block_magic(antimagic_flags)) L.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK var/atom/throw_target = get_edge_target_turf(L, angle2dir(Angle)) @@ -490,7 +503,7 @@ . = ..() if(isliving(target)) var/mob/living/L = target - if(L.anti_magic_check() || !firer) + if(L.can_block_magic(antimagic_flags) || !firer) L.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK L.apply_status_effect(STATUS_EFFECT_BOUNTY, firer) @@ -504,7 +517,7 @@ . = ..() if(isliving(target)) var/mob/living/L = target - if(L.anti_magic_check()) + if(L.can_block_magic(antimagic_flags)) L.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK L.apply_status_effect(STATUS_EFFECT_ANTIMAGIC) @@ -518,7 +531,7 @@ . = ..() if(isliving(target)) var/mob/living/L = target - if(L.anti_magic_check() || !firer) + if(L.can_block_magic(antimagic_flags) || !firer) L.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK var/atom/throw_target = get_edge_target_turf(L, get_dir(L, firer)) @@ -533,10 +546,10 @@ . = ..() if(ismob(target)) var/mob/M = target - if(M.anti_magic_check()) + if(M.can_block_magic(antimagic_flags)) M.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, src, /datum/mood_event/sapped) + SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, REF(src), /datum/mood_event/sapped) /obj/projectile/magic/necropotence name = "bolt of necropotence" @@ -545,21 +558,16 @@ /obj/projectile/magic/necropotence/on_hit(target) . = ..() - if(isliving(target)) - var/mob/living/L = target - if(L.anti_magic_check() || !L.mind || !L.mind.hasSoul) - L.visible_message("[src] vanishes on contact with [target]!") - return BULLET_ACT_BLOCK - to_chat(L, "Your body feels drained and there is a burning pain in your chest.") - L.maxHealth -= 20 - L.health = min(L.health, L.maxHealth) - if(L.maxHealth <= 0) - to_chat(L, "Your weakened soul is completely consumed by the [src]!") - L.mind.hasSoul = FALSE - for(var/obj/effect/proc_holder/spell/spell in L.mind.spell_list) - spell.charge_counter = spell.charge_max - spell.recharging = FALSE - spell.update_icon() + if(!isliving(target)) + return + + // Performs a soul tap on living targets hit. + // Takes away max health, but refreshes their spell cooldowns (if any) + var/datum/action/spell/tap/tap = new(src) + if(tap.is_valid_spell(target, target)) + tap.on_cast(target, target) + + qdel(tap) /obj/projectile/magic/wipe name = "bolt of possession" @@ -570,7 +578,7 @@ . = ..() if(iscarbon(target)) var/mob/living/carbon/M = target - if(M.anti_magic_check() || istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) + if(M.can_block_magic(antimagic_flags) || istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) M.visible_message("[src] vanishes on contact with [target]!") return BULLET_ACT_BLOCK for(var/x in M.get_traumas())//checks to see if the victim is already going through possession @@ -611,59 +619,96 @@ to_chat(M, "Your mind has managed to go unnoticed in the spirit world.") qdel(trauma) +/// Gives magic projectiles an area of effect radius that will bump into any nearby mobs /obj/projectile/magic/aoe - name = "Area Bolt" - desc = "What the fuck does this do?!" damage = 0 - var/proxdet = TRUE - martial_arts_no_deflect = FALSE + + /// The AOE radius that the projectile will trigger on people. + var/trigger_range = 1 + /// Whether our projectile will only be able to hit the original target / clicked on atom + var/can_only_hit_target = FALSE + + /// Whether our projectile leaves a trail behind it as it moves. + var/trail = FALSE + /// The duration of the trail before deleting. + var/trail_lifespan = 0 SECONDS + /// The icon the trail uses. + var/trail_icon = 'icons/obj/wizard.dmi' + /// The icon state the trail uses. + var/trail_icon_state = "trail" /obj/projectile/magic/aoe/Range() - if(proxdet) - for(var/mob/living/L in range(1, get_turf(src))) - if(L.stat != DEAD && L != firer && !L.anti_magic_check()) - return Bump(L) - ..() + if(trigger_range >= 1) + for(var/mob/living/nearby_guy in range(trigger_range, get_turf(src))) + if(nearby_guy.stat == DEAD) + continue + if(nearby_guy == firer) + continue + // Bump handles anti-magic checks for us, conveniently. + return Bump(nearby_guy) + + return ..() + +/obj/projectile/magic/aoe/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) + if(can_only_hit_target && target != original) + return FALSE + return ..() + +/obj/projectile/magic/aoe/Moved(atom/OldLoc, Dir) + . = ..() + if(trail) + create_trail() + +/// Creates and handles the trail that follows the projectile. +/obj/projectile/magic/aoe/proc/create_trail() + if(!trajectory) + return + var/datum/point/vector/previous = trajectory.return_vector_after_increments(1, -1) + var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) + trail.pixel_x = previous.return_px() + trail.pixel_y = previous.return_py() + trail.icon = trail_icon + trail.icon_state = trail_icon_state + //might be changed to temp overlay + trail.set_density(FALSE) + trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + QDEL_IN(trail, trail_lifespan) /obj/projectile/magic/aoe/lightning name = "lightning bolt" - icon_state = "tesla_projectile" //Better sprites are REALLY needed and appreciated!~ + icon_state = "tesla_projectile" //Better sprites are REALLY needed and appreciated!~ damage = 15 damage_type = BURN nodamage = FALSE speed = 0.3 - var/tesla_power = 20000 - var/tesla_range = 15 - var/tesla_flags = TESLA_MOB_DAMAGE | TESLA_MOB_STUN | TESLA_OBJ_DAMAGE - var/chain - var/mob/living/caster - -/obj/projectile/magic/aoe/lightning/New(loc, spell_level) - . = ..() - tesla_power += 5000 * spell_level - tesla_range += 2 * spell_level + /// The power of the zap itself when it electrocutes someone + var/zap_power = 20000 + /// The range of the zap itself when it electrocutes someone + var/zap_range = 15 + /// The flags of the zap itself when it electrocutes someone + var/zap_flags = TESLA_MOB_DAMAGE | TESLA_MOB_STUN | TESLA_OBJ_DAMAGE + /// A reference to the chain beam between the caster and the projectile + var/datum/beam/chain /obj/projectile/magic/aoe/lightning/fire(setAngle) - if(caster) - chain = caster.Beam(src, icon_state = "lightning[rand(1, 12)]") - ..() + if(firer) + chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]") + return ..() /obj/projectile/magic/aoe/lightning/on_hit(target) . = ..() - if(ismob(target)) - var/mob/M = target - if(M.anti_magic_check()) - visible_message("[src] fizzles on contact with [target]!") - qdel(src) - return BULLET_ACT_BLOCK - tesla_zap(src, tesla_range, tesla_power, tesla_flags) - qdel(src) + tesla_zap(src, zap_range, zap_power, zap_flags) /obj/projectile/magic/aoe/lightning/Destroy() - qdel(chain) - . = ..() + QDEL_NULL(chain) + return ..() + +/obj/projectile/magic/aoe/lightning/no_zap + zap_power = 10000 + zap_range = 4 + zap_flags = TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE /obj/projectile/magic/fireball name = "bolt of fireball" @@ -672,50 +717,81 @@ damage_type = BRUTE nodamage = FALSE - //explosion values + /// Heavy explosion range of the fireball var/exp_heavy = 0 + /// Light explosion range of the fireball var/exp_light = 2 - var/exp_flash = 3 + /// Fire radius of the fireball var/exp_fire = 2 - var/magic = TRUE - var/holy = FALSE - -/obj/projectile/magic/fireball/New(loc, spell_level) - . = ..() - exp_fire += spell_level - exp_flash += spell_level - exp_light += spell_level - exp_heavy = max(spell_level - 2, 0) + /// Flash radius of the fireball + var/exp_flash = 3 -/obj/projectile/magic/fireball/on_hit(target) +/obj/projectile/magic/fireball/on_hit(atom/target, blocked = FALSE, pierce_hit) . = ..() - if(ismob(target)) - var/mob/living/M = target - if(M.anti_magic_check()) - visible_message("[src] vanishes into smoke on contact with [target]!") - return BULLET_ACT_BLOCK - M.take_overall_damage(0,10) //between this 10 burn, the 10 brute, the explosion brute, and the onfire burn, your at about 65 damage if you stop drop and roll immediately - var/turf/T = get_turf(target) - explosion(T, -1, exp_heavy, exp_light, exp_flash, 0, flame_range = exp_fire, magic = magic, holy = holy) - -/obj/projectile/magic/fireball/infernal - name = "infernal fireball" - exp_heavy = -1 - exp_light = -1 - exp_flash = 4 - exp_fire= 5 - magic = FALSE - holy = TRUE - -/obj/projectile/magic/fireball/infernal/on_hit(target) + if(isliving(target)) + var/mob/living/mob_target = target + // between this 10 burn, the 10 brute, the explosion brute, and the onfire burn, + // you are at about 65 damage if you stop drop and roll immediately + mob_target.take_overall_damage(burn = 10) + + var/turf/target_turf = get_turf(target) + + explosion( + target_turf, + devastation_range = -1, + heavy_impact_range = exp_heavy, + light_impact_range = exp_light, + flame_range = exp_fire, + flash_range = exp_flash, + adminlog = FALSE, + ) + +/obj/projectile/magic/aoe/magic_missile + name = "magic missile" + icon_state = "magicm" + range = 20 + speed = 5 + trigger_range = 0 + can_only_hit_target = TRUE + nodamage = FALSE + paralyze = 6 SECONDS + hitsound = 'sound/magic/mm_hit.ogg' + + trail = TRUE + trail_lifespan = 0.5 SECONDS + trail_icon_state = "magicmd" + +/obj/projectile/magic/aoe/magic_missile/lesser + color = "red" //Looks more culty this way + range = 10 + +/obj/projectile/magic/aoe/juggernaut + name = "Gauntlet Echo" + icon_state = "cultfist" + alpha = 180 + damage = 30 + damage_type = BRUTE + knockdown = 50 + hitsound = 'sound/weapons/punch3.ogg' + trigger_range = 0 + antimagic_flags = MAGIC_RESISTANCE_HOLY + ignored_factions = list("cult") + range = 15 + speed = 7 + +/obj/projectile/magic/spell/juggernaut/on_hit(atom/target, blocked) . = ..() - if(ismob(target)) - var/mob/living/M = target - if(M.anti_magic_check()) - return BULLET_ACT_BLOCK - var/turf/T = get_turf(target) - for(var/i=0, i<50, i+=10) - addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, FALSE, TRUE), i) + var/turf/target_turf = get_turf(src) + playsound(target_turf, 'sound/weapons/resonator_blast.ogg', 100, FALSE) + new /obj/effect/temp_visual/cult/sac(target_turf) + for(var/obj/adjacent_object in range(1, src)) + if(!adjacent_object.density) + continue + if(istype(adjacent_object, /obj/structure/destructible/cult)) + continue + + adjacent_object.take_damage(90, BRUTE, MELEE, 0) + new /obj/effect/temp_visual/cult/turf/floor(get_turf(adjacent_object)) //still magic related, but a different path diff --git a/code/modules/projectiles/projectile/magic/spellcard.dm b/code/modules/projectiles/projectile/magic/spellcard.dm index 45c9e3f04d998..2089a22547edf 100644 --- a/code/modules/projectiles/projectile/magic/spellcard.dm +++ b/code/modules/projectiles/projectile/magic/spellcard.dm @@ -1,10 +1,11 @@ -/obj/projectile/spellcard +/obj/projectile/magic/spellcard name = "enchanted card" desc = "A piece of paper enchanted to give it extreme durability and stiffness, along with edges sharp enough to slice anyone unfortunate enough to get hit by a charged one." icon_state = "spellcard" damage_type = BRUTE damage = 2 -/obj/projectile/spellcard/New(loc, spell_level) +/obj/projectile/magic/spellcard/New(loc, spell_level) . = ..() damage += spell_level + diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index 4fc01d28afca0..673ffaccc9c18 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -927,7 +927,34 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "Just looking at this thing makes the hair at the back of your neck stand up." -/datum/reagent/consumable/ethanol/devilskiss //If eaten by a slaughter demon, the demon will regret it. +/datum/reagent/consumable/ethanol/demonsblood/on_mob_metabolize(mob/living/metabolizer) + . = ..() + RegisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED, PROC_REF(pre_bloodcrawl_consumed)) + +/datum/reagent/consumable/ethanol/demonsblood/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + UnregisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED) + +/// Prevents the imbiber from being dragged into a pool of blood by a slaughter demon. +/datum/reagent/consumable/ethanol/demonsblood/proc/pre_bloodcrawl_consumed( + mob/living/source, + datum/action/spell/jaunt/bloodcrawl/crawl, + mob/living/jaunter, + obj/effect/decal/cleanable/blood, +) + + SIGNAL_HANDLER + + var/turf/jaunt_turf = get_turf(jaunter) + jaunt_turf.visible_message( + ("Something prevents [source] from entering [blood]!"), + blind_message = ("You hear a splash and a thud.") + ) + to_chat(jaunter, ("A strange force is blocking [source] from entering!")) + + return COMPONENT_STOP_CONSUMPTION + +/datum/reagent/consumable/ethanol/devilskiss name = "Devil's Kiss" description = "A creepy time!" color = "#A68310" // rgb: 166, 131, 16 @@ -939,6 +966,40 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Devils Kiss" glass_desc = "A creepy time!" +/datum/reagent/consumable/ethanol/devilskiss/on_mob_metabolize(mob/living/metabolizer) + . = ..() + RegisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_CONSUMED, PROC_REF(on_bloodcrawl_consumed)) + +/datum/reagent/consumable/ethanol/devilskiss/on_mob_end_metabolize(mob/living/metabolizer) + . = ..() + UnregisterSignal(metabolizer, COMSIG_LIVING_BLOOD_CRAWL_CONSUMED) + +/// If eaten by a slaughter demon, the demon will regret it. +/datum/reagent/consumable/ethanol/devilskiss/proc/on_bloodcrawl_consumed( + mob/living/source, + datum/action/spell/jaunt/bloodcrawl/crawl, + mob/living/jaunter, +) + + SIGNAL_HANDLER + + . = COMPONENT_STOP_CONSUMPTION + + to_chat(jaunter, ("AAH! THEIR FLESH! IT BURNS!")) + jaunter.apply_damage(25, BRUTE) + + for(var/obj/effect/decal/cleanable/nearby_blood in range(1, get_turf(source))) + if(!nearby_blood.can_bloodcrawl_in()) + continue + source.forceMove(get_turf(nearby_blood)) + source.visible_message(("[nearby_blood] violently expels [source]!")) + crawl.exit_blood_effect(source) + return + + // Fuck it, just eject them, thanks to some split second cleaning + source.forceMove(get_turf(source)) + source.visible_message(("[source] appears from nowhere, covered in blood!")) + crawl.exit_blood_effect(source) /datum/reagent/consumable/ethanol/vodkatonic name = "Vodka and Tonic" @@ -2446,7 +2507,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_icon_state = "sarsaparilliansunset" glass_name = "Sarsaparillian Sunset" glass_desc = "The view of a sunset over an irradiated wasteland. Calms your burns, but don't drink too much." - var/power = /obj/effect/proc_holder/spell/aimed/firebreath/weak + var/datum/action/spell/power = /datum/action/spell/basic_projectile/weak overdose_threshold = 50 metabolization_rate = 0.5 @@ -2457,7 +2518,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/sarsaparilliansunset/overdose_start(mob/living/M) to_chat(M, "You feel a heat from your abdomen, burning you from the inside!") power = new power() - M.AddSpell(power) + power.Grant(M) . = ..() /datum/reagent/consumable/ethanol/sarsaparilliansunset/overdose_process(mob/living/M) @@ -2466,14 +2527,14 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/sarsaparilliansunset/on_mob_end_metabolize(mob/living/M) to_chat(M, "The fire inside of you calms down.") - M.RemoveSpell(power) + power.Remove(M) return ..() -/obj/effect/proc_holder/spell/aimed/firebreath/weak +/datum/action/spell/basic_projectile/weak name = "Fire Upchuck" desc = "You can feel heat rising from your stomach" - range = 20 - charge_max = 300 + projectile_range = 20 + cooldown_time = 300 projectile_type = /obj/projectile/magic/fireball/firebreath/weak /obj/projectile/magic/fireball/firebreath/weak diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm old mode 100755 new mode 100644 diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index acb1cd5550fca..ecf1a845e5eb0 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -1628,8 +1628,6 @@ M.dizziness = max(0, M.dizziness-6) M.confused = max(0, M.confused-6) M.disgust = max(0, M.disgust-6) - - var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) if(mood.sanity <= SANITY_NEUTRAL) // only take effect if in negative sanity and then... mood.setSanity(min(mood.sanity+5, SANITY_NEUTRAL)) // set minimum to prevent unwanted spiking over neutral diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index fe71fbc591037..6155ab81e9fdf 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -248,7 +248,7 @@ /datum/reagent/water/holywater/on_mob_metabolize(mob/living/L) ..() - L.AddComponent(/datum/component/anti_magic, type, _magic = FALSE, _holy = TRUE) + L.AddComponent(/datum/component/anti_magic, type, MAGIC_RESISTANCE_HOLY) /datum/reagent/water/holywater/on_mob_end_metabolize(mob/living/L) for (var/datum/component/anti_magic/anti_magic in L.GetComponents(/datum/component/anti_magic)) diff --git a/code/modules/religion/sects/necro_sect.dm b/code/modules/religion/sects/necro_sect.dm index 1adbc03fdd4f0..a7cb20bcbccc2 100644 --- a/code/modules/religion/sects/necro_sect.dm +++ b/code/modules/religion/sects/necro_sect.dm @@ -42,7 +42,7 @@ /// the creature chosen for the rite var/mob/living/lich_to_be /// the the typepath of the spell to gran - var/lichspell = /obj/effect/proc_holder/spell/targeted/lesserlichdom + var/datum/action/spell/lichspell = /datum/action/spell/lesserlichdom /datum/religion_rites/create_lesser_lich/perform_rite(mob/living/user, atom/religious_tool) if(!ismovable(religious_tool)) @@ -56,7 +56,7 @@ to_chat(user,"[lich_to_be] has no soul, as such this rite would not help them. To empower another, they must be buckled to [movable_reltool].") lich_to_be = null return FALSE - for(var/obj/effect/proc_holder/spell/knownspell in lich_to_be.mob_spell_list) + for(var/datum/action/spell/knownspell in lich_to_be.actions) if(knownspell.type == lichspell) to_chat(user,"You've already empowered [lich_to_be], get them to use the spell granted to them! To empower another, they must be buckled to [movable_reltool].") lich_to_be = null @@ -72,7 +72,7 @@ to_chat(user,"You have no soul, as such this rite would not help you. To empower another, they must be buckled to [movable_reltool].") lich_to_be = null return FALSE - for(var/obj/effect/proc_holder/spell/knownspell in lich_to_be.mob_spell_list) + for(var/datum/action/spell/knownspell in lich_to_be.actions) if(knownspell.type == lichspell) to_chat(user,"You've already empowered yourself, use the spell granted to you! To empower another, they must be buckled to [movable_reltool].") lich_to_be = null @@ -94,7 +94,8 @@ break if(!lich_to_be) return FALSE - lich_to_be.AddSpell(new lichspell(null)) + lichspell = new /datum/action/spell/lesserlichdom + lichspell.Grant(lich_to_be) lich_to_be.visible_message("[lich_to_be] has been empowered by the soul pool!") lich_to_be = null return ..() @@ -133,7 +134,8 @@ undead.equip_to_slot_or_del(new /obj/item/clothing/under/costume/skeleton(undead), ITEM_SLOT_ICLOTHING) undead.equip_to_slot_or_del(new /obj/item/clothing/suit/hooded/chaplain_hoodie(undead), ITEM_SLOT_OCLOTHING) undead.equip_to_slot_or_del(new /obj/item/clothing/shoes/sneakers/black(undead), ITEM_SLOT_FEET) - undead.AddSpell(new /obj/effect/proc_holder/spell/targeted/smoke(null)) + var/datum/action/spell/smoke = new /datum/action/spell/smoke + smoke.Grant(undead) if(GLOB.religion) var/obj/item/storage/book/bible/booze/B = new undead.mind?.holy_role = HOLY_ROLE_PRIEST diff --git a/code/modules/research/nanites/nanite_programs/utility.dm b/code/modules/research/nanites/nanite_programs/utility.dm index a7b47538dca53..ab0ae5aad60fc 100644 --- a/code/modules/research/nanites/nanite_programs/utility.dm +++ b/code/modules/research/nanites/nanite_programs/utility.dm @@ -333,7 +333,6 @@ var/datum/nanite_extra_setting/bn_icon = extra_settings[NES_ICON] if(!button) button = new(src, bn_name.get_value(), bn_icon.get_value(), "red") - button.target = host_mob button.Grant(host_mob) /datum/nanite_program/dermal_button/disable_passive_effect() @@ -365,12 +364,12 @@ name = _name button_icon_state = "[_icon]_[_color]" -/datum/action/innate/nanite_button/Activate() +/datum/action/innate/nanite_button/on_activate() program.press() /datum/action/innate/nanite_button/proc/update_icon(icon, color) button_icon_state = "[icon]_[color]" - UpdateButtonIcon() + update_buttons() /datum/nanite_program/dermal_button/toggle name = "Dermal Toggle" diff --git a/code/modules/research/xenobiology/crossbreeding/_clothing.dm b/code/modules/research/xenobiology/crossbreeding/_clothing.dm index 6866dfe45e168..d525937e2b8d1 100644 --- a/code/modules/research/xenobiology/crossbreeding/_clothing.dm +++ b/code/modules/research/xenobiology/crossbreeding/_clothing.dm @@ -75,9 +75,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/light_prism) icon_icon = 'icons/obj/slimecrossing.dmi' button_icon_state = "prismcolor" -/datum/action/item_action/change_prism_colour/Trigger() - if(!IsAvailable()) - return +/datum/action/item_action/change_prism_colour/on_activate(mob/user, atom/target) var/obj/item/clothing/glasses/prism_glasses/glasses = target var/new_color = tgui_color_picker(owner, "Choose the lens color:", "Color change",glasses.glasses_color) if(!new_color) @@ -89,9 +87,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/light_prism) icon_icon = 'icons/obj/slimecrossing.dmi' button_icon_state = "lightprism" -/datum/action/item_action/place_light_prism/Trigger() - if(!IsAvailable()) - return +/datum/action/item_action/place_light_prism/on_activate(mob/user, atom/target) var/obj/item/clothing/glasses/prism_glasses/glasses = target if(locate(/obj/structure/light_prism) in get_turf(owner)) to_chat(owner, "There isn't enough ambient energy to fabricate another light prism here.") diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm index b0395579c9401..d9de9072db3fb 100644 --- a/code/modules/research/xenobiology/crossbreeding/_misc.dm +++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm @@ -131,7 +131,7 @@ Slimecrossing Items icon_state = "slimebarrier_thick" CanAtmosPass = ATMOS_PASS_NO opacity = TRUE - timeleft = 100 + initial_duration = 10 SECONDS //Rainbow barrier - Chilling Rainbow /obj/effect/forcefield/slimewall/rainbow diff --git a/code/modules/research/xenobiology/crossbreeding/_mobs.dm b/code/modules/research/xenobiology/crossbreeding/_mobs.dm index 0fab9be12c3b6..a6c7f8fc2a32a 100644 --- a/code/modules/research/xenobiology/crossbreeding/_mobs.dm +++ b/code/modules/research/xenobiology/crossbreeding/_mobs.dm @@ -4,30 +4,36 @@ Slimecrossing Mobs Collected here for clarity. */ -//Slime transformation power - Burning Black -/obj/effect/proc_holder/spell/targeted/shapeshift/slimeform +/// Slime transformation power - from Burning Black +/datum/action/spell/shapeshift/slime_form name = "Slime Transformation" desc = "Transform from a human to a slime, or back again!" - action_icon_state = "transformslime" - cooldown_min = 0 - charge_max = 0 + button_icon_state = "transformslime" + cooldown_time = 0 SECONDS + invocation_type = INVOCATION_NONE - shapeshift_type = /mob/living/simple_animal/slime/transformedslime + convert_damage = TRUE convert_damage_type = CLONE + possible_shapes = list(/mob/living/simple_animal/slime/transformed_slime) + + /// If TRUE, we self-delete (remove ourselves) the next time we turn back into a human var/remove_on_restore = FALSE -/obj/effect/proc_holder/spell/targeted/shapeshift/slimeform/Restore(mob/living/M) +/datum/action/spell/shapeshift/slime_form/restore_form(mob/living/shape) + . = ..() + if(!.) + return + if(remove_on_restore) - if(M.mind) - M.mind.RemoveSpell(src) - ..() + qdel(src) -//Transformed slime - Burning Black -/mob/living/simple_animal/slime/transformedslime +/// Transformed slime - from Burning Black +/mob/living/simple_animal/slime/transformed_slime -/mob/living/simple_animal/slime/transformedslime/Reproduce() //Just in case. - to_chat(src, "I can't reproduce...") +// Just in case. +/mob/living/simple_animal/slime/transformed_slime/Reproduce() + to_chat(src, ("I can't reproduce...")) // Mood return //Slime corgi - Chilling Pink diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm index 9edba79689ae9..34c92a5b84759 100644 --- a/code/modules/research/xenobiology/crossbreeding/burning.dm +++ b/code/modules/research/xenobiology/crossbreeding/burning.dm @@ -272,15 +272,14 @@ Burning extracts: effect_desc = "Transforms the user into a slime. They can transform back at will and do not lose any items." /obj/item/slimecross/burning/black/do_effect(mob/user) - var/mob/living/L = user - if(!istype(L)) + if(!isliving(user)) return - user.visible_message("[src] absorbs [user], transforming [user.p_them()] into a slime!") - var/obj/effect/proc_holder/spell/targeted/shapeshift/slimeform/S = new() - S.remove_on_restore = TRUE - user.mind.AddSpell(S) - S.cast(list(user),user) - ..() + user.visible_message(("[src] absorbs [user], transforming [user.p_them()] into a slime!")) + var/datum/action/spell/shapeshift/slime_form/transform = new(user.mind || user) + transform.remove_on_restore = TRUE + transform.Grant(user) + transform.on_cast(user, user) + return ..() /obj/item/slimecross/burning/lightpink colour = "light pink" diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index 4d8bbe72d3fb4..8b5ed1d48b4e0 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -50,13 +50,13 @@ /obj/machinery/computer/camera_advanced/xenobio/Initialize(mapload) . = ..() - slime_place_action = new - slime_up_action = new - feed_slime_action = new - monkey_recycle_action = new - scan_action = new - potion_action = new - hotkey_help = new + slime_place_action = new(src) + slime_up_action = new(src) + feed_slime_action = new(src) + monkey_recycle_action = new(src) + scan_action = new(src) + potion_action = new(src) + hotkey_help = new(src) stored_slimes = list() RegisterSignal(src, COMSIG_ATOM_CONTENTS_DEL, PROC_REF(on_contents_del)) for(var/obj/machinery/monkey_recycler/recycler in GLOB.monkey_recyclers) @@ -84,37 +84,30 @@ ..() if(slime_up_action) - slime_up_action.target = src slime_up_action.Grant(user) actions += slime_up_action if(slime_place_action) - slime_place_action.target = src slime_place_action.Grant(user) actions += slime_place_action if(feed_slime_action) - feed_slime_action.target = src feed_slime_action.Grant(user) actions += feed_slime_action if(monkey_recycle_action) - monkey_recycle_action.target = src monkey_recycle_action.Grant(user) actions += monkey_recycle_action if(scan_action) - scan_action.target = src scan_action.Grant(user) actions += scan_action if(potion_action) - potion_action.target = src potion_action.Grant(user) actions += potion_action if(hotkey_help) - hotkey_help.target = src hotkey_help.Grant(user) actions += hotkey_help @@ -193,12 +186,12 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/camera_advanced/xenobio) icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "slime_down" -/datum/action/innate/slime_place/Activate() - if(!target || !isliving(owner)) +/datum/action/innate/slime_place/on_activate() + if(!master || !isliving(owner)) return var/mob/living/C = owner var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/obj/machinery/computer/camera_advanced/xenobio/X = master if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) for(var/mob/living/simple_animal/slime/S in X.stored_slimes) @@ -213,12 +206,12 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/camera_advanced/xenobio) icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "slime_up" -/datum/action/innate/slime_pick_up/Activate() - if(!target || !isliving(owner)) +/datum/action/innate/slime_pick_up/on_activate() + if(!master || !isliving(owner)) return var/mob/living/C = owner var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/obj/machinery/computer/camera_advanced/xenobio/X = master if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) for(var/mob/living/simple_animal/slime/S in remote_eye.loc) @@ -239,12 +232,12 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/camera_advanced/xenobio) icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "monkey_down" -/datum/action/innate/feed_slime/Activate() - if(!target || !isliving(owner)) +/datum/action/innate/feed_slime/on_activate() + if(!master || !isliving(owner)) return var/mob/living/C = owner var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/obj/machinery/computer/camera_advanced/xenobio/X = master if(GLOB.cameranet.checkTurfVis(remote_eye.loc)) if(X.monkeys >= 1) @@ -264,12 +257,12 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/camera_advanced/xenobio) icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "monkey_up" -/datum/action/innate/monkey_recycle/Activate() - if(!target || !isliving(owner)) +/datum/action/innate/monkey_recycle/on_activate() + if(!master || !isliving(owner)) return var/mob/living/C = owner var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/obj/machinery/computer/camera_advanced/xenobio/X = master var/obj/machinery/monkey_recycler/recycler = X.connected_recycler if(!recycler) @@ -292,8 +285,8 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/camera_advanced/xenobio) icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "slime_scan" -/datum/action/innate/slime_scan/Activate() - if(!target || !isliving(owner)) +/datum/action/innate/slime_scan/on_activate() + if(!master || !isliving(owner)) return var/mob/living/C = owner var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control @@ -309,13 +302,13 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/camera_advanced/xenobio) icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "slime_potion" -/datum/action/innate/feed_potion/Activate() - if(!target || !isliving(owner)) +/datum/action/innate/feed_potion/on_activate() + if(!master || !isliving(owner)) return var/mob/living/C = owner var/mob/camera/ai_eye/remote/xenobio/remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/xenobio/X = target + var/obj/machinery/computer/camera_advanced/xenobio/X = master if(QDELETED(X.current_potion)) to_chat(owner, "No potion loaded.") @@ -333,8 +326,8 @@ DEFINE_BUFFER_HANDLER(/obj/machinery/computer/camera_advanced/xenobio) icon_icon = 'icons/hud/actions/actions_silicon.dmi' button_icon_state = "hotkey_help" -/datum/action/innate/hotkey_help/Activate() - if(!target || !isliving(owner)) +/datum/action/innate/hotkey_help/on_activate() + if(!master || !isliving(owner)) return to_chat(owner, "Click shortcuts:") to_chat(owner, "Shift-click a slime to pick it up, or the floor to drop all held slimes.") diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm index 5e83faddef81d..d5e5ec92b57ac 100644 --- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm @@ -323,15 +323,14 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337) if(get_dist(get_turf(src), get_turf(user)) >= 2) user.unset_machine() for(var/datum/action/peepholeCancel/PHC in user.actions) - PHC.Trigger() + PHC.trigger() /datum/action/peepholeCancel name = "Cancel View" desc = "Stop looking through the bluespace peephole." button_icon_state = "cancel_peephole" -/datum/action/peepholeCancel/Trigger() - . = ..() +/datum/action/peepholeCancel/on_activate(mob/user, atom/target) to_chat(owner, "You move away from the peephole.") owner.reset_perspective() owner.clear_fullscreen("remote_view", 0) diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm index 08c4db66a0699..7cb13c101b1fc 100644 --- a/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_actions.dm @@ -1,16 +1,17 @@ //============ Actions ============ /datum/action/innate/shuttle_creator icon_icon = 'icons/hud/actions/actions_shuttle.dmi' + button_icon_state = null var/mob/living/C var/mob/camera/ai_eye/remote/shuttle_creation/remote_eye var/obj/item/shuttle_creator/shuttle_creator -/datum/action/innate/shuttle_creator/Activate() - if(!target) +/datum/action/innate/shuttle_creator/on_activate() + if(!master) return TRUE C = owner remote_eye = C.remote_control - var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = target + var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = master shuttle_creator = internal_console.owner_rsd if(shuttle_creator.update_origin()) to_chat(usr, "Warning, the shuttle has moved during designation. Please wait for the shuttle to dock and try again.") @@ -23,7 +24,7 @@ name = "Designate Room" button_icon_state = "designate_area" -/datum/action/innate/shuttle_creator/designate_area/Activate() +/datum/action/innate/shuttle_creator/designate_area/on_activate() if(..()) return shuttle_creator.add_saved_area(remote_eye) @@ -33,7 +34,7 @@ name = "Designate Turf" button_icon_state = "designate_turf" -/datum/action/innate/shuttle_creator/designate_turf/Activate() +/datum/action/innate/shuttle_creator/designate_turf/on_activate() if(..()) return var/turf/T = get_turf(remote_eye) @@ -56,7 +57,7 @@ name = "Clear Turf" button_icon_state = "clear_turf" -/datum/action/innate/shuttle_creator/clear_turf/Activate() +/datum/action/innate/shuttle_creator/clear_turf/on_activate() if(..()) return shuttle_creator.remove_single_turf(get_turf(remote_eye)) @@ -66,7 +67,7 @@ name = "Reset Buffer" button_icon_state = "clear_area" -/datum/action/innate/shuttle_creator/reset/Activate() +/datum/action/innate/shuttle_creator/reset/on_activate() if(..()) return shuttle_creator.reset_saved_area() @@ -76,7 +77,7 @@ name = "Select Docking Airlock" button_icon_state = "select_airlock" -/datum/action/innate/shuttle_creator/airlock/Activate() +/datum/action/innate/shuttle_creator/airlock/on_activate() if(..()) return var/turf/T = get_turf(remote_eye) @@ -98,14 +99,14 @@ if(shuttle_creator.shuttle_create_docking_port(A, C)) to_chat(C, "Shuttle created!") //Remove eye control - var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = target + var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = master internal_console.remove_eye_control(owner) /datum/action/innate/shuttle_creator/modify name = "Confirm Shuttle Modifications" button_icon_state = "modify" -/datum/action/innate/shuttle_creator/modify/Activate() +/datum/action/innate/shuttle_creator/modify/on_activate() if(..()) return if(shuttle_creator.loggedTurfs.len > SHUTTLE_CREATOR_MAX_SIZE) @@ -114,5 +115,5 @@ if(shuttle_creator.modify_shuttle_area(C)) to_chat(C, "Shuttle modifications have been finalized.") //Remove eye control - var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = target + var/obj/machinery/computer/camera_advanced/shuttle_creator/internal_console = master internal_console.remove_eye_control(owner) diff --git a/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm index ba44301f382eb..0b8882058edb7 100644 --- a/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm +++ b/code/modules/shuttle/shuttle_creation/shuttle_creator_console.dm @@ -11,12 +11,21 @@ smoothing_groups = null canSmoothWith = null var/obj/item/shuttle_creator/owner_rsd - var/datum/action/innate/shuttle_creator/designate_area/area_action = new - var/datum/action/innate/shuttle_creator/designate_turf/turf_action = new - var/datum/action/innate/shuttle_creator/clear_turf/clear_turf_action = new - var/datum/action/innate/shuttle_creator/reset/reset_action = new - var/datum/action/innate/shuttle_creator/airlock/airlock_action = new - var/datum/action/innate/shuttle_creator/modify/modify_action = new + var/datum/action/innate/shuttle_creator/designate_area/area_action + var/datum/action/innate/shuttle_creator/designate_turf/turf_action + var/datum/action/innate/shuttle_creator/clear_turf/clear_turf_action + var/datum/action/innate/shuttle_creator/reset/reset_action + var/datum/action/innate/shuttle_creator/airlock/airlock_action + var/datum/action/innate/shuttle_creator/modify/modify_action + +/obj/machinery/computer/camera_advanced/shuttle_creator/Initialize(mapload) + . = ..() + area_action = new(src) + turf_action = new(src) + clear_turf_action = new(src) + reset_action = new(src) + airlock_action = new(src) + modify_action = new(src) /obj/machinery/computer/camera_advanced/shuttle_creator/check_eye(mob/user) if(user.incapacitated()) @@ -39,27 +48,21 @@ ..(user) eyeobj.invisibility = SEE_INVISIBLE_LIVING if(area_action) - area_action.target = src area_action.Grant(user) actions += area_action if(turf_action) - turf_action.target = src turf_action.Grant(user) actions += turf_action if(clear_turf_action) - clear_turf_action.target = src clear_turf_action.Grant(user) actions += clear_turf_action if(reset_action) - reset_action.target = src reset_action.Grant(user) actions += reset_action if(!owner_rsd.linkedShuttleId && airlock_action) - airlock_action.target = src airlock_action.Grant(user) actions += airlock_action if(owner_rsd.linkedShuttleId && modify_action) - modify_action.target = src modify_action.Grant(user) actions += modify_action diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index eaa3b9c0e54b5..7873bb61182d3 100644 --- a/code/modules/shuttle/special.dm +++ b/code/modules/shuttle/special.dm @@ -220,7 +220,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/structure/table/wood/bar) /obj/effect/forcefield/luxury_shuttle name = "Luxury shuttle ticket booth" desc = "A forceful money collector." - timeleft = 0 + initial_duration = 0 var/threshold = 500 var/static/list/approved_passengers = list() var/static/list/check_times = list() diff --git a/code/modules/shuttle/super_cruise/shuttle_components/shuttle_docking.dm b/code/modules/shuttle/super_cruise/shuttle_components/shuttle_docking.dm index b3af1b2a9e7d2..010e12ff9923c 100644 --- a/code/modules/shuttle/super_cruise/shuttle_components/shuttle_docking.dm +++ b/code/modules/shuttle/super_cruise/shuttle_components/shuttle_docking.dm @@ -45,32 +45,26 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/machinery/computer/shuttle_flight) /obj/machinery/computer/shuttle_flight/proc/GrantActions(mob/living/user) if(off_action) - off_action.target = user off_action.Grant(user) actions += off_action if(rotate_action) - rotate_action.target = user rotate_action.Grant(user) actions += rotate_action if(place_action) - place_action.target = user place_action.Grant(user) actions += place_action if(docker_action) - docker_action.target = user docker_action.Grant(user) actions += docker_action if(move_up_action) - move_up_action.target = user move_up_action.Grant(user) actions += move_up_action if(move_down_action) - move_down_action.target = user move_down_action.Grant(user) actions += move_down_action @@ -389,10 +383,10 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/ai_eye/remote/shuttle_docker) icon_icon = 'icons/hud/actions/actions_mecha.dmi' button_icon_state = "mech_cycle_equip_off" -/datum/action/innate/shuttledocker_rotate/Activate() - if(QDELETED(target) || !isliving(target)) +/datum/action/innate/shuttledocker_rotate/on_activate() + if(QDELETED(owner) || !isliving(owner)) return - var/mob/living/C = target + var/mob/living/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control var/obj/machinery/computer/shuttle_flight/origin = remote_eye.origin origin.rotateLandingSpot() @@ -402,22 +396,22 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/ai_eye/remote/shuttle_docker) icon_icon = 'icons/hud/actions/actions_mecha.dmi' button_icon_state = "mech_zoom_off" -/datum/action/innate/shuttledocker_place/Activate() - if(QDELETED(target) || !isliving(target)) +/datum/action/innate/shuttledocker_place/on_activate() + if(QDELETED(owner) || !isliving(owner)) return - var/mob/living/C = target + var/mob/living/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control var/obj/machinery/computer/shuttle_flight/origin = remote_eye.origin - origin.placeLandingSpot(target) + origin.placeLandingSpot(owner) /datum/action/innate/camera_jump/shuttle_docker name = "Jump to Location" button_icon_state = "camera_jump" -/datum/action/innate/camera_jump/shuttle_docker/Activate() - if(QDELETED(target) || !isliving(target)) +/datum/action/innate/camera_jump/shuttle_docker/on_activate() + if(QDELETED(owner) || !isliving(owner)) return - var/mob/living/C = target + var/mob/living/C = owner var/mob/camera/ai_eye/remote/remote_eye = C.remote_control var/obj/machinery/computer/shuttle_flight/console = remote_eye.origin @@ -437,7 +431,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/ai_eye/remote/shuttle_docker) playsound(console, 'sound/machines/terminal_prompt.ogg', 25, FALSE) var/selected = input("Choose location to jump to", "Locations", null) as null|anything in L - if(QDELETED(src) || QDELETED(target) || !isliving(target)) + if(QDELETED(src) || QDELETED(owner) || !isliving(owner)) return playsound(src, "terminal_type", 25, 0) if(selected) @@ -445,7 +439,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/mob/camera/ai_eye/remote/shuttle_docker) if(T) playsound(console, 'sound/machines/terminal_prompt_confirm.ogg', 25, 0) remote_eye.setLoc(T) - to_chat(target, "Jumped to [selected].") + to_chat(owner, "Jumped to [selected].") C.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash/static) C.clear_fullscreen("flash", 3) else diff --git a/code/modules/spells/__DEFINES/spell.dm b/code/modules/spells/__DEFINES/spell.dm deleted file mode 100644 index 0764f970e3f89..0000000000000 --- a/code/modules/spells/__DEFINES/spell.dm +++ /dev/null @@ -1,696 +0,0 @@ -#define TARGET_CLOSEST 1 -#define TARGET_RANDOM 2 - -/obj/effect/proc_holder - var/panel = "Debug"//What panel the proc holder needs to go on. - var/active = FALSE //Used by toggle based abilities. - var/ranged_mousepointer - var/mob/living/ranged_ability_user - var/ranged_clickcd_override = -1 - var/has_action = TRUE - var/datum/action/spell_action/action = null - var/action_icon = 'icons/hud/actions/actions_spells.dmi' - var/action_icon_state = "spell_default" - var/action_background_icon_state = "bg_spell" - var/base_action = /datum/action/spell_action - -/obj/effect/proc_holder/Initialize(mapload) - . = ..() - if(has_action) - action = new base_action(src) - -/obj/effect/proc_holder/Destroy() - if(!QDELETED(action)) - qdel(action) - action = null - return ..() - -/obj/effect/proc_holder/proc/on_gain(mob/living/user) - return - -/obj/effect/proc_holder/proc/on_lose(mob/living/user) - return - -/obj/effect/proc_holder/proc/fire(mob/living/user) - return TRUE - -/obj/effect/proc_holder/proc/get_panel_text() - return "" - -GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for the badmin verb for now - -/obj/effect/proc_holder/Destroy() - QDEL_NULL(action) - if(ranged_ability_user) - remove_ranged_ability() - return ..() - -/obj/effect/proc_holder/singularity_act() - return - -/obj/effect/proc_holder/singularity_pull() - return - -/obj/effect/proc_holder/proc/InterceptClickOn(mob/living/caller, params, atom/A) - if(caller.ranged_ability != src || ranged_ability_user != caller) //I'm not actually sure how these would trigger, but, uh, safety, I guess? - to_chat(caller, "[caller.ranged_ability.name] has been disabled.") - caller.ranged_ability.remove_ranged_ability() - return TRUE //TRUE for failed, FALSE for passed. - if(ranged_clickcd_override >= 0) - ranged_ability_user.next_click = world.time + ranged_clickcd_override - else - ranged_ability_user.next_click = world.time + CLICK_CD_CLICK_ABILITY - ranged_ability_user.face_atom(A) - return FALSE - -/obj/effect/proc_holder/proc/add_ranged_ability(mob/living/user, msg, forced) - if(!user || !user.client) - return - if(user.ranged_ability && user.ranged_ability != src) - if(forced) - to_chat(user, "[user.ranged_ability.name] has been replaced by [name].") - user.ranged_ability.remove_ranged_ability() - else - return - user.ranged_ability = src - user.click_intercept = src - user.update_mouse_pointer() - ranged_ability_user = user - if(msg) - to_chat(ranged_ability_user, msg) - active = TRUE - update_icon() - -/obj/effect/proc_holder/proc/remove_ranged_ability(msg) - if(!ranged_ability_user || !ranged_ability_user.client || (ranged_ability_user.ranged_ability && ranged_ability_user.ranged_ability != src)) //To avoid removing the wrong ability - return - ranged_ability_user.ranged_ability = null - ranged_ability_user.click_intercept = null - ranged_ability_user.update_mouse_pointer() - if(msg) - to_chat(ranged_ability_user, msg) - ranged_ability_user = null - active = FALSE - update_icon() - -/obj/effect/proc_holder/spell - name = "Spell" - desc = "A wizard spell." - panel = "Spells" - var/sound = null //The sound the spell makes when it is cast - anchored = TRUE // Crap like fireball projectiles are proc_holders, this is needed so fireballs don't get blown back into your face via atmos etc. - pass_flags = PASSTABLE - density = FALSE - opacity = FALSE - - 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? - var/requires_heretic_focus = FALSE //If the spell requires one of the heretic focus items to cast - var/charge_type = "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 = 10 SECONDS //recharge time in deciseconds if charge_type = "recharge" or starting charges if charge_type = "charges" - var/charge_counter = 0 //can only cast spells if it equals recharge, ++ each deciseconds if charge_type = "recharge" or -- each cast if charge_type = "charges" - var/still_recharging_msg = "The spell is still recharging." - var/recharging = TRUE - - 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/clothes_req = TRUE //see if it requires clothes - var/cult_req = FALSE //SPECIAL SNOWFLAKE clothes required for cult only spells - var/human_req = FALSE //spell can only be cast by humans - var/nonabstract_req = FALSE //spell can only be cast by mobs that are physical entities - var/stat_allowed = FALSE //see if it requires being conscious/alive, need to set to 1 for ghostpells - var/phase_allowed = FALSE // If true, the spell can be cast while phased, eg. blood crawling, ethereal jaunting - var/antimagic_allowed = FALSE // If false, the spell cannot be cast while under the effect of antimagic - var/invocation = "HURP DURP" //what is uttered when the wizard casts the spell - var/invocation_emote_self = null - var/invocation_type = INVOCATION_NONE //can be none, whisper, emote and shout - 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/spell_level = 0 //if a spell can be taken multiple times, this raises - var/level_max = 4 //The max possible level_max is 4 - var/cooldown_min = 0 //This defines what spell quickened four times has as a cooldown. Make sure to set this for every spell - var/player_lock = TRUE //If it can be used by simple mobs - var/invocation_time = 0 //Time needed to cast the spell - - var/overlay = 0 - var/overlay_icon = 'icons/obj/wizard.dmi' - var/overlay_icon_state = "spell" - var/overlay_lifespan = 0 - - var/mutable_appearance/timer_overlay - var/mutable_appearance/text_overlay - var/timer_overlay_active = FALSE - var/timer_icon = 'icons/effects/cooldown.dmi' - var/timer_icon_state_active = "second" - - 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/centcom_cancast = TRUE //Whether or not the spell should be allowed on z2 - - - /// Typecache of clothing needed to cast the spell. Used in actual checks. Override in Initialize if your spell requires different clothing. - /// !!Shared between instances, make a copy to modify. - var/list/casting_clothes - - /// Base typecache of clothing needed to cast spells. Do not modify, make a separate static var in subtypes if necessary. - var/static/list/casting_clothes_base - - action_icon = 'icons/hud/actions/actions_spells.dmi' - action_icon_state = "spell_default" - action_background_icon_state = "bg_spell" - base_action = /datum/action/spell_action/spell - -/obj/effect/proc_holder/spell/proc/cast_check(skipcharge = 0,mob/user = usr) //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(SEND_SIGNAL(user, COMSIG_MOB_PRE_CAST_SPELL, src) & COMPONENT_CANCEL_SPELL) - return FALSE - - if(player_lock) - if(!user.mind || !(src in user.mind.spell_list) && !(src in user.mob_spell_list)) - to_chat(user, "You shouldn't have this spell! Something's wrong.") - return FALSE - else - if(!(src in user.mob_spell_list)) - return FALSE - - if(!do_after(user,invocation_time, target = user, progress = 1)) //checks if there is a invocation time set for this spell and cancels the spell if the user is interrupted. - to_chat(user, "You get interrupted.") - return FALSE - - var/turf/T = get_turf(user) - if(is_centcom_level(T.z) && !centcom_cancast) //Certain spells are not allowed on the centcom zlevel - to_chat(user, "You can't cast this spell here.") - return FALSE - - if(!skipcharge) - if(!charge_check(user)) - return FALSE - - if(user.stat && !stat_allowed) - to_chat(user, "Not when you're incapacitated.") - return FALSE - - if(!antimagic_allowed) - var/antimagic = user.anti_magic_check(TRUE, FALSE, major = FALSE, self = TRUE) - if(antimagic) - if(isitem(antimagic)) - to_chat(user, "[antimagic] is interfering with your magic.") - else - to_chat(user, "Magic seems to flee from you, you can't gather enough power to cast this spell.") - return FALSE - - if(!phase_allowed && istype(user.loc, /obj/effect/dummy)) - to_chat(user, "[name] cannot be cast unless you are completely manifested in the material plane.") - return FALSE - - if(ishuman(user)) - - var/mob/living/carbon/human/H = user - - if((invocation_type == "whisper" || invocation_type == "shout") && !H.can_speak_vocal()) - to_chat(user, "You can't get the words out!") - return FALSE - - if(clothes_req) //clothes check - if(!is_type_in_typecache(H.wear_suit, casting_clothes)) - to_chat(H, "I don't feel strong enough without my robe.") - return FALSE - if(!is_type_in_typecache(H.head, casting_clothes)) - to_chat(H, "I don't feel strong enough without my hat.") - return FALSE - if(cult_req) //CULT_REQ CLOTHES CHECK - if(!istype(H.wear_suit, /obj/item/clothing/suit/magusred) && !istype(H.wear_suit, /obj/item/clothing/suit/hooded/cultrobes)) - to_chat(H, "I don't feel strong enough without my armor.") - return FALSE - if(!istype(H.head, /obj/item/clothing/head/wizard/magus) && !istype(H.head, /obj/item/clothing/head/hooded/cult_hoodie)) - to_chat(H, "I don't feel strong enough without my helmet.") - return FALSE - else - if(clothes_req || human_req) - to_chat(user, "This spell can only be cast by humans!") - return FALSE - if(nonabstract_req && (isbrain(user) || ispAI(user))) - to_chat(user, "This spell can only be cast by physical beings!") - return FALSE - if(action) - action.UpdateButtonIcon() - return TRUE - -/obj/effect/proc_holder/spell/proc/use_charge(mob/user) - switch(charge_type) - if("recharge") - charge_counter = 0 //doesn't start recharging until the targets selecting ends - if("charges") - charge_counter-- //returns the charge if the targets selecting fails - if("holdervar") - adjust_var(user, holder_var_type, holder_var_amount) - start_recharge() - -/obj/effect/proc_holder/spell/proc/charge_check(mob/user, silent = FALSE) - switch(charge_type) - if("recharge") - if(charge_counter < charge_max) - if(!silent) - to_chat(user, still_recharging_msg) - return FALSE - if("charges") - if(!charge_counter) - if(!silent) - to_chat(user, "[name] has no charges left.") - return FALSE - return TRUE - -/obj/effect/proc_holder/spell/proc/invocation(mob/user = usr) //spelling the spell out and setting it on recharge/reducing charges amount - switch(invocation_type) - if("shout") - if(prob(50))//Auto-mute? Fuck that noise - user.say(invocation, forced = "spell") - else - user.say(replacetext(invocation," ","`"), forced = "spell") - if("whisper") - if(prob(50)) - user.whisper(invocation) - else - user.whisper(replacetext(invocation," ","`")) - if("emote") - user.visible_message(invocation, invocation_emote_self) //same style as in mob/living/emote.dm - -/obj/effect/proc_holder/spell/proc/playMagSound() - playsound(get_turf(usr), sound,50,1) - -/obj/effect/proc_holder/spell/Initialize(mapload) - . = ..() - - if(!casting_clothes_base) - casting_clothes_base = typecacheof(list(/obj/item/clothing/suit/wizrobe, - /obj/item/clothing/suit/space/hardsuit/wizard, - /obj/item/clothing/head/wizard, - /obj/item/clothing/head/helmet/space/hardsuit/wizard, - /obj/item/clothing/suit/space/hardsuit/shielded/wizard, - /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard)) - - casting_clothes = casting_clothes_base - - still_recharging_msg = "[name] is still recharging." - charge_counter = charge_max - -/obj/effect/proc_holder/spell/Destroy() - end_timer_animation() - qdel(action) - return ..() - -/obj/effect/proc_holder/spell/Click() - if(cast_check()) - choose_targets() - return 1 - -/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf - return - -/obj/effect/proc_holder/spell/proc/can_target(mob/living/target) - return TRUE - -/obj/effect/proc_holder/spell/proc/start_recharge() - recharging = TRUE - begin_timer_animation() - -/obj/effect/proc_holder/spell/process(delta_time) - if(recharging && charge_type == "recharge" && (charge_counter < charge_max)) - charge_counter += delta_time * 10 - update_timer_animation() - if(charge_counter >= charge_max) - end_timer_animation() - action.UpdateButtonIcon() - charge_counter = charge_max - recharging = FALSE - else - end_timer_animation() - action.UpdateButtonIcon() - charge_counter = charge_max - recharging = FALSE - -/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = TRUE, mob/user = usr) //if recharge is started is important for the trigger spells - if(!cast_check()) - return - use_charge(user) - before_cast(targets) - invocation(user) - if(user?.ckey) - user.log_message("cast the spell [name].", LOG_ATTACK) - if(recharge) - start_recharge() - if(sound) - playMagSound() - cast(targets,user=user) - after_cast(targets) - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/before_cast(list/targets) - if(overlay) - for(var/atom/target in targets) - 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.set_anchored(TRUE) - spell.set_density(FALSE) - QDEL_IN(spell, overlay_lifespan) - -/obj/effect/proc_holder/spell/proc/after_cast(list/targets) - for(var/atom/target in targets) - var/location - if(isliving(target)) - location = target.loc - else if(isturf(target)) - location = target - if(isliving(target) && message) - to_chat(target, "[message]") - if(sparks_spread) - do_sparks(sparks_amt, FALSE, location) - if(smoke_spread) - if(smoke_spread == 1) - var/datum/effect_system/smoke_spread/smoke = new - smoke.set_up(smoke_amt, location) - smoke.start() - else if(smoke_spread == 2) - var/datum/effect_system/smoke_spread/bad/smoke = new - smoke.set_up(smoke_amt, location) - smoke.start() - else if(smoke_spread == 3) - var/datum/effect_system/smoke_spread/sleeping/smoke = new - smoke.set_up(smoke_amt, location) - smoke.start() - - -/obj/effect/proc_holder/spell/proc/cast(list/targets,mob/user = usr) - return - -/obj/effect/proc_holder/spell/proc/revert_cast(mob/user = usr) //resets recharge or readds a charge - switch(charge_type) - if("recharge") - charge_counter = charge_max - if("charges") - charge_counter++ - if("holdervar") - adjust_var(user, holder_var_type, -holder_var_amount) - end_timer_animation() - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/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 - if (!istype(target)) - return - switch(type) - if("bruteloss") - target.adjustBruteLoss(amount) - if("fireloss") - target.adjustFireLoss(amount) - if("toxloss") - target.adjustToxLoss(amount) - if("oxyloss") - target.adjustOxyLoss(amount) - if("stun") - target.AdjustStun(amount) - if("knockdown") - target.AdjustKnockdown(amount) - if("paralyze") - target.AdjustParalyzed(amount) - if("immobilize") - target.AdjustImmobilized(amount) - if("unconscious") - target.AdjustUnconscious(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-existent vars - -/obj/effect/proc_holder/spell/targeted //can mean aoe for mobs (limited/unlimited number) or one target mob - ranged_mousepointer = 'icons/effects/cult_target.dmi' - var/max_targets = 1 //leave 0 for unlimited targets in range, 1 for one selectable target 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/include_user = 0 //if it includes usr in the target list - var/random_target = 0 // chooses random viable target instead of asking the caster - var/random_target_priority = TARGET_CLOSEST // if random_target is enabled how it will pick the target - var/ranged_selection_active = FALSE - -/obj/effect/proc_holder/spell/aoe_turf //affects all turfs in view or range (depends) - var/inner_radius = -1 //for all your ring spell needs - -/obj/effect/proc_holder/spell/targeted/Click() - if(ranged_selection_active) - remove_ranged_ability("You are no longer casting [src].") - return - . = ..() - -/obj/effect/proc_holder/spell/targeted/choose_targets(mob/user = usr) - var/list/targets = list() - - switch(max_targets) - if(0) //unlimited - for(var/mob/living/target in view_or_range(range, user, selection_type)) - if(!can_target(target)) - continue - targets += target - if(1) //single target can be picked - if(range < 0) - targets += user - else - var/possible_targets = list() - - for(var/mob/living/M in view_or_range(range, user, selection_type)) - if(!include_user && user == M) - continue - if(!can_target(M)) - continue - possible_targets += M - - //targets += input("Choose the target for the spell.", "Targeting") as mob in possible_targets - //Adds a safety check post-input to make sure those targets are actually in range. - var/mob/M - if(!random_target) - add_ranged_ability(user, "Click on a target for which to cast [src] upon.", TRUE) - return - else - switch(random_target_priority) - if(TARGET_RANDOM) - M = pick(possible_targets) - if(TARGET_CLOSEST) - for(var/mob/living/L in possible_targets) - if(M) - if(get_dist(user,L) < get_dist(user,M)) - if(los_check(user,L)) - M = L - else - if(los_check(user,L)) - M = L - if(M in view_or_range(range, user, selection_type)) - targets += M - - else - var/list/possible_targets = list() - for(var/mob/living/target in view_or_range(range, user, selection_type)) - if(!can_target(target)) - continue - possible_targets += target - for(var/i in 1 to max_targets) - 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(!include_user && (user in targets)) - targets -= user - - if(!targets.len) //doesn't waste the spell - revert_cast(user) - return - - perform(targets,user=user) - -/obj/effect/proc_holder/spell/targeted/remove_ranged_ability(msg) - . = ..() - ranged_selection_active = FALSE - -/obj/effect/proc_holder/spell/targeted/add_ranged_ability(mob/living/user, msg, forced) - . = ..() - ranged_selection_active = TRUE - -/obj/effect/proc_holder/spell/targeted/InterceptClickOn(mob/living/caller, params, atom/A) - if(..()) - return TRUE - if(ismob(A)) - if(A == caller && !include_user) - to_chat(caller, "You cannot target yourself!") - return TRUE - - var/list/targets = list(A) - - remove_ranged_ability() - - if(!targets.len) //doesn't waste the spell - revert_cast(caller) - return TRUE - - perform(targets, user=caller) - return FALSE - return TRUE - -/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr) - var/list/targets = list() - - for(var/turf/target in view_or_range(range,user,selection_type)) - if(!can_target(target)) - continue - if(!(target in view_or_range(inner_radius,user,selection_type))) - targets += target - - if(!targets.len) //doesn't waste the spell - revert_cast() - return - - perform(targets,user=user) - -/obj/effect/proc_holder/spell/proc/updateButtonIcon(status_only, force) - action.UpdateButtonIcon(status_only, force) - -/obj/effect/proc_holder/spell/proc/can_be_cast_by(mob/caster) - if((human_req || clothes_req) && !ishuman(caster)) - return 0 - return 1 - -/obj/effect/proc_holder/spell/targeted/proc/los_check(mob/A,mob/B) - //Checks for obstacles from A to B - var/obj/dummy = new(A.loc) - dummy.pass_flags |= PASSTABLE - var/turf/previous_step = get_turf(A) - var/first_step = TRUE - for(var/turf/next_step as anything in (getline(A, B) - previous_step)) - if(first_step) - for(var/obj/blocker in previous_step) - if(!blocker.density || !(blocker.flags_1 & ON_BORDER_1)) - continue - if(blocker.CanPass(dummy, get_dir(previous_step, next_step))) - continue - return FALSE // Could not leave the first turf. - first_step = FALSE - for(var/atom/movable/movable as anything in next_step) - if(!movable.CanPass(dummy, get_dir(next_step, previous_step))) - qdel(dummy) - return FALSE - previous_step = next_step - qdel(dummy) - return TRUE - -/obj/effect/proc_holder/spell/proc/can_cast(mob/user = usr) - if(((!user.mind) || !(src in user.mind.spell_list)) && !(src in user.mob_spell_list)) - return FALSE - - if(!charge_check(user,TRUE)) - return FALSE - - if(user.stat && !stat_allowed) - return FALSE - - if(!antimagic_allowed && user.anti_magic_check(TRUE, FALSE, major = FALSE, self = TRUE)) - return FALSE - - if(!ishuman(user)) - if(clothes_req || human_req) - return FALSE - if(nonabstract_req && (isbrain(user) || ispAI(user))) - return FALSE - return TRUE - -//===Timer animation=== - -/obj/effect/proc_holder/spell/update_icon() - . = ..() - if(timer_overlay_active && !recharging) - end_timer_animation() - if(action) - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/proc/begin_timer_animation() - if(!(action?.button) || timer_overlay_active) - return - - timer_overlay_active = TRUE - timer_overlay = mutable_appearance(timer_icon, timer_icon_state_active) - timer_overlay.alpha = 180 - - if(!text_overlay) - text_overlay = image(loc = action.button) - text_overlay.maptext_width = 64 - text_overlay.maptext_height = 64 - text_overlay.maptext_x = -8 - text_overlay.maptext_y = -6 - text_overlay.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - - if(action.owner?.client) - action.owner.client.images += text_overlay - - action.button.add_overlay(timer_overlay) - action.has_cooldown_timer = TRUE - update_timer_animation() - - START_PROCESSING(SSfastprocess, src) - -/obj/effect/proc_holder/spell/proc/update_timer_animation() - //Update map text (todo) - if(!(action?.button)) - return - text_overlay.maptext = "
[FLOOR((charge_max-charge_counter)/10, 1)]
" - -/obj/effect/proc_holder/spell/proc/end_timer_animation() - if(!(action?.button) || !timer_overlay_active) - return - timer_overlay_active = FALSE - if(action.owner?.client) - action.owner.client.images -= text_overlay - action.button.cut_overlays(timer_overlay) - timer_overlay = null - qdel(text_overlay) - text_overlay = null - action.has_cooldown_timer = FALSE - - STOP_PROCESSING(SSfastprocess, src) - -//===================== - -/obj/effect/proc_holder/spell/self //Targets only the caster. Good for buffs and heals, but probably not wise for fireballs (although they usually fireball themselves anyway, honke) - range = -1 //Duh - -/obj/effect/proc_holder/spell/self/choose_targets(mob/user = usr) - if(!user) - revert_cast() - return - perform(null,user=user) - -/obj/effect/proc_holder/spell/self/basic_heal //This spell exists mainly for debugging purposes, and also to show how casting works - name = "Lesser Heal" - desc = "Heals a small amount of brute and burn damage." - human_req = TRUE - clothes_req = FALSE - charge_max = 100 - cooldown_min = 50 - invocation = "Victus sano!" - invocation_type = INVOCATION_WHISPER - school = "restoration" - sound = 'sound/magic/staff_healing.ogg' - -/obj/effect/proc_holder/spell/self/basic_heal/cast(mob/living/carbon/human/user) //Note the lack of "list/targets" here. Instead, use a "user" var depending on mob requirements. - //Also, notice the lack of a "for()" statement that looks through the targets. This is, again, because the spell can only have a single target. - user.visible_message("A wreath of gentle light passes over [user]!", "You wreath yourself in healing light!") - user.adjustBruteLoss(-10) - user.adjustFireLoss(-10) diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm new file mode 100644 index 0000000000000..ff488f468226c --- /dev/null +++ b/code/modules/spells/spell.dm @@ -0,0 +1,424 @@ +/** + * # The spell action + * + * This is the base action for how many of the game's + * spells (and spell adjacent) abilities function. + * These spells function off of a cooldown-based system. + * + * ## Pre-spell checks: + * - [can_cast_spell][/datum/action/spell/can_cast_spell] checks if the OWNER + * of the spell is able to cast the spell. + * - [is_valid_spell][/datum/action/spell/is_valid_spell] checks if the user and target + * are valid for this particular spell + * - [can_invoke][/datum/action/spell/can_invoke] is run in can_cast_spell to check if + * the OWNER of the spell is able to say the current invocation. + * + * ## The spell chain: + * - [pre_cast][/datum/action/spell/pre_cast] is the last chance for being able + * to interrupt a spell cast. This returns a bitflag. if SPELL_CANCEL_CAST is set, the spell will not continue. + * - [spell_feedback][/datum/action/spell/spell_feedback] is called right before cast, and handles + * invocation and sound effects. Overridable, if you want a special method of invocation or sound effects, + * or you want your spell to handle invocation / sound via special means. + * - [cast][/datum/action/spell/cast] is where the brunt of the spell effects should be done + * and implemented. + * - [post_cast][/datum/action/spell/post_cast] is the aftermath - final effects that follow + * the main cast of the spell. By now, the spell cooldown has already started + * + * ## Other procs called / may be called within the chain: + * - [invocation][/datum/action/spell/invocation] handles saying any vocal (or emotive) invocations the spell + * may have, and can be overriden or extended. Called by spell_feedback. + * - [reset_spell_cooldown][/datum/action/spell/reset_spell_cooldown] is a way to handle reverting a spell's + * cooldown and making it ready again if it fails to go off at any point. Not called anywhere by default. If you + * want to cancel a spell in pre_cast and would like the cooldown restart, call this. + * + * ## Other procs of note: + * - [level_spell][/datum/action/spell/level_spell] is where the process of adding a spell level is handled. + * this can be extended if you wish to add unique effects on level up for wizards. + * - [delevel_spell][/datum/action/spell/delevel_spell] is where the process of removing a spell level is handled. + * this can be extended if you wish to undo unique effects on level up for wizards. + * - [update_spell_name][/datum/action/spell/update_spell_name] updates the prefix of the spell name based on its level. + */ +/datum/action/spell + name = "Spell" + desc = "A wizard spell." + background_icon_state = "bg_spell" + icon_icon = 'icons/hud/actions/actions_spells.dmi' + button_icon_state = "spell_default" + check_flags = AB_CHECK_CONSCIOUS + + /// The sound played on cast. + var/sound = null + /// The school of magic the spell belongs to. + /// Checked by some holy sects to punish the + /// caster for casting things that do not align + /// with their sect's alignment - see magic.dm in defines to learn more + var/school = SCHOOL_UNSET + /// If the spell uses the wizard spell rank system, the cooldown reduction per rank of the spell + var/cooldown_reduction_per_rank = 0 SECONDS + /// What is uttered when the user casts the spell + var/invocation + /// What is shown in chat when the user casts the spell, only matters for INVOCATION_EMOTE + var/invocation_self_message + /// What type of invocation the spell is. + /// Can be "none", "whisper", "shout", "emote" + var/invocation_type = INVOCATION_NONE + /// Flag for certain states that the spell requires the user be in to cast. + var/spell_requirements = SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_NO_ANTIMAGIC + /// This determines what type of antimagic is needed to block the spell. + /// (MAGIC_RESISTANCE, MAGIC_RESISTANCE_MIND, MAGIC_RESISTANCE_HOLY) + /// If SPELL_REQUIRES_NO_ANTIMAGIC is set in Spell requirements, + /// The spell cannot be cast if the caster has any of the antimagic flags set. + var/antimagic_flags = MAGIC_RESISTANCE + /// The current spell level, if taken multiple times by a wizard + var/spell_level = 1 + /// The max possible spell level + var/spell_max_level = 5 + /// If set to a positive number, the spell will produce sparks when casted. + var/sparks_amt = 0 + /// The typepath of the smoke to create on cast. + var/smoke_type + /// The amount of smoke to create on cast. This is a range, so a value of 5 will create enough smoke to cover everything within 5 steps. + var/smoke_amt = 0 + //Whether the spell is bound to our minds or is a result of hacky coding + var/mindbound = TRUE + +/datum/action/spell/Grant(mob/grant_to) + // If our spell is mind-bound, we only wanna grant it to our mind + if(istype(master, /datum/mind)) + var/datum/mind/mind_target = master + if(mind_target.current != grant_to) + return + + . = ..() + if(!owner) + return + + // Register some signals so our button's icon stays up to date + if(spell_requirements & SPELL_REQUIRES_OFF_CENTCOM) + RegisterSignal(owner, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(update_icon_on_signal)) + if(spell_requirements & (SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_WIZARD_GARB)) + RegisterSignal(owner, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(update_icon_on_signal)) + RegisterSignals(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_icon_on_signal)) + //owner.client?.stat_panel.send_message("check_spells") we are missing this from code +/datum/action/spell/Remove(mob/living/remove_from) + + //remove_from.client?.stat_panel.send_message("check_spells") + UnregisterSignal(remove_from, list( + COMSIG_MOB_AFTER_EXIT_JAUNT, + COMSIG_MOB_ENTER_JAUNT, + COMSIG_MOB_EQUIPPED_ITEM, + COMSIG_MOVABLE_Z_CHANGED, + )) + + return ..() + +/datum/action/spell/is_available() + return ..() && can_cast_spell(feedback = FALSE) + +/datum/action/spell/pre_activate(mob/user, atom/target) + // We implement this can_cast_spell check before the parent call of Trigger() + // to allow people to click unavailable abilities to get a feedback chat message + // about why the ability is unavailable. + // It is otherwise redundant, however, as is_available() checks can_cast_spell as well. + if(!can_cast_spell()) + return FALSE + + return ..() + +/datum/action/spell/set_click_ability(mob/on_who) + if(SEND_SIGNAL(on_who, COMSIG_MOB_SPELL_ACTIVATED, src) & SPELL_CANCEL_CAST) + return FALSE + + return ..() + +// Where the cast chain starts +/datum/action/spell/pre_activate(mob/user, atom/target) + if(!is_valid_spell(user, target)) + return FALSE + + return on_activate(user, target) + +/// Checks if the owner of the spell can currently cast it. +/// Does not check anything involving potential targets. +/datum/action/spell/proc/can_cast_spell(feedback = TRUE) + if(!owner) + CRASH("[type] - can_cast_spell called on a spell without an owner!") + + // Certain spells are not allowed on the centcom zlevel + var/turf/caster_turf = get_turf(owner) + if((spell_requirements & SPELL_REQUIRES_OFF_CENTCOM) && is_centcom_level(caster_turf.z)) + if(feedback) + to_chat(owner, ("You can't cast [src] here!")) + return FALSE + + if((spell_requirements & SPELL_REQUIRES_MIND) && !owner.mind) + // No point in feedback here, as mindless mobs aren't players + return FALSE + + if((spell_requirements & SPELL_REQUIRES_MIME_VOW) && !owner.mind?.miming) + // In the future this can be moved out of spell checks exactly + if(feedback) + to_chat(owner, ("You must dedicate yourself to silence first!")) + return FALSE + + // If the spell requires the user has no antimagic equipped, and they're holding antimagic + // that corresponds with the spell's antimagic, then they can't actually cast the spell + if((spell_requirements & SPELL_REQUIRES_NO_ANTIMAGIC) && !owner.can_cast_magic(antimagic_flags)) + if(feedback) + to_chat(owner, ("Some form of antimagic is preventing you from casting [src]!")) + return FALSE + + if(!(spell_requirements & SPELL_CASTABLE_WHILE_PHASED) && HAS_TRAIT(owner, TRAIT_MAGICALLY_PHASED)) + if(feedback) + to_chat(owner, ("[src] cannot be cast unless you are completely manifested in the material plane!")) + return FALSE + + if(!can_invoke(feedback = feedback)) + return FALSE + + if(ishuman(owner)) + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + var/mob/living/carbon/human/human_owner = owner + if(!(human_owner.wear_suit?.clothing_flags & CASTING_CLOTHES)) + to_chat(owner, ("You don't feel strong enough without your robe!")) + return FALSE + if(!(human_owner.head?.clothing_flags & CASTING_CLOTHES)) + to_chat(owner, ("You don't feel strong enough without your hat!")) + return FALSE + + else + // If the spell requires wizard equipment and we're not a human (can't wear robes or hats), that's just a given + if(spell_requirements & (SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_HUMAN)) + if(feedback) + to_chat(owner, ("[src] can only be cast by humans!")) + return FALSE + + if(!(spell_requirements & SPELL_CASTABLE_AS_BRAIN) && isbrain(owner)) + if(feedback) + to_chat(owner, ("[src] can't be cast in this state!")) + return FALSE + + // Being put into a card form breaks a lot of spells, so we'll just forbid them in these states + if(ispAI(owner) || (isAI(owner) && istype(owner.loc, /obj/item/aicard))) + return FALSE + + return TRUE + +/** + * Check if the target we're casting on is a valid target. + * For self-casted spells, the target being checked (cast_on) is the caster. + * + * Return TRUE if cast_on is valid, FALSE otherwise + */ +/datum/action/spell/proc/is_valid_spell(mob/user, atom/target) + return TRUE + +// The actual cast chain occurs here, in Activate(). +// You should generally not be overriding or extending Activate() for spells. +// Defer to any of the cast chain procs instead. +/datum/action/spell/on_activate(mob/user, atom/target) + SHOULD_NOT_OVERRIDE(TRUE) + + // Pre-casting of the spell + // Pre-cast is the very last chance for a spell to cancel + // Stuff like target input can go here. + var/precast_result = pre_cast(user, target) + if(precast_result & SPELL_CANCEL_CAST) + return FALSE + + // Spell is officially being cast + if(!(precast_result & SPELL_NO_FEEDBACK)) + // We do invocation and sound effects here, before actual cast + // That way stuff like teleports or shape-shifts can be invoked before ocurring + spell_feedback() + + // Actually cast the spell. Main effects go here + on_cast(user, target) + + if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) + // The entire spell is done, start the actual cooldown at its set duration + start_cooldown() + + // And then proceed with the aftermath of the cast + // Final effects that happen after all the casting is done can go here + post_cast(user, target) + update_buttons() + + return TRUE + +/** + * Actions done before the actual cast is called. + * This is the last chance to cancel the spell from being cast. + * + * Can be used for target selection or to validate checks on the caster (cast_on). + * + * Returns a bitflag. + * - SPELL_CANCEL_CAST will stop the spell from being cast. + * - SPELL_NO_FEEDBACK will prevent the spell from calling [proc/spell_feedback] on cast. (invocation, sounds) + * - SPELL_NO_IMMEDIATE_COOLDOWN will prevent the spell from starting its cooldown between cast and before post_cast. + */ +/datum/action/spell/proc/pre_cast(mob/user, atom/target) + SHOULD_CALL_PARENT(TRUE) + + var/sig_return = SEND_SIGNAL(src, COMSIG_SPELL_PRE_CAST, user, target) + if(owner) + sig_return |= SEND_SIGNAL(owner, COMSIG_MOB_PRE_SPELL_CAST, src, user, target) + return sig_return + +/** + * Actions done as the main effect of the spell. + * + * User is the mob that is casting the spell. + * If the spell is a click spell, then target will be thing + * that the user clicked on. + */ +/datum/action/spell/proc/on_cast(mob/user, atom/target) + SHOULD_CALL_PARENT(TRUE) + + SEND_SIGNAL(src, COMSIG_SPELL_CAST, user, target) + if(owner) + SEND_SIGNAL(owner, COMSIG_MOB_CAST_SPELL, src, user, target) + if(owner.ckey) + owner.log_message("cast the spell [name] targeting [target].", LOG_ATTACK) + +/** + * Actions done after the main cast is finished. + * This is called after the cooldown's already begun. + * + * It can be used to apply late spell effects where order matters + * (for example, causing smoke *after* a teleport occurs in cast()) + * or to clean up variables or references post-cast. + */ +/datum/action/spell/proc/post_cast(mob/user, atom/target) + SHOULD_CALL_PARENT(TRUE) + + SEND_SIGNAL(src, COMSIG_SPELL_POST_CAST, user, target) + if(!owner) + return + + SEND_SIGNAL(owner, COMSIG_MOB_POST_SPELL_CAST, src, user, target) + + // Sparks and smoke can only occur if there's an owner to source them from. + if(sparks_amt) + do_sparks(sparks_amt, FALSE, get_turf(owner)) + + + if(ispath(smoke_type, /obj/effect/particle_effect/smoke)) + do_smoke(smoke_amt, owner.loc, smoke_type) + + +/// Provides feedback after a spell cast occurs, in the form of a cast sound and/or invocation +/datum/action/spell/proc/spell_feedback() + if(!owner) + return + + if(invocation_type != INVOCATION_NONE) + invocation() + if(sound) + playsound(get_turf(owner), sound, 50, TRUE) + +/// The invocation that accompanies the spell, called from spell_feedback() before cast(). +/datum/action/spell/proc/invocation() + switch(invocation_type) + if(INVOCATION_SHOUT) + if(prob(50)) + owner.say(invocation, forced = "spell ([src])") + else + owner.say(replacetext(invocation," ","`"), forced = "spell ([src])") + + if(INVOCATION_WHISPER) + if(prob(50)) + owner.whisper(invocation, forced = "spell ([src])") + else + owner.whisper(replacetext(invocation," ","`"), forced = "spell ([src])") + + if(INVOCATION_EMOTE) + owner.visible_message(invocation, invocation_self_message) + +/// Checks if the current OWNER of the spell is in a valid state to say the spell's invocation +/datum/action/spell/proc/can_invoke(feedback = TRUE) + if(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION) + return TRUE + + if(invocation_type == INVOCATION_NONE) + return TRUE + + // If you want a spell usable by ghosts for some reason, it must be INVOCATION_NONE + if(!isliving(owner)) + if(feedback) + to_chat(owner, ("You need to be living to invoke [src]!")) + return FALSE + + var/mob/living/living_owner = owner + if(invocation_type == INVOCATION_EMOTE && HAS_TRAIT(living_owner, TRAIT_EMOTEMUTE)) + if(feedback) + to_chat(owner, ("You can't position your hands correctly to invoke [src]!")) + return FALSE + + if((invocation_type == INVOCATION_WHISPER || invocation_type == INVOCATION_SHOUT) && !living_owner.can_speak_vocal()) + if(feedback) + to_chat(owner, ("You can't get the words out to invoke [src]!")) + return FALSE + + return TRUE + +/// Resets the cooldown of the spell, sending COMSIG_SPELL_CAST_RESET +/// and allowing it to be used immediately (+ updating button icon accordingly) +/datum/action/spell/proc/reset_spell_cooldown() + SEND_SIGNAL(src, COMSIG_SPELL_CAST_RESET) + next_use_time -= cooldown_time // Basically, ensures that the ability can be used now + update_buttons() + +/** + * Levels the spell up a single level, reducing the cooldown. + * If bypass_cap is TRUE, will level the spell up past it's set cap. + */ +/datum/action/spell/proc/level_spell(bypass_cap = FALSE) + // Spell cannot be levelled + if(spell_max_level <= 1) + return FALSE + + // Spell is at cap, and we will not bypass it + if(!bypass_cap && (spell_level >= spell_max_level)) + return FALSE + + spell_level++ + cooldown_time = max(cooldown_time - cooldown_reduction_per_rank, 0) + update_spell_name() + return TRUE + +/** + * Levels the spell down a single level, down to 1. + */ +/datum/action/spell/proc/delevel_spell() + // Spell cannot be levelled + if(spell_max_level <= 1) + return FALSE + + if(spell_level <= 1) + return FALSE + + spell_level-- + cooldown_time = min(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time)) + update_spell_name() + return TRUE + +/** + * Updates the spell's name based on its level. + */ +/datum/action/spell/proc/update_spell_name() + var/spell_title = "" + switch(spell_level) + if(2) + spell_title = "Efficient " + if(3) + spell_title = "Quickened " + if(4) + spell_title = "Free " + if(5) + spell_title = "Instant " + if(6) + spell_title = "Ludicrous " + + name = "[spell_title][initial(name)]" + update_buttons() diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm deleted file mode 100644 index f54839860ac9a..0000000000000 --- a/code/modules/spells/spell_types/aimed.dm +++ /dev/null @@ -1,184 +0,0 @@ - -/obj/effect/proc_holder/spell/aimed - name = "aimed projectile spell" - var/projectile_type = /obj/projectile/magic/teleport - var/deactive_msg = "You discharge your projectile..." - var/active_msg = "You charge your projectile!" - base_icon_state = "projectile" - var/active_icon_state = "projectile" - var/list/projectile_var_overrides = list() - var/projectile_amount = 1 //Projectiles per cast. - var/current_amount = 0 //How many projectiles left. - var/projectiles_per_fire = 1 //Projectiles per fire. Probably not a good thing to use unless you override ready_projectile(). - -/obj/effect/proc_holder/spell/aimed/Click() - var/mob/living/user = usr - if(!istype(user)) - return - var/msg - if(!can_cast(user)) - msg = "You can no longer cast [name]!" - remove_ranged_ability(msg) - return - if(active) - msg = "[deactive_msg]" - if(charge_type == "recharge") - var/refund_percent = current_amount/projectile_amount - charge_counter = charge_max * refund_percent - start_recharge() - remove_ranged_ability(msg) - on_deactivation(user) - else - msg = "[active_msg] Left-click to shoot it at a target!" - current_amount = projectile_amount - add_ranged_ability(user, msg, TRUE) - on_activation(user) - -/obj/effect/proc_holder/spell/aimed/proc/on_activation(mob/user) - return - -/obj/effect/proc_holder/spell/aimed/proc/on_deactivation(mob/user) - return - -/obj/effect/proc_holder/spell/aimed/update_icon() - if(!action) - return - action.button_icon_state = "[base_icon_state][active]" - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/aimed/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return FALSE - var/ran_out = (current_amount <= 0) - if(!cast_check(!ran_out, ranged_ability_user)) - remove_ranged_ability() - return FALSE - var/list/targets = list(target) - perform(targets, ran_out, user = ranged_ability_user) - return TRUE - -/obj/effect/proc_holder/spell/aimed/cast(list/targets, mob/living/user) - var/target = targets[1] - var/turf/T = user.loc - var/turf/U = get_step(user, user.dir) // Get the tile infront of the move, based on their direction - if(!isturf(U) || !isturf(T)) - return FALSE - fire_projectile(user, target) - user.newtonian_move(get_dir(U, T)) - if(current_amount <= 0) - remove_ranged_ability() //Auto-disable the ability once you run out of bullets. - charge_counter = 0 - start_recharge() - on_deactivation(user) - return TRUE - -/obj/effect/proc_holder/spell/aimed/proc/fire_projectile(mob/living/user, atom/target) - current_amount-- - if(!projectile_type) - return - for(var/i in 1 to projectiles_per_fire) - var/obj/projectile/P = new projectile_type(user.loc, spell_level) - P.firer = user - P.preparePixelProjectile(target, user) - for(var/V in projectile_var_overrides) - if(P.vars[V]) - P.vv_edit_var(V, projectile_var_overrides[V]) - ready_projectile(P, target, user, i) - P.fire() - return TRUE - -/obj/effect/proc_holder/spell/aimed/proc/ready_projectile(obj/projectile/P, atom/target, mob/user, iteration) - return - -/obj/effect/proc_holder/spell/aimed/lightningbolt - name = "Lightning Bolt" - desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down." - school = "evocation" - charge_max = 150 - clothes_req = FALSE - invocation = "UN'LTD P'WAH" - invocation_type = INVOCATION_SHOUT - cooldown_min = 50 - base_icon_state = "lightning" - action_icon_state = "lightning0" - sound = 'sound/magic/lightningbolt.ogg' - active = FALSE - projectile_var_overrides = list("tesla_range" = 15, "tesla_power" = 20000, "tesla_flags" = TESLA_MOB_DAMAGE) - active_msg = "You energize your hand with arcane lightning!" - deactive_msg = "You let the energy flow out of your hands back into yourself..." - projectile_type = /obj/projectile/magic/aoe/lightning - -/obj/effect/proc_holder/spell/aimed/fireball - name = "Fireball" - desc = "This spell fires an explosive fireball at a target." - school = "evocation" - charge_max = 140 - clothes_req = TRUE - invocation = "ONI SOMA" - invocation_type = INVOCATION_SHOUT - range = 20 - cooldown_min = 40 //10 deciseconds reduction per rank - projectile_type = /obj/projectile/magic/fireball - base_icon_state = "fireball" - action_icon_state = "fireball0" - sound = 'sound/magic/fireball.ogg' - active_msg = "You prepare to cast your fireball spell!" - deactive_msg = "You extinguish your fireball... for now." - active = FALSE - -/obj/effect/proc_holder/spell/aimed/spell_cards - name = "Spell Cards" - desc = "Magically sharpened rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" - school = "evocation" - charge_max = 90 - clothes_req = FALSE - invocation = "Sigi'lu M'Fan 'Tasia" - invocation_type = INVOCATION_SHOUT - range = 40 - cooldown_min = 30 - projectile_amount = 5 - projectiles_per_fire = 7 - projectile_type = /obj/projectile/spellcard - base_icon_state = "spellcard" - action_icon_state = "spellcard0" - var/datum/weakref/current_target_weakref - var/projectile_turnrate = 10 - var/projectile_pixel_homing_spread = 32 - var/projectile_initial_spread_amount = 30 - var/projectile_location_spread_amount = 12 - var/datum/component/lockon_aiming/lockon_component - ranged_clickcd_override = TRUE - -/obj/effect/proc_holder/spell/aimed/spell_cards/on_activation(mob/M) - QDEL_NULL(lockon_component) - lockon_component = M.AddComponent(/datum/component/lockon_aiming, 5, typecacheof(list(/mob/living)), 1, null, CALLBACK(src, PROC_REF(on_lockon_component))) - -/obj/effect/proc_holder/spell/aimed/spell_cards/proc/on_lockon_component(list/locked_weakrefs) - if(!length(locked_weakrefs)) - current_target_weakref = null - return - current_target_weakref = locked_weakrefs[1] - var/atom/A = current_target_weakref.resolve() - if(A) - var/mob/M = lockon_component.parent - M.face_atom(A) - -/obj/effect/proc_holder/spell/aimed/spell_cards/on_deactivation(mob/M) - QDEL_NULL(lockon_component) - -/obj/effect/proc_holder/spell/aimed/spell_cards/ready_projectile(obj/projectile/P, atom/target, mob/user, iteration) - if(current_target_weakref) - var/atom/A = current_target_weakref.resolve() - if(A && get_dist(A, user) < 7) - P.homing_turn_speed = projectile_turnrate - P.homing_inaccuracy_min = projectile_pixel_homing_spread - P.homing_inaccuracy_max = projectile_pixel_homing_spread - P.set_homing_target(current_target_weakref.resolve()) - var/rand_spr = rand() - var/total_angle = projectile_initial_spread_amount * 2 - var/adjusted_angle = total_angle - ((projectile_initial_spread_amount / projectiles_per_fire) * 0.5) - var/one_fire_angle = adjusted_angle / projectiles_per_fire - var/current_angle = iteration * one_fire_angle * rand_spr - (projectile_initial_spread_amount / 2) - P.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount) - P.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount) - P.preparePixelProjectile(target, user, null, current_angle) diff --git a/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm b/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm new file mode 100644 index 0000000000000..627ce5373cebb --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm @@ -0,0 +1,57 @@ +/** + * ## AOE spells + * + * A spell that iterates over atoms near the caster and casts a spell on them. + * Calls cast_on_thing_in_aoe on all atoms returned by get_things_to_cast_on by default. + */ +/datum/action/spell/aoe + /// The max amount of targets we can affect via our AOE. 0 = unlimited + var/max_targets = 0 + /// The radius of the aoe. + var/aoe_radius = 7 + +// At this point, cast_on == owner. Either works. +/datum/action/spell/aoe/on_cast(mob/user, atom/target) + . = ..() + // Get every atom around us to our aoe cast on + var/list/atom/things_to_cast_on = get_things_to_cast_on(user) + // If we have a target limit, shuffle it (for fariness) + if(max_targets > 0) + things_to_cast_on = shuffle(things_to_cast_on) + + SEND_SIGNAL(src, COMSIG_SPELL_AOE_ON_CAST, things_to_cast_on, user) + + // Now go through and cast our spell where applicable + var/num_targets = 0 + for(var/thing_to_target in things_to_cast_on) + if(max_targets > 0 && num_targets >= max_targets) + continue + + cast_on_thing_in_aoe(thing_to_target, user) + num_targets++ + +/** + * Gets a list of atoms around [center] + * that are within range and affected by our aoe. + */ +/datum/action/spell/aoe/proc/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/atom/nearby_thing in range(aoe_radius, center)) + if(nearby_thing == owner || nearby_thing == center) + continue + + things += nearby_thing + + return things + +/** + * Actually cause effects on the thing in our aoe. + * Override this for your spell! Not cast(). + * + * Arguments + * * victim - the atom being affected by our aoe + * * caster - the mob who cast the aoe + */ +/datum/action/spell/aoe/proc/cast_on_thing_in_aoe(atom/victim, atom/caster) + SHOULD_CALL_PARENT(FALSE) + CRASH("[type] did not implement cast_on_thing_in_aoe and either has no effects or implemented the spell incorrectly.") diff --git a/code/modules/spells/spell_types/aoe_spell/area_conversion.dm b/code/modules/spells/spell_types/aoe_spell/area_conversion.dm new file mode 100644 index 0000000000000..748ea8b823e90 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/area_conversion.dm @@ -0,0 +1,25 @@ +/datum/action/spell/aoe/area_conversion + name = "Area Conversion" + desc = "This spell instantly converts a small area around you." + background_icon_state = "bg_cult" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "areaconvert" + + school = SCHOOL_TRANSMUTATION + cooldown_time = 5 SECONDS + + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + aoe_radius = 2 + +/datum/action/spell/aoe/area_conversion/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/turf/nearby_turf in range(aoe_radius, center)) + things += nearby_turf + + return things + +/datum/action/spell/aoe/area_conversion/cast_on_thing_in_aoe(turf/victim, atom/caster) + playsound(victim, 'sound/items/welder.ogg', 75, TRUE) + victim.narsie_act(FALSE, TRUE, 100 - (get_dist(victim, caster) * 25)) diff --git a/code/modules/spells/spell_types/aoe_spell/knock.dm b/code/modules/spells/spell_types/aoe_spell/knock.dm new file mode 100644 index 0000000000000..9f78cd57e3c6d --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/knock.dm @@ -0,0 +1,20 @@ +/datum/action/spell/aoe/knock + name = "Knock" + desc = "This spell opens nearby doors and closets." + button_icon_state = "knock" + + sound = 'sound/magic/knock.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 2 SECONDS + + invocation = "AULIE OXIN FIERA" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + aoe_radius = 3 + +/datum/action/spell/aoe/knock/get_things_to_cast_on(atom/center) + return RANGE_TURFS(aoe_radius, center) + +/datum/action/spell/aoe/knock/cast_on_thing_in_aoe(turf/victim, atom/caster) + SEND_SIGNAL(victim, COMSIG_ATOM_MAGICALLY_UNLOCKED, src, caster) diff --git a/code/modules/spells/spell_types/aoe_spell/magic_missile.dm b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm new file mode 100644 index 0000000000000..41117f6993143 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -0,0 +1,47 @@ +/datum/action/spell/aoe/magic_missile + name = "Magic Missile" + desc = "This spell fires several, slow moving, magic projectiles at nearby targets." + button_icon_state = "magicm" + sound = 'sound/magic/magic_missile.ogg' + + school = SCHOOL_EVOCATION + cooldown_time = 20 SECONDS + cooldown_reduction_per_rank = 3.5 SECONDS + + invocation = "FORTI GY AMA" + invocation_type = INVOCATION_SHOUT + + aoe_radius = 7 + + /// The projectile type fired at all people around us + var/obj/projectile/projectile_type = /obj/projectile/magic/aoe/magic_missile + +/datum/action/spell/aoe/magic_missile/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/mob/living/nearby_mob in view(aoe_radius, center)) + if(nearby_mob == owner || nearby_mob == center) + continue + + things += nearby_mob + + return things + +/datum/action/spell/aoe/magic_missile/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + fire_projectile(victim, caster) + +/datum/action/spell/aoe/magic_missile/proc/fire_projectile(atom/victim, mob/caster) + var/obj/projectile/to_fire = new projectile_type() + to_fire.preparePixelProjectile(victim, caster) + to_fire.fire() + +/datum/action/spell/aoe/magic_missile/lesser + name = "Lesser Magic Missile" + desc = "This spell fires several, slow moving, magic projectiles at nearby targets." + background_icon_state = "bg_demon" + + cooldown_time = 40 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + max_targets = 6 + projectile_type = /obj/projectile/magic/aoe/magic_missile/lesser diff --git a/code/modules/spells/spell_types/aoe_spell/repulse.dm b/code/modules/spells/spell_types/aoe_spell/repulse.dm new file mode 100644 index 0000000000000..43e28a230fd01 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/repulse.dm @@ -0,0 +1,86 @@ +/datum/action/spell/aoe/repulse + /// The max throw range of the repulsioon. + var/max_throw = 5 + /// A visual effect to be spawned on people who are thrown away. + var/obj/effect/sparkle_path = /obj/effect/temp_visual/gravpush + /// The moveforce of the throw done by the repulsion. + var/repulse_force = MOVE_FORCE_EXTREMELY_STRONG + +/datum/action/spell/aoe/repulse/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/atom/movable/nearby_movable in view(aoe_radius, center)) + if(nearby_movable == owner || nearby_movable == center) + continue + if(nearby_movable.anchored) + continue + + things += nearby_movable + + return things + +/datum/action/spell/aoe/repulse/cast_on_thing_in_aoe(atom/movable/victim, atom/caster) + if(ismob(victim)) + var/mob/victim_mob = victim + if(victim_mob.can_block_magic(antimagic_flags)) + return + + var/turf/throwtarget = get_edge_target_turf(caster, get_dir(caster, get_step_away(victim, caster))) + var/dist_from_caster = get_dist(victim, caster) + + if(dist_from_caster == 0) + if(isliving(victim)) + var/mob/living/victim_living = victim + victim_living.Paralyze(10 SECONDS) + victim_living.adjustBruteLoss(5) + to_chat(victim, ("You're slammed into the floor by [caster]!")) + else + if(sparkle_path) + // Created sparkles will disappear on their own + new sparkle_path(get_turf(victim), get_dir(caster, victim)) + + if(isliving(victim)) + var/mob/living/victim_living = victim + victim_living.Paralyze(4 SECONDS) + to_chat(victim, ("You're thrown back by [caster]!")) + + // So stuff gets tossed around at the same time. + victim.safe_throw_at(throwtarget, ((clamp((max_throw - (clamp(dist_from_caster - 2, 0, dist_from_caster))), 3, max_throw))), 1, caster, force = repulse_force) + +/datum/action/spell/aoe/repulse/wizard + name = "Repulse" + desc = "This spell throws everything around the user away." + button_icon_state = "repulse" + sound = 'sound/magic/repulse.ogg' + + school = SCHOOL_EVOCATION + invocation = "GITTAH WEIGH" + invocation_type = INVOCATION_SHOUT + aoe_radius = 5 + + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 6.25 SECONDS + +/datum/action/spell/aoe/repulse/xeno + name = "Tail Sweep" + desc = "Throw back attackers with a sweep of your tail." + background_icon_state = "bg_alien" + icon_icon = 'icons/hud/actions/actions_xeno.dmi' + button_icon_state = "tailsweep" + sound = 'sound/magic/tail_swing.ogg' + + cooldown_time = 15 SECONDS + spell_requirements = NONE + + invocation_type = INVOCATION_NONE + antimagic_flags = NONE + aoe_radius = 2 + + sparkle_path = /obj/effect/temp_visual/dir_setting/tailsweep + +/datum/action/spell/aoe/repulse/xeno/on_cast(mob/user, atom/target) + if(iscarbon(user)) + var/mob/living/carbon/carbon_caster = user + playsound(get_turf(carbon_caster), 'sound/voice/hiss5.ogg', 80, TRUE, TRUE) + carbon_caster.spin(6, 1) + + return ..() diff --git a/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm b/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm new file mode 100644 index 0000000000000..473658de83b34 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm @@ -0,0 +1,39 @@ +/datum/action/spell/aoe/sacred_flame + name = "Sacred Flame" + desc = "Makes everyone around you more flammable, and lights yourself on fire." + button_icon_state = "sacredflame" + sound = 'sound/magic/fireball.ogg' + + school = SCHOOL_EVOCATION + cooldown_time = 6 SECONDS + + invocation = "FI'RAN DADISKO" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + aoe_radius = 6 + + /// The amount of firestacks to put people afflicted. + var/firestacks_to_give = 20 + +/datum/action/spell/aoe/sacred_flame/get_things_to_cast_on(atom/center) + var/list/things = list() + for(var/mob/living/nearby_mob in view(aoe_radius, center)) + things += nearby_mob + + return things + +/datum/action/spell/aoe/sacred_flame/cast_on_thing_in_aoe(mob/living/victim, mob/living/caster) + if(victim.can_block_magic(antimagic_flags)) + return + + victim.adjust_fire_stacks(firestacks_to_give) + // Let people who got afflicted know they're suddenly a matchstick + // But skip the caster - they'll know anyways. + if(victim != caster) + to_chat(victim, ("You suddenly feel very flammable.")) + +/datum/action/spell/aoe/sacred_flame/on_cast(mob/living/user, atom/target) + . = ..() + user.IgniteMob() + to_chat(user, "You feel a roaring flame build up inside you!") diff --git a/code/modules/spells/spell_types/area_teleport.dm b/code/modules/spells/spell_types/area_teleport.dm deleted file mode 100644 index 66362c4f43cae..0000000000000 --- a/code/modules/spells/spell_types/area_teleport.dm +++ /dev/null @@ -1,89 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/area_teleport - name = "Area teleport" - desc = "This spell teleports you to a type of area of your selection." - nonabstract_req = TRUE - - var/randomise_selection = FALSE //if it lets the usr choose the teleport loc or picks it from the list - var/invocation_area = TRUE //if the invocation appends the selected area - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - - var/say_destination = TRUE - -/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1,mob/living/user = usr) - var/thearea = before_cast(targets) - if(!thearea || !cast_check(1)) - revert_cast() - return - invocation(thearea,user) - if(charge_type == "recharge" && recharge) - INVOKE_ASYNC(src, PROC_REF(start_recharge)) - cast(targets,thearea,user) - after_cast(targets) - -/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets) - var/A = null - - if(!randomise_selection) - A = input("Area to teleport to", "Teleport", A) as null|anything in GLOB.teleportlocs - else - A = pick(GLOB.teleportlocs) - if(!A) - return - var/area/thearea = GLOB.teleportlocs[A] - - return thearea - -/obj/effect/proc_holder/spell/targeted/area_teleport/cast(list/targets,area/thearea,mob/user = usr) - playsound(get_turf(user), sound1, 50,1) - for(var/mob/living/target in targets) - var/list/L = list() - for(var/turf/T in get_area_turfs(thearea.type)) - if(!T.density) - var/clear = TRUE - for(var/obj/O in T) - if(O.density) - clear = FALSE - break - if(clear) - L+=T - - if(!L.len) - to_chat(usr, "The spell matrix was unable to locate a suitable teleport destination for an unknown reason. Sorry.") - return - - if(target?.buckled) - target.buckled.unbuckle_mob(target, force=1) - - var/list/tempL = L - var/attempt = null - var/success = FALSE - while(tempL.len) - attempt = pick(tempL) - do_teleport(target, attempt, channel = TELEPORT_CHANNEL_MAGIC, teleport_mode = TELEPORT_ALLOW_WIZARD) - if(get_turf(target) == attempt) - success = TRUE - break - else - tempL.Remove(attempt) - - if(!success) - do_teleport(target, L, channel = TELEPORT_CHANNEL_MAGIC, teleport_mode = TELEPORT_ALLOW_WIZARD) - playsound(get_turf(user), sound2, 50,1) - -/obj/effect/proc_holder/spell/targeted/area_teleport/invocation(area/chosenarea = null,mob/living/user = usr) - if(!invocation_area || !chosenarea) - ..() - else - var/words - if(say_destination) - words = "[invocation] [uppertext(chosenarea.name)]" - else - words = "[invocation]" - - switch(invocation_type) - if("shout") - user.say(words, forced = "spell") - playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) - if("whisper") - user.whisper(words, forced = "spell") diff --git a/code/modules/spells/spell_types/barnyard.dm b/code/modules/spells/spell_types/barnyard.dm deleted file mode 100644 index b9b8605517ea4..0000000000000 --- a/code/modules/spells/spell_types/barnyard.dm +++ /dev/null @@ -1,50 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/barnyardcurse - name = "Curse of the Barnyard" - desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." - school = "transmutation" - charge_type = "recharge" - charge_max = 150 - clothes_req = FALSE - stat_allowed = FALSE - invocation = "KN'A FTAGHU, PUCK 'BTHNK!" - invocation_type = INVOCATION_SHOUT - range = 7 - cooldown_min = 30 - selection_type = "range" - var/static/list/compatible_mobs_typecache = typecacheof(list(/mob/living/carbon/human, /mob/living/carbon/monkey)) - - action_icon_state = "barn" - -/obj/effect/proc_holder/spell/targeted/barnyardcurse/cast(list/targets, mob/user = usr) - if(!length(targets)) - to_chat(user, "No target found in range.") - return - - var/mob/living/carbon/target = targets[1] - - - if(!compatible_mobs_typecache[target.type]) - to_chat(user, "You are unable to curse [target]'s head!") - return - - if(!(target in oview(range))) - to_chat(user, "[target.p_theyre(TRUE)] too far away!") - return - - if(target.anti_magic_check() || HAS_TRAIT(target, TRAIT_WARDED)) - to_chat(user, "The spell had no effect!") - target.visible_message("[target]'s face bursts into flames, which instantly burst outward, leaving [target] unharmed!", \ - "Your face starts burning up, but the flames are repulsed by your anti-magic protection!") - return - - var/list/masks = list(/obj/item/clothing/mask/pig/cursed, /obj/item/clothing/mask/cowmask/cursed, /obj/item/clothing/mask/horsehead/cursed) - - var/choice = pick(masks) - var/obj/item/clothing/mask/magichead = new choice(get_turf(target)) - target.visible_message("[target]'s face bursts into flames, and a barnyard animal's head takes its place!", \ - "Your face burns up, and shortly after the fire you realise you have the face of a barnyard animal!") - if(!target.dropItemToGround(target.wear_mask)) - qdel(target.wear_mask) - target.equip_to_slot_if_possible(magichead, ITEM_SLOT_MASK, 1, 1) - - target.flash_act() diff --git a/code/modules/spells/spell_types/bee_spells/cluwnecurse.dm b/code/modules/spells/spell_types/bee_spells/cluwnecurse.dm new file mode 100644 index 0000000000000..219781273cf01 --- /dev/null +++ b/code/modules/spells/spell_types/bee_spells/cluwnecurse.dm @@ -0,0 +1,24 @@ +/datum/action/spell/pointed/cluwnecurse + name = "Curse of the Cluwne" + desc = "This spell dooms the fate of any unlucky soul to the live of a pitiful cluwne, a terrible creature that is hunted for fun." + school = "transmutation" + cooldown_time = 60 SECONDS + invocation = "CLU WO'NIS CA'TE'BEST'IS MAXIMUS!" + invocation_type = INVOCATION_SHOUT + cast_range = 3 + icon_icon = 'icons/obj/clothing/masks.dmi' + ranged_mousepointer = 'icons/effects/mouse_pointers/cluwne.dmi' + button_icon_state = "cluwne" + +/datum/action/spell/pointed/cluwnecurse/is_valid_spell(mob/user, atom/target) + return ..() && istype(target, /mob/living/carbon) + +/datum/action/spell/pointed/cluwnecurse/on_cast(mob/user, mob/living/carbon/target) + . = ..() + if(target.can_block_magic(MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND)) + return + target.cluwneify() + +/datum/spellbook_entry/cluwnecurse + name = "Cluwne Curse" + spell_type = /datum/action/spell/pointed/cluwnecurse diff --git a/code/modules/spells/spell_types/curse.dm b/code/modules/spells/spell_types/bee_spells/curse.dm similarity index 96% rename from code/modules/spells/spell_types/curse.dm rename to code/modules/spells/spell_types/bee_spells/curse.dm index 53e84ed867321..088f1d5409b39 100644 --- a/code/modules/spells/spell_types/curse.dm +++ b/code/modules/spells/spell_types/bee_spells/curse.dm @@ -16,7 +16,7 @@ GLOBAL_VAR_INIT(curse_of_madness_triggered, FALSE) var/turf/T = get_turf(H) if(T && !is_station_level(T.z)) continue - if(H.anti_magic_check(TRUE, FALSE) || HAS_TRAIT(H, TRAIT_WARDED)) + if(H.can_block_magic(MAGIC_RESISTANCE)) to_chat(H, "You have a strange feeling for a moment, but then it passes.") continue if(istype(H.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) diff --git a/code/modules/spells/spell_types/lesserlichdom.dm b/code/modules/spells/spell_types/bee_spells/lesserlichdom.dm similarity index 57% rename from code/modules/spells/spell_types/lesserlichdom.dm rename to code/modules/spells/spell_types/bee_spells/lesserlichdom.dm index 06a35f742dbbc..5f079d5cfb140 100644 --- a/code/modules/spells/spell_types/lesserlichdom.dm +++ b/code/modules/spells/spell_types/bee_spells/lesserlichdom.dm @@ -1,4 +1,4 @@ -/obj/effect/proc_holder/spell/targeted/lesserlichdom +/datum/action/spell/lesserlichdom name = "Lesser Bind Soul" desc = "A weak version of the dark necromantic pact that can forever bind your soul to an \ item of your choosing. So long as both your body and the item remain \ @@ -7,70 +7,64 @@ that the new skeleton body will experience upon 'birth'. Note that \ becoming a lesser lich destroys all internal organs except the brain." school = "necromancy" - charge_max = 10 - clothes_req = FALSE - centcom_cancast = FALSE + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC invocation = "MINUS POTENS NECREM IMORTIUM!" invocation_type = INVOCATION_SHOUT - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 10 - include_user = TRUE - - action_icon = 'icons/hud/actions/actions_spells.dmi' - action_icon_state = "skeleton" - -/obj/effect/proc_holder/spell/targeted/lesserlichdom/cast(list/targets,mob/user = usr) - for(var/mob/M in targets) - var/list/hand_items = list() - if(iscarbon(M)) - hand_items = list(M.get_active_held_item(),M.get_inactive_held_item()) - if(!length(hand_items)) - to_chat(M, "You must hold an item you wish to make your phylactery...") - return - if(!M.mind.hasSoul) - to_chat(user, "You do not possess a soul.") - return - - var/obj/item/marked_item - - for(var/obj/item/item in hand_items) - // I ensouled the nuke disk once. But it's probably a really - // mean tactic, so probably should discourage it. - if((item.item_flags & ABSTRACT) || HAS_TRAIT(item, TRAIT_NODROP) || SEND_SIGNAL(item, COMSIG_ITEM_IMBUE_SOUL, user)) - continue - marked_item = item - to_chat(M, "You begin to focus your very being into [item]...") - break - - if(!marked_item) - to_chat(M, "None of the items you hold are suitable for emplacement of your fragile soul.") - return - - playsound(user, 'sound/effects/pope_entry.ogg', 100) - - if(!do_after(M, 5 SECONDS, target = marked_item, timed_action_flags = IGNORE_HELD_ITEM)) - to_chat(M, "Your soul snaps back to your body as you stop ensouling [marked_item]!") - return - - marked_item.name = "lesser ensouled [marked_item.name]" - marked_item.desc += "\nA terrible aura surrounds this item, its very existence is offensive to life itself..." - marked_item.add_atom_colour("#187918", ADMIN_COLOUR_PRIORITY) - - new /obj/item/lesserphylactery(marked_item, M.mind) - - to_chat(M, "With a hideous feeling of emptiness you watch in horrified fascination as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! As your organs crumble to dust in your fleshless chest you come to terms with your choice. You're a lesser lich!") - M.mind.hasSoul = FALSE - // No revival other than lichdom revival - if(isliving(M)) - var/mob/living/L = M - L.sethellbound() - else - M.mind.hellbound = TRUE - M.set_species(/datum/species/skeleton) - // no robes spawn for a lesser spell - // you only get one phylactery. - M.mind.RemoveSpell(src) + cooldown_time = 10 SECONDS + button_icon = 'icons/hud/actions/actions_spells.dmi' + button_icon_state = "skeleton" + +/datum/action/spell/lesserlichdom/on_cast(mob/user, atom/target) + . = ..() + var/list/hand_items = list() + if(iscarbon(user)) + hand_items = list(user.get_active_held_item(),user.get_inactive_held_item()) + if(!length(hand_items)) + to_chat(user, "You must hold an item you wish to make your phylactery...") + return + if(!user.mind.hasSoul) + to_chat(user, "You do not possess a soul.") + return + + var/obj/item/marked_item + + for(var/obj/item/item in hand_items) + // I ensouled the nuke disk once. But it's probably a really + // mean tactic, so probably should discourage it. + if((item.item_flags & ABSTRACT) || HAS_TRAIT(item, TRAIT_NODROP) || SEND_SIGNAL(item, COMSIG_ITEM_IMBUE_SOUL, user)) + continue + marked_item = item + to_chat(user, "You begin to focus your very being into [item]...") + break + + if(!marked_item) + to_chat(user, "None of the items you hold are suitable for emplacement of your fragile soul.") + return + + playsound(user, 'sound/effects/pope_entry.ogg', 100) + + if(!do_after(user, 5 SECONDS, target = marked_item, timed_action_flags = IGNORE_HELD_ITEM)) + to_chat(user, "Your soul snaps back to your body as you stop ensouling [marked_item]!") + return + + marked_item.name = "lesser ensouled [marked_item.name]" + marked_item.desc += "\nA terrible aura surrounds this item, its very existence is offensive to life itself..." + marked_item.add_atom_colour("#187918", ADMIN_COLOUR_PRIORITY) + + new /obj/item/lesserphylactery(marked_item, user.mind) + + to_chat(user, "With a hideous feeling of emptiness you watch in horrified fascination as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! As your organs crumble to dust in your fleshless chest you come to terms with your choice. You're a lesser lich!") + user.mind.hasSoul = FALSE + // No revival other than lichdom revival + if(isliving(user)) + var/mob/living/L = user + L.sethellbound() + else + user.mind.hellbound = TRUE + user.set_species(/datum/species/skeleton) + // no robes spawn for a lesser spell + // you only get one phylactery. + src.Remove(user) /obj/item/lesserphylactery diff --git a/code/modules/spells/spell_types/rightandwrong.dm b/code/modules/spells/spell_types/bee_spells/rightandwrong.dm similarity index 94% rename from code/modules/spells/spell_types/rightandwrong.dm rename to code/modules/spells/spell_types/bee_spells/rightandwrong.dm index c301093154e1a..8aa6e5eb04b9b 100644 --- a/code/modules/spells/spell_types/rightandwrong.dm +++ b/code/modules/spells/spell_types/bee_spells/rightandwrong.dm @@ -50,15 +50,15 @@ GLOBAL_LIST_INIT(summoned_guns, list( //if you add anything that isn't covered by the typepaths below, add it to summon_magic_objective_types GLOBAL_LIST_INIT(summoned_magic, list( - /obj/item/book/granter/spell/fireball, - /obj/item/book/granter/spell/smoke, - /obj/item/book/granter/spell/blind, - /obj/item/book/granter/spell/mindswap, - /obj/item/book/granter/spell/forcewall, - /obj/item/book/granter/spell/knock, - /obj/item/book/granter/spell/barnyard, - /obj/item/book/granter/spell/charge, - /obj/item/book/granter/spell/summonitem, + /obj/item/book/granter/action/spell/fireball, + /obj/item/book/granter/action/spell/smoke, + /obj/item/book/granter/action/spell/blind, + /obj/item/book/granter/action/spell/mindswap, + /obj/item/book/granter/action/spell/forcewall, + /obj/item/book/granter/action/spell/knock, + /obj/item/book/granter/action/spell/barnyard, + /obj/item/book/granter/action/spell/charge, + /obj/item/book/granter/action/spell/summonitem, /obj/item/gun/magic/wand, /obj/item/gun/magic/wand/death, /obj/item/gun/magic/wand/resurrection, @@ -191,4 +191,5 @@ GLOBAL_VAR_INIT(summon_magic_triggered, FALSE) message_admins("Summon Events intensifies, events will now occur every [SSevents.frequency_lower / 600] to [SSevents.frequency_upper / 600] minutes.") log_game("Summon Events was increased!") + #undef SPECIALIST_MAGIC_PROB diff --git a/code/modules/spells/spell_types/bee_spells/taeclowndo.dm b/code/modules/spells/spell_types/bee_spells/taeclowndo.dm new file mode 100644 index 0000000000000..88d2aa26830b6 --- /dev/null +++ b/code/modules/spells/spell_types/bee_spells/taeclowndo.dm @@ -0,0 +1,157 @@ +/datum/action/spell/conjure_item/summon_pie + name = "Summon Creampie" + desc = "A clown's weapon of choice. Use this to summon a fresh pie, just waiting to acquaintain itself with someone's face." + invocation_type = INVOCATION_NONE + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + item_type = /obj/item/food/pie/cream + cooldown_time = 5 SECONDS + icon_icon = 'icons/obj/food/piecake.dmi' + button_icon_state = "pie" + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/datum/action/spell/pointed/banana_peel + name = "Conjure Banana Peel" + desc = "Make a banana peel appear out of thin air right under someone's feet!" + cooldown_time = 5 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + invocation_type = INVOCATION_NONE + + active_msg = "You focus, your mind reaching to the clown dimension, ready to make a peel matrialize wherever you want!" + deactive_msg = "You relax, the peel remaining right in the \"thin air\" it would appear out of." + icon_icon = 'icons/obj/hydroponics/harvest.dmi' + base_icon_state = "banana_peel" + button_icon_state = "banana" + +/datum/action/spell/pointed/banana_peel/on_cast(mob/user, atom/target) + . = ..() + if(get_dist(owner,target)>cast_range) + to_chat(owner, "\The [target] is too far away!") + return + new /obj/item/grown/bananapeel(get_turf(target)) + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/datum/action/spell/touch/megahonk + name = "Mega HoNk" + desc = "This spell channels your inner clown powers, concentrating them into one massive HONK." + hand_path = /obj/item/melee/touch_attack/megahonk + + cooldown_time = 10 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + icon_icon = 'icons/mecha/mecha_equipment.dmi' + button_icon_state = "mecha_honker" + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/datum/action/spell/touch/bspie + name = "Bluespace Banana Pie" + desc = "An entire body would fit in there!" + hand_path = /obj/item/melee/touch_attack/bspie + + cooldown_time = 60 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + icon_icon = 'icons/obj/food/piecake.dmi' + button_icon_state = "blumpkinpieslice" + + + + +/obj/item/melee/touch_attack/megahonk + name = "\improper honkmother's blessing" + desc = "You've got a feeling they won't be laughing after this one. Honk honk." + attack_verb_simple = "HONKDOOOOUKEN!" + hitsound = 'sound/items/airhorn.ogg' + icon = 'icons/mecha/mecha_equipment.dmi' + icon_state = "mecha_honker" + +/datum/action/spell/touch/megahonk/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return TRUE + +/obj/item/melee/touch_attack/megahonk/afterattack(atom/target, mob/living/carbon/user, proximity) + if(!proximity || !iscarbon(target) || !iscarbon(user) || user.handcuffed) + return + playsound(get_turf(target), hitsound,100,1) + for(var/mob/living/carbon/M in (hearers(1, target) - user)) //3x3 around the target, not affecting the user + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) + continue + var/mul = (M==target ? 1 : 0.5) + to_chat(M, "HONK") + M.SetSleeping(0) + M.stuttering += 20*mul + M.adjustEarDamage(0, 30*mul) + M.Knockdown(60*mul) + if(prob(40)) + M.Knockdown(200*mul) + else + M.Jitter(500*mul) + + . = ..() + +/obj/item/melee/touch_attack/megahonk/attack_self(mob/user) + . = ..() + to_chat(user, "\The [src] disappears, to honk another day.") + qdel(src) + +/obj/item/melee/touch_attack/bspie + name = "\improper bluespace pie" + desc = "A thing you can barely comprehend as you hold it in your hand. You're fairly sure you could fit an entire body inside." + hitsound = 'sound/magic/demon_consume.ogg' + icon = 'icons/obj/food/piecake.dmi' + icon_state = "frostypie" + color = "#000077" + +/obj/item/melee/touch_attack/bspie/attack_self(mob/user) + . = ..() + to_chat(user, "You smear \the [src] on your chest! ") + qdel(src) + +/obj/item/melee/touch_attack/bspie/afterattack(atom/target, mob/living/carbon/user, proximity) + if(!proximity || !iscarbon(target) || !iscarbon(user) || user.handcuffed) + return + if(target == user) + to_chat(user, "You smear \the [src] on your chest!") + qdel(src) + return + var/mob/living/carbon/M = target + + user.visible_message("[user] is trying to stuff [M]\s body into \the [src]!") + if(do_after(user, 25 SECONDS, M)) + var/name = M.real_name + var/obj/item/food/pie/cream/body/pie = new(get_turf(M)) + pie.name = "\improper [name] [pie.name]" + + . = ..() + + M.forceMove(pie) + +/datum/action/spell/touch/bspie/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return TRUE + + +/obj/item/reagent_containers/food/snacks/pie/cream/body + +/obj/item/reagent_containers/food/snacks/pie/cream/body/Destroy() + var/turf/T = get_turf(src) + for(var/atom/movable/A in contents) + A.forceMove(T) + A.throw_at(T, 1, 1) + . = ..() + +/* +/obj/item/reagent_containers/food/snacks/pie/cream/body/on_consume(mob/living/M) // :shrug: + if(!reagents.total_volume) //so that it happens on the last bite + if(iscarbon(M) && contents.len) + var/turf/T = get_turf(src) + for(var/atom/movable/A in contents) + A.forceMove(T) + A.throw_at(T, 1, 1) + M.visible_message("[src] bursts out of [M]!
") + M.emote("scream") + M.Knockdown(40) + M.adjustBruteLoss(60) +*/ diff --git a/code/modules/spells/spell_types/blind.dm b/code/modules/spells/spell_types/blind.dm deleted file mode 100644 index 9949cf3374c35..0000000000000 --- a/code/modules/spells/spell_types/blind.dm +++ /dev/null @@ -1,49 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/blind - name = "Blind" - desc = "This spell temporarily blinds a single target." - school = "transmutation" - charge_max = 300 - clothes_req = FALSE - invocation = "STI KALY" - invocation_type = INVOCATION_WHISPER - cooldown_min = 50 //12 deciseconds reduction per rank - ranged_mousepointer = 'icons/effects/blind_target.dmi' - action_icon_state = "blind" - range = 7 - selection_type = "range" - var/duration = 300 //30 seconds - var/static/list/compatible_mobs_typecache = typecacheof(list(/mob/living/carbon/human)) - - -/obj/effect/proc_holder/spell/targeted/blind/cast(list/targets, mob/user = usr) - if(!length(targets)) - to_chat(user, "No target found in range.") - revert_cast() - return - - var/mob/living/carbon/target = targets[1] - - if(!compatible_mobs_typecache[target.type]) - to_chat(user, "You are unable to curse [target] with blindness!") - revert_cast() - return - - if(!(target in oview(range))) - to_chat(user, "[target.p_theyre(TRUE)] too far away!") - revert_cast() - return - - if(target.anti_magic_check() || HAS_TRAIT(target, TRAIT_WARDED)) - to_chat(user, "The spell had no effect!") - target.visible_message("[target]'s eyes darken, but instantly turn back to their regular color, leaving [target] unharmed!", \ - "Your eyes hurt for a moment, but the blindness is repulsed by your anti-magic protection!") - return - - target.visible_message("[target]'s eyes darken as black smoke starts coming out of them!", \ - "Your eyes hurt as they start smoking, you panic as you realise you're blind!") - target.emote("scream") - target.become_blind(MAGIC_BLIND) - addtimer(CALLBACK(src, PROC_REF(cure_blindness), target), duration) - -/obj/effect/proc_holder/spell/targeted/blind/proc/cure_blindness(mob/living/L) - L.cure_blind(MAGIC_BLIND) diff --git a/code/modules/spells/spell_types/bloodcrawl.dm b/code/modules/spells/spell_types/bloodcrawl.dm deleted file mode 100644 index 381f19aa0ab0a..0000000000000 --- a/code/modules/spells/spell_types/bloodcrawl.dm +++ /dev/null @@ -1,43 +0,0 @@ -/obj/effect/proc_holder/spell/bloodcrawl - name = "Blood Crawl" - desc = "Use pools of blood to phase out of existence." - charge_max = 0 - clothes_req = FALSE - //If you couldn't cast this while phased, you'd have a problem - phase_allowed = TRUE - selection_type = "range" - range = 1 - cooldown_min = 0 - overlay = null - action_icon = 'icons/hud/actions/actions_minor_antag.dmi' - action_icon_state = "bloodcrawl" - action_background_icon_state = "bg_demon" - var/phased = FALSE - COOLDOWN_DECLARE(resurface_cooldown) - -/obj/effect/proc_holder/spell/bloodcrawl/choose_targets(mob/user = usr) - for(var/obj/effect/decal/cleanable/target in view(range, get_turf(user))) - if(target.can_bloodcrawl_in()) - perform(target) - return - revert_cast() - to_chat(user, "There must be a nearby source of blood!") - -/obj/effect/proc_holder/spell/bloodcrawl/perform(obj/effect/decal/cleanable/target, recharge = 1, mob/living/user = usr) - if(!istype(user)) - revert_cast() - to_chat(user, "You are unable to blood crawl!") - return - - if(phased) - if(!COOLDOWN_FINISHED(src, resurface_cooldown)) - to_chat(user, "You need to wait until you can resurface!") - return - if(user.phasein(target)) - phased = FALSE - else - if(user.phaseout(target)) - phased = TRUE - COOLDOWN_START(src, resurface_cooldown, 5 SECONDS) - start_recharge() - diff --git a/code/modules/spells/spell_types/charge.dm b/code/modules/spells/spell_types/charge.dm deleted file mode 100644 index b15deaf470061..0000000000000 --- a/code/modules/spells/spell_types/charge.dm +++ /dev/null @@ -1,103 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/charge - name = "Charge" - desc = "This spell can be used to recharge a variety of things in your hands, from magical artifacts to electrical components. A creative wizard can even use it to grant magical power to a fellow magic user." - - school = "transmutation" - charge_max = 600 - clothes_req = FALSE - invocation = "DIRI CEL" - invocation_type = INVOCATION_WHISPER - range = -1 - cooldown_min = 400 //50 deciseconds reduction per rank - include_user = TRUE - action_icon_state = "charge" - -/obj/effect/proc_holder/spell/targeted/charge/cast(list/targets,mob/user = usr) - for(var/mob/living/L in targets) - var/list/hand_items = list(L.get_active_held_item(),L.get_inactive_held_item()) - var/charged_item = null - var/burnt_out = FALSE - - if(L.pulling && isliving(L.pulling)) - var/mob/living/M = L.pulling - if(M.mob_spell_list.len != 0 || (M.mind && M.mind.spell_list.len != 0)) - for(var/obj/effect/proc_holder/spell/S in M.mob_spell_list) - S.charge_counter = S.charge_max - if(M.mind) - for(var/obj/effect/proc_holder/spell/S in M.mind.spell_list) - S.charge_counter = S.charge_max - to_chat(M, "You feel raw magic flowing through you. It feels good!") - else - to_chat(M, "You feel very strange for a moment, but then it passes.") - burnt_out = TRUE - charged_item = M - break - for(var/obj/item in hand_items) - if(istype(item, /obj/item/spellbook)) - to_chat(L, "Glowing red letters appear on the front cover...") - to_chat(L, "[pick("NICE TRY BUT NO!","CLEVER BUT NOT CLEVER ENOUGH!", "SUCH FLAGRANT CHEESING IS WHY WE ACCEPTED YOUR APPLICATION!", "CUTE! VERY CUTE!", "YOU DIDN'T THINK IT'D BE THAT EASY, DID YOU?")]") - burnt_out = TRUE - else if(istype(item, /obj/item/book/granter/spell)) - var/obj/item/book/granter/spell/I = item - if(!I.oneuse) - to_chat(L, "This book is infinite use and can't be recharged, yet the magic has improved the book somehow...") - burnt_out = TRUE - I.pages_to_mastery-- - break - if(prob(80)) - L.visible_message("[I] catches fire!") - qdel(I) - else - I.used = FALSE - charged_item = I - break - else if(istype(item, /obj/item/gun/magic)) - var/obj/item/gun/magic/I = item - if(prob(80) && !I.can_charge) - I.max_charges-- - if(I.max_charges <= 0) - I.max_charges = 0 - burnt_out = TRUE - I.charges = I.max_charges - if(istype(item, /obj/item/gun/magic/wand) && I.max_charges != 0) - var/obj/item/gun/magic/W = item - W.icon_state = initial(W.icon_state) - I.recharge_newshot() - charged_item = I - break - else if(istype(item, /obj/item/stock_parts/cell)) - var/obj/item/stock_parts/cell/C = item - if(!C.self_recharge) - if(prob(80)) - C.maxcharge -= 200 - if(C.maxcharge <= 1) //Div by 0 protection - C.maxcharge = 1 - burnt_out = TRUE - C.charge = C.maxcharge - charged_item = C - break - else if(item.contents) - var/obj/I = null - for(I in item.contents) - if(istype(I, /obj/item/stock_parts/cell/)) - var/obj/item/stock_parts/cell/C = I - if(!C.self_recharge) - if(prob(80)) - C.maxcharge -= 200 - if(C.maxcharge <= 1) //Div by 0 protection - C.maxcharge = 1 - burnt_out = TRUE - C.charge = C.maxcharge - if(istype(C.loc, /obj/item/gun)) - var/obj/item/gun/G = C.loc - G.process_chamber() - item.update_icon() - charged_item = item - break - if(!charged_item) - to_chat(L, "You feel magical power surging through your hands, but the feeling rapidly fades...") - else if(burnt_out) - to_chat(L, "[charged_item] doesn't seem to be reacting to the spell...") - else - playsound(get_turf(L), 'sound/magic/charge.ogg', 50, 1) - to_chat(L, "[charged_item] suddenly feels very warm!") diff --git a/code/modules/spells/spell_types/cluwnecurse.dm b/code/modules/spells/spell_types/cluwnecurse.dm deleted file mode 100644 index ac2b320bc7989..0000000000000 --- a/code/modules/spells/spell_types/cluwnecurse.dm +++ /dev/null @@ -1,39 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/cluwnecurse - name = "Curse of the Cluwne" - desc = "This spell dooms the fate of any unlucky soul to the live of a pitiful cluwne, a terrible creature that is hunted for fun." - school = "transmutation" - charge_type = "recharge" - charge_max = 600 - clothes_req = 1 - stat_allowed = 0 - invocation = "CLU WO'NIS CA'TE'BEST'IS MAXIMUS!" - invocation_type = INVOCATION_SHOUT - range = 3 - cooldown_min = 75 - selection_type = "range" - var/list/compatible_mobs = list(/mob/living/carbon/human) - action_icon = 'icons/hud/actions/action_generic.dmi' - action_icon_state = "cluwne" - -/obj/effect/proc_holder/spell/targeted/cluwnecurse/cast(list/targets, mob/user = usr) - if(!targets.len) - to_chat(user, "No target found in range.") - return - var/mob/living/carbon/target = targets[1] - if(!(target.type in compatible_mobs)) - to_chat(user, "You are unable to curse [target]!") - return - if(!(target in oview(range))) - to_chat(user, "They are too far away!") - return - var/mob/living/carbon/human/H = target - H.cluwneify() - -/datum/spellbook_entry/cluwnecurse - name = "Cluwne Curse" - spell_type = /obj/effect/proc_holder/spell/targeted/cluwnecurse - -/datum/action/spell_action/New(Target) - ..() - var/obj/effect/proc_holder/spell/S = Target - icon_icon = S.action_icon diff --git a/code/modules/spells/spell_types/cone/_cone.dm b/code/modules/spells/spell_types/cone/_cone.dm new file mode 100644 index 0000000000000..d1fa967404002 --- /dev/null +++ b/code/modules/spells/spell_types/cone/_cone.dm @@ -0,0 +1,123 @@ +/** + * ## Cone spells + * + * Cone spells shoot off as a cone from the caster. + */ +/datum/action/spell/cone + /// This controls how many levels the cone has. Increase this value to make a bigger cone. + var/cone_levels = 3 + /// This value determines if the cone penetrates walls. + var/respect_density = FALSE + +/datum/action/spell/cone/on_cast(mob/user, atom/target) + . = ..() + var/list/cone_turfs = get_cone_turfs(get_turf(user), user.dir, cone_levels) + SEND_SIGNAL(src, COMSIG_SPELL_CONE_ON_CAST, cone_turfs, user) + make_cone(cone_turfs, user) + +/datum/action/spell/cone/proc/make_cone(list/cone_turfs, atom/caster) + for(var/list/turf_list in cone_turfs) + do_cone_effects(turf_list, caster) + +/// This proc does obj, mob and turf cone effects on all targets in the passed list. +/datum/action/spell/cone/proc/do_cone_effects(list/target_turf_list, atom/caster, level = 1) + SEND_SIGNAL(src, COMSIG_SPELL_CONE_ON_LAYER_EFFECT, target_turf_list, caster, level) + for(var/turf/target_turf as anything in target_turf_list) + if(QDELETED(target_turf)) //if turf is no longer there + continue + + do_turf_cone_effect(target_turf, caster, level) + if(!isopenturf(target_turf)) + continue + + for(var/atom/movable/movable_content as anything in target_turf) + if(isobj(movable_content)) + do_obj_cone_effect(movable_content, level) + else if(isliving(movable_content)) + do_mob_cone_effect(movable_content, level) + +///This proc deterimines how the spell will affect turfs. +/datum/action/spell/cone/proc/do_turf_cone_effect(turf/target_turf, atom/caster, level) + return + +///This proc deterimines how the spell will affect objects. +/datum/action/spell/cone/proc/do_obj_cone_effect(obj/target_obj, atom/caster, level) + return + +///This proc deterimines how the spell will affect mobs. +/datum/action/spell/cone/proc/do_mob_cone_effect(mob/living/target_mob, atom/caster, level) + return + +///This proc creates a list of turfs that are hit by the cone. +/datum/action/spell/cone/proc/get_cone_turfs(turf/starter_turf, dir_to_use, cone_levels = 3) + var/list/turfs_to_return = list() + var/turf/turf_to_use = starter_turf + var/turf/left_turf + var/turf/right_turf + var/right_dir + var/left_dir + switch(dir_to_use) + if(NORTH) + left_dir = WEST + right_dir = EAST + if(SOUTH) + left_dir = EAST + right_dir = WEST + if(EAST) + left_dir = NORTH + right_dir = SOUTH + if(WEST) + left_dir = SOUTH + right_dir = NORTH + + for(var/i in 1 to cone_levels) + var/list/level_turfs = list() + turf_to_use = get_step(turf_to_use, dir_to_use) + level_turfs += turf_to_use + if(i != 1) + left_turf = get_step(turf_to_use, left_dir) + level_turfs += left_turf + right_turf = get_step(turf_to_use, right_dir) + level_turfs += right_turf + for(var/left_i in 1 to i -calculate_cone_shape(i)) + if(left_turf.density && respect_density) + break + left_turf = get_step(left_turf, left_dir) + level_turfs += left_turf + for(var/right_i in 1 to i -calculate_cone_shape(i)) + if(right_turf.density && respect_density) + break + right_turf = get_step(right_turf, right_dir) + level_turfs += right_turf + turfs_to_return += list(level_turfs) + if(i == cone_levels) + continue + if(turf_to_use.density && respect_density) + break + return turfs_to_return + +///This proc adjusts the cones width depending on the level. +/datum/action/spell/cone/proc/calculate_cone_shape(current_level) + var/end_taper_start = round(cone_levels * 0.8) + if(current_level > end_taper_start) + return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula. + else + return 2 + +/** + * ### Staggered Cone + * + * Staggered Cone spells will reach each cone level + * gradually / with a delay, instead of affecting the entire + * cone area at once. + */ +/datum/action/spell/cone/staggered + + /// The delay between each cone level triggering. + var/delay_between_level = 0.2 SECONDS + +/datum/action/spell/cone/staggered/make_cone(list/cone_turfs, atom/caster) + var/level_counter = 0 + for(var/list/turf_list in cone_turfs) + level_counter++ + addtimer(CALLBACK(src, PROC_REF(do_cone_effects), turf_list, caster, level_counter), delay_between_level * level_counter) diff --git a/code/modules/spells/spell_types/cone_spells.dm b/code/modules/spells/spell_types/cone_spells.dm deleted file mode 100644 index ee0e776fa45a3..0000000000000 --- a/code/modules/spells/spell_types/cone_spells.dm +++ /dev/null @@ -1,117 +0,0 @@ -/obj/effect/proc_holder/spell/cone - name = "Cone of Nothing" - desc = "Does nothing in a cone! Wow!" - school = "evocation" - charge_max = 100 - clothes_req = FALSE - invocation = "FUKAN NOTHAN" - invocation_type = INVOCATION_SHOUT - sound = 'sound/magic/forcewall.ogg' - action_icon_state = "shield" - range = -1 - cooldown_min = 0.5 SECONDS - ///This controls how many levels the cone has, increase this value to make a bigger cone. - var/cone_levels = 3 - ///This value determines if the cone penetrates walls. - var/respect_density = FALSE - -/obj/effect/proc_holder/spell/cone/choose_targets(mob/user = usr) - perform(null, user=user) - -///This proc creates a list of turfs that are hit by the cone -/obj/effect/proc_holder/spell/cone/proc/cone_helper(var/turf/starter_turf, var/dir_to_use, var/cone_levels = 3) - var/list/turfs_to_return = list() - var/turf/turf_to_use = starter_turf - var/turf/left_turf - var/turf/right_turf - var/right_dir - var/left_dir - switch(dir_to_use) - if(NORTH) - left_dir = WEST - right_dir = EAST - if(SOUTH) - left_dir = EAST - right_dir = WEST - if(EAST) - left_dir = NORTH - right_dir = SOUTH - if(WEST) - left_dir = SOUTH - right_dir = NORTH - - - for(var/i in 1 to cone_levels) - var/list/level_turfs = list() - turf_to_use = get_step(turf_to_use, dir_to_use) - level_turfs += turf_to_use - if(i != 1) - left_turf = get_step(turf_to_use, left_dir) - level_turfs += left_turf - right_turf = get_step(turf_to_use, right_dir) - level_turfs += right_turf - for(var/left_i in 1 to i -calculate_cone_shape(i)) - if(left_turf.density && respect_density) - break - left_turf = get_step(left_turf, left_dir) - level_turfs += left_turf - for(var/right_i in 1 to i -calculate_cone_shape(i)) - if(right_turf.density && respect_density) - break - right_turf = get_step(right_turf, right_dir) - level_turfs += right_turf - turfs_to_return += list(level_turfs) - if(i == cone_levels) - continue - if(turf_to_use.density && respect_density) - break - return turfs_to_return - -/obj/effect/proc_holder/spell/cone/cast(list/targets,mob/user = usr) - var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels) - for(var/list/turf_list in cone_turfs) - do_cone_effects(turf_list) - -///This proc does obj, mob and turf cone effects on all targets in a list -/obj/effect/proc_holder/spell/cone/proc/do_cone_effects(list/target_turf_list, level) - for(var/target_turf in target_turf_list) - if(!target_turf) //if turf is no longer there - continue - do_turf_cone_effect(target_turf, level) - if(isopenturf(target_turf)) - var/turf/open/open_turf = target_turf - for(var/movable_content in open_turf) - if(isobj(movable_content)) - do_obj_cone_effect(movable_content, level) - else if(isliving(movable_content)) - do_mob_cone_effect(movable_content, level) - -///This proc deterimines how the spell will affect turfs. -/obj/effect/proc_holder/spell/cone/proc/do_turf_cone_effect(turf/target_turf, level) - return - -///This proc deterimines how the spell will affect objects. -/obj/effect/proc_holder/spell/cone/proc/do_obj_cone_effect(obj/target_obj, level) - return - -///This proc deterimines how the spell will affect mobs. -/obj/effect/proc_holder/spell/cone/proc/do_mob_cone_effect(mob/living/target_mob, level) - return - -///This proc adjusts the cones width depending on the level. -/obj/effect/proc_holder/spell/cone/proc/calculate_cone_shape(current_level) - var/end_taper_start = round(cone_levels * 0.8) - if(current_level > end_taper_start) - return (current_level % end_taper_start) * 2 //someone more talented and probably come up with a better formula. - else - return 2 - -///This type of cone gradually affects each level of the cone instead of affecting the entire area at once. -/obj/effect/proc_holder/spell/cone/staggered - -/obj/effect/proc_holder/spell/cone/staggered/cast(list/targets,mob/user = usr) - var/level_counter = 0 - var/list/cone_turfs = cone_helper(get_turf(user), user.dir, cone_levels) - for(var/list/turf_list in cone_turfs) - level_counter++ - addtimer(CALLBACK(src, PROC_REF(do_cone_effects), turf_list, level_counter), 2 * level_counter) diff --git a/code/modules/spells/spell_types/conjure.dm b/code/modules/spells/spell_types/conjure.dm deleted file mode 100644 index b705d6c3b776a..0000000000000 --- a/code/modules/spells/spell_types/conjure.dm +++ /dev/null @@ -1,103 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure - name = "Conjure" - desc = "This spell conjures objs of the specified types in range." - - var/list/summon_type = list() //determines what exactly will be summoned - //should be text, like list("/mob/living/simple_animal/bot/ed209") - - var/summon_lifespan = 0 // 0=permanent, any other time in deciseconds - var/summon_amt = 1 //amount of objects summoned - var/summon_ignore_density = FALSE //if set to TRUE, adds dense tiles to possible spawn places - var/summon_ignore_prev_spawn_points = TRUE //if set to TRUE, each new object is summoned on a new spawn point - - 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" = "Wizard's Justicebot"), for example - - var/cast_sound = 'sound/items/welder.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/conjure/cast(list/targets,mob/user = usr) - playsound(get_turf(user), cast_sound, 50,1) - for(var/turf/T in targets) - if(T.density && !summon_ignore_density) - targets -= T - - for(var/i in 1 to summon_amt) - if(!targets.len) - break - var/summoned_object_type = pick(summon_type) - var/spawn_place = pick(targets) - if(summon_ignore_prev_spawn_points) - targets -= spawn_place - if(ispath(summoned_object_type, /turf)) - var/turf/O = spawn_place - var/N = summoned_object_type - if(istype(O, /turf/open) && ispath(N, /turf/closed) || istype(O, /turf/open/floor/plating)) - new N(O) - else - O.ChangeTurf(N, flags = CHANGETURF_INHERIT_AIR) - else - var/atom/summoned_object = new summoned_object_type(spawn_place) - - for(var/varName in newVars) - if(varName in newVars) - summoned_object.vv_edit_var(varName, newVars[varName]) - summoned_object.flags_1 |= ADMIN_SPAWNED_1 - if(summon_lifespan) - QDEL_IN(summoned_object, summon_lifespan) - - post_summon(summoned_object, user) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/proc/post_summon(atom/summoned_object, mob/user) - return - -/obj/effect/proc_holder/spell/aoe_turf/conjure/summonEdSwarm //test purposes - Also a lot of fun - name = "Dispense Wizard Justice" - desc = "This spell dispenses wizard justice." - - summon_type = list(/mob/living/simple_animal/bot/ed209) - summon_amt = 10 - range = 3 - newVars = list("emagged" = 2, "remote_disabled" = 1,"shoot_sound" = 'sound/weapons/laser.ogg',"projectile" = /obj/projectile/beam/laser, "declare_arrests" = 0,"name" = "Wizard's Justicebot") - -/obj/effect/proc_holder/spell/aoe_turf/conjure/linkWorlds - name = "Link Worlds" - desc = "A whole new dimension for you to play with! They won't be happy about it, though." - invocation = "WTF" - clothes_req = FALSE - charge_max = 600 - cooldown_min = 200 - summon_type = list(/obj/structure/spawner/nether) - summon_amt = 1 - range = 1 - cast_sound = 'sound/weapons/marauder.ogg' - -/obj/effect/proc_holder/spell/targeted/conjure_item - name = "Summon weapon" - desc = "A generic spell that should not exist. This summons an instance of a specific type of item, or if one already exists, un-summons it. Summons into hand if possible." - invocation_type = INVOCATION_NONE - include_user = TRUE - range = -1 - clothes_req = FALSE - var/obj/item/item - var/item_type = /obj/item/banhammer - school = "conjuration" - charge_max = 150 - cooldown_min = 10 - var/delete_old = TRUE //TRUE to delete the last summoned object if it's still there, FALSE for infinite item stream weeeee - -/obj/effect/proc_holder/spell/targeted/conjure_item/cast(list/targets, mob/user = usr) - if (delete_old && item && !QDELETED(item)) - QDEL_NULL(item) - else - for(var/mob/living/carbon/C in targets) - if(C.dropItemToGround(C.get_active_held_item())) - C.put_in_hands(make_item(), TRUE) - -/obj/effect/proc_holder/spell/targeted/conjure_item/Destroy() - if(item) - qdel(item) - return ..() - -/obj/effect/proc_holder/spell/targeted/conjure_item/proc/make_item() - item = new item_type - return item diff --git a/code/modules/spells/spell_types/conjure/_conjure.dm b/code/modules/spells/spell_types/conjure/_conjure.dm new file mode 100644 index 0000000000000..5d83f19656358 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/_conjure.dm @@ -0,0 +1,50 @@ +/datum/action/spell/conjure + sound = 'sound/items/welder.ogg' + school = SCHOOL_CONJURATION + + /// The radius around the caster the items will appear. 0 = spawns on top of the caster. + var/summon_radius = 7 + /// A list of types that will be created on summon. + /// The type is picked from this list, not all provided are guaranteed. + var/list/summon_type = list() + /// How long before the summons will be despawned. Set to 0 for permanent. + var/summon_lifespan = 0 + /// Amount of summons to create. + var/summon_amount = 1 + /// If TRUE, summoned objects will not be spawned in dense turfs. + var/summon_respects_density = FALSE + /// If TRUE, no two summons can be spawned in the same turf. + var/summon_respects_prev_spawn_points = TRUE + +/datum/action/spell/conjure/on_cast(mob/user, atom/target) + . = ..() + var/list/to_summon_in = list() + for(var/turf/summon_turf in range(summon_radius, user)) + if(summon_respects_density && summon_turf.density) + continue + to_summon_in += summon_turf + + for(var/i in 1 to summon_amount) + if(!length(to_summon_in)) + break + + var/atom/summoned_object_type = pick(summon_type) + var/turf/spawn_place = pick(to_summon_in) + if(summon_respects_prev_spawn_points) + to_summon_in -= spawn_place + + if(ispath(summoned_object_type, /turf)) + spawn_place.ChangeTurf(summoned_object_type, flags = CHANGETURF_INHERIT_AIR) + + else + var/atom/summoned_object = new summoned_object_type(spawn_place) + + summoned_object.flags_1 |= ADMIN_SPAWNED_1 + if(summon_lifespan > 0) + QDEL_IN(summoned_object, summon_lifespan) + + post_summon(summoned_object, user) + +/// Called on atoms summoned after they are created, allows extra variable editing and such of created objects +/datum/action/spell/conjure/proc/post_summon(atom/summoned_object, atom/cast_on) + return diff --git a/code/modules/spells/spell_types/conjure/bees.dm b/code/modules/spells/spell_types/conjure/bees.dm new file mode 100644 index 0000000000000..2c11b79fca6e6 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/bees.dm @@ -0,0 +1,18 @@ +/datum/action/spell/conjure/bee + name = "Lesser Summon Bees" + desc = "This spell magically kicks a transdimensional beehive, \ + instantly summoning a swarm of bees to your location. \ + These bees are NOT friendly to anyone." + button_icon_state = "bee" + sound = 'sound/voice/moth/scream_moth.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "NOT THE BEES" + invocation_type = INVOCATION_SHOUT + + summon_radius = 3 + summon_type = list(/mob/living/simple_animal/hostile/poison/bees) + summon_amount = 9 diff --git a/code/modules/spells/spell_types/conjure/carp.dm b/code/modules/spells/spell_types/conjure/carp.dm new file mode 100644 index 0000000000000..d9d5fd3798b94 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/carp.dm @@ -0,0 +1,13 @@ +/datum/action/spell/conjure/carp + name = "Summon Carp" + desc = "This spell conjures a simple carp." + sound = 'sound/magic/summon_karp.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 2 MINUTES + + invocation = "NOUK FHUNMM SACP RISSKA" + invocation_type = INVOCATION_SHOUT + + summon_radius = 1 + summon_type = list(/mob/living/simple_animal/hostile/carp) diff --git a/code/modules/spells/spell_types/conjure/constructs.dm b/code/modules/spells/spell_types/conjure/constructs.dm new file mode 100644 index 0000000000000..fe64c8d0dc779 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/constructs.dm @@ -0,0 +1,20 @@ +/datum/action/spell/conjure/construct + name = "Summon Construct Shell" + desc = "This spell conjures a construct which may be controlled by Shades." + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "artificer" + sound = 'sound/magic/summonitems_generic.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 1 MINUTES + + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/obj/structure/constructshell) + +/datum/action/spell/conjure/construct/lesser // Used by artificers. + name = "Create Construct Shell" + background_icon_state = "bg_demon" + cooldown_time = 3 MINUTES diff --git a/code/modules/spells/spell_types/conjure/creatures.dm b/code/modules/spells/spell_types/conjure/creatures.dm new file mode 100644 index 0000000000000..7b5e6f348c253 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/creatures.dm @@ -0,0 +1,15 @@ +/datum/action/spell/conjure/creature + name = "Summon Creature Swarm" + desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth." + sound = 'sound/magic/summonitems_generic.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 2 MINUTES + + invocation = "IA IA" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + + summon_radius = 3 + summon_type = list(/mob/living/simple_animal/hostile/netherworld) + summon_amount = 10 diff --git a/code/modules/spells/spell_types/conjure/cult_turfs.dm b/code/modules/spells/spell_types/conjure/cult_turfs.dm new file mode 100644 index 0000000000000..1017ae7a3acb8 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/cult_turfs.dm @@ -0,0 +1,29 @@ +/datum/action/spell/conjure/cult_floor + name = "Summon Cult Floor" + desc = "This spell constructs a cult floor." + background_icon_state = "bg_cult" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "floorconstruct" + + school = SCHOOL_CONJURATION + cooldown_time = 2 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/turf/open/floor/engine/cult) + +/datum/action/spell/conjure/cult_wall + name = "Summon Cult Wall" + desc = "This spell constructs a cult wall." + background_icon_state = "bg_cult" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "lesserconstruct" + + school = SCHOOL_CONJURATION + cooldown_time = 10 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/turf/closed/wall/mineral/cult/artificer) // We don't want artificer-based runed metal farms. diff --git a/code/modules/spells/spell_types/conjure/ed_swarm.dm b/code/modules/spells/spell_types/conjure/ed_swarm.dm new file mode 100644 index 0000000000000..0cf31935faa7f --- /dev/null +++ b/code/modules/spells/spell_types/conjure/ed_swarm.dm @@ -0,0 +1,18 @@ +// test purposes - Also a lot of fun +/datum/action/spell/conjure/summon_ed_swarm + name = "Dispense Wizard Justice" + desc = "This spell dispenses wizard justice." + + summon_radius = 3 + summon_type = list(/mob/living/simple_animal/bot/ed209) + summon_amount = 10 + +/datum/action/spell/conjure/summon_ed_swarm/post_summon(atom/summoned_object, atom/cast_on) + if(!istype(summoned_object, /mob/living/simple_animal/bot/ed209)) + return + + var/mob/living/simple_animal/bot/ed209/summoned_bot = summoned_object + summoned_bot.name = "Wizard's Justicebot" + + summoned_bot.projectile = /obj/projectile/beam/laser + summoned_bot.shoot_sound = 'sound/weapons/laser.ogg' diff --git a/code/modules/spells/spell_types/conjure/invisible_chair.dm b/code/modules/spells/spell_types/conjure/invisible_chair.dm new file mode 100644 index 0000000000000..465af41c70c52 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/invisible_chair.dm @@ -0,0 +1,33 @@ +/datum/action/spell/conjure/invisible_chair + name = "Invisible Chair" + desc = "The mime's performance transmutates a chair into physical reality." + background_icon_state = "bg_mime" + icon_icon = 'icons/hud/actions/actions_mime.dmi' + button_icon_state = "invisible_chair" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = ("You conjure an invisible chair and sit down.") + invocation_type = INVOCATION_EMOTE + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + summon_radius = 0 + summon_type = list(/obj/structure/chair/mime) + summon_lifespan = 25 SECONDS + +/datum/action/spell/conjure/invisible_chair/pre_cast(mob/user, atom/target) + . = ..() + invocation = "[user] pulls out an invisible chair and sits down." + +/datum/action/spell/conjure/invisible_chair/post_summon(atom/summoned_object, mob/living/carbon/human/cast_on) + if(!isobj(summoned_object)) + return + + var/obj/chair = summoned_object + chair.setDir(cast_on.dir) + chair.buckle_mob(cast_on) diff --git a/code/modules/spells/spell_types/conjure/invisible_wall.dm b/code/modules/spells/spell_types/conjure/invisible_wall.dm new file mode 100644 index 0000000000000..9ed715ac5d6bd --- /dev/null +++ b/code/modules/spells/spell_types/conjure/invisible_wall.dm @@ -0,0 +1,25 @@ +/datum/action/spell/conjure/invisible_wall + name = "Invisible Wall" + desc = "The mime's performance transmutates a wall into physical reality." + background_icon_state = "bg_mime" + icon_icon = 'icons/hud/actions/actions_mime.dmi' + button_icon_state = "invisible_wall" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = ("You form a wall in front of yourself.") + invocation_type = INVOCATION_EMOTE + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + summon_radius = 0 + summon_type = list(/obj/effect/forcefield/mime) + summon_lifespan = 30 SECONDS + +/datum/action/spell/conjure/invisible_wall/pre_cast(mob/user, atom/target) + . = ..() + invocation = "[user] looks as if a wall is in front of [user.p_them()]." diff --git a/code/modules/spells/spell_types/conjure/link_words.dm b/code/modules/spells/spell_types/conjure/link_words.dm new file mode 100644 index 0000000000000..642aa057b3d19 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/link_words.dm @@ -0,0 +1,15 @@ +/datum/action/spell/conjure/link_worlds + name = "Link Worlds" + desc = "A whole new dimension for you to play with! They won't be happy about it, though." + + sound = 'sound/weapons/marauder.ogg' + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "WTF" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + + summon_radius = 1 + summon_type = list(/obj/structure/spawner/nether) + summon_amount = 1 diff --git a/code/modules/spells/spell_types/conjure/presents.dm b/code/modules/spells/spell_types/conjure/presents.dm new file mode 100644 index 0000000000000..1ed46f2ffe724 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/presents.dm @@ -0,0 +1,14 @@ +/datum/action/spell/conjure/presents + name = "Conjure Presents!" + desc = "This spell lets you reach into S-space and retrieve presents! Yay!" + + school = SCHOOL_CONJURATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 13.75 SECONDS + + invocation = "HO HO HO" + invocation_type = INVOCATION_SHOUT + + summon_radius = 3 + summon_type = list(/obj/item/a_gift) + summon_amount = 5 diff --git a/code/modules/spells/spell_types/conjure/soulstone.dm b/code/modules/spells/spell_types/conjure/soulstone.dm new file mode 100644 index 0000000000000..9222e7084be9e --- /dev/null +++ b/code/modules/spells/spell_types/conjure/soulstone.dm @@ -0,0 +1,30 @@ +/datum/action/spell/conjure/soulstone + name = "Summon Soulstone" + desc = "This spell reaches into Nar'Sie's realm, summoning one of the legendary fragments across time and space." + background_icon_state = "bg_demon" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "summonsoulstone" + + school = SCHOOL_CONJURATION + cooldown_time = 4 MINUTES + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + summon_radius = 0 + summon_type = list(/obj/item/soulstone) + +/datum/action/spell/conjure/soulstone/cult + name = "Create Nar'sian Soulstone" + cooldown_time = 6 MINUTES + +/datum/action/spell/conjure/soulstone/noncult + name = "Create Soulstone" + summon_type = list(/obj/item/soulstone/anybody) + +/datum/action/spell/conjure/soulstone/purified + name = "Create Purified Soulstone" + summon_type = list(/obj/item/soulstone/anybody/purified) + +/datum/action/spell/conjure/soulstone/mystic + name = "Create Mystic Soulstone" + summon_type = list(/obj/item/soulstone/mystic) diff --git a/code/modules/spells/spell_types/conjure/the_traps.dm b/code/modules/spells/spell_types/conjure/the_traps.dm new file mode 100644 index 0000000000000..46659f39a9d48 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/the_traps.dm @@ -0,0 +1,35 @@ +/datum/action/spell/conjure/the_traps + name = "The Traps!" + desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." + button_icon_state = "the_traps" + + cooldown_time = 25 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + invocation = "CAVERE INSIDIAS" + invocation_type = INVOCATION_SHOUT + + summon_radius = 3 + summon_type = list( + /obj/structure/trap/stun, + /obj/structure/trap/fire, + /obj/structure/trap/chill, + /obj/structure/trap/damage, + ) + summon_lifespan = 5 MINUTES + summon_amount = 5 + + /// The amount of charges the traps spawn with. + var/trap_charges = 1 + +/datum/action/spell/conjure/the_traps/post_summon(atom/summoned_object, atom/cast_on) + if(!istype(summoned_object, /obj/structure/trap)) + return + + var/obj/structure/trap/summoned_trap = summoned_object + summoned_trap.charges = trap_charges + + if(ismob(cast_on)) + var/mob/mob_caster = cast_on + if(mob_caster.mind) + summoned_trap.immune_minds += owner.mind diff --git a/code/modules/spells/spell_types/conjure_item/_conjure_item.dm b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm new file mode 100644 index 0000000000000..3fcc2ced126f5 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm @@ -0,0 +1,47 @@ +/datum/action/spell/conjure_item + school = SCHOOL_CONJURATION + invocation_type = INVOCATION_NONE + + /// Typepath of whatever item we summon + var/obj/item/item_type + /// If TRUE, we delete any previously created items when we cast the spell + var/delete_old = TRUE + /// List of weakrefs to items summoned + var/list/datum/weakref/item_refs + +/datum/action/spell/conjure_item/Destroy() + // If we delete_old, clean up all of our items on delete + if(delete_old) + QDEL_LAZYLIST(item_refs) + + // If we don't delete_old, just let all the items be free + else + LAZYNULL(item_refs) + + return ..() + +/datum/action/spell/conjure_item/is_valid_spell(mob/user, atom/target) + return iscarbon(user) + +/datum/action/spell/conjure_item/on_cast(mob/user, atom/target) + . = ..() + if(delete_old && LAZYLEN(item_refs)) + QDEL_LAZYLIST(item_refs) + + var/obj/item/existing_item = user.get_active_held_item() + if(existing_item) + user.dropItemToGround(existing_item) + + var/obj/item/created = make_item() + if(QDELETED(created)) + CRASH("[type] tried to create an item, but failed. It's item type is [item_type].") + + user.put_in_hands(created, del_on_fail = TRUE) + return ..() + +/// Instantiates the item we're conjuring and returns it. +/// Item is made in nullspace and moved out in cast(). +/datum/action/spell/conjure_item/proc/make_item() + var/obj/item/made_item = new item_type() + LAZYADD(item_refs, WEAKREF(made_item)) + return made_item diff --git a/code/modules/spells/spell_types/conjure_item/infinite_guns.dm b/code/modules/spells/spell_types/conjure_item/infinite_guns.dm new file mode 100644 index 0000000000000..e1686e49d9f54 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/infinite_guns.dm @@ -0,0 +1,41 @@ +/datum/action/spell/conjure_item/infinite_guns + school = SCHOOL_CONJURATION + cooldown_time = 1.25 MINUTES + cooldown_reduction_per_rank = 18.5 SECONDS + + invocation_type = INVOCATION_NONE + + item_type = /obj/item/gun/ballistic/rifle + // Enchanted guns self delete / do wacky stuff, anyways + delete_old = FALSE + +/datum/action/spell/conjure_item/infinite_guns/Remove(mob/living/remove_from) + var/obj/item/existing = remove_from.is_holding_item_of_type(item_type) + if(existing) + qdel(existing) + + return ..() + +// Because enchanted guns self-delete and regenerate themselves, +// override make_item here and let's not bother with tracking their weakrefs. +/datum/action/spell/conjure_item/infinite_guns/make_item() + return new item_type() + +/datum/action/spell/conjure_item/infinite_guns/gun + name = "Lesser Summon Guns" + desc = "Why reload when you have infinite guns? \ + Summons an unending stream of bolt action rifles that deal little damage, \ + but will knock targets down. Requires both hands free to use. \ + Learning this spell makes you unable to learn Arcane Barrage." + button_icon_state = "bolt_action" + + item_type = /obj/item/gun/ballistic/rifle/boltaction/enchanted + +/datum/action/spell/conjure_item/infinite_guns/arcane_barrage + name = "Arcane Barrage" + desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. \ + Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. \ + Learning this spell makes you unable to learn Lesser Summon Gun." + button_icon_state = "arcane_barrage" + + item_type = /obj/item/gun/ballistic/rifle/boltaction/enchanted/arcane_barrage diff --git a/code/modules/spells/spell_types/conjure_item/invisible_box.dm b/code/modules/spells/spell_types/conjure_item/invisible_box.dm new file mode 100644 index 0000000000000..591ebb6ddcf3f --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/invisible_box.dm @@ -0,0 +1,41 @@ +/datum/action/spell/conjure_item/invisible_box + name = "Invisible Box" + desc = "The mime's performance transmutates a box into physical reality." + background_icon_state = "bg_mime" + icon_icon = 'icons/hud/actions/actions_mime.dmi' + button_icon_state = "invisible_box" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = ("You conjure up an invisible box, large enough to store a few things.") + invocation_type = INVOCATION_EMOTE + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + delete_old = FALSE + item_type = /obj/item/storage/box/mime + /// How long boxes last before going away + var/box_lifespan = 50 SECONDS + +/datum/action/spell/conjure_item/invisible_box/pre_cast(mob/user, atom/target) + . = ..() + invocation = ("[user] moves [user.p_their()] hands in the shape of a cube, pressing a box out of the air.") + +/datum/action/spell/conjure_item/invisible_box/make_item() + . = ..() + var/obj/item/made_box = . + made_box.alpha = 255 + addtimer(CALLBACK(src, PROC_REF(cleanup_box), made_box), box_lifespan) + +/// Callback that gets rid out of box and removes the weakref from our list +/datum/action/spell/conjure_item/invisible_box/proc/cleanup_box(obj/item/storage/box/box) + if(QDELETED(box) || !istype(box)) + return + + box.emptyStorage() + LAZYREMOVE(item_refs, WEAKREF(box)) + qdel(box) diff --git a/code/modules/spells/spell_types/conjure_item/lightning_packet.dm b/code/modules/spells/spell_types/conjure_item/lightning_packet.dm new file mode 100644 index 0000000000000..61956dd5c6a65 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/lightning_packet.dm @@ -0,0 +1,41 @@ +/datum/action/spell/conjure_item/spellpacket + name = "Thrown Lightning" + desc = "Forged from eldrich energies, a packet of pure power, \ + known as a spell packet will appear in your hand, that - when thrown - will stun the target." + button_icon_state = "thrownlightning" + + cooldown_time = 1 SECONDS + spell_max_level = 1 + + item_type = /obj/item/spellpacket/lightningbolt + +/datum/action/spell/conjure_item/spellpacket/is_valid_spell(mob/user, atom/target) + return ..() && istype(user, /mob/living/carbon) + +/datum/action/spell/conjure_item/spellpacket/on_cast(mob/living/carbon/user, atom/target) + . = ..() + user.throw_mode_on(THROW_MODE_TOGGLE) + +/obj/item/spellpacket/lightningbolt + name = "\improper Lightning bolt Spell Packet" + desc = "Some birdseed wrapped in cloth that crackles with electricity." + icon = 'icons/obj/toy.dmi' + icon_state = "snappop" + w_class = WEIGHT_CLASS_TINY + +/obj/item/spellpacket/lightningbolt/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + . = ..() + if(.) + return + + if(isliving(hit_atom)) + var/mob/living/hit_living = hit_atom + if(!hit_living.can_block_magic()) + hit_living.electrocute_act(80, src, flags = SHOCK_ILLUSION) + qdel(src) + +/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin = TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, quickstart = TRUE) + . = ..() + if(ishuman(thrower)) + var/mob/living/carbon/human/human_thrower = thrower + human_thrower.say("LIGHTNINGBOLT!!", forced = "spell") diff --git a/code/modules/spells/spell_types/conjure_item/snowball.dm b/code/modules/spells/spell_types/conjure_item/snowball.dm new file mode 100644 index 0000000000000..aff69a0a7308d --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/snowball.dm @@ -0,0 +1,8 @@ +/datum/action/spell/conjure_item/snowball + name = "Snowball" + desc = "Concentrates cryokinetic forces to create snowballs, useful for throwing at people." + icon_icon = 'icons/obj/toy.dmi' + button_icon_state = "snowball" + + cooldown_time = 1.5 SECONDS + item_type = /obj/item/toy/snowball diff --git a/code/modules/spells/spell_types/construct_spells.dm b/code/modules/spells/spell_types/construct_spells.dm deleted file mode 100644 index 7f697c2a300f5..0000000000000 --- a/code/modules/spells/spell_types/construct_spells.dm +++ /dev/null @@ -1,353 +0,0 @@ -//////////////////////////////Construct Spells///////////////////////// - -/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser - charge_max = 3 MINUTES - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "artificer" - action_background_icon_state = "bg_demon" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser/cult - clothes_req = TRUE - charge_max = 250 SECONDS - -/obj/effect/proc_holder/spell/aoe_turf/area_conversion - name = "Area Conversion" - desc = "This spell instantly converts a small area around you." - - school = "transmutation" - charge_max = 5 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = 2 - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "areaconvert" - action_background_icon_state = "bg_cult" - -/obj/effect/proc_holder/spell/aoe_turf/area_conversion/cast(list/targets, mob/user = usr) - playsound(get_turf(user), 'sound/items/welder.ogg', 75, 1) - for(var/turf/T in targets) - T.narsie_act(FALSE, TRUE, 100 - (get_dist(user, T) * 25)) - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/floor - name = "Summon Cult Floor" - desc = "This spell constructs a cult floor." - - school = "conjuration" - charge_max = 2 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = 0 - summon_type = list(/turf/open/floor/engine/cult) - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "floorconstruct" - action_background_icon_state = "bg_cult" - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/wall - name = "Summon Cult Wall" - desc = "This spell constructs a cult wall." - - school = "conjuration" - charge_max = 10 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = 0 - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "lesserconstruct" - action_background_icon_state = "bg_cult" - - summon_type = list(/turf/closed/wall/mineral/cult/artificer) //we don't want artificer-based runed metal farms - -/obj/effect/proc_holder/spell/aoe_turf/conjure/door - name = "Summon Cult Door" - desc = "This spell constructs a cult Airlock." - - school = "conjuration" - charge_max = 30 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - invocation_time = 50 - range = 0 - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "airlockconstruct" - action_background_icon_state = "bg_cult" - - summon_type = list(/obj/machinery/door/airlock/cult) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/wall/reinforced - name = "Greater Construction" - desc = "This spell constructs a reinforced metal wall." - - school = "conjuration" - charge_max = 30 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = 0 - - summon_type = list(/turf/closed/wall/r_wall) - -/obj/effect/proc_holder/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." - - school = "conjuration" - charge_max = 4 MINUTES - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = 0 - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "summonsoulstone" - action_background_icon_state = "bg_demon" - - summon_type = list(/obj/item/soulstone) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/cult - clothes_req = TRUE - charge_max = 6 MINUTES - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult - summon_type = list(/obj/item/soulstone/anybody) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/purified - summon_type = list(/obj/item/soulstone/anybody/purified) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/mystic - summon_type = list(/obj/item/soulstone/mystic) - -/obj/effect/proc_holder/spell/targeted/forcewall/cult - name = "Shield" - desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire." - school = "transmutation" - charge_max = 40 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - wall_type = /obj/effect/forcefield/cult - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "cultforcewall" - action_background_icon_state = "bg_demon" - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift - name = "Phase Shift" - desc = "This spell allows you to pass through walls." - - school = "transmutation" - charge_max = 25 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - jaunt_duration = 5 SECONDS - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "phaseshift" - action_background_icon_state = "bg_demon" - jaunt_in_time = 0.6 SECONDS - jaunt_out_time = 0.6 SECONDS - jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith - jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/jaunt_steam(mobloc) - return - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/angelic - jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith/angelic - jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out/angelic - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/mystic - jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith/mystic - jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out/mystic - -/obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser - name = "Lesser Magic Missile" - desc = "This spell fires several, slow moving, magic projectiles at nearby targets." - - school = "evocation" - charge_max = 40 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - max_targets = 6 - action_icon_state = "magicm" - action_background_icon_state = "bg_demon" - proj_type = /obj/projectile/magic/spell/magic_missile/lesser - -/obj/projectile/magic/spell/magic_missile/lesser - color = "red" //Looks more culty this way - range = 10 - -/obj/projectile/magic/spell/magic_missile/lesser/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) - if(ismob(target) && iscultist(target)) - return FALSE - return ..() - -/obj/effect/proc_holder/spell/targeted/smoke/disable - name = "Paralysing Smoke" - desc = "This spell spawns a cloud of paralysing smoke." - - school = "conjuration" - charge_max = 20 SECONDS - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = -1 - include_user = TRUE - cooldown_min = 20 //25 deciseconds reduction per rank - - smoke_spread = 3 - smoke_amt = 4 - action_icon_state = "smoke" - action_background_icon_state = "bg_cult" - - -/obj/effect/proc_holder/spell/targeted/abyssal_gaze - name = "Abyssal Gaze" - desc = "This spell instills a deep terror in your target, temporarily chilling and blinding it." - - charge_max = 75 SECONDS - range = 5 - include_user = FALSE - selection_type = "range" - stat_allowed = FALSE - - school = "evocation" - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_background_icon_state = "bg_demon" - action_icon_state = "abyssal_gaze" - -/obj/effect/proc_holder/spell/targeted/abyssal_gaze/cast(list/targets, mob/user = usr) - if(!LAZYLEN(targets)) - to_chat(user, "No target found in range.") - revert_cast() - return - - var/mob/living/carbon/target = targets[1] - - if(!(target in oview(range))) - to_chat(user, "[target] is too far away!") - revert_cast() - return - - if(target.anti_magic_check(TRUE, TRUE)) - to_chat(target, "You feel a freezing darkness closing in on you, but it rapidly dissipates.") - return - - to_chat(target, "A freezing darkness surrounds you...") - target.playsound_local(get_turf(target), 'sound/hallucinations/i_see_you1.ogg', 50, 1) - user.playsound_local(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1) - target.become_blind(MAGIC_BLIND) - addtimer(CALLBACK(src, PROC_REF(cure_blindness), target), 40) - if(ishuman(targets[1])) - var/mob/living/carbon/human/humi = targets[1] - humi.adjust_coretemperature(-200) - target.adjust_bodytemperature(-200) - -/obj/effect/proc_holder/spell/targeted/abyssal_gaze/proc/cure_blindness(mob/living/L) - L.cure_blind(MAGIC_BLIND) - -/obj/effect/proc_holder/spell/targeted/dominate - name = "Dominate" - desc = "This spell dominates the mind of a lesser creature to the will of Nar'Sie, allying it only to her direct followers." - - charge_max = 1 MINUTES - range = 7 - include_user = FALSE - selection_type = "range" - stat_allowed = FALSE - - school = "evocation" - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_background_icon_state = "bg_demon" - action_icon_state = "dominate" - -/obj/effect/proc_holder/spell/targeted/dominate/cast(list/targets, mob/user = usr) - if(!LAZYLEN(targets)) - to_chat(user, "No target found in range.") - revert_cast() - return - - var/mob/living/simple_animal/S = targets[1] - - if(S.ckey) - to_chat(user, "[S] is too intelligent to dominate!") - revert_cast() - return - - if(S.stat) - to_chat(user, "[S] is dead!") - revert_cast() - return - - if(!istype(S) || S.sentience_type != SENTIENCE_ORGANIC) - to_chat(user, "[S] cannot be dominated!") - revert_cast() - return - - if(!(S in oview(range))) - to_chat(user, "[S] is too far away!") - revert_cast() - return - - S.add_atom_colour("#990000", FIXED_COLOUR_PRIORITY) - S.faction = list("cult") - playsound(get_turf(S), 'sound/effects/ghost.ogg', 100, 1) - new /obj/effect/temp_visual/cult/sac(get_turf(S)) - -/obj/effect/proc_holder/spell/targeted/dominate/can_target(mob/living/target) - if(!isanimal(target) || target.stat) - return FALSE - if("cult" in target.faction) - return FALSE - return TRUE - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/golem - charge_max = 80 SECONDS - jaunt_in_type = /obj/effect/temp_visual/dir_setting/cult/phase - jaunt_out_type = /obj/effect/temp_visual/dir_setting/cult/phase/out - - -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/juggernaut - name = "Gauntlet Echo" - desc = "Channels energy into your gauntlet - firing its essence forward in a slow moving, yet devastating, attack." - proj_type = /obj/projectile/magic/spell/juggernaut - charge_max = 35 SECONDS - clothes_req = FALSE - action_icon = 'icons/hud/actions/actions_cult.dmi' - action_icon_state = "cultfist" - action_background_icon_state = "bg_demon" - sound = 'sound/weapons/resonator_blast.ogg' - -/obj/projectile/magic/spell/juggernaut - name = "Gauntlet Echo" - icon_state = "cultfist" - alpha = 180 - damage = 30 - damage_type = BRUTE - knockdown = 50 - hitsound = 'sound/weapons/punch3.ogg' - trigger_range = 0 - check_holy = TRUE - ignored_factions = list("cult") - range = 15 - speed = 7 - -/obj/projectile/magic/spell/juggernaut/on_hit(atom/target, blocked) - . = ..() - var/turf/T = get_turf(src) - playsound(T, 'sound/weapons/resonator_blast.ogg', 100, FALSE) - new /obj/effect/temp_visual/cult/sac(T) - for(var/obj/O in range(1, src)) - if(O.density && !istype(O, /obj/structure/destructible/cult)) - O.take_damage(90, BRUTE, MELEE, 0) - new /obj/effect/temp_visual/cult/turf/floor(get_turf(O)) diff --git a/code/modules/spells/spell_types/emplosion.dm b/code/modules/spells/spell_types/emplosion.dm deleted file mode 100644 index 9929b342d622b..0000000000000 --- a/code/modules/spells/spell_types/emplosion.dm +++ /dev/null @@ -1,17 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/emplosion - name = "Emplosion" - desc = "This spell emplodes an area." - - var/emp_heavy = 2 - var/emp_light = 3 - - action_icon_state = "emp" - sound = 'sound/weapons/zapbang.ogg' - -/obj/effect/proc_holder/spell/targeted/emplosion/cast(list/targets,mob/user = usr) - playsound(get_turf(user), sound, 50,1) - for(var/mob/living/target in targets) - if(target.anti_magic_check() && target != user) - continue - empulse(target.loc, emp_heavy, emp_light, magic=TRUE) - return diff --git a/code/modules/spells/spell_types/ethereal_jaunt.dm b/code/modules/spells/spell_types/ethereal_jaunt.dm deleted file mode 100644 index 198cd51b0ee4c..0000000000000 --- a/code/modules/spells/spell_types/ethereal_jaunt.dm +++ /dev/null @@ -1,123 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt - name = "Ethereal Jaunt" - desc = "This spell turns your form ethereal, temporarily making you invisible and able to pass through walls." - - school = "transmutation" - charge_max = 30 SECONDS - clothes_req = TRUE - invocation = "none" - invocation_type = INVOCATION_NONE - range = -1 - cooldown_min = 10 SECONDS - include_user = TRUE - nonabstract_req = TRUE - action_icon_state = "jaunt" - /// For how long are we jaunting? - var/jaunt_duration = 5 SECONDS - /// For how long we become immobilized after exiting the jaunt. - var/jaunt_in_time = 0.5 SECONDS - /// For how long we become immobilized when using this spell. - var/jaunt_out_time = 0 SECONDS - /// Visual for jaunting - var/jaunt_in_type = /obj/effect/temp_visual/wizard - /// Visual for exiting the jaunt - var/jaunt_out_type = /obj/effect/temp_visual/wizard/out - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/cast(list/targets,mob/user = usr) //magnets, so mostly hardcoded - play_sound("enter",user) - for(var/mob/living/target in targets) - INVOKE_ASYNC(src, PROC_REF(do_jaunt), target) - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/do_jaunt(mob/living/target) - target.notransform = 1 - var/turf/mobloc = get_turf(target) - var/obj/effect/dummy/phased_mob/spell_jaunt/holder = new /obj/effect/dummy/phased_mob/spell_jaunt(mobloc) - new jaunt_out_type(mobloc, target.dir) - target.ExtinguishMob() - target.forceMove(holder) - target.reset_perspective(holder) - target.notransform=0 //mob is safely inside holder now, no need for protection. - target.ignore_slowdown(JAUNT_TRAIT) - jaunt_steam(mobloc) - if(jaunt_out_time) - //ADD_TRAIT(target, TRAIT_IMMOBILIZED, type) - sleep(jaunt_out_time) - //REMOVE_TRAIT(target, TRAIT_IMMOBILIZED, type) - - sleep(jaunt_duration) - - target.unignore_slowdown(JAUNT_TRAIT) - if(target.loc != holder) //mob warped out of the warp - qdel(holder) - return - mobloc = get_turf(target.loc) - jaunt_steam(mobloc) - ADD_TRAIT(target, TRAIT_IMMOBILIZED, type) - holder.reappearing = 1 - play_sound("exit",target) - sleep(25 - jaunt_in_time) - new jaunt_in_type(mobloc, holder.dir) - target.setDir(holder.dir) - sleep(jaunt_in_time) - qdel(holder) - if(!QDELETED(target)) - if(mobloc.density) - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(mobloc, direction) - if(T) - if(target.Move(T)) - break - REMOVE_TRAIT(target, TRAIT_IMMOBILIZED, type) - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/jaunt_steam(mobloc) - var/datum/effect_system/steam_spread/steam = new /datum/effect_system/steam_spread() - steam.set_up(10, 0, mobloc) - steam.start() - -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/proc/play_sound(type,mob/living/target) - switch(type) - if("enter") - playsound(get_turf(target), 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1) - if("exit") - playsound(get_turf(target), 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) - -/obj/effect/dummy/phased_mob/spell_jaunt - name = "water" - icon = 'icons/effects/effects.dmi' - icon_state = "nothing" - var/reappearing = FALSE - var/movedelay = 0 - var/movespeed = 2 - density = FALSE - anchored = TRUE - invisibility = 60 - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/dummy/phased_mob/spell_jaunt/Destroy() - // Eject contents if deleted somehow - for(var/atom/movable/AM in src) - AM.forceMove(get_turf(src)) - return ..() - -/obj/effect/dummy/phased_mob/spell_jaunt/relaymove(mob/living/user, direction) - if ((movedelay > world.time) || reappearing || !direction) - return - var/turf/newLoc = get_step(src,direction) - setDir(direction) - - movedelay = world.time + movespeed - - if(newLoc.flags_1 & NOJAUNT_1) - to_chat(user, "Some strange aura is blocking the way.") - return - if (newLoc.is_holy()) - to_chat(user, "Holy energies block your path!") - return - - forceMove(newLoc) - -/obj/effect/dummy/phased_mob/spell_jaunt/ex_act(blah) - return - -/obj/effect/dummy/phased_mob/spell_jaunt/bullet_act(blah) - return BULLET_ACT_FORCE_PIERCE diff --git a/code/modules/spells/spell_types/explosion.dm b/code/modules/spells/spell_types/explosion.dm deleted file mode 100644 index 201c11048b571..0000000000000 --- a/code/modules/spells/spell_types/explosion.dm +++ /dev/null @@ -1,16 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/explosion - name = "Explosion" - desc = "This spell explodes an area." - - var/ex_severe = 1 - var/ex_heavy = 2 - var/ex_light = 3 - var/ex_flash = 4 - -/obj/effect/proc_holder/spell/targeted/explosion/cast(list/targets,mob/user = usr) - for(var/mob/living/target in targets) - if(target.anti_magic_check()) - continue - explosion(target.loc,ex_severe,ex_heavy,ex_light,ex_flash, magic = TRUE) - - return diff --git a/code/modules/spells/spell_types/forcewall.dm b/code/modules/spells/spell_types/forcewall.dm deleted file mode 100644 index 2ac9260ea4f86..0000000000000 --- a/code/modules/spells/spell_types/forcewall.dm +++ /dev/null @@ -1,42 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/forcewall - name = "Forcewall" - desc = "Create a magical barrier that only you can pass through." - school = "transmutation" - charge_max = 100 - clothes_req = FALSE - invocation = "TARCOL MINTI ZHERI" - invocation_type = INVOCATION_SHOUT - sound = 'sound/magic/forcewall.ogg' - action_icon_state = "shield" - range = -1 - include_user = TRUE - cooldown_min = 50 //12 deciseconds reduction per rank - var/wall_type = /obj/effect/forcefield/wizard - -/obj/effect/proc_holder/spell/targeted/forcewall/cast(list/targets,mob/user = usr) - new wall_type(get_turf(user), null, user) - if(user.dir == SOUTH || user.dir == NORTH) - new wall_type(get_step(user, EAST), null, user) - new wall_type(get_step(user, WEST), null, user) - else - new wall_type(get_step(user, NORTH), null, user) - new wall_type(get_step(user, SOUTH), null, user) - - -/obj/effect/forcefield/wizard - var/mob/wizard - -CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/forcefield/wizard) - -/obj/effect/forcefield/wizard/Initialize(mapload, ntimeleft, mob/summoner) - . = ..() - wizard = summoner - -/obj/effect/forcefield/wizard/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - if(mover == wizard) - return TRUE - if(isliving(mover)) - var/mob/living/living_mover = mover - if(living_mover.anti_magic_check(major = FALSE)) - return TRUE diff --git a/code/modules/spells/spell_types/genetic.dm b/code/modules/spells/spell_types/genetic.dm deleted file mode 100644 index 07bac681beb14..0000000000000 --- a/code/modules/spells/spell_types/genetic.dm +++ /dev/null @@ -1,45 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/genetic - name = "Genetic" - desc = "This spell inflicts a set of mutations and disabilities upon the target." - - var/list/active_on = list() - var/list/traits = list() //disabilities - var/list/mutations = list() //mutation defines - var/duration = 100 //deciseconds - /* - Disabilities - 1st bit - ? - 2nd bit - ? - 3rd bit - ? - 4th bit - ? - 5th bit - ? - 6th bit - ? - */ - -/obj/effect/proc_holder/spell/targeted/genetic/cast(list/targets,mob/user = usr) - playMagSound() - for(var/mob/living/carbon/target in targets) - if(target.anti_magic_check()) - continue - if(!target.has_dna()) - continue - for(var/A in mutations) - target.dna.add_mutation(A) - for(var/A in traits) - ADD_TRAIT(target, A, GENETICS_SPELL) - active_on += target - if(duration < charge_max) - addtimer(CALLBACK(src, PROC_REF(remove), target), duration, TIMER_OVERRIDE|TIMER_UNIQUE) - -/obj/effect/proc_holder/spell/targeted/genetic/Destroy() - . = ..() - for(var/V in active_on) - remove(V) - -/obj/effect/proc_holder/spell/targeted/genetic/proc/remove(mob/living/carbon/target) - active_on -= target - if(!QDELETED(target) && target.has_dna()) - for(var/A in mutations) - target.dna.remove_mutation(A) - for(var/A in traits) - REMOVE_TRAIT(target, A, GENETICS_SPELL) diff --git a/code/modules/spells/spell_types/godhand.dm b/code/modules/spells/spell_types/godhand.dm deleted file mode 100644 index e796bdc959a81..0000000000000 --- a/code/modules/spells/spell_types/godhand.dm +++ /dev/null @@ -1,208 +0,0 @@ -/obj/item/melee/touch_attack - name = "\improper outstretched hand" - desc = "High Five?" - var/catchphrase = "High Five!" - var/on_use_sound = null - var/obj/effect/proc_holder/spell/targeted/touch/attached_spell - icon = 'icons/obj/items_and_weapons.dmi' - lefthand_file = 'icons/mob/inhands/misc/touchspell_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/touchspell_righthand.dmi' - icon_state = "syndballoon" - item_state = null - item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL | ISWEAPON - w_class = WEIGHT_CLASS_HUGE - force = 0 - throwforce = 0 - throw_range = 0 - throw_speed = 0 - var/charges = 1 - -CREATION_TEST_IGNORE_SUBTYPES(/obj/item/melee/touch_attack) - -/obj/item/melee/touch_attack/Initialize(mapload, obj/effect/proc_holder/spell/targeted/touch/_spell) - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - if(istype(_spell)) - attached_spell = _spell - -/obj/item/melee/touch_attack/attack(mob/target, mob/living/carbon/user) - if(!iscarbon(user)) //Look ma, no hands - return - if(!(user.mobility_flags & MOBILITY_USE)) - to_chat(user, "You can't reach out!") - return - ..() - -/obj/item/melee/touch_attack/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity) - return - if(charges > 0) - use_charge(user) - - -/obj/item/melee/touch_attack/proc/use_charge(mob/living/user, whisper = FALSE) - if(QDELETED(src)) - return - - if(catchphrase) - if(whisper) - user.say("#[catchphrase]", forced = "spell") - else - user.say(catchphrase, forced = "spell") - if(!isnull(on_use_sound)) - playsound(get_turf(user), on_use_sound, 50, TRUE) - if(--charges <= 0) - attached_spell.use_charge() - qdel(src) - -/obj/item/melee/touch_attack/Destroy() - if(attached_spell) - attached_spell.on_hand_destroy(src) - return ..() - -/obj/item/melee/touch_attack/disintegrate - name = "\improper disintegrating touch" - desc = "This hand of mine glows with an awesome power!" - catchphrase = "EI NATH!!" - on_use_sound = 'sound/magic/disintegrate.ogg' - icon_state = "disintegrate" - item_state = "disintegrate" - -/obj/item/melee/touch_attack/disintegrate/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !ismob(target) || !iscarbon(user) || !(user.mobility_flags & MOBILITY_USE)) //exploding after touching yourself would be bad - return - if(!user.can_speak_vocal()) - to_chat(user, "You can't get the words out!") - return - var/mob/M = target - do_sparks(4, FALSE, M.loc) - for(var/mob/living/L in viewers(7, get_turf(src))) - if(L != user) - L.flash_act(affect_silicon = FALSE) - var/atom/A = M.anti_magic_check() - if(A) - if(isitem(A)) - target.visible_message("[target]'s [A] glows brightly as it wards off the spell!") - user.visible_message("The feedback blows [user]'s arm off!","The spell bounces from [M]'s skin back into your arm!") - user.flash_act() - var/obj/item/bodypart/part = user.get_holding_bodypart_of_item(src) - if(part) - part.dismember() - return ..() - var/obj/item/clothing/suit/hooded/bloated_human/suit = M.get_item_by_slot(ITEM_SLOT_OCLOTHING) - if(istype(suit)) - M.visible_message("[M]'s [suit] explodes off of them into a puddle of gore!") - M.dropItemToGround(suit) - qdel(suit) - new /obj/effect/gibspawner(M.loc) - return ..() - M.gib() - return ..() - -/obj/item/melee/touch_attack/fleshtostone - name = "\improper petrifying touch" - desc = "That's the bottom line, because flesh to stone said so!" - catchphrase = "STAUN EI!!" - on_use_sound = 'sound/magic/fleshtostone.ogg' - icon_state = "fleshtostone" - item_state = "fleshtostone" - -/obj/item/melee/touch_attack/fleshtostone/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user)) //getting hard after touching yourself would also be bad - return - if(!(user.mobility_flags & MOBILITY_USE)) - to_chat(user, "You can't reach out!") - return - if(!user.can_speak_vocal()) - to_chat(user, "You can't get the words out!") - return - var/mob/living/M = target - if(M.anti_magic_check()) - to_chat(user, "The spell can't seem to affect [M]!") - to_chat(M, "You feel your flesh turn to stone for a moment, then revert back!") - ..() - return - M.Stun(40) - M.petrify() - return ..() - - -/obj/item/melee/touch_attack/megahonk - name = "\improper honkmother's blessing" - desc = "You've got a feeling they won't be laughing after this one. Honk honk." - catchphrase = "HONKDOOOOUKEN!" - on_use_sound = 'sound/items/airhorn.ogg' - icon = 'icons/mecha/mecha_equipment.dmi' - icon_state = "mecha_honker" - -/obj/item/melee/touch_attack/megahonk/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || !iscarbon(target) || !iscarbon(user) || user.handcuffed) - return - playsound(get_turf(target), on_use_sound,100,1) - for(var/mob/living/carbon/M in (hearers(1, target) - user)) //3x3 around the target, not affecting the user - if(ishuman(M)) - var/mob/living/carbon/human/H = M - if(istype(H.ears, /obj/item/clothing/ears/earmuffs)) - continue - var/mul = (M==target ? 1 : 0.5) - to_chat(M, "HONK") - M.SetSleeping(0) - M.stuttering += 20*mul - M.adjustEarDamage(0, 30*mul) - M.Knockdown(60*mul) - if(prob(40)) - M.Knockdown(200*mul) - else - M.Jitter(500*mul) - - . = ..() - -/obj/item/melee/touch_attack/megahonk/attack_self(mob/user) - . = ..() - to_chat(user, "\The [src] disappears, to honk another day.") - qdel(src) - -/obj/item/melee/touch_attack/bspie - name = "\improper bluespace pie" - desc = "A thing you can barely comprehend as you hold it in your hand. You're fairly sure you could fit an entire body inside." - on_use_sound = 'sound/magic/demon_consume.ogg' - icon = 'icons/obj/food/piecake.dmi' - icon_state = "frostypie" - color = "#000077" - -/obj/item/melee/touch_attack/bspie/attack_self(mob/user) - . = ..() - to_chat(user, "You smear \the [src] on your chest! ") - qdel(src) - -/obj/item/melee/touch_attack/bspie/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || !iscarbon(target) || !iscarbon(user) || user.handcuffed) - return - if(target == user) - to_chat(user, "You smear \the [src] on your chest!") - qdel(src) - return - var/mob/living/carbon/M = target - - user.visible_message("[user] is trying to stuff [M]\s body into \the [src]!") - if(do_after(user, 25 SECONDS, M)) - var/name = M.real_name - var/obj/item/food/pie/cream/body/pie = new(get_turf(M)) - pie.name = "\improper [name] [pie.name]" - - . = ..() - - M.forceMove(pie) - -/obj/item/melee/touch_attack/mutation - catchphrase = null - var/datum/mutation/parent_mutation - -CREATION_TEST_IGNORE_SUBTYPES(/obj/item/melee/touch_attack/mutation) - -/obj/item/melee/touch_attack/mutation/Initialize(mapload, obj/effect/proc_holder/spell/targeted/touch/_spell, datum/mutation/_parent) - . = ..() - if(!istype(_parent)) - return INITIALIZE_HINT_QDEL - parent_mutation = _parent diff --git a/code/modules/spells/spell_types/infinite_guns.dm b/code/modules/spells/spell_types/infinite_guns.dm deleted file mode 100644 index 2330e9338810c..0000000000000 --- a/code/modules/spells/spell_types/infinite_guns.dm +++ /dev/null @@ -1,27 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/infinite_guns - name = "Lesser Summon Guns" - desc = "Why reload when you have infinite guns? Summons an unending stream of bolt action rifles that deal little damage, but will knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Arcane Barrage." - invocation_type = INVOCATION_NONE - include_user = TRUE - range = -1 - - school = "conjuration" - charge_max = 750 - clothes_req = TRUE - cooldown_min = 10 //Gun wizard - action_icon_state = "bolt_action" - var/summon_path = /obj/item/gun/ballistic/rifle/boltaction/enchanted - -/obj/effect/proc_holder/spell/targeted/infinite_guns/cast(list/targets, mob/user = usr) - for(var/mob/living/carbon/C in targets) - C.drop_all_held_items() - var/GUN = new summon_path - C.put_in_hands(GUN) - -/obj/effect/proc_holder/spell/targeted/infinite_guns/gun - -/obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - name = "Arcane Barrage" - desc = "Fire a torrent of arcane energy at your foes with this (powerful) spell. Deals much more damage than Lesser Summon Guns, but won't knock targets down. Requires both hands free to use. Learning this spell makes you unable to learn Lesser Summon Gun." - action_icon_state = "arcane_barrage" - summon_path = /obj/item/gun/ballistic/rifle/boltaction/enchanted/arcane_barrage diff --git a/code/modules/spells/spell_types/jaunt/_jaunt.dm b/code/modules/spells/spell_types/jaunt/_jaunt.dm new file mode 100644 index 0000000000000..7ed9299a6c8d6 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/_jaunt.dm @@ -0,0 +1,92 @@ +/** + * ## Jaunt spells + * + * A basic subtype for jaunt related spells. + * Jaunt spells put their caster in a dummy + * phased_mob effect that allows them to float + * around incorporeally. + * + * Doesn't actually implement any behavior on cast to + * enter or exit the jaunt - that must be done via subtypes. + * + * Use enter_jaunt() and exit_jaunt() as wrappers. + */ +/datum/action/spell/jaunt + school = SCHOOL_TRANSMUTATION + + invocation_type = INVOCATION_NONE + + /// What dummy mob type do we put jaunters in on jaunt? + var/jaunt_type = /obj/effect/dummy/phased_mob + +/datum/action/spell/jaunt/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + var/area/owner_area = get_area(owner) + var/turf/owner_turf = get_turf(owner) + if(!owner_area || !owner_turf) + return FALSE // nullspaced? + + if(owner_area.teleport_restriction == TELEPORT_ALLOW_NONE) + if(feedback) + to_chat(owner, ("Some dull, universal force is stopping you from jaunting here.")) + return FALSE + + if(owner_turf?.turf_flags & NOJAUNT_1) + if(feedback) + to_chat(owner, ("An otherwordly force is preventing you from jaunting here.")) + return FALSE + + return isliving(owner) + + +/** + * Places the [jaunter] in a jaunt holder mob + * If [loc_override] is supplied, + * the jaunt will be moved to that turf to start at + * + * Returns the holder mob that was created + */ +/datum/action/spell/jaunt/proc/enter_jaunt(mob/living/jaunter, turf/loc_override) + var/obj/effect/dummy/phased_mob/jaunt = new jaunt_type(loc_override || get_turf(jaunter), jaunter) + spell_requirements |= SPELL_CASTABLE_WHILE_PHASED + ADD_TRAIT(jaunter, TRAIT_MAGICALLY_PHASED, REF(src)) + + // This needs to happen at the end, after all the traits and stuff is handled + SEND_SIGNAL(jaunter, COMSIG_MOB_ENTER_JAUNT, src, jaunt) + return jaunt + +/** + * Ejects the [unjaunter] from jaunt + * If [loc_override] is supplied, + * the jaunt will be moved to that turf + * before ejecting the unjaunter + * + * Returns TRUE on successful exit, FALSE otherwise + */ +/datum/action/spell/jaunt/proc/exit_jaunt(mob/living/unjaunter, turf/loc_override) + var/obj/effect/dummy/phased_mob/jaunt = unjaunter.loc + if(!istype(jaunt)) + return FALSE + + if(jaunt.jaunter != unjaunter) + CRASH("Jaunt spell attempted to exit_jaunt with an invalid unjaunter, somehow.") + + if(loc_override) + jaunt.forceMove(loc_override) + jaunt.eject_jaunter() + spell_requirements &= ~SPELL_CASTABLE_WHILE_PHASED + REMOVE_TRAIT(unjaunter, TRAIT_MAGICALLY_PHASED, REF(src)) + + // Ditto - this needs to happen at the end, after all the traits and stuff is handled + SEND_SIGNAL(unjaunter, COMSIG_MOB_AFTER_EXIT_JAUNT, src) + return TRUE + +/// Simple helper to check if the passed mob is currently jaunting or not +/datum/action/spell/jaunt/proc/is_jaunting(mob/living/user) + return istype(user.loc, /obj/effect/dummy/phased_mob) + +/datum/action/spell/jaunt/Remove(mob/living/remove_from) + exit_jaunt(remove_from) + return ..() diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm new file mode 100644 index 0000000000000..242375effb099 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -0,0 +1,315 @@ +/** + * ### Blood Crawl + * + * Lets the caster enter and exit pools of blood. + */ +/datum/action/spell/jaunt/bloodcrawl + name = "Blood Crawl" + desc = "Allows you to phase in and out of existance via pools of blood." + background_icon_state = "bg_demon" + icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' + button_icon_state = "bloodcrawl" + + spell_requirements = NONE + + /// The time it takes to enter blood + var/enter_blood_time = 0 SECONDS + /// The time it takes to exit blood + var/exit_blood_time = 2 SECONDS + /// The radius around us that we look for blood in + var/blood_radius = 1 + /// If TRUE, we equip "blood crawl" hands to the jaunter to prevent using items + var/equip_blood_hands = TRUE + +/datum/action/spell/jaunt/bloodcrawl/on_cast(mob/living/user, atom/target) + . = ..() + for(var/obj/effect/decal/cleanable/blood_nearby in range(blood_radius, get_turf(user))) + if(blood_nearby.can_bloodcrawl_in()) + return do_bloodcrawl(blood_nearby, user) + + reset_spell_cooldown() + to_chat(user, ("There must be a nearby source of blood!")) + +/** + * Attempts to enter or exit the passed blood pool. + * Returns TRUE if we successfully entered or exited said pool, FALSE otherwise + */ +/datum/action/spell/jaunt/bloodcrawl/proc/do_bloodcrawl(obj/effect/decal/cleanable/blood, mob/living/jaunter) + if(is_jaunting(jaunter)) + . = try_exit_jaunt(blood, jaunter) + else + . = try_enter_jaunt(blood, jaunter) + + if(!.) + reset_spell_cooldown() + to_chat(jaunter, ("You are unable to blood crawl!")) + +/** + * Attempts to enter the passed blood pool. + * If forced is TRUE, it will override enter_blood_time. + */ +/datum/action/spell/jaunt/bloodcrawl/proc/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter, forced = FALSE) + if(!forced) + if(enter_blood_time > 0 SECONDS) + blood.visible_message(("[jaunter] starts to sink into [blood]!")) + if(!do_after(jaunter, enter_blood_time, target = blood)) + return FALSE + + // The actual turf we enter + var/turf/jaunt_turf = get_turf(blood) + + // Begin the jaunt + jaunter.notransform = TRUE + var/obj/effect/dummy/phased_mob/holder = enter_jaunt(jaunter, jaunt_turf) + if(!holder) + jaunter.notransform = FALSE + return FALSE + + if(equip_blood_hands && iscarbon(jaunter)) + jaunter.drop_all_held_items() + // Give them some bloody hands to prevent them from doing things + var/obj/item/bloodcrawl/left_hand = new(jaunter) + var/obj/item/bloodcrawl/right_hand = new(jaunter) + left_hand.icon_state = "bloodhand_right" // Icons swapped intentionally.. + right_hand.icon_state = "bloodhand_left" // ..because perspective, or something + jaunter.put_in_hands(left_hand) + jaunter.put_in_hands(right_hand) + + blood.visible_message(("[jaunter] sinks into [blood]!")) + playsound(jaunt_turf, 'sound/magic/enter_blood.ogg', 50, TRUE, -1) + jaunter.ExtinguishMob() + + jaunter.notransform = FALSE + return TRUE + +/** + * Attempts to Exit the passed blood pool. + * If forced is TRUE, it will override exit_blood_time, and if we're currently consuming someone. + */ +/datum/action/spell/jaunt/bloodcrawl/proc/try_exit_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter, forced = FALSE) + if(!forced) + if(jaunter.notransform) + to_chat(jaunter, ("You cannot exit yet!!")) + return FALSE + + if(exit_blood_time > 0 SECONDS) + blood.visible_message(("[blood] starts to bubble...")) + if(!do_after(jaunter, exit_blood_time, target = blood)) + return FALSE + + if(!exit_jaunt(jaunter, get_turf(blood))) + return FALSE + + if(equip_blood_hands && iscarbon(jaunter)) + for(var/obj/item/bloodcrawl/blood_hand in jaunter.held_items) + jaunter.temporarilyRemoveItemFromInventory(blood_hand, force = TRUE) + qdel(blood_hand) + + blood.visible_message(("[jaunter] rises out of [blood]!")) + return TRUE + +/datum/action/spell/jaunt/bloodcrawl/exit_jaunt(mob/living/unjaunter, turf/loc_override) + . = ..() + if(!.) + return + + exit_blood_effect(unjaunter) + +/// Adds an coloring effect to mobs which exit blood crawl. +/datum/action/spell/jaunt/bloodcrawl/proc/exit_blood_effect(mob/living/exited) + var/turf/landing_turf = get_turf(exited) + playsound(landing_turf, 'sound/magic/exit_blood.ogg', 50, TRUE, -1) + + // Make the mob have the color of the blood pool it came out of + var/obj/effect/decal/cleanable/came_from = locate() in landing_turf + var/new_color = came_from?.get_blood_color() + if(!new_color) + return + + exited.add_atom_colour(new_color, TEMPORARY_COLOUR_PRIORITY) + // ...but only for a few seconds + addtimer(CALLBACK(exited, TYPE_PROC_REF(/atom, remove_atom_colour), TEMPORARY_COLOUR_PRIORITY, new_color), 6 SECONDS) + +/** + * Slaughter demon's blood crawl + * Allows the blood crawler to consume people they are dragging. + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon + name = "Voracious Blood Crawl" + desc = "Allows you to phase in and out of existance via pools of blood. If you are dragging someone in critical or dead, \ + they will be consumed by you, fully healing you." + /// The sound played when someone's consumed. + var/consume_sound = 'sound/magic/demon_consume.ogg' + +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter) + // Save this before the actual jaunt + var/atom/coming_with = jaunter.pulling + + // Does the actual jaunt + . = ..() + if(!.) + return + + var/turf/jaunt_turf = get_turf(jaunter) + // if we're not pulling anyone, or we can't what we're pulling + if(!isliving(coming_with)) + return + + var/mob/living/victim = coming_with + + if(victim.stat == CONSCIOUS) + jaunt_turf.visible_message( + ("[victim] kicks free of [blood] just before entering it!"), + blind_message = ("You hear splashing and struggling."), + ) + return FALSE + + if(SEND_SIGNAL(victim, COMSIG_LIVING_BLOOD_CRAWL_PRE_CONSUMED, src, jaunter, blood) & COMPONENT_STOP_CONSUMPTION) + return FALSE + + victim.forceMove(jaunter) + victim.emote("scream") + jaunt_turf.visible_message( + ("[jaunter] drags [victim] into [blood]!"), + blind_message = ("You hear a splash."), + ) + + jaunter.notransform = TRUE + consume_victim(victim, jaunter) + jaunter.notransform = FALSE + + return TRUE + +/** + * Consumes the [victim] from the [jaunter], fully healing them + * and calling [proc/on_victim_consumed] if successful. + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/proc/consume_victim(mob/living/victim, mob/living/jaunter) + on_victim_start_consume(victim, jaunter) + + for(var/i in 1 to 3) + playsound(get_turf(jaunter), consume_sound, 50, TRUE) + if(!do_after(jaunter, 3 SECONDS, victim)) + to_chat(jaunter, ("You lose your victim!")) + return FALSE + if(QDELETED(src)) + return FALSE + + if(SEND_SIGNAL(victim, COMSIG_LIVING_BLOOD_CRAWL_CONSUMED, src, jaunter) & COMPONENT_STOP_CONSUMPTION) + return FALSE + + jaunter.revive(full_heal = TRUE, admin_revive = FALSE) + + // No defib possible after laughter + victim.apply_damage(1000, BRUTE) + victim.death() + on_victim_consumed(victim, jaunter) + +/** + * Called when a victim starts to be consumed. + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_start_consume(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, ("You begin to feast on [victim]... You can not move while you are doing this.")) + +/** + * Called when a victim is successfully consumed. + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_consumed(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, ("You devour [victim]. Your health is fully restored.")) + qdel(victim) + +/** + * Laughter demon's blood crawl + * All mobs consumed are revived after the demon is killed. + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny + name = "Friendly Blood Crawl" + desc = "Allows you to phase in and out of existance via pools of blood. If you are dragging someone in critical or dead - I mean, \ + sleeping, when entering a blood pool, they will be invited to a party and fully heal you!" + consume_sound = 'sound/misc/scary_horn.ogg' + + // Keep the people we hug! + var/list/mob/living/consumed_mobs = list() + +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/Destroy() + consumed_mobs.Cut() + return ..() + +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/Grant(mob/grant_to) + . = ..() + if(owner) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/Remove(mob/living/remove_from) + UnregisterSignal(remove_from, COMSIG_LIVING_DEATH) + return ..() + +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/on_victim_start_consume(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, ("You invite [victim] to your party! You can not move while you are doing this.")) + +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/on_victim_consumed(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, ("[victim] joins your party! Your health is fully restored.")) + consumed_mobs += victim + RegisterSignal(victim, COMSIG_MOB_STATCHANGE, PROC_REF(on_victim_statchange)) + RegisterSignal(victim, COMSIG_PARENT_QDELETING, PROC_REF(on_victim_deleted)) + +/** + * Signal proc for COMSIG_LIVING_DEATH and COMSIG_PARENT_QDELETING + * + * If our demon is deleted or destroyed, expel all of our consumed mobs + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/proc/on_death(datum/source) + SIGNAL_HANDLER + + var/turf/release_turf = get_turf(source) + for(var/mob/living/friend as anything in consumed_mobs) + + // Unregister the signals first + UnregisterSignal(friend, list(COMSIG_MOB_STATCHANGE, COMSIG_PARENT_QDELETING)) + + friend.forceMove(release_turf) + if(!friend.revive(full_heal = TRUE, admin_revive = TRUE)) + continue + friend.grab_ghost(force = TRUE) + playsound(release_turf, consumed_mobs, 50, TRUE, -1) + to_chat(friend, ("You leave [source]'s warm embrace, and feel ready to take on the world.")) + + +/** + * Handle signal from a consumed mob changing stat. + * + * A signal handler for if one of the laughter demon's consumed mobs has + * changed stat. If they're no longer dead (because they were dead when + * swallowed), eject them so they can't rip their way out from the inside. + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/proc/on_victim_statchange(mob/living/victim, new_stat) + SIGNAL_HANDLER + + if(new_stat == DEAD) + return + // Someone we've eaten has spontaneously revived; maybe regen coma, maybe a changeling + victim.forceMove(get_turf(victim)) + victim.visible_message(("[victim] falls out of the air, covered in blood, with a confused look on their face.")) + exit_blood_effect(victim) + + consumed_mobs -= victim + UnregisterSignal(victim, COMSIG_MOB_STATCHANGE) + +/** + * Handle signal from a consumed mob being deleted. Clears any references. + */ +/datum/action/spell/jaunt/bloodcrawl/slaughter_demon/funny/proc/on_victim_deleted(datum/source) + SIGNAL_HANDLER + + consumed_mobs -= source + +/// Bloodcrawl "hands", prevent the user from holding items in bloodcrawl +/obj/item/bloodcrawl + name = "blood crawl" + desc = "You are unable to hold anything while in this form." + icon = 'icons/effects/blood.dmi' + item_flags = ABSTRACT | DROPDEL + +/obj/item/bloodcrawl/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) diff --git a/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm new file mode 100644 index 0000000000000..97134d60fe3d8 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm @@ -0,0 +1,256 @@ +/datum/action/spell/jaunt/ethereal_jaunt + name = "Ethereal Jaunt" + desc = "This spell turns your form ethereal, temporarily making you invisible and able to pass through walls." + button_icon_state = "jaunt" + sound = 'sound/magic/ethereal_enter.ogg' + + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + jaunt_type = /obj/effect/dummy/phased_mob/spell_jaunt + + var/exit_jaunt_sound = 'sound/magic/ethereal_exit.ogg' + /// For how long are we jaunting? + var/jaunt_duration = 5 SECONDS + /// For how long we become immobilized after exiting the jaunt. + var/jaunt_in_time = 0.5 SECONDS + /// For how long we become immobilized when using this spell. + var/jaunt_out_time = 0 SECONDS + /// Visual for jaunting + var/obj/effect/jaunt_in_type = /obj/effect/temp_visual/wizard + /// Visual for exiting the jaunt + var/obj/effect/jaunt_out_type = /obj/effect/temp_visual/wizard/out + /// List of valid exit points + var/list/exit_point_list + +/datum/action/spell/jaunt/ethereal_jaunt/enter_jaunt(mob/living/jaunter) + . = ..() + if(!.) + return + + var/turf/cast_turf = get_turf(.) + new jaunt_out_type(cast_turf, jaunter.dir) + jaunter.ExtinguishMob() + do_steam_effects(cast_turf) + +/datum/action/spell/jaunt/ethereal_jaunt/on_cast(mob/living/user, atom/target) + . = ..() + do_jaunt(user) + +/** + * Begin the jaunt, and the entire jaunt chain. + * Puts cast_on in the phased mob holder here. + * + * Calls do_jaunt_out: + * - if jaunt_out_time is set to more than 0, + * Or immediately calls start_jaunt: + * - if jaunt_out_time = 0 + */ +/datum/action/spell/jaunt/ethereal_jaunt/proc/do_jaunt(mob/living/cast_on) + // Makes sure they don't die or get jostled or something during the jaunt entry + // Honestly probably not necessary anymore, but better safe than sorry + cast_on.notransform = TRUE + var/obj/effect/dummy/phased_mob/holder = enter_jaunt(cast_on) + cast_on.notransform = FALSE + + if(!holder) + CRASH("[type] attempted do_jaunt but failed to create a jaunt holder via enter_jaunt.") + + if(jaunt_out_time > 0) + ADD_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + addtimer(CALLBACK(src, PROC_REF(do_jaunt_out), cast_on, holder), jaunt_out_time) + else + start_jaunt(cast_on, holder) + +/** + * The wind-up to the jaunt. + * Optional, only called if jaunt_out_time is set. + * + * Calls start_jaunt. + */ +/datum/action/spell/jaunt/ethereal_jaunt/proc/do_jaunt_out(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + REMOVE_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + start_jaunt(cast_on, holder) + +/** + * The actual process of starting the jaunt. + * Sets up the signals and exit points and allows + * the caster to actually start moving around. + * + * Calls stop_jaunt after the jaunt runs out. + */ +/datum/action/spell/jaunt/ethereal_jaunt/proc/start_jaunt(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + LAZYINITLIST(exit_point_list) + RegisterSignal(holder, COMSIG_MOVABLE_MOVED, PROC_REF(update_exit_point)) + addtimer(CALLBACK(src, PROC_REF(stop_jaunt), cast_on, holder, get_turf(holder)), jaunt_duration) + +/** + * The stopping of the jaunt. + * Unregisters and signals and places + * the jaunter on the turf they will exit at. + * + * Calls do_jaunt_in: + * - immediately, if jaunt_in_time >= 2.5 seconds + * - 2.5 seconds - jaunt_in_time seconds otherwise + */ +/datum/action/spell/jaunt/ethereal_jaunt/proc/stop_jaunt(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder, turf/start_point) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + UnregisterSignal(holder, COMSIG_MOVABLE_MOVED) + // The caster escaped our holder somehow? + if(cast_on.loc != holder) + qdel(holder) + return + + // Pick an exit turf to deposit the jaunter + var/turf/found_exit + for(var/turf/possible_exit as anything in exit_point_list) + if(possible_exit.is_blocked_turf_ignore_climbable()) + continue + found_exit = possible_exit + break + + // No valid exit was found + if(!found_exit) + // It's possible no exit was found, because we literally didn't even move + if(get_turf(cast_on) != start_point) + to_chat(cast_on, ("Unable to find an unobstructed space, you find yourself ripped back to where you started.")) + // Either way, default to where we started + found_exit = start_point + + exit_point_list = null + holder.forceMove(found_exit) + do_steam_effects(found_exit) + holder.reappearing = TRUE + if(exit_jaunt_sound) + playsound(found_exit, exit_jaunt_sound, 50, TRUE) + + ADD_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + + if(2.5 SECONDS - jaunt_in_time <= 0) + do_jaunt_in(cast_on, holder, found_exit) + else + addtimer(CALLBACK(src, PROC_REF(do_jaunt_in), cast_on, holder, found_exit), 2.5 SECONDS - jaunt_in_time) + +/** + * The wind-up (wind-out?) of exiting the jaunt. + * Optional, only called if jaunt_in_time is above 2.5 seconds. + * + * Calls end_jaunt. + */ +/datum/action/spell/jaunt/ethereal_jaunt/proc/do_jaunt_in(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder, turf/final_point) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + + new jaunt_in_type(final_point, holder.dir) + cast_on.setDir(holder.dir) + + if(jaunt_in_time > 0) + addtimer(CALLBACK(src, PROC_REF(end_jaunt), cast_on, holder, final_point), jaunt_in_time) + else + end_jaunt(cast_on, holder, final_point) + +/** + * Finally, the actual veritable end of the jaunt chains. + * Deletes the phase holder, ejecting the caster at final_point. + * + * If the final_point is dense for some reason, + * tries to put the caster in an adjacent turf. + */ +/datum/action/spell/jaunt/ethereal_jaunt/proc/end_jaunt(mob/living/cast_on, obj/effect/dummy/phased_mob/spell_jaunt/holder, turf/final_point) + if(QDELETED(cast_on) || QDELETED(holder) || QDELETED(src)) + return + cast_on.notransform = TRUE + exit_jaunt(cast_on) + cast_on.notransform = FALSE + + REMOVE_TRAIT(cast_on, TRAIT_IMMOBILIZED, REF(src)) + + if(final_point.density) + var/list/aside_turfs = get_adjacent_open_turfs(final_point) + if(length(aside_turfs)) + cast_on.forceMove(pick(aside_turfs)) + +/** + * Updates the exit point of the jaunt + * + * Called when the jaunting mob holder moves, this updates the backup exit-jaunt + * location, in case the jaunt ends with the mob still in a wall. Five + * spots are kept in the list, in case the last few changed since we passed + * by (doors closing, engineers building walls, etc) + */ +/datum/action/spell/jaunt/ethereal_jaunt/proc/update_exit_point(mob/living/source) + SIGNAL_HANDLER + + var/turf/location = get_turf(source) + if(location.is_blocked_turf_ignore_climbable()) + return + exit_point_list.Insert(1, location) + if(length(exit_point_list) >= 5) + exit_point_list.Cut(5) + +/// Does some steam effects from the jaunt at passed loc. +/datum/action/spell/jaunt/ethereal_jaunt/proc/do_steam_effects(turf/loc) + var/datum/effect_system/steam_spread/steam = new() + steam.set_up(10, FALSE, loc) + steam.start() + + +/datum/action/spell/jaunt/ethereal_jaunt/shift + name = "Phase Shift" + desc = "This spell allows you to pass through walls." + background_icon_state = "bg_demon" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "phaseshift" + + cooldown_time = 25 SECONDS + spell_requirements = NONE + + jaunt_duration = 5 SECONDS + jaunt_in_time = 0.6 SECONDS + jaunt_out_time = 0.6 SECONDS + jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith + jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out + +/datum/action/spell/jaunt/ethereal_jaunt/shift/do_steam_effects(mobloc) + return + +/datum/action/spell/jaunt/ethereal_jaunt/shift/angelic + name = "Purified Phase Shift" + jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith/angelic + jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out/angelic + +/datum/action/spell/jaunt/ethereal_jaunt/shift/mystic + name = "Mystic Phase Shift" + jaunt_in_type = /obj/effect/temp_visual/dir_setting/wraith/mystic + jaunt_out_type = /obj/effect/temp_visual/dir_setting/wraith/out/mystic + +/datum/action/spell/jaunt/ethereal_jaunt/shift/golem + name = "Runic Phase Shift" + cooldown_time = 80 SECONDS + jaunt_in_type = /obj/effect/temp_visual/dir_setting/cult/phase + jaunt_out_type = /obj/effect/temp_visual/dir_setting/cult/phase/out + + +/// The dummy that holds people jaunting. Maybe one day we can replace it. +/obj/effect/dummy/phased_mob/spell_jaunt + movespeed = 2 //quite slow. + /// Whether we're currently reappearing - we can't move if so + var/reappearing = FALSE + +/obj/effect/dummy/phased_mob/spell_jaunt/phased_check(mob/living/user, direction) + if(reappearing) + return + . = ..() + if(!.) + return + if (locate(/obj/effect/blessing) in .) + to_chat(user, ("Holy energies block your path!")) + return null diff --git a/code/modules/spells/spell_types/jaunt/shadow_walk.dm b/code/modules/spells/spell_types/jaunt/shadow_walk.dm new file mode 100644 index 0000000000000..fc6b691d67f05 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/shadow_walk.dm @@ -0,0 +1,82 @@ +/datum/action/spell/jaunt/shadow_walk + name = "Shadow Walk" + desc = "Grants unlimited movement in darkness." + background_icon_state = "bg_alien" + icon_icon = 'icons/hud/actions/actions_minor_antag.dmi' + button_icon_state = "ninja_cloak" + + spell_requirements = NONE + jaunt_type = /obj/effect/dummy/phased_mob/shadow + +/datum/action/spell/jaunt/shadow_walk/on_cast(mob/living/user, atom/target) + . = ..() + if(is_jaunting(user)) + exit_jaunt(user) + return + + var/turf/cast_turf = get_turf(user) + if(cast_turf.get_lumcount() >= SHADOW_SPECIES_LIGHT_THRESHOLD) + to_chat(user, ("It isn't dark enough here!")) + return + + playsound(cast_turf, 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1) + user.visible_message(("[user] melts into the shadows!")) + user.SetAllImmobility(0) + user.setStaminaLoss(0, FALSE) + enter_jaunt(user) + +/obj/effect/dummy/phased_mob/shadow + name = "shadows" + /// The amount that shadow heals us per SSobj tick (times delta_time) + var/healing_rate = 1.5 + +/obj/effect/dummy/phased_mob/shadow/Initialize(mapload) + . = ..() + START_PROCESSING(SSobj, src) + +/obj/effect/dummy/phased_mob/shadow/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/effect/dummy/phased_mob/shadow/process(delta_time) + var/turf/T = get_turf(src) + var/light_amount = T.get_lumcount() + if(!jaunter || jaunter.loc != src) + qdel(src) + return + + if(light_amount < 0.2 && !QDELETED(jaunter) && isliving(jaunter)) //heal in the dark + var/mob/living/living_jaunter = jaunter + living_jaunter.heal_overall_damage((healing_rate * delta_time), (healing_rate * delta_time), 0, BODYTYPE_ORGANIC) + + check_light_level() + +/obj/effect/dummy/phased_mob/shadow/relaymove(mob/living/user, direction) + var/turf/oldloc = loc + . = ..() + if(loc != oldloc) + check_light_level() + +/obj/effect/dummy/phased_mob/shadow/phased_check(mob/living/user, direction) + . = ..() + if(. && isspaceturf(.)) + to_chat(user, ("It really would not be wise to go into space.")) + return FALSE + +/obj/effect/dummy/phased_mob/shadow/proc/check_light_level() + var/turf/T = get_turf(src) + var/light_amount = T.get_lumcount() + if(light_amount > 0.2) // jaunt ends + eject_jaunter(TRUE) + +/obj/effect/dummy/phased_mob/shadow/eject_jaunter(forced_out = FALSE) + var/turf/reveal_turf = get_turf(src) + + if(istype(reveal_turf)) + if(forced_out) + reveal_turf.visible_message(("[jaunter] is revealed by the light!")) + else + reveal_turf.visible_message(("[jaunter] emerges from the darkness!")) + playsound(reveal_turf, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + + return ..() diff --git a/code/modules/spells/spell_types/knock.dm b/code/modules/spells/spell_types/knock.dm deleted file mode 100644 index 34fa4c026041f..0000000000000 --- a/code/modules/spells/spell_types/knock.dm +++ /dev/null @@ -1,32 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/knock - name = "Knock" - desc = "This spell opens nearby doors and closets." - - school = "transmutation" - charge_max = 100 - clothes_req = FALSE - invocation = "AULIE OXIN FIERA" - invocation_type = INVOCATION_WHISPER - range = 3 - cooldown_min = 20 //20 deciseconds reduction per rank - - action_icon_state = "knock" - -/obj/effect/proc_holder/spell/aoe_turf/knock/cast(list/targets,mob/user = usr) - SEND_SOUND(user, sound('sound/magic/knock.ogg')) - for(var/turf/T in targets) - for(var/obj/machinery/door/door in T.contents) - INVOKE_ASYNC(src, PROC_REF(open_door), door) - for(var/obj/structure/closet/C in T.contents) - INVOKE_ASYNC(src, PROC_REF(open_closet), C) - -/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_door(var/obj/machinery/door/door) - if(istype(door, /obj/machinery/door/airlock)) - var/obj/machinery/door/airlock/A = door - A.locked = FALSE - A.wires.ui_update() - door.open() - -/obj/effect/proc_holder/spell/aoe_turf/knock/proc/open_closet(var/obj/structure/closet/C) - C.locked = FALSE - C.open() diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm deleted file mode 100644 index 481802bcfc9b1..0000000000000 --- a/code/modules/spells/spell_types/lichdom.dm +++ /dev/null @@ -1,162 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/lichdom - name = "Bind Soul" - desc = "A dark necromantic pact that can forever bind your soul to an \ - item of your choosing. So long as both your body and the item remain \ - intact and on the same plane you can revive from death, though the time \ - between reincarnations grows steadily with use, along with the weakness \ - that the new skeleton body will experience upon 'birth'. Note that \ - becoming a lich destroys all internal organs except the brain." - school = "necromancy" - charge_max = 10 - clothes_req = FALSE - centcom_cancast = FALSE - invocation = "NECREM IMORTIUM!" - invocation_type = INVOCATION_SHOUT - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 10 - include_user = TRUE - - action_icon = 'icons/hud/actions/actions_spells.dmi' - action_icon_state = "skeleton" - -/obj/effect/proc_holder/spell/targeted/lichdom/cast(list/targets,mob/user = usr) - for(var/mob/M in targets) - var/list/hand_items = list() - if(iscarbon(M)) - hand_items = list(M.get_active_held_item(),M.get_inactive_held_item()) - if(!hand_items.len) - to_chat(M, "You must hold an item you wish to make your phylactery...") - return - if(!M.mind.hasSoul) - to_chat(user, "You do not possess a soul.") - return - - var/obj/item/marked_item - - for(var/obj/item/item in hand_items) - // I ensouled the nuke disk once. But it's probably a really - // mean tactic, so probably should discourage it. - if((item.item_flags & ABSTRACT) || HAS_TRAIT(item, TRAIT_NODROP) || SEND_SIGNAL(item, COMSIG_ITEM_IMBUE_SOUL, user)) - continue - marked_item = item - to_chat(M, "You begin to focus your very being into [item]...") - break - - if(!marked_item) - to_chat(M, "None of the items you hold are suitable for emplacement of your fragile soul.") - return - - playsound(user, 'sound/effects/pope_entry.ogg', 100) - - if(!do_after(M, 50, target=marked_item, timed_action_flags = IGNORE_HELD_ITEM)) - to_chat(M, "Your soul snaps back to your body as you stop ensouling [marked_item]!") - return - - marked_item.name = "ensouled [marked_item.name]" - marked_item.desc += "\nA terrible aura surrounds this item, its very existence is offensive to life itself..." - marked_item.add_atom_colour("#003300", ADMIN_COLOUR_PRIORITY) - - new /obj/item/phylactery(marked_item, M.mind) - - to_chat(M, "With a hideous feeling of emptiness you watch in horrified fascination as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! As your organs crumble to dust in your fleshless chest you come to terms with your choice. You're a lich!") - M.mind.hasSoul = FALSE - M.set_species(/datum/species/skeleton) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.dropItemToGround(H.w_uniform) - H.dropItemToGround(H.wear_suit) - H.dropItemToGround(H.head) - H.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(H), ITEM_SLOT_OCLOTHING) - H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), ITEM_SLOT_HEAD) - H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), ITEM_SLOT_ICLOTHING) - - // you only get one phylactery. - M.mind.RemoveSpell(src) - - -/obj/item/phylactery - name = "phylactery" - desc = "Stores souls. Revives liches. Also repels mosquitos." - icon = 'icons/obj/projectiles.dmi' - icon_state = "bluespace" - color = "#003300" - light_color = "#003300" - light_system = MOVABLE_LIGHT - light_range = 3 - var/lon_range = 3 - var/resurrections = 0 - var/datum/mind/mind - var/respawn_time = 1800 - - var/static/active_phylacteries = 0 - -CREATION_TEST_IGNORE_SUBTYPES(/obj/item/phylactery) - -/obj/item/phylactery/Initialize(mapload, datum/mind/newmind) - . = ..() - mind = newmind - name = "phylactery of [mind.name]" - - active_phylacteries++ - AddElement(/datum/element/point_of_interest) - START_PROCESSING(SSobj, src) - if(initial(SSticker.mode.round_ends_with_antag_death)) - SSticker.mode.round_ends_with_antag_death = FALSE - -/obj/item/phylactery/Destroy(force=FALSE) - STOP_PROCESSING(SSobj, src) - active_phylacteries-- - if(!active_phylacteries) - SSticker.mode.round_ends_with_antag_death = initial(SSticker.mode.round_ends_with_antag_death) - . = ..() - -/obj/item/phylactery/process() - if(QDELETED(mind)) - qdel(src) - return - - if(!mind.current || (mind.current && mind.current.stat == DEAD)) - addtimer(CALLBACK(src, PROC_REF(rise)), respawn_time, TIMER_UNIQUE) - -/obj/item/phylactery/proc/rise() - if(mind.current && mind.current.stat != DEAD) - return "[mind] already has a living body: [mind.current]" - - var/turf/item_turf = get_turf(src) - if(!item_turf) - return "[src] is not at a turf? NULLSPACE!?" - - var/mob/old_body = mind.current - var/mob/living/carbon/human/lich = new(item_turf) - - lich.equip_to_slot_or_del(new /obj/item/clothing/shoes/sandal/magic(lich), ITEM_SLOT_FEET) - lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), ITEM_SLOT_ICLOTHING) - lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), ITEM_SLOT_OCLOTHING) - lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), ITEM_SLOT_HEAD) - - lich.real_name = mind.name - mind.transfer_to(lich) - mind.grab_ghost(force=TRUE) - lich.hardset_dna(null,null,lich.real_name,null, new /datum/species/skeleton,null) - to_chat(lich, "Your bones clatter and shudder as you are pulled back into this world!") - var/turf/body_turf = get_turf(old_body) - lich.Paralyze(200 + 200*resurrections) - resurrections++ - if(old_body?.loc) - if(iscarbon(old_body)) - var/mob/living/carbon/C = old_body - for(var/obj/item/W in C) - C.dropItemToGround(W) - for(var/X in C.internal_organs) - var/obj/item/organ/I = X - I.Remove(C) - I.forceMove(body_turf) - var/wheres_wizdo = dir2text(get_dir(body_turf, item_turf)) - if(wheres_wizdo) - old_body.visible_message("Suddenly [old_body.name]'s corpse falls to pieces! You see a strange energy rise from the remains, and speed off towards the [wheres_wizdo]!") - body_turf.Beam(item_turf,icon_state="lichbeam", time = 10 + 10 * resurrections) - old_body.dust() - - - return "Respawn of [mind] successful." diff --git a/code/modules/spells/spell_types/lightning.dm b/code/modules/spells/spell_types/lightning.dm deleted file mode 100644 index c14df503cf91b..0000000000000 --- a/code/modules/spells/spell_types/lightning.dm +++ /dev/null @@ -1,86 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/tesla - name = "Tesla Blast" - desc = "Charge up a tesla arc and release it at a random nearby target! You can move freely while it charges. The arc jumps between targets and can knock them down." - charge_type = "recharge" - charge_max = 300 - clothes_req = TRUE - invocation = "UN'LTD P'WAH!" - invocation_type = INVOCATION_SHOUT - range = 7 - cooldown_min = 30 - selection_type = "view" - random_target = TRUE - var/ready = FALSE - var/static/mutable_appearance/halo - var/sound/Snd // so far only way i can think of to stop a sound, thank MSO for the idea. - - action_icon_state = "lightning" - -/obj/effect/proc_holder/spell/targeted/tesla/Click() - if(!ready && cast_check()) - StartChargeup() - return TRUE - -/obj/effect/proc_holder/spell/targeted/tesla/proc/StartChargeup(mob/user = usr) - ready = TRUE - to_chat(user, "You start gathering the power.") - Snd = new/sound('sound/magic/lightning_chargeup.ogg',channel = 7) - halo = halo || mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER) - user.add_overlay(halo) - playsound(get_turf(user), Snd, 50, 0) - if(do_after(user, 10 SECONDS, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM))) - if(ready && cast_check(skipcharge=1)) - choose_targets() - else - revert_cast(user, 0) - else - revert_cast(user, 0) - -/obj/effect/proc_holder/spell/targeted/tesla/proc/Reset(mob/user = usr) - ready = FALSE - user.cut_overlay(halo) - -/obj/effect/proc_holder/spell/targeted/tesla/revert_cast(mob/user = usr, message = 1) - if(message) - to_chat(user, "No target found in range.") - Reset(user) - ..() - -/obj/effect/proc_holder/spell/targeted/tesla/cast(list/targets, mob/user = usr) - ready = FALSE - var/mob/living/carbon/target = targets[1] - Snd=sound(null, repeat = 0, wait = 1, channel = Snd.channel) //byond, why you suck? - playsound(get_turf(user),Snd,50,0)// Sorry MrPerson, but the other ways just didn't do it the way i needed to work, this is the only way. - if(get_dist(user,target)>range) - to_chat(user, "[target.p_theyre(TRUE)] too far away!") - Reset(user) - return - - playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, 1) - user.Beam(target,icon_state="lightning[rand(1,12)]", time = 5) - - Bolt(user,target,30,5,user) - Reset(user) - -/obj/effect/proc_holder/spell/targeted/tesla/proc/Bolt(mob/origin,mob/target,bolt_energy,bounces,mob/user = usr) - origin.Beam(target,icon_state="lightning[rand(1,12)]", time = 5) - var/mob/living/carbon/current = target - if(current.anti_magic_check()) - playsound(get_turf(current), 'sound/magic/lightningshock.ogg', 50, 1, -1) - current.visible_message("[current] absorbs the spell, remaining unharmed!", "You absorb the spell, remaining unharmed!") - else if(bounces < 1) - current.electrocute_act(bolt_energy,"Lightning Bolt",flags = SHOCK_NOGLOVES) - playsound(get_turf(current), 'sound/magic/lightningshock.ogg', 50, 1, -1) - else - current.electrocute_act(bolt_energy,"Lightning Bolt",flags = SHOCK_NOGLOVES) - playsound(get_turf(current), 'sound/magic/lightningshock.ogg', 50, 1, -1) - var/list/possible_targets = new - for(var/mob/living/M in view_or_range(range,target,"view")) - if(user == M || target == M && los_check(current,M)) // || origin == M ? Not sure double shockings is good or not - continue - possible_targets += M - if(!possible_targets.len) - return - var/mob/living/next = pick(possible_targets) - if(next) - Bolt(current,next,max((bolt_energy-5),5),bounces-1,user) diff --git a/code/modules/spells/spell_types/list_targets/telepathy.dm b/code/modules/spells/spell_types/list_targets/telepathy.dm new file mode 100644 index 0000000000000..e77c36876107c --- /dev/null +++ b/code/modules/spells/spell_types/list_targets/telepathy.dm @@ -0,0 +1,53 @@ +/datum/action/spell/telepathy + name = "Telepathy" + desc = "Telepathically transmits a message to the target." + icon_icon = 'icons/hud/actions/actions_revenant.dmi' + button_icon_state = "r_transmit" + requires_target = TRUE + + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + antimagic_flags = MAGIC_RESISTANCE_MIND + + /// The message we send to the next person via telepathy. + var/message + /// The span surrounding the telepathy message + var/telepathy_span = "notice" + /// The bolded span surrounding the telepathy message + var/bold_telepathy_span = "boldnotice" + +/datum/action/spell/telepathy/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + message = tgui_input_text(owner, "What do you wish to whisper to [target]?", "[src]") + if(QDELETED(src) || QDELETED(owner) || QDELETED(target) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + + if(!message) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + +/datum/action/spell/telepathy/is_valid_spell(mob/user, atom/target) + return ..() && isliving(user) + +/datum/action/spell/telepathy/on_cast(mob/living/user, mob/living/target) + . = ..() + log_directed_talk(owner, target, message, LOG_SAY, name) + + var/formatted_message = "[message]" + + to_chat(owner, "You transmit to [target]: [formatted_message]") + if(!target.can_block_magic(antimagic_flags)) //hear no evil + to_chat(target, "You hear something behind you talking... [formatted_message]") + target.balloon_alert(target, "You hear a voice in your head...") + for(var/mob/dead/ghost as anything in GLOB.dead_mob_list) + if(!isobserver(ghost)) + continue + + var/from_link = FOLLOW_LINK(ghost, owner) + var/from_mob_name = "[owner] [src]:" + var/to_link = FOLLOW_LINK(ghost, target) + var/to_mob_name = "[target]" + + to_chat(ghost, "[from_link] [from_mob_name] [formatted_message] [to_link] [to_mob_name]") diff --git a/code/modules/spells/spell_types/mime.dm b/code/modules/spells/spell_types/mime.dm deleted file mode 100644 index 949a0623d7591..0000000000000 --- a/code/modules/spells/spell_types/mime.dm +++ /dev/null @@ -1,242 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall - name = "Invisible Wall" - desc = "The mime's performance transmutates a wall into physical reality." - school = "mime" - panel = JOB_NAME_MIME - summon_type = list(/obj/effect/forcefield/mime) - invocation_type = "emote" - invocation_emote_self = "You form a wall in front of yourself." - summon_lifespan = 100 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/hud/actions/actions_mime.dmi' - action_icon_state = "invisible_wall" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] looks as if a wall is in front of [usr.p_them()]." - else - invocation_type ="none" - ..() - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair - name = "Invisible Chair" - desc = "The mime's performance transmutates a chair into physical reality." - school = "mime" - panel = JOB_NAME_MIME - summon_type = list(/obj/structure/chair/mime) - invocation_type = "emote" - invocation_emote_self = "You conjure an invisible chair and sit down." - summon_lifespan = 250 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/hud/actions/actions_mime.dmi' - action_icon_state = "invisible_chair" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] pulls out an invisible chair and sits down." - else - invocation_type ="none" - ..() - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_chair/cast(list/targets,mob/user = usr) - ..() - var/turf/T = user.loc - for (var/obj/structure/chair/A in T) - if (is_type_in_list(A, summon_type)) - A.setDir(user.dir) - A.buckle_mob(user) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box - name = "Invisible Box" - desc = "The mime's performance transmutates a box into physical reality." - school = "mime" - panel = JOB_NAME_MIME - summon_type = list(/obj/item/storage/box/mime) - invocation_type = "emote" - invocation_emote_self = "You conjure up an invisible box, large enough to store a few things." - summon_lifespan = 500 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/hud/actions/actions_mime.dmi' - action_icon_state = "invisible_box" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/cast(list/targets,mob/user = usr) - ..() - var/turf/T = user.loc - for (var/obj/item/storage/box/mime/B in T) - user.put_in_hands(B) - B.alpha = 255 - addtimer(CALLBACK(B, TYPE_PROC_REF(/obj/item/storage/box/mime, emptyStorage), FALSE), (summon_lifespan - 1)) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_box/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] moves [usr.p_their()] hands in the shape of a cube, pressing a box out of the air." - else - invocation_type ="none" - ..() - - -/obj/effect/proc_holder/spell/targeted/mime/speak - name = "Speech" - desc = "Make or break a vow of silence." - school = "mime" - panel = JOB_NAME_MIME - clothes_req = FALSE - human_req = TRUE - antimagic_allowed = TRUE - charge_max = 3000 - range = -1 - include_user = TRUE - - action_icon = 'icons/hud/actions/actions_mime.dmi' - action_icon_state = "mime_speech" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/targeted/mime/speak/Click() - if(!usr) - return - if(!ishuman(usr)) - return - var/mob/living/carbon/human/H = usr - if(H.mind.miming) - still_recharging_msg = "You can't break your vow of silence that fast!" - else - still_recharging_msg = "You'll have to wait before you can give your vow of silence again!" - ..() - -/obj/effect/proc_holder/spell/targeted/mime/speak/cast(list/targets,mob/user = usr) - for(var/mob/living/carbon/human/H in targets) - H.mind.miming=!H.mind.miming - if(H.mind.miming) - to_chat(H, "You make a vow of silence.") - SEND_SIGNAL(H, COMSIG_CLEAR_MOOD_EVENT, "vow") - else - SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vow", /datum/mood_event/broken_vow) - to_chat(H, "You break your vow of silence.") - for(var/datum/objective/crew/vow/obj in H.mind.crew_objectives) - obj.broken = TRUE - -// These spells can only be gotten from the "Guide for Advanced Mimery series" for Mime Traitors. - -/obj/effect/proc_holder/spell/targeted/forcewall/mime - name = "Invisible Blockade" - desc = "Form an invisible three tile wide blockade." - school = "mime" - panel = JOB_NAME_MIME - wall_type = /obj/effect/forcefield/mime/advanced - invocation_type = "emote" - invocation_emote_self = "You form a blockade in front of yourself." - charge_max = 600 - sound = null - clothes_req = FALSE - antimagic_allowed = TRUE - range = -1 - include_user = TRUE - - action_icon = 'icons/hud/actions/actions_mime.dmi' - action_icon_state = "invisible_blockade" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/targeted/forcewall/mime/Click() - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - invocation = "[usr.real_name] looks as if a blockade is in front of [usr.p_them()]." - else - invocation_type ="none" - ..() - -/obj/effect/proc_holder/spell/targeted/mime/finger_guns - name = "Finger Guns" - desc = "Shoot a mimed bullet from your fingers that stuns and does some damage." - school = "mime" - panel = JOB_NAME_MIME - charge_max = 300 - range = -1 - clothes_req = FALSE - antimagic_allowed = TRUE - include_user = TRUE - invocation_type = "emote" - invocation_emote_self = "You fire your finger gun!" - sound = null - - action_icon = 'icons/hud/actions/actions_mime.dmi' - action_icon_state = "finger_guns0" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/targeted/mime/finger_guns/Click() - if(!usr) - return - if(!ishuman(usr)) - return - if(usr?.mind) - if(!usr.mind.miming) - to_chat(usr, "You must dedicate yourself to silence first.") - return - var/obj/item/gun/ballistic/revolver/mime/magic/N = new(usr) - if(usr.put_in_hands(N)) - to_chat(usr, "You form your fingers into a gun.") - else - qdel(N) - to_chat(usr, "You don't have any free hands to make fingerguns with.") - ..() - -/obj/item/book/granter/spell/mimery_blockade - spell = /obj/effect/proc_holder/spell/targeted/forcewall/mime - spellname = "Invisible Blockade" - name = "Guide to Advanced Mimery Vol 1" - desc = "The pages don't make any sound when turned." - icon_state ="bookmime" - remarks = list("...") - -/obj/item/book/granter/spell/mimery_blockade/attack_self(mob/user) - . = ..() - if(!.) - return - if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak) - -/obj/item/book/granter/spell/mimery_guns - spell = /obj/effect/proc_holder/spell/targeted/mime/finger_guns - spellname = "Finger Guns" - name = "Guide to Advanced Mimery Vol 2" - desc = "There aren't any words written..." - icon_state ="bookmime" - remarks = list("...") - -/obj/item/book/granter/spell/mimery_guns/attack_self(mob/user) - . = ..() - if(!.) - return - if(!locate(/obj/effect/proc_holder/spell/targeted/mime/speak) in user.mind.spell_list) - user.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak) diff --git a/code/modules/spells/spell_types/mind_transfer.dm b/code/modules/spells/spell_types/mind_transfer.dm deleted file mode 100644 index 3f220dcc0fcc7..0000000000000 --- a/code/modules/spells/spell_types/mind_transfer.dm +++ /dev/null @@ -1,108 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/mind_transfer - name = "Mind Transfer" - desc = "This spell allows the user to switch bodies with a target." - - school = "transmutation" - charge_max = 600 - clothes_req = FALSE - invocation = "GIN'YU CAPAN" - invocation_type = INVOCATION_WHISPER - range = 1 - cooldown_min = 200 //100 deciseconds reduction per rank - var/unconscious_amount_caster = 400 //how much the caster is stunned for after the spell - var/unconscious_amount_victim = 400 //how much the victim is stunned for after the spell - - action_icon_state = "mindswap" - -/* -Urist: I don't feel like figuring out how you store object spells so I'm leaving this for you to do. -Make sure spells that are removed from spell_list are actually removed and deleted when mind transferring. -Also, you never added distance checking after target is selected. I've went ahead and did that. -*/ -/obj/effect/proc_holder/spell/targeted/mind_transfer/cast(list/targets, mob/living/user = usr, distanceoverride, silent = FALSE) - if(!targets.len) - if(!silent) - to_chat(user, "No mind found!") - return - - if(targets.len > 1) - if(!silent) - to_chat(user, "Too many minds! You're not a hive damnit!") - return - - var/mob/living/target = targets[1] - - var/t_He = target.p_they(TRUE) - var/t_is = target.p_are() - - if(!(target in oview(range)) && !distanceoverride)//If they are not in overview after selection. Do note that !() is necessary for in to work because ! takes precedence over it. - if(!silent) - to_chat(user, "[t_He] [t_is] too far away!") - return - - if(ismegafauna(target)) - if(!silent) - to_chat(user, "This creature is too powerful to control!") - return - - if(target.stat == DEAD) - if(!silent) - to_chat(user, "You don't particularly want to be dead!") - return - - if(!target.key || !target.mind) - if(!silent) - to_chat(user, "[t_He] appear[target.p_s()] to be catatonic! Not even magic can affect [target.p_their()] vacant mind.") - return - - if(user.suiciding) - if(!silent) - to_chat(user, "You're killing yourself! You can't concentrate enough to do this!") - return - - var/datum/mind/TM = target.mind - if(target.anti_magic_check() || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev) || target.key[1] == "@") - if(!silent) - to_chat(user, "[target.p_their(TRUE)] mind is resisting your spell!") - return - - if(istype(target.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) - to_chat(target, "Your protective headgear successfully deflects mind controlling brainwaves!") - to_chat(user, "[target.p_their(TRUE)] mind is protected by a strange ward on their headgear!") - return - - if(istype(target, /mob/living/simple_animal/hostile/holoparasite)) - var/mob/living/simple_animal/hostile/holoparasite/stand = target - if(stand.summoner) - if(stand.summoner == user) - if(!silent) - to_chat(user, "Swapping minds with your own guardian would just put you back into your own head!") - return - else - target = stand.summoner - - if(istype(target, /mob/living/simple_animal/slaughter)) //No. - to_chat(user, "Your mind recoils from the infernal hellfire of [target]'s soul!") - user.Unconscious(unconscious_amount_caster) - return - - var/mob/living/victim = target//The target of the spell whos body will be transferred to. - var/mob/living/caster = user//The wizard/whomever doing the body transferring. - - //MIND TRANSFER BEGIN - var/mob/dead/observer/ghost = victim.ghostize(FALSE) - caster.mind.transfer_to(victim) - - ghost.mind.transfer_to(caster) - if(ghost.key) - caster.key = ghost.key //have to transfer the key since the mind was not active - qdel(ghost) - - //MIND TRANSFER END - - //Here we knock both mobs out for a time. - caster.Unconscious(unconscious_amount_caster) - victim.Unconscious(unconscious_amount_victim) - SEND_SOUND(caster, sound('sound/magic/mandswap.ogg')) - SEND_SOUND(victim, sound('sound/magic/mandswap.ogg'))// only the caster and victim hear the sounds, that way no one knows for sure if the swap happened - return TRUE diff --git a/code/modules/spells/spell_types/personality_commune.dm b/code/modules/spells/spell_types/personality_commune.dm deleted file mode 100644 index f7fc09f8320f5..0000000000000 --- a/code/modules/spells/spell_types/personality_commune.dm +++ /dev/null @@ -1,36 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/personality_commune - name = "Personality Commune" - desc = "Sends thoughts to your alternate consciousness." - charge_max = 0 - clothes_req = FALSE - range = -1 - include_user = TRUE - action_icon_state = "telepathy" - action_background_icon_state = "bg_spell" - var/datum/brain_trauma/severe/split_personality/trauma - var/flufftext = "You hear an echoing voice in the back of your head..." - -/obj/effect/proc_holder/spell/targeted/personality_commune/New(datum/brain_trauma/severe/split_personality/T) - . = ..() - trauma = T - -// Pillaged and adapted from telepathy code -/obj/effect/proc_holder/spell/targeted/personality_commune/cast(list/targets, mob/user) - if(!istype(trauma)) - to_chat(user, "Something is wrong; Either due a bug or admemes, you are trying to cast this spell without a split personality!") - return - var/msg = stripped_input(usr, "What would you like to tell your other self?", null , "") - if(!msg) - charge_counter = charge_max - return - if(CHAT_FILTER_CHECK(msg)) - to_chat(usr, "Your message contains forbidden words.") - return - msg = user.treat_message_min(msg) - to_chat(user, "You concentrate and send thoughts to your other self: [msg]") - to_chat(trauma.owner, "[flufftext] [msg]") - log_directed_talk(user, trauma.owner, msg, LOG_SAY ,"[name]") - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) - continue - to_chat(ded, "[FOLLOW_LINK(ded, user)] [user] [name]: \"[msg]\" to [trauma]") diff --git a/code/modules/spells/spell_types/pointed.dm b/code/modules/spells/spell_types/pointed.dm deleted file mode 100644 index 6dc0eece9ef96..0000000000000 --- a/code/modules/spells/spell_types/pointed.dm +++ /dev/null @@ -1,75 +0,0 @@ -/obj/effect/proc_holder/spell/pointed - name = "pointed spell" - ranged_mousepointer = 'icons/effects/throw_target.dmi' - /// Message showing to the spell owner upon deactivating pointed spell. - var/deactive_msg = "You dispel the magic..." - /// Message showing to the spell owner upon activating pointed spell. - var/active_msg = "You prepare to use the spell on a target..." - /// Default icon for the pointed spell, used for active/inactive states switching. - base_icon_state = "projectile" - -/obj/effect/proc_holder/spell/pointed/Click() - var/mob/living/user = usr - if(!istype(user)) - return - var/msg - if(!can_cast(user)) - msg = "You can no longer cast [name]!" - remove_ranged_ability(msg) - return - if(active) - msg = "[deactive_msg]" - remove_ranged_ability(msg) - on_deactivation(user) - else - msg = "[active_msg] Left-click to activate spell on a target!" - add_ranged_ability(user, msg, TRUE) - on_activation(user) - -/** - * - * What happens upon pointed spell activation. - * - * user mob The mob interacting owning the spell. - * - **/ -/obj/effect/proc_holder/spell/pointed/proc/on_activation(mob/user) - return - -/** - * - * What happens upon pointed spell deactivation. - * - * user mob The mob interacting owning the spell. - * - **/ -/obj/effect/proc_holder/spell/pointed/proc/on_deactivation(mob/user) - return - -/obj/effect/proc_holder/spell/pointed/update_icon() - if(!action) - return - action.button_icon_state = "[base_icon_state][active]" - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return FALSE - if(!intercept_check(ranged_ability_user, target)) - return FALSE - if(!cast_check(user = ranged_ability_user)) - return FALSE - perform(list(target), user = ranged_ability_user) - remove_ranged_ability() - return TRUE - -/** - * - * Specific spell checks for InterceptClickOn() targets. - * - * user mob The mob using the ranged spell via intercept. - * target atom The atom being targeted by the spell via intercept. - * - **/ -/obj/effect/proc_holder/spell/pointed/proc/intercept_check(mob/user, atom/target) - return TRUE diff --git a/code/modules/spells/spell_types/pointed/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm new file mode 100644 index 0000000000000..549313774a78f --- /dev/null +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -0,0 +1,182 @@ +/** + * ## Pointed spells + * + * These spells override the caster's click, + * allowing them to cast the spell on whatever is clicked on. + * + * To add effects on cast, override "cast(atom/cast_on)". + * The cast_on atom is the person who was clicked on. + */ +/datum/action/spell/pointed + requires_target = TRUE + + /// The base icon state of the spell's button icon, used for editing the icon "on" and "off" + var/base_icon_state + /// Message showing to the spell owner upon activating pointed spell. + var/active_msg + /// Message showing to the spell owner upon deactivating pointed spell. + var/deactive_msg + /// The casting range of our spell + var/cast_range = 7 + /// Variable dictating if the spell will use turf based aim assist + var/aim_assist = TRUE + +/datum/action/spell/pointed/New(Target) + . = ..() + if(!active_msg) + active_msg = "You prepare to use [src] on a target..." + if(!deactive_msg) + deactive_msg = "You dispel [src]." + +/datum/action/spell/pointed/set_click_ability(mob/on_who) + . = ..() + if(!.) + return + + on_activation(on_who) + +// Note: Destroy() calls Remove(), Remove() calls unset_click_ability() if our spell is active. +/datum/action/spell/pointed/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + + on_deactivation(on_who, refund_cooldown = refund_cooldown) + +/datum/action/spell/pointed/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + on_deactivation(owner, refund_cooldown = FALSE) + +/// Called when the spell is activated / the click ability is set to our spell +/datum/action/spell/pointed/proc/on_activation(mob/on_who) + SHOULD_CALL_PARENT(TRUE) + + to_chat(on_who, ("[active_msg] Left-click to cast the spell on a target!")) + if(base_icon_state) + button_icon_state = "[base_icon_state]1" + update_buttons() + return TRUE + +/// Called when the spell is deactivated / the click ability is unset from our spell +/datum/action/spell/pointed/proc/on_deactivation(mob/on_who, refund_cooldown = TRUE) + SHOULD_CALL_PARENT(TRUE) + + if(refund_cooldown) + // Only send the "deactivation" message if they're willingly disabling the ability + to_chat(on_who, ("[deactive_msg]")) + if(base_icon_state) + button_icon_state = "[base_icon_state]0" + update_buttons() + return TRUE + +/datum/action/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/click_target) + + var/atom/aim_assist_target + if(aim_assist && isturf(click_target)) + // Find any human in the list. We aren't picky, it's aim assist after all + aim_assist_target = locate(/mob/living/carbon/human) in click_target + if(!aim_assist_target) + // If we didn't find a human, we settle for any living at all + aim_assist_target = locate(/mob/living) in click_target + + return ..(caller, params, aim_assist_target || click_target) + +/datum/action/spell/pointed/is_valid_spell(mob/user, atom/target) + if(target == owner) + to_chat(owner, ("You cannot cast [src] on yourself!")) + return FALSE + + if(get_dist(owner, target) > cast_range) + to_chat(owner, ("[target.p_theyre(TRUE)] too far away!")) + return FALSE + + return TRUE + +/** + * ### Pointed projectile spells + * + * Pointed spells that, instead of casting a spell directly on the target that's clicked, + * will instead fire a projectile pointed at the target's direction. + */ +/datum/action/spell/pointed/projectile + /// What projectile we create when we shoot our spell. + var/obj/projectile/magic/projectile_type = /obj/projectile/magic/teleport + /// How many projectiles we can fire per cast. Not all at once, per click, kinda like charges + var/projectile_amount = 1 + /// How many projectiles we have yet to fire, based on projectile_amount + var/current_amount = 0 + /// How many projectiles we fire every fire_projectile() call. + /// Unwise to change without overriding or extending ready_projectile. + var/projectiles_per_fire = 1 + +/datum/action/spell/pointed/projectile/New(Target) + . = ..() + if(projectile_amount > 1) + unset_after_click = FALSE + +/datum/action/spell/pointed/projectile/is_valid_spell(mob/user, atom/target) + return TRUE + +/datum/action/spell/pointed/projectile/on_activation(mob/on_who) + . = ..() + if(!.) + return + + current_amount = projectile_amount + +/datum/action/spell/pointed/projectile/on_deactivation(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(projectile_amount > 1 && current_amount) + start_cooldown(cooldown_time * ((projectile_amount - current_amount) / projectile_amount)) + current_amount = 0 + +// cast_on is a turf, or atom target, that we clicked on to fire at. +/datum/action/spell/pointed/projectile/on_cast(mob/user, atom/target) + . = ..() + if(!isturf(owner.loc)) + return FALSE + + var/turf/caster_turf = get_turf(owner) + // Get the tile infront of the caster, based on their direction + var/turf/caster_front_turf = get_step(owner, owner.dir) + + fire_projectile(target) + owner.newtonian_move(get_dir(caster_front_turf, caster_turf)) + if(current_amount <= 0) + unset_click_ability(owner, refund_cooldown = FALSE) + + return TRUE + +/datum/action/spell/pointed/projectile/post_cast(mob/user, atom/target) + . = ..() + if(current_amount > 0) + // We still have projectiles to cast! + // Reset our cooldown and let them fire away + reset_spell_cooldown() + +/datum/action/spell/pointed/projectile/proc/fire_projectile(atom/target) + current_amount-- + for(var/i in 1 to projectiles_per_fire) + var/obj/projectile/to_fire = new projectile_type() + ready_projectile(to_fire, target, owner, i) + to_fire.fire() + return TRUE + +/datum/action/spell/pointed/projectile/proc/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) + to_fire.firer = owner + to_fire.fired_from = get_turf(owner) + to_fire.preparePixelProjectile(target, owner) + RegisterSignal(to_fire, COMSIG_PROJECTILE_ON_HIT, PROC_REF(on_cast_hit)) + + if(istype(to_fire, /obj/projectile/magic)) + var/obj/projectile/magic/magic_to_fire = to_fire + magic_to_fire.antimagic_flags = antimagic_flags + +/// Signal proc for whenever the projectile we fire hits someone. +/// Pretty much relays to the spell when the projectile actually hits something. +/datum/action/spell/pointed/projectile/proc/on_cast_hit(atom/source, mob/firer, atom/hit, angle) + SIGNAL_HANDLER + + SEND_SIGNAL(src, COMSIG_SPELL_PROJECTILE_HIT, hit, firer, source) + diff --git a/code/modules/spells/spell_types/pointed/abyssal_gaze.dm b/code/modules/spells/spell_types/pointed/abyssal_gaze.dm new file mode 100644 index 0000000000000..f1c2a93086166 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/abyssal_gaze.dm @@ -0,0 +1,43 @@ +/datum/action/spell/pointed/abyssal_gaze + name = "Abyssal Gaze" + desc = "This spell instills a deep terror in your target, temporarily chilling and blinding it." + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' + background_icon_state = "bg_demon" + button_icon_state = "bg_demon_border" + + + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "abyssal_gaze" + + school = SCHOOL_EVOCATION + cooldown_time = 75 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY + + cast_range = 5 + active_msg = "You prepare to instill a deep terror in a target..." + + /// The duration of the blind on our target + var/blind_duration = 4 SECONDS + /// The amount of temperature we take from our target + var/amount_to_cool = 200 + +/datum/action/spell/pointed/abyssal_gaze/is_valid_spell(mob/user, atom/target) + return iscarbon(target) + +/datum/action/spell/pointed/abyssal_gaze/on_cast(mob/user, mob/living/carbon/target) + . = ..() + if(target.can_block_magic(antimagic_flags)) + to_chat(owner, ("The spell had no effect!")) + to_chat(target, ("You feel a freezing darkness closing in on you, but it rapidly dissipates.")) + return FALSE + + to_chat(target, ("A freezing darkness surrounds you...")) + target.playsound_local(get_turf(target), 'sound/hallucinations/i_see_you1.ogg', 50, 1) + owner.playsound_local(get_turf(owner), 'sound/effects/ghost2.ogg', 50, 1) + target.set_blindness(blind_duration) + if(ishuman(target)) + var/mob/living/carbon/human/human_cast_on = target + human_cast_on.adjust_coretemperature(-amount_to_cool) + target.adjust_bodytemperature(-amount_to_cool) diff --git a/code/modules/spells/spell_types/pointed/barnyard.dm b/code/modules/spells/spell_types/pointed/barnyard.dm new file mode 100644 index 0000000000000..5c3afbda7ec1f --- /dev/null +++ b/code/modules/spells/spell_types/pointed/barnyard.dm @@ -0,0 +1,57 @@ +/datum/action/spell/pointed/barnyardcurse + name = "Curse of the Barnyard" + desc = "This spell dooms an unlucky soul to possess the speech and facial attributes of a barnyard animal." + button_icon_state = "barn" + ranged_mousepointer = 'icons/effects/mouse_pointers/barn_target.dmi' + + school = SCHOOL_TRANSMUTATION + cooldown_time = 15 SECONDS + cooldown_reduction_per_rank = 3 SECONDS + + invocation = "KN'A FTAGHU, PUCK 'BTHNK!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + active_msg = "You prepare to curse a target..." + deactive_msg = "You dispel the curse." + +/datum/action/spell/pointed/barnyardcurse/is_valid_spell(mob/user, atom/target) + . = ..() + if(!.) + return FALSE + if (target == user) + return FALSE + if(!ishuman(target)) + return FALSE + + var/mob/living/carbon/human/human_target = target + if(!human_target.wear_mask) + return TRUE + + return !(human_target.wear_mask.type in GLOB.cursed_animal_masks) + +/datum/action/spell/pointed/barnyardcurse/on_cast(mob/user, mob/living/carbon/human/target) + . = ..() + if(target.can_block_magic(antimagic_flags)) + target.visible_message( + ("[target]'s face bursts into flames, which instantly burst outward, leaving [target.p_them()] unharmed!"), + ("Your face starts burning up, but the flames are repulsed by your anti-magic protection!"), + ) + to_chat(owner, ("The spell had no effect!")) + return FALSE + + var/chosen_type = pick(GLOB.cursed_animal_masks) + var/obj/item/clothing/mask/cursed_mask = new chosen_type(get_turf(target)) + + target.visible_message( + ("[target]'s face bursts into flames, and a barnyard animal's head takes its place!"), + ("Your face burns up, and shortly after the fire you realise you have the [cursed_mask.name]!"), + ) + + // Can't drop? Nuke it + if(!target.dropItemToGround(target.wear_mask)) + qdel(target.wear_mask) + + target.equip_to_slot_if_possible(cursed_mask, ITEM_SLOT_MASK, TRUE, TRUE) + target.flash_act() + return TRUE diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm new file mode 100644 index 0000000000000..bbc0a34db56db --- /dev/null +++ b/code/modules/spells/spell_types/pointed/blind.dm @@ -0,0 +1,47 @@ +/datum/action/spell/pointed/blind + name = "Blind" + desc = "This spell temporarily blinds a single target." + button_icon_state = "blind" + ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi' + + sound = 'sound/magic/blind.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 6.25 SECONDS + + invocation = "STI KALY" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + active_msg = "You prepare to blind a target..." + + /// The amount of blind to apply + var/eye_blind_amount = 10 + /// The amount of blurriness to apply + var/eye_blurry_amount = 20 + /// The duration of the blind mutation placed on the person + var/blind_mutation_duration = 30 SECONDS + +/datum/action/spell/pointed/blind/is_valid_spell(mob/user, atom/target) + . = ..() + if(!.) + return FALSE + if (target == user) + return FALSE + if(!ishuman(target)) + return FALSE + + var/mob/living/carbon/human/human_target = target + return !human_target.is_blind() + +/datum/action/spell/pointed/blind/on_cast(mob/user, mob/living/carbon/human/target) + . = ..() + if(target.can_block_magic(antimagic_flags)) + to_chat(target, ("Your eye itches, but it passes momentarily.")) + to_chat(owner, ("The spell had no effect!")) + return FALSE + + to_chat(target, ("Your eyes cry out in pain!")) + target.set_blindness(eye_blind_amount) + target.blur_eyes(eye_blurry_amount) + return TRUE diff --git a/code/modules/spells/spell_types/pointed/dominate.dm b/code/modules/spells/spell_types/pointed/dominate.dm new file mode 100644 index 0000000000000..1bc63ddf5900d --- /dev/null +++ b/code/modules/spells/spell_types/pointed/dominate.dm @@ -0,0 +1,49 @@ +/datum/action/spell/pointed/dominate + name = "Dominate" + desc = "This spell dominates the mind of a lesser creature to the will of Nar'Sie, \ + allying it only to her direct followers." + background_icon_state = "bg_demon" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "dominate" + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' + + school = SCHOOL_EVOCATION + cooldown_time = 1 MINUTES + invocation_type = INVOCATION_NONE + spell_requirements = NONE + // An UNHOLY, MAGIC SPELL that INFLUECNES THE MIND - all things work here, logically + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND + + cast_range = 7 + active_msg = "You prepare to dominate the mind of a target..." + +/datum/action/spell/pointed/dominate/is_valid_spell(mob/user, atom/target) + if(!isanimal(target)) + return FALSE + + var/mob/living/simple_animal/animal = target + if(animal.mind) + return FALSE + if(animal.stat == DEAD) + return FALSE + if(animal.sentience_type != SENTIENCE_ORGANIC) + return FALSE + if("cult" in animal.faction) + return FALSE + if(HAS_TRAIT(animal, TRAIT_HOLY)) + return FALSE + + return TRUE + +/datum/action/spell/pointed/dominate/on_cast(mob/user, mob/living/simple_animal/target) + . = ..() + if(target.can_block_magic(antimagic_flags)) + to_chat(target, "Your feel someone attempting to subject your mind to terrible machinations!") + to_chat(owner, "[target] resists your domination!") + return FALSE + + var/turf/cast_turf = get_turf(target) + target.add_atom_colour("#990000", FIXED_COLOUR_PRIORITY) + target.faction |= "cult" + playsound(cast_turf, 'sound/effects/ghost.ogg', 100, TRUE) + new /obj/effect/temp_visual/cult/sac(cast_turf) diff --git a/code/modules/spells/spell_types/pointed/finger_guns.dm b/code/modules/spells/spell_types/pointed/finger_guns.dm new file mode 100644 index 0000000000000..14173c99473c4 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/finger_guns.dm @@ -0,0 +1,47 @@ +/datum/action/spell/pointed/projectile/finger_guns + name = "Finger Guns" + desc = "Shoot up to three mimed bullets from your fingers that damage and mute their targets. \ + Can't be used if you have something in your hands." + background_icon_state = "bg_mime" + icon_icon = 'icons/hud/actions/actions_mime.dmi' + button_icon_state = "finger_guns0" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + + invocation = "" + invocation_type = INVOCATION_EMOTE + invocation_self_message = ("You fire your finger gun!") + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + spell_max_level = 1 + + base_icon_state = "finger_guns" + active_msg = "You draw your fingers!" + deactive_msg = "You put your fingers at ease. Another time." + cast_range = 20 + projectile_type = /obj/projectile/bullet/mime + projectile_amount = 3 + +/datum/action/spell/pointed/projectile/finger_guns/can_invoke(feedback = TRUE) + if(invocation_type == INVOCATION_EMOTE) + if(!ishuman(owner)) + return FALSE + + var/mob/living/carbon/human/human_owner = owner + if(human_owner.incapacitated()) + if(feedback) + to_chat(owner, ("You can't properly point your fingers while incapacitated.")) + return FALSE + if(human_owner.get_active_held_item()) + if(feedback) + to_chat(owner, ("You can't properly fire your finger guns with something in your hand.")) + return FALSE + + return ..() + +/datum/action/spell/pointed/projectile/finger_guns/pre_cast(mob/user, atom/target) + . = ..() + invocation = "[user] fires [user.p_their()] finger gun!" diff --git a/code/modules/spells/spell_types/pointed/fireball.dm b/code/modules/spells/spell_types/pointed/fireball.dm new file mode 100644 index 0000000000000..34fd90660466f --- /dev/null +++ b/code/modules/spells/spell_types/pointed/fireball.dm @@ -0,0 +1,23 @@ +/datum/action/spell/pointed/projectile/fireball + name = "Fireball" + desc = "This spell fires an explosive fireball at a target." + button_icon_state = "fireball0" + + sound = 'sound/magic/fireball.ogg' + school = SCHOOL_EVOCATION + cooldown_time = 6 SECONDS + cooldown_reduction_per_rank = 1 SECONDS // 1 second reduction per rank + + invocation = "ONI SOMA!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + base_icon_state = "fireball" + active_msg = "You prepare to cast your fireball spell!" + deactive_msg = "You extinguish your fireball... for now." + cast_range = 8 + projectile_type = /obj/projectile/magic/fireball + +/datum/action/spell/pointed/projectile/fireball/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + to_fire.range = (6 + 2 * spell_level) diff --git a/code/modules/spells/spell_types/pointed/lightning_bolt.dm b/code/modules/spells/spell_types/pointed/lightning_bolt.dm new file mode 100644 index 0000000000000..b8263b272dcec --- /dev/null +++ b/code/modules/spells/spell_types/pointed/lightning_bolt.dm @@ -0,0 +1,43 @@ +/datum/action/spell/pointed/projectile/lightningbolt + name = "Lightning Bolt" + desc = "Fire a lightning bolt at your foes! It will jump between targets, but can't knock them down." + button_icon_state = "lightning0" + + sound = 'sound/magic/lightningbolt.ogg' + school = SCHOOL_EVOCATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 2 SECONDS + + invocation = "P'WAH, UNLIM'TED P'WAH!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + base_icon_state = "lightning" + active_msg = "You energize your hands with arcane lightning!" + deactive_msg = "You let the energy flow out of your hands back into yourself..." + projectile_type = /obj/projectile/magic/aoe/lightning + + /// The range the bolt itself (different to the range of the projectile) + var/bolt_range = 15 + /// The power of the bolt itself + var/bolt_power = 20000 + /// The flags the bolt itself takes when zapping someone + var/bolt_flags = TESLA_MOB_DAMAGE + +/datum/action/spell/pointed/projectile/lightningbolt/Grant(mob/grant_to) + . = ..() + ADD_TRAIT(owner, TRAIT_TESLA_SHOCKIMMUNE, type) + +/datum/action/spell/pointed/projectile/lightningbolt/Remove(mob/living/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_TESLA_SHOCKIMMUNE, type) + return ..() + +/datum/action/spell/pointed/projectile/lightningbolt/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + if(!istype(to_fire, /obj/projectile/magic/aoe/lightning)) + return + + var/obj/projectile/magic/aoe/lightning/bolt = to_fire + bolt.zap_range = bolt_range + bolt.zap_power = bolt_power + bolt.zap_flags = bolt_flags diff --git a/code/modules/spells/spell_types/pointed/mind_transfer.dm b/code/modules/spells/spell_types/pointed/mind_transfer.dm new file mode 100644 index 0000000000000..4cf0885393c7f --- /dev/null +++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm @@ -0,0 +1,125 @@ +/datum/action/spell/pointed/mind_transfer + name = "Mind Swap" + desc = "This spell allows the user to switch bodies with a target next to him." + button_icon_state = "mindswap" + ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi' + + school = SCHOOL_TRANSMUTATION + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 10 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_MIND|SPELL_CASTABLE_AS_BRAIN + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND + + invocation = "GIN'YU CAPAN" + invocation_type = INVOCATION_WHISPER + + active_msg = "You prepare to swap minds with a target..." + deactive_msg = "You dispel mind swap." + cast_range = 1 + + /// If TRUE, we cannot mindswap into mobs with minds if they do not currently have a key / player. + var/target_requires_key = TRUE + /// For how long is the caster stunned for after the spell + var/unconscious_amount_caster = 40 SECONDS + /// For how long is the victim stunned for after the spell + var/unconscious_amount_victim = 40 SECONDS + /// List of mobs we cannot mindswap into. + var/static/list/mob/living/blacklisted_mobs = typecacheof(list( + /mob/living/brain, + /mob/living/silicon/pai, + /mob/living/simple_animal/hostile/imp/slaughter, + /mob/living/simple_animal/hostile/megafauna, + )) + +/datum/action/spell/pointed/mind_transfer/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(!isliving(owner)) + return FALSE + if(owner.suiciding) + if(feedback) + to_chat(owner, "You're killing yourself! You can't concentrate enough to do this!") + return FALSE + return TRUE + +/datum/action/spell/pointed/mind_transfer/is_valid_spell(mob/user, atom/target) + . = ..() + if(!.) + return FALSE + + if (target == user) + to_chat(user, "You cannot swap mind with yourself!") + return FALSE + if(!isliving(target)) + to_chat(owner, "You can only swap minds with living beings!") + return FALSE + if(is_type_in_typecache(target, blacklisted_mobs)) + to_chat(owner, "This creature is too [pick("powerful", "strange", "arcane", "obscene")] to control!") + return FALSE + if(isholopara(target)) + var/mob/living/simple_animal/hostile/holoparasite/stand = target + if(stand.summoner && stand.summoner == owner) + to_chat(owner, "Swapping minds with your own guardian would just put you back into your own head!") + return FALSE + var/mob/living/living_target = target + if(living_target.stat == DEAD) + to_chat(owner, "You don't particularly want to be dead!") + return FALSE + if(!living_target.mind) + to_chat(owner, "[living_target.p_theyve(TRUE)] doesn't appear to have a mind to swap into!") + return FALSE + if(!living_target.key && target_requires_key) + to_chat(owner, "[living_target.p_theyve(TRUE)] appear[living_target.p_s()] to be catatonic! \ + Not even magic can affect [living_target.p_their()] vacant mind.") + return FALSE + + return TRUE + +/datum/action/spell/pointed/mind_transfer/on_cast(mob/living/user, atom/target) + . = ..() + swap_minds(user, target) + +/datum/action/spell/pointed/mind_transfer/proc/swap_minds(mob/living/caster, mob/living/cast_on) + + var/mob/living/to_swap = cast_on + if(isholopara(cast_on)) + var/mob/living/simple_animal/hostile/holoparasite/stand = cast_on + if(stand.summoner) + to_swap = stand.summoner + var/datum/mind/mind_to_swap = to_swap.mind + if(to_swap.can_block_magic(antimagic_flags) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/wizard) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/cult) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/changeling) \ + || mind_to_swap.has_antag_datum(/datum/antagonist/rev) \ + || mind_to_swap.key?[1] == "@" \ + ) + to_chat(caster, ("[to_swap.p_their(TRUE)] mind is resisting your spell!")) + return FALSE + + // MIND TRANSFER BEGIN + + var/datum/mind/caster_mind = caster.mind + var/datum/mind/to_swap_mind = to_swap.mind + + var/to_swap_key = to_swap.key + + caster_mind.transfer_to(to_swap) + to_swap_mind.transfer_to(caster) + + // Just in case the swappee's key wasn't grabbed by transfer_to... + if(to_swap_key) + caster.key = to_swap_key + // MIND TRANSFER END + + // Now we knock both mobs out for a time. + caster.Unconscious(unconscious_amount_caster) + to_swap.Unconscious(unconscious_amount_victim) + + // Only the caster and victim hear the sounds, + // that way no one knows for sure if the swap happened + SEND_SOUND(caster, sound('sound/magic/mandswap.ogg')) + SEND_SOUND(to_swap, sound('sound/magic/mandswap.ogg')) + + return TRUE diff --git a/code/modules/spells/spell_types/pointed/spell_cards.dm b/code/modules/spells/spell_types/pointed/spell_cards.dm new file mode 100644 index 0000000000000..1367bb54906b1 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/spell_cards.dm @@ -0,0 +1,82 @@ +/datum/action/spell/pointed/projectile/spell_cards + name = "Spell Cards" + desc = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" + button_icon_state = "spellcard0" + click_cd_override = 1 + + school = SCHOOL_EVOCATION + cooldown_time = 5 SECONDS + cooldown_reduction_per_rank = 1 SECONDS + + invocation = "Sigi'lu M'Fan 'Tasia!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + base_icon_state = "spellcard" + cast_range = 40 + projectile_type = /obj/projectile/magic/spellcard + projectile_amount = 5 + projectiles_per_fire = 7 + + /// A weakref to the mob we're currently targeting with the lockon component. + var/datum/weakref/current_target_weakref + /// The turn rate of the spell cards in flight. (They track onto locked on targets) + var/projectile_turnrate = 10 + /// The homing spread of the spell cards in flight. + var/projectile_pixel_homing_spread = 32 + /// The initial spread of the spell cards when fired. + var/projectile_initial_spread_amount = 30 + /// The location spread of the spell cards when fired. + var/projectile_location_spread_amount = 12 + /// A ref to our lockon component, which is created and destroyed on activation and deactivation. + var/datum/component/lockon_aiming/lockon_component + +/datum/action/spell/pointed/projectile/spell_cards/Destroy() + QDEL_NULL(lockon_component) + return ..() + +/datum/action/spell/pointed/projectile/spell_cards/on_activation(mob/on_who) + . = ..() + if(!.) + return + + QDEL_NULL(lockon_component) + lockon_component = owner.AddComponent( \ + /datum/component/lockon_aiming, \ + range = 5, \ + typecache = GLOB.typecache_living, \ + amount = 1, \ + when_locked = CALLBACK(src, PROC_REF(on_lockon_component))) + +/datum/action/spell/pointed/projectile/spell_cards/proc/on_lockon_component(list/locked_weakrefs) + if(!length(locked_weakrefs)) + current_target_weakref = null + return + current_target_weakref = locked_weakrefs[1] + var/atom/real_target = current_target_weakref.resolve() + if(real_target) + owner.face_atom(real_target) + +/datum/action/spell/pointed/projectile/spell_cards/on_deactivation(mob/on_who, refund_cooldown = TRUE) + . = ..() + QDEL_NULL(lockon_component) + +/datum/action/spell/pointed/projectile/spell_cards/ready_projectile(obj/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + if(current_target_weakref) + var/atom/real_target = current_target_weakref?.resolve() + if(real_target && get_dist(real_target, user) < 7) + to_fire.homing_turn_speed = projectile_turnrate + to_fire.homing_inaccuracy_min = projectile_pixel_homing_spread + to_fire.homing_inaccuracy_max = projectile_pixel_homing_spread + to_fire.set_homing_target(real_target) + + var/rand_spr = rand() + var/total_angle = projectile_initial_spread_amount * 2 + var/adjusted_angle = total_angle - ((projectile_initial_spread_amount / projectiles_per_fire) * 0.5) + var/one_fire_angle = adjusted_angle / projectiles_per_fire + var/current_angle = iteration * one_fire_angle * rand_spr - (projectile_initial_spread_amount / 2) + + to_fire.pixel_x = rand(-projectile_location_spread_amount, projectile_location_spread_amount) + to_fire.pixel_y = rand(-projectile_location_spread_amount, projectile_location_spread_amount) + to_fire.preparePixelProjectile(target, user, null, current_angle) diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm deleted file mode 100644 index aaf468899fab7..0000000000000 --- a/code/modules/spells/spell_types/projectile.dm +++ /dev/null @@ -1,130 +0,0 @@ - - -/obj/projectile/magic/spell - name = "custom spell projectile" - var/list/ignored_factions //Do not hit these - var/check_holy = FALSE - var/check_antimagic = FALSE - var/trigger_range = 0 //How far we do we need to be to hit - var/linger = FALSE //Can't hit anything but the intended target - - var/trail = FALSE //if it leaves a trail - var/trail_lifespan = 0 //deciseconds - var/trail_icon = 'icons/obj/wizard.dmi' - var/trail_icon_state = "trail" - -//todo unify this and magic/aoe under common path -/obj/projectile/magic/spell/Range() - if(trigger_range > 1) - for(var/mob/living/L in range(trigger_range, get_turf(src))) - if(can_hit_target(L, ignore_loc = TRUE)) - return Bump(L) - . = ..() - -/obj/projectile/magic/spell/Moved(atom/OldLoc, Dir) - . = ..() - if(trail) - create_trail() - -/obj/projectile/magic/spell/proc/create_trail() - if(!trajectory) - return - var/datum/point/vector/previous = trajectory.return_vector_after_increments(1,-1) - var/obj/effect/overlay/trail = new /obj/effect/overlay(previous.return_turf()) - trail.pixel_x = previous.return_px() - trail.pixel_y = previous.return_py() - trail.icon = trail_icon - trail.icon_state = trail_icon_state - //might be changed to temp overlay - trail.set_density(FALSE) - trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - QDEL_IN(trail, trail_lifespan) - -/obj/projectile/magic/spell/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) - . = ..() - if(linger && target != original) - return FALSE - if(ismob(target) && !direct_target) //Unsure about the direct target, i guess it could always skip these. - var/mob/M = target - if(M.anti_magic_check(check_antimagic, check_holy)) - return FALSE - if(ignored_factions?.len && faction_check(M.faction,ignored_factions)) - return FALSE - - -//NEEDS MAJOR CODE CLEANUP. - -/obj/effect/proc_holder/spell/targeted/projectile - name = "Projectile" - desc = "This spell summons projectiles which try to hit the targets." - - - - var/proj_type = /obj/projectile/magic/spell //IMPORTANT use only subtypes of this - - - var/update_projectile = FALSE //So you want to admin abuse magic bullets ? This is for you - //Below only apply if update_projectile is true - var/proj_icon = 'icons/obj/projectiles.dmi' - var/proj_icon_state = "spell" - var/proj_name = "a spell projectile" - var/proj_trail = FALSE //if it leaves a trail - var/proj_trail_lifespan = 0 //deciseconds - var/proj_trail_icon = 'icons/obj/wizard.dmi' - var/proj_trail_icon_state = "trail" - var/proj_lingering = FALSE //if it lingers or disappears upon hitting an obstacle - var/proj_homing = TRUE //if it follows the target - var/proj_insubstantial = FALSE //if it can pass through dense objects or not - var/proj_trigger_range = 0 //the range from target at which the projectile triggers cast(target) - var/proj_lifespan = 15 //in deciseconds * proj_step_delay - var/proj_step_delay = 1 //lower = faster - var/list/ignore_factions = list() //Faction types that will be ignored - var/check_antimagic = TRUE - var/check_holy = FALSE - -/obj/effect/proc_holder/spell/targeted/projectile/proc/fire_projectile(atom/target, mob/user) - var/obj/projectile/magic/spell/projectile = new proj_type(null, spell_level) - - if(update_projectile) - //Generally these should already be set on the projectile, this is mostly here for varedited spells. - projectile.icon = proj_icon - projectile.icon_state = proj_icon_state - projectile.name = proj_name - if(proj_insubstantial) - projectile.movement_type |= PHASING - if(proj_homing) - projectile.homing = TRUE - projectile.homing_turn_speed = 360 //Perfect tracking - if(proj_lingering) - projectile.linger = TRUE - projectile.trigger_range = proj_trigger_range - projectile.ignored_factions = ignore_factions - projectile.range = proj_lifespan - projectile.speed = proj_step_delay - projectile.trail = proj_trail - projectile.trail_lifespan = proj_trail_lifespan - projectile.trail_icon = proj_trail_icon - projectile.trail_icon_state = proj_trail_icon_state - - projectile.preparePixelProjectile(target,user) - if(projectile.homing) - projectile.set_homing_target(target) - projectile.fire() - -/obj/effect/proc_holder/spell/targeted/projectile/cast(list/targets, mob/user = usr) - playMagSound() - for(var/atom/target in targets) - fire_projectile(target, user) - -//This one just pops one projectile in direction user is facing, irrelevant of max_targets etc -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire - name = "Dumbfire projectile" - -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/choose_targets(mob/user = usr) - var/turf/T = get_turf(user) - for(var/i in 1 to range-1) - var/turf/new_turf = get_step(T, user.dir) - if(new_turf.density) - break - T = new_turf - perform(list(T),user = user) diff --git a/code/modules/spells/spell_types/projectile/_basic_projectile.dm b/code/modules/spells/spell_types/projectile/_basic_projectile.dm new file mode 100644 index 0000000000000..042c622a46ab5 --- /dev/null +++ b/code/modules/spells/spell_types/projectile/_basic_projectile.dm @@ -0,0 +1,29 @@ +/** + * ## Basic Projectile spell + * + * Simply fires specified projectile type the direction the caster is facing. + * + * Behavior could / should probably be unified with pointed projectile spells + * and aoe projectile spells in the future. + */ +/datum/action/spell/basic_projectile + /// How far we try to fire the basic projectile. Blocked by dense objects. + var/projectile_range = 7 + /// The projectile type fired at all people around us + var/obj/projectile/projectile_type = /obj/projectile/magic/aoe/magic_missile + +/datum/action/spell/basic_projectile/on_cast(mob/user, atom/target) + . = ..() + var/turf/target_turf = get_turf(user) + for(var/i in 1 to projectile_range - 1) + var/turf/next_turf = get_step(target_turf, user.dir) + if(next_turf.density) + break + target_turf = next_turf + + fire_projectile(target_turf, user) + +/datum/action/spell/basic_projectile/proc/fire_projectile(atom/target, atom/caster) + var/obj/projectile/to_fire = new projectile_type() + to_fire.preparePixelProjectile(target, caster) + to_fire.fire() diff --git a/code/modules/spells/spell_types/projectile/juggernaut.dm b/code/modules/spells/spell_types/projectile/juggernaut.dm new file mode 100644 index 0000000000000..e3bcb4273c6be --- /dev/null +++ b/code/modules/spells/spell_types/projectile/juggernaut.dm @@ -0,0 +1,12 @@ +/datum/action/spell/basic_projectile/juggernaut + name = "Gauntlet Echo" + desc = "Channels energy into your gauntlet - firing its essence forward in a slow moving, yet devastating, attack." + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "cultfist" + background_icon_state = "bg_demon" + sound = 'sound/weapons/resonator_blast.ogg' + + cooldown_time = 35 SECONDS + spell_requirements = NONE + + projectile_type = /obj/projectile/magic/aoe/juggernaut diff --git a/code/modules/spells/spell_types/rod_form.dm b/code/modules/spells/spell_types/rod_form.dm deleted file mode 100644 index 28be733dafe77..0000000000000 --- a/code/modules/spells/spell_types/rod_form.dm +++ /dev/null @@ -1,56 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/rod_form - name = "Rod Form" - desc = "Take on the form of an immovable rod, destroying all in your path. Purchasing this spell multiple times will also increase the rod's damage and travel range." - clothes_req = TRUE - human_req = FALSE - charge_max = 250 - cooldown_min = 100 - range = -1 - include_user = TRUE - invocation = "CLANG!" - invocation_type = INVOCATION_SHOUT - action_icon_state = "immrod" - -/obj/effect/proc_holder/spell/targeted/rod_form/cast(list/targets,mob/user = usr) - var/area/A = get_area(user) - if(istype(A, /area/wizard_station)) - to_chat(user, "You know better than to trash Wizard Federation property. Best wait until you leave to use [src].") - return - for(var/mob/living/M in targets) - var/turf/start = get_turf(M) - var/obj/effect/immovablerod/wizard/W = new(start, get_ranged_target_turf(start, M.dir, (15 + spell_level * 3))) - W.wizard = M - W.max_distance += spell_level * 3 //You travel farther when you upgrade the spell - W.damage_bonus += spell_level * 20 //You do more damage when you upgrade the spell - W.start_turf = start - M.forceMove(W) - M.notransform = TRUE - M.status_flags |= GODMODE - -//Wizard Version of the Immovable Rod - -/obj/effect/immovablerod/wizard - var/max_distance = 13 - var/damage_bonus = 0 - var/turf/start_turf - notify = FALSE - -/obj/effect/immovablerod/wizard/Moved() - . = ..() - if(get_dist(start_turf, get_turf(src)) >= max_distance && !QDELETED(src)) - qdel(src) - -/obj/effect/immovablerod/wizard/Destroy() - if(wizard) - wizard.status_flags &= ~GODMODE - wizard.notransform = FALSE - wizard.forceMove(get_turf(src)) - return ..() - -/obj/effect/immovablerod/wizard/penetrate(mob/living/L) - if(L.anti_magic_check()) - L.visible_message("[src] hits [L], but it bounces back, then vanishes!" , "[src] hits you... but it bounces back, then vanishes!" , "You hear a weak, sad, CLANG.") - qdel(src) - return - L.visible_message("[L] is penetrated by an immovable rod!" , "The rod penetrates you!" , "You hear a CLANG!") - L.adjustBruteLoss(70 + damage_bonus) diff --git a/code/modules/spells/spell_types/santa.dm b/code/modules/spells/spell_types/santa.dm deleted file mode 100644 index 350206eaf7391..0000000000000 --- a/code/modules/spells/spell_types/santa.dm +++ /dev/null @@ -1,16 +0,0 @@ -//Santa spells! -/obj/effect/proc_holder/spell/aoe_turf/conjure/presents - name = "Conjure Presents!" - desc = "This spell lets you reach into S-space and retrieve presents! Yay!" - school = "santa" - charge_max = 600 - clothes_req = FALSE - antimagic_allowed = TRUE - invocation = "HO HO HO" - invocation_type = INVOCATION_SHOUT - range = 3 - cooldown_min = 50 - - summon_type = list("/obj/item/a_gift") - summon_lifespan = 0 - summon_amt = 5 diff --git a/code/modules/spells/spell_types/self/basic_heal.dm b/code/modules/spells/spell_types/self/basic_heal.dm new file mode 100644 index 0000000000000..ee75b2f7c5983 --- /dev/null +++ b/code/modules/spells/spell_types/self/basic_heal.dm @@ -0,0 +1,27 @@ +// This spell exists mainly for debugging purposes, and also to show how casting works +/datum/action/spell/basic_heal + name = "Lesser Heal" + desc = "Heals a small amount of brute and burn damage to the caster." + + sound = 'sound/magic/staff_healing.ogg' + school = SCHOOL_RESTORATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 1.25 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_HUMAN + + invocation = "Victus sano!" + invocation_type = INVOCATION_WHISPER + + /// Amount of brute to heal to the spell caster on cast + var/brute_to_heal = 10 + /// Amount of burn to heal to the spell caster on cast + var/burn_to_heal = 10 + +/datum/action/spell/basic_heal/on_cast(mob/living/user, atom/target) + . = ..() + user.visible_message( + "A wreath of gentle light passes over [user]!", + "You wreath yourself in healing light!", + ) + user.adjustBruteLoss(-brute_to_heal, FALSE) + user.adjustFireLoss(-burn_to_heal) diff --git a/code/modules/spells/spell_types/self/charge.dm b/code/modules/spells/spell_types/self/charge.dm new file mode 100644 index 0000000000000..ff6e6e0d537f2 --- /dev/null +++ b/code/modules/spells/spell_types/self/charge.dm @@ -0,0 +1,58 @@ +/datum/action/spell/charge + name = "Charge" + desc = "This spell can be used to recharge a variety of things in your hands, \ + from magical artifacts to electrical components. A creative wizard can even use it \ + to grant magical power to a fellow magic user." + button_icon_state = "charge" + + sound = 'sound/magic/charge.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 60 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + invocation = "DIRI CEL" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + +/datum/action/spell/charge/is_valid_spell(mob/user, atom/target) + return isliving(user) + +/datum/action/spell/charge/on_cast(mob/living/user, atom/target) + . = ..() + + // Charge people we're pulling first and foremost + if(isliving(user.pulling)) + var/mob/living/pulled_living = user.pulling + var/pulled_has_spells = FALSE + + for(var/datum/action/spell/spell in pulled_living.actions) + spell.reset_spell_cooldown() + pulled_has_spells = TRUE + + if(pulled_has_spells) + to_chat(pulled_living, ("You feel raw magic flowing through you. It feels good!")) + to_chat(user, "[pulled_living] suddenly feels very warm!") + return + + to_chat(pulled_living, ("You feel very strange for a moment, but then it passes.")) + + // Then charge their main hand item, then charge their offhand item + var/obj/item/to_charge = user.get_active_held_item() || user.get_inactive_held_item() + if(!to_charge) + to_chat(user, ("You feel magical power surging through your hands, but the feeling rapidly fades.")) + return + + var/charge_return = SEND_SIGNAL(to_charge, COMSIG_ITEM_MAGICALLY_CHARGED, src, user) + + if(QDELETED(to_charge)) + to_chat(user, ("[src] seems to react adversely with [to_charge]!")) + return + + if(charge_return & COMPONENT_ITEM_BURNT_OUT) + to_chat(user, ("[to_charge] seems to react negatively to [src], becoming uncomfortably warm!")) + + else if(charge_return & COMPONENT_ITEM_CHARGED) + to_chat(user, ("[to_charge] suddenly feels very warm!")) + + else + to_chat(user, ("[to_charge] doesn't seem to be react to [src].")) diff --git a/code/modules/spells/spell_types/self/disable_tech.dm b/code/modules/spells/spell_types/self/disable_tech.dm new file mode 100644 index 0000000000000..f42c4a8e3ff0b --- /dev/null +++ b/code/modules/spells/spell_types/self/disable_tech.dm @@ -0,0 +1,30 @@ +/datum/action/spell/emp + name = "Emplosion" + desc = "This spell emplodes an area." + button_icon_state = "emp" + sound = 'sound/weapons/zapbang.ogg' + + school = SCHOOL_EVOCATION + + /// The heavy radius of the EMP + var/emp_heavy = 2 + /// The light radius of the EMP + var/emp_light = 3 + +/datum/action/spell/emp/on_cast(mob/user, atom/target) + . = ..() + empulse(get_turf(user), emp_heavy, emp_light) + +/datum/action/spell/emp/disable_tech + name = "Disable Tech" + desc = "This spell disables all weapons, cameras and most other technology in range." + sound = 'sound/magic/disable_tech.ogg' + + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 5 SECONDS + + invocation = "NEC CANTIO" + invocation_type = INVOCATION_SHOUT + + emp_heavy = 6 + emp_light = 10 diff --git a/code/modules/spells/spell_types/self/forcewall.dm b/code/modules/spells/spell_types/self/forcewall.dm new file mode 100644 index 0000000000000..af8dd3dba8d37 --- /dev/null +++ b/code/modules/spells/spell_types/self/forcewall.dm @@ -0,0 +1,65 @@ +/datum/action/spell/forcewall + name = "Forcewall" + desc = "Create a magical barrier that only you can pass through." + button_icon_state = "shield" + + sound = 'sound/magic/forcewall.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + cooldown_reduction_per_rank = 1.25 SECONDS + + invocation = "TARCOL MINTI ZHERI" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + /// The typepath to the wall we create on cast. + var/wall_type = /obj/effect/forcefield/wizard + +/datum/action/spell/forcewall/on_cast(mob/user, atom/target) + . = ..() + new wall_type(get_turf(owner), owner) + + if(owner.dir == SOUTH || owner.dir == NORTH) + new wall_type(get_step(owner, EAST), owner, antimagic_flags) + new wall_type(get_step(owner, WEST), owner, antimagic_flags) + + else + new wall_type(get_step(owner, NORTH), owner, antimagic_flags) + new wall_type(get_step(owner, SOUTH), owner, antimagic_flags) + +/datum/action/spell/forcewall/cult + name = "Shield" + desc = "This spell creates a temporary forcefield to shield yourself and allies from incoming fire." + background_icon_state = "bg_demon" + icon_icon = 'icons/hud/actions/actions_cult.dmi' + button_icon_state = "cultforcewall" + + cooldown_time = 40 SECONDS + invocation_type = INVOCATION_NONE + + wall_type = /obj/effect/forcefield/cult + +/datum/action/spell/forcewall/mime + name = "Invisible Blockade" + desc = "Form an invisible three tile wide blockade." + background_icon_state = "bg_mime" + icon_icon = 'icons/hud/actions/actions_mime.dmi' + button_icon_state = "invisible_blockade" + sound = null + + school = SCHOOL_MIME + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 0 SECONDS + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIME_VOW + antimagic_flags = NONE + + invocation = "" + invocation_type = INVOCATION_EMOTE + invocation_self_message = ("You form a blockade in front of yourself.") + spell_max_level = 1 + + wall_type = /obj/effect/forcefield/mime/advanced + +/datum/action/spell/forcewall/mime/pre_cast(mob/user, atom/target) + . = ..() + invocation = ("[user] looks as if a blockade is in front of [user.p_them()].") diff --git a/code/modules/spells/spell_types/self/lichdom.dm b/code/modules/spells/spell_types/self/lichdom.dm new file mode 100644 index 0000000000000..202d18ec67ad5 --- /dev/null +++ b/code/modules/spells/spell_types/self/lichdom.dm @@ -0,0 +1,83 @@ +/datum/action/spell/lichdom + name = "Bind Soul" + desc = "A spell that binds your soul to an item in your hands. \ + Binding your soul to an item will turn you into an immortal Lich. \ + So long as the item remains intact, you will revive from death, \ + no matter the circumstances." + icon_icon = 'icons/hud/actions/actions_spells.dmi' + button_icon_state = "skeleton" + + school = SCHOOL_NECROMANCY + cooldown_time = 1 SECONDS + + invocation = "NECREM IMORTIUM!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_OFF_CENTCOM|SPELL_REQUIRES_MIND + spell_max_level = 1 + +/datum/action/spell/lichdom/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + + // We call this here so we can get feedback if they try to cast it when they shouldn't. + if(!is_valid_spell(owner, owner)) + if(feedback) + to_chat(owner, ("You don't have a soul to bind!")) + return FALSE + + return TRUE + +/datum/action/spell/lichdom/is_valid_spell(mob/user, atom/target) + return isliving(user) && !HAS_TRAIT(user, TRAIT_NO_SOUL) + +/datum/action/spell/lichdom/on_cast(mob/living/user, atom/target) + var/obj/item/marked_item = user.get_active_held_item() + if(!marked_item || marked_item.item_flags & ABSTRACT) + return + if(HAS_TRAIT(marked_item, TRAIT_NODROP)) + to_chat(user, ("[marked_item] is stuck to your hand - it wouldn't be a wise idea to place your soul into it.")) + return + // I ensouled the nuke disk once. + // But it's a really mean tactic, so we probably should disallow it. + if(SEND_SIGNAL(marked_item, COMSIG_ITEM_IMBUE_SOUL, src, user) & COMPONENT_BLOCK_IMBUE) + to_chat(user, ("[marked_item] is not suitable for emplacement of your fragile soul.")) + return + + . = ..() + playsound(user, 'sound/effects/pope_entry.ogg', 100) + + to_chat(user, ("You begin to focus your very being into [marked_item]...")) + if(!do_after(user, 5 SECONDS, target = marked_item, timed_action_flags = IGNORE_HELD_ITEM)) + to_chat(user, ("Your soul snaps back to your body as you stop ensouling [marked_item]!")) + return + + marked_item.AddComponent(/datum/component/phylactery, user.mind) + + user.set_species(/datum/species/skeleton) + to_chat(user, ("With a hideous feeling of emptiness you watch in horrified fascination \ + as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! \ + As your organs crumble to dust in your fleshless chest you come to terms with your choice. \ + You're a lich!")) + + if(iscarbon(user)) + var/mob/living/carbon/carbon_cast_on = user + var/obj/item/organ/brain/lich_brain = carbon_cast_on.getorganslot(ORGAN_SLOT_BRAIN) + if(lich_brain) // This prevents MMIs being used to stop lich revives + lich_brain.organ_flags &= ~ORGAN_VITAL + lich_brain.decoy_override = TRUE + + if(ishuman(user)) + var/mob/living/carbon/human/human_cast_on = user + human_cast_on.dropItemToGround(human_cast_on.w_uniform) + human_cast_on.dropItemToGround(human_cast_on.wear_suit) + human_cast_on.dropItemToGround(human_cast_on.head) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(human_cast_on), ITEM_SLOT_OCLOTHING) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(human_cast_on), ITEM_SLOT_HEAD) + human_cast_on.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(human_cast_on), ITEM_SLOT_ICLOTHING) + + + // No soul. You just sold it + ADD_TRAIT(user, TRAIT_NO_SOUL, LICH_TRAIT) + // You only get one phylactery. + qdel(src) diff --git a/code/modules/spells/spell_types/self/lightning.dm b/code/modules/spells/spell_types/self/lightning.dm new file mode 100644 index 0000000000000..77cc3503a88d3 --- /dev/null +++ b/code/modules/spells/spell_types/self/lightning.dm @@ -0,0 +1,118 @@ +/datum/action/spell/tesla + name = "Tesla Blast" + desc = "Charge up a tesla arc and release it at random nearby targets! \ + You can move freely while it charges. The arc jumps between targets and can knock them down." + button_icon_state = "lightning" + + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 6.75 SECONDS + + invocation = "UN'LTD P'WAH!" + invocation_type = INVOCATION_SHOUT + school = SCHOOL_EVOCATION + + /// Whether we're currently channelling a tesla blast or not + var/currently_channeling = FALSE + /// How long it takes to channel the zap. + var/channel_time = 10 SECONDS + /// The radius around (either the caster or people shocked) to which the tesla blast can reach + var/shock_radius = 7 + /// The halo that appears around the caster while charging the spell + var/static/mutable_appearance/halo + /// The sound played while charging the spell + /// Quote: "the only way i can think of to stop a sound, thank MSO for the idea." + var/sound/charge_sound + +/datum/action/spell/tesla/Remove(mob/living/remove_from) + reset_tesla(remove_from) + return ..() + +/datum/action/spell/tesla/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(currently_channeling) + if(feedback) + to_chat(owner, ("You're already channeling [src]!")) + return FALSE + + return TRUE + +/datum/action/spell/tesla/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + to_chat(user, ("You start gathering power...")) + charge_sound = new /sound('sound/magic/lightning_chargeup.ogg', channel = 7) + halo ||= mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER) + user.add_overlay(halo) + playsound(get_turf(user), charge_sound, 50, FALSE) + + currently_channeling = TRUE + if(!do_after(user, channel_time, timed_action_flags = (IGNORE_USER_LOC_CHANGE|IGNORE_HELD_ITEM))) + reset_tesla(user) + return . | SPELL_CANCEL_CAST + +/datum/action/spell/tesla/reset_spell_cooldown() + reset_tesla(owner) + return ..() + +/// Resets the tesla effect. +/datum/action/spell/tesla/proc/reset_tesla(atom/to_reset) + to_reset.cut_overlay(halo) + currently_channeling = FALSE + +/datum/action/spell/tesla/on_cast(mob/user, atom/target) + . = ..() + + // byond, why you suck? + charge_sound = sound(null, repeat = 0, wait = 1, channel = charge_sound.channel) + // Sorry MrPerson, but the other ways just didn't do it the way i needed to work, this is the only way. + playsound(get_turf(user), charge_sound, 50, FALSE) + + var/mob/living/carbon/to_zap_first = get_target(user) + if(QDELETED(to_zap_first)) + user.balloon_alert(user, "no targets nearby!") + reset_spell_cooldown() + return FALSE + + playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, TRUE) + zap_target(user, to_zap_first) + reset_tesla(user) + return TRUE + +/// Zaps a target, the bolt originating from origin. +/datum/action/spell/tesla/proc/zap_target(atom/origin, mob/living/carbon/to_zap, bolt_energy = 30, bounces = 5) + origin.Beam(to_zap, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS) + playsound(get_turf(to_zap), 'sound/magic/lightningshock.ogg', 50, TRUE, -1) + + if(to_zap.can_block_magic(antimagic_flags)) + to_zap.visible_message( + ("[to_zap] absorbs the spell, remaining unharmed!"), + ("You absorb the spell, remaining unharmed!"), + ) + + else + to_zap.electrocute_act(bolt_energy, "Lightning Bolt", flags = SHOCK_NOGLOVES) + + if(bounces >= 1) + var/mob/living/carbon/to_zap_next = get_target(to_zap) + if(!QDELETED(to_zap_next)) + zap_target(to_zap, to_zap_next, max((bolt_energy - 5), 5), bounces - 1) + +/// Get a target in view of us to zap next. Returns a carbon, or null if none were found. +/datum/action/spell/tesla/proc/get_target(atom/center) + var/list/possibles = list() + for(var/mob/living/carbon/to_check in view(shock_radius, center)) + if(to_check == center || to_check == owner) + continue + if(!length(get_path_to(center, to_check, max_distance = shock_radius, simulated_only = FALSE))) + continue + + possibles += to_check + + if(!length(possibles)) + return null + + return pick(possibles) diff --git a/code/modules/spells/spell_types/self/mime_vow.dm b/code/modules/spells/spell_types/self/mime_vow.dm new file mode 100644 index 0000000000000..2512a9810ecaa --- /dev/null +++ b/code/modules/spells/spell_types/self/mime_vow.dm @@ -0,0 +1,23 @@ +/datum/action/spell/vow_of_silence + name = "Speech" + desc = "Make (or break) a vow of silence." + background_icon_state = "bg_mime" + icon_icon = 'icons/hud/actions/actions_mime.dmi' + button_icon_state = "mime_speech" + + school = SCHOOL_MIME + cooldown_time = 5 MINUTES + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIND + spell_max_level = 1 + +/datum/action/spell/vow_of_silence/on_cast(mob/user, atom/target) + . = ..() + user.mind.miming = !user.mind.miming + if(user.mind.miming) + to_chat(user, ("You make a vow of silence.")) + SEND_SIGNAL(user, COMSIG_CLEAR_MOOD_EVENT, "vow") + else + to_chat(user, ("You break your vow of silence.")) + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "vow", /datum/mood_event/broken_vow) + user.update_action_buttons_icon() diff --git a/code/modules/spells/spell_types/self/mutate.dm b/code/modules/spells/spell_types/self/mutate.dm new file mode 100644 index 0000000000000..a3029bef0de78 --- /dev/null +++ b/code/modules/spells/spell_types/self/mutate.dm @@ -0,0 +1,49 @@ +/// A spell type that adds mutations to the caster temporarily. +/datum/action/spell/apply_mutations + button_icon_state = "mutate" + sound = 'sound/magic/mutate.ogg' + + school = SCHOOL_TRANSMUTATION + + /// A list of all mutations we add on cast + var/list/mutations_to_add = list() + /// The duration the mutations will last afetr cast (keep this above the minimum cooldown) + var/mutation_duration = 10 SECONDS + +/datum/action/spell/apply_mutations/New(Target) + . = ..() + spell_requirements |= SPELL_REQUIRES_HUMAN // The spell involves mutations, so it always require human / dna + +/datum/action/spell/apply_mutations/Remove(mob/living/remove_from) + remove_mutations(remove_from) + return ..() + +/datum/action/spell/apply_mutations/is_valid_spell(mob/user, atom/target) + var/mob/living/carbon/human/human_caster = user // Requires human anyways + return !!human_caster.dna + +/datum/action/spell/apply_mutations/on_cast(mob/living/carbon/human/user, atom/target) + . = ..() + for(var/mutation in mutations_to_add) + user.dna.add_mutation(mutation) + addtimer(CALLBACK(src, PROC_REF(remove_mutations), user), mutation_duration, TIMER_DELETE_ME) + +/// Removes the mutations we added from casting our spell +/datum/action/spell/apply_mutations/proc/remove_mutations(mob/living/carbon/human/cast_on) + if(QDELETED(cast_on) || !is_valid_spell(cast_on)) + return + + for(var/mutation in mutations_to_add) + cast_on.dna.remove_mutation(mutation) + +/datum/action/spell/apply_mutations/mutate + name = "Mutate" + desc = "This spell causes you to turn into a hulk for a short while." + cooldown_time = 40 SECONDS + cooldown_reduction_per_rank = 2.5 SECONDS + + invocation = "BIRUZ BENNAR" + invocation_type = INVOCATION_SHOUT + + mutations_to_add = list(/datum/mutation/hulk) + mutation_duration = 30 SECONDS diff --git a/code/modules/spells/spell_types/self/night_vision.dm b/code/modules/spells/spell_types/self/night_vision.dm new file mode 100644 index 0000000000000..ccc86ed272353 --- /dev/null +++ b/code/modules/spells/spell_types/self/night_vision.dm @@ -0,0 +1,39 @@ +//Toggle Night Vision +/datum/action/spell/night_vision + name = "Toggle Nightvision" + desc = "Toggle your nightvision mode." + + cooldown_time = 1 SECONDS + spell_requirements = NONE + + /// The span the "toggle" message uses when sent to the user + var/toggle_span = "notice" + +/datum/action/spell/night_vision/New(Target) + . = ..() + name = "[name] \[ON\]" + +/datum/action/spell/night_vision/is_valid_spell(mob/user, atom/target) + return isliving(user) + +/datum/action/spell/night_vision/on_cast(mob/living/user, atom/target) + . = ..() + to_chat(user, "You toggle your night vision.") + + var/next_mode_text = "" + switch(user.lighting_alpha) + if (LIGHTING_PLANE_ALPHA_VISIBLE) + user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + next_mode_text = "More" + if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) + user.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + next_mode_text = "Full" + if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) + user.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + next_mode_text = "OFF" + else + user.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE + next_mode_text = "ON" + + user.update_sight() + name = "[initial(name)] \[[next_mode_text]\]" diff --git a/code/modules/spells/spell_types/self/personality_commune.dm b/code/modules/spells/spell_types/self/personality_commune.dm new file mode 100644 index 0000000000000..eb1030d62e3d4 --- /dev/null +++ b/code/modules/spells/spell_types/self/personality_commune.dm @@ -0,0 +1,52 @@ +// This can probably be changed to use mind linker at some point +/datum/action/spell/personality_commune + name = "Personality Commune" + desc = "Sends thoughts to your alternate consciousness." + button_icon_state = "telepathy" + cooldown_time = 0 SECONDS + spell_requirements = NONE + + /// Fluff text shown when a message is sent to the pair + var/fluff_text = ("You hear an echoing voice in the back of your head...") + /// The message to send to the corresponding person on cast + var/to_send + +/datum/action/spell/personality_commune/New(master) + . = ..() + if(!istype(master, /datum/brain_trauma/severe/split_personality)) + stack_trace("[type] was created on a target that isn't a /datum/brain_trauma/severe/split_personality, this doesn't work.") + qdel(src) + +/datum/action/spell/personality_commune/is_valid_spell(mob/user, atom/target) + return isliving(user) + +/datum/action/spell/personality_commune/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/datum/brain_trauma/severe/split_personality/trauma = master + if(!istype(trauma)) // hypothetically impossible but you never know + return . | SPELL_CANCEL_CAST + + to_send = tgui_input_text(user, "What would you like to tell your other self?", "Commune") + if(QDELETED(src) || QDELETED(trauma)|| QDELETED(user) || QDELETED(trauma.owner) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!to_send) + reset_cooldown() + return . | SPELL_CANCEL_CAST + +// Pillaged and adapted from telepathy code +/datum/action/spell/personality_commune/on_cast(mob/living/user, atom/target) + . = ..() + var/datum/brain_trauma/severe/split_personality/trauma = master + + var/user_message = "You concentrate and send thoughts to your other self:" + var/user_message_body = "[to_send]" + to_chat(user, "[user_message] [user_message_body]") + to_chat(trauma.owner, "[fluff_text] [user_message_body]") + log_directed_talk(user, trauma.owner, to_send, LOG_SAY, "[name]") + for(var/dead_mob in GLOB.dead_mob_list) + if(!isobserver(dead_mob)) + continue + to_chat(dead_mob, "[FOLLOW_LINK(dead_mob, user)] ["[user] [name]:"] ["\"[to_send]\" to"] ["[trauma]"]") diff --git a/code/modules/spells/spell_types/self/rod_form.dm b/code/modules/spells/spell_types/self/rod_form.dm new file mode 100644 index 0000000000000..9241f56f3d8bb --- /dev/null +++ b/code/modules/spells/spell_types/self/rod_form.dm @@ -0,0 +1,135 @@ +/// The base distance a wizard rod will go without upgrades. +#define BASE_WIZ_ROD_RANGE 13 + +/datum/action/spell/rod_form + name = "Rod Form" + desc = "Take on the form of an immovable rod, destroying all in your path. \ + Purchasing this spell multiple times will also increase the rod's damage and travel range." + button_icon_state = "immrod" + + school = SCHOOL_TRANSMUTATION + cooldown_time = 25 SECONDS + cooldown_reduction_per_rank = 3.75 SECONDS + + invocation = "CLANG!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_OFF_CENTCOM + + /// The extra distance we travel per additional spell level. + var/distance_per_spell_rank = 3 + /// The extra damage we deal per additional spell level. + var/damage_per_spell_rank = 20 + /// The max distance the rod goes on cast + var/rod_max_distance = BASE_WIZ_ROD_RANGE + /// The damage bonus applied to the rod on cast + var/rod_damage_bonus = 0 + +/datum/action/spell/rod_form/on_cast(mob/user, atom/target) + . = ..() + // The destination turf of the rod - just a bit over the max range we calculated, for safety + var/turf/distant_turf = get_ranged_target_turf(get_turf(user), user.dir, (rod_max_distance + 2)) + + new /obj/effect/immovablerod/wizard( + get_turf(user), + distant_turf, + null, + FALSE, + user, + rod_max_distance, + rod_damage_bonus, + ) + +/datum/action/spell/rod_form/level_spell(bypass_cap = FALSE) + . = ..() + if(!.) + return FALSE + + rod_max_distance += distance_per_spell_rank + rod_damage_bonus += damage_per_spell_rank + return TRUE + +/datum/action/spell/rod_form/delevel_spell() + . = ..() + if(!.) + return FALSE + + rod_max_distance -= distance_per_spell_rank + rod_damage_bonus -= damage_per_spell_rank + return TRUE + +/// Wizard Version of the Immovable Rod. +/obj/effect/immovablerod/wizard + notify = FALSE + /// The wizard who's piloting our rod. + var/datum/weakref/our_wizard + /// The distance the rod will go. + var/max_distance = BASE_WIZ_ROD_RANGE + /// The damage bonus of the rod when it smacks people. + var/damage_bonus = 0 + /// The turf the rod started from, to calcuate distance. + var/turf/start_turf +/obj/effect/immovablerod/wizard/Initialize(mapload, atom/target_atom, atom/specific_target, force_looping = FALSE, mob/living/wizard, max_distance = BASE_WIZ_ROD_RANGE, damage_bonus = 0) + . = ..() + if(wizard) + set_wizard(wizard) + start_turf = get_turf(src) + src.max_distance = max_distance + src.damage_bonus = damage_bonus +/obj/effect/immovablerod/wizard/Destroy(force) + start_turf = null + return ..() +/obj/effect/immovablerod/wizard/Move() + if(get_dist(start_turf, get_turf(src)) >= max_distance) + stop_travel() + return + return ..() +/obj/effect/immovablerod/wizard/penetrate(mob/living/penetrated) + if(penetrated.can_block_magic()) + penetrated.visible_message( + ("[src] hits [penetrated], but it bounces back, then vanishes!"), + ("[src] hits you... but it bounces back, then vanishes!"), + ("You hear a weak, sad, CLANG.") + ) + stop_travel() + return + penetrated.visible_message( + ("[penetrated] is penetrated by an immovable rod!"), + ("The [src] penetrates you!"), + ("You hear a CLANG!"), + ) + penetrated.adjustBruteLoss(70 + damage_bonus) + +/** + * Called when the wizard rod reaches it's maximum distance + * or is otherwise stopped by something. + * Dumps out the wizard, and deletes. + */ +/obj/effect/immovablerod/wizard/proc/stop_travel() + eject_wizard() + qdel(src) +/** + * Set wizard as our_wizard, placing them in the rod + * and preparing them for travel. + */ +/obj/effect/immovablerod/wizard/proc/set_wizard(mob/living/wizard) + our_wizard = WEAKREF(wizard) + wizard.forceMove(src) + wizard.notransform = TRUE + wizard.status_flags |= GODMODE + ADD_TRAIT(wizard, TRAIT_MAGICALLY_PHASED, REF(src)) + +/** + * Eject our current wizard, removing them from the rod + * and fixing all of the variables we changed. + */ +/obj/effect/immovablerod/wizard/proc/eject_wizard() + var/mob/living/wizard = our_wizard?.resolve() + if(QDELETED(wizard)) + return + wizard.status_flags &= ~GODMODE + wizard.notransform = FALSE + wizard.forceMove(get_turf(src)) + our_wizard = null + REMOVE_TRAIT(wizard, TRAIT_MAGICALLY_PHASED, REF(src)) + +#undef BASE_WIZ_ROD_RANGE diff --git a/code/modules/spells/spell_types/self/smoke.dm b/code/modules/spells/spell_types/self/smoke.dm new file mode 100644 index 0000000000000..1c8aad877bfc0 --- /dev/null +++ b/code/modules/spells/spell_types/self/smoke.dm @@ -0,0 +1,37 @@ +/// Basic smoke spell. +/datum/action/spell/smoke + name = "Smoke" + desc = "This spell spawns a cloud of smoke at your location. \ + People within will begin to choke and drop their items." + button_icon_state = "smoke" + + school = SCHOOL_CONJURATION + cooldown_time = 12 SECONDS + cooldown_reduction_per_rank = 2.5 SECONDS + + invocation_type = INVOCATION_NONE + + smoke_type = /obj/effect/particle_effect/smoke + smoke_amt = 4 + +/// Chaplain smoke. +/datum/action/spell/smoke/lesser + name = "Holy Smoke" + desc = "This spell spawns a small cloud of smoke at your location." + + school = SCHOOL_HOLY + cooldown_time = 36 SECONDS + spell_requirements = NONE + + smoke_type = /obj/effect/particle_effect/smoke/bad + smoke_amt = 2 + +/// Unused smoke that makes people sleep. Used to be for cult? +/datum/action/spell/smoke/disable + name = "Paralysing Smoke" + desc = "This spell spawns a cloud of paralysing smoke." + background_icon_state = "bg_cult" + + cooldown_time = 20 SECONDS + + smoke_type = /obj/effect/particle_effect/smoke/sleeping diff --git a/code/modules/spells/spell_types/self/soultap.dm b/code/modules/spells/spell_types/self/soultap.dm new file mode 100644 index 0000000000000..dc21db42b1334 --- /dev/null +++ b/code/modules/spells/spell_types/self/soultap.dm @@ -0,0 +1,63 @@ + +/** + * SOUL TAP! + * + * Trades 20 max health for a refresh on all of your spells. + * I was considering making it depend on the cooldowns of your spells, but I want to support "Big spell wizard" with this loadout. + * The two spells that sound most problematic with this is mindswap and lichdom, + * but soul tap requires clothes for mindswap and lichdom takes your soul. + */ +/datum/action/spell/tap + name = "Soul Tap" + desc = "Fuel your spells using your own soul!" + button_icon_state = "soultap" + + // I could see why this wouldn't be necromancy, but messing with souls or whatever. Ectomancy? + school = SCHOOL_NECROMANCY + cooldown_time = 1 SECONDS + invocation = "AT ANY COST!" + invocation_type = INVOCATION_SHOUT + spell_max_level = 1 + + /// The amount of health we take on tap + var/tap_health_taken = 20 + +/datum/action/spell/tap/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + + // We call this here so we can get feedback if they try to cast it when they shouldn't. + if(!is_valid_spell(owner)) + if(feedback) + to_chat(owner, ("You have no soul to tap into!")) + return FALSE + + return TRUE + +/datum/action/spell/tap/is_valid_spell(mob/user, atom/target) + return isliving(user) && !HAS_TRAIT(owner, TRAIT_NO_SOUL) + +/datum/action/spell/tap/on_cast(mob/living/user, atom/target) + . = ..() + user.maxHealth -= tap_health_taken + user.health = min(user.health, user.maxHealth) + + for(var/datum/action/spell/spell in user.actions) + spell.reset_spell_cooldown() + + // If the tap took all of our life, we die and lose our soul! + if(user.maxHealth <= 0) + to_chat(user, ("Your weakened soul is completely consumed by the tap!")) + ADD_TRAIT(user, TRAIT_NO_SOUL, MAGIC_TRAIT) + + user.visible_message(("[user] suddenly dies!"), ignored_mobs = user) + user.death() + + // If the next tap will kill us, give us a heads-up + else if(user.maxHealth - tap_health_taken <= 0) + to_chat(user, ("Your body feels incredibly drained, and the burning is hard to ignore!")) + + // Otherwise just give them some feedback + else + to_chat(user, ("Your body feels drained and there is a burning pain in your chest.")) diff --git a/code/modules/spells/spell_types/self/spacetime_distortion.dm b/code/modules/spells/spell_types/self/spacetime_distortion.dm new file mode 100644 index 0000000000000..e6c78f62e1845 --- /dev/null +++ b/code/modules/spells/spell_types/self/spacetime_distortion.dm @@ -0,0 +1,160 @@ +// This could probably be an aoe spell but it's a little cursed, so I'm not touching it +/datum/action/spell/spacetime_dist + name = "Spacetime Distortion" + desc = "Entangle the strings of space-time in an area around you, \ + randomizing the layout and making proper movement impossible. The strings vibrate..." + sound = 'sound/effects/magic.ogg' + button_icon_state = "spacetime" + + school = SCHOOL_EVOCATION + cooldown_time = 30 SECONDS + spell_requirements = SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_OFF_CENTCOM + spell_max_level = 1 + + /// Weather we're ready to cast again yet or not + var/ready = TRUE + /// The radius of the scramble around the caster + var/scramble_radius = 7 + /// The duration of the scramble + var/duration = 15 SECONDS + /// A lazylist of all scramble effects this spell has created. + var/list/effects + +/datum/action/spell/spacetime_dist/Destroy() + QDEL_LAZYLIST(effects) + return ..() + +/datum/action/spell/spacetime_dist/can_cast_spell(feedback = TRUE) + return ..() && ready + +/datum/action/spell/spacetime_dist/on_cast(mob/user, atom/target) + . = ..() + var/list/turf/to_switcharoo = get_targets_to_scramble(user) + if(!length(to_switcharoo)) + to_chat(user, "For whatever reason, the strings nearby aren't keen on being tangled.") + reset_spell_cooldown() + return + + ready = FALSE + + for(var/turf/swap_a as anything in to_switcharoo) + var/turf/swap_b = to_switcharoo[swap_a] + var/obj/effect/cross_action/spacetime_dist/effect_a = new /obj/effect/cross_action/spacetime_dist(swap_a, antimagic_flags) + var/obj/effect/cross_action/spacetime_dist/effect_b = new /obj/effect/cross_action/spacetime_dist(swap_b, antimagic_flags) + effect_a.linked_dist = effect_b + effect_a.add_overlay(swap_b.photograph()) + effect_b.linked_dist = effect_a + effect_b.add_overlay(swap_a.photograph()) + effect_b.set_light(4, 30, "#c9fff5") + LAZYADD(effects, effect_a) + LAZYADD(effects, effect_b) + +/datum/action/spell/spacetime_dist/post_cast(mob/user, atom/target) + . = ..() + addtimer(CALLBACK(src, PROC_REF(clean_turfs)), duration) + +/// Callback which cleans up our effects list after the duration expires. +/datum/action/spell/spacetime_dist/proc/clean_turfs() + QDEL_LAZYLIST(effects) + ready = TRUE + +/** + * Gets a list of turfs around the center atom to scramble. + * + * Returns an assoc list of [turf] to [turf]. These pairs are what turfs are + * swapped between one another when the cast is done. + */ +/datum/action/spell/spacetime_dist/proc/get_targets_to_scramble(atom/center) + // Get turfs around the center + var/list/turfs = spiral_range_turfs(scramble_radius, center) + if(!length(turfs)) + return + + var/list/turf_steps = list() + + // Go through the turfs we got and pair them up + // This is where we determine what to swap where + var/num_to_scramble = round(length(turfs) * 0.5) + for(var/i in 1 to num_to_scramble) + turf_steps[pick_n_take(turfs)] = pick_n_take(turfs) + + // If there's any turfs unlinked with a friend, + // just randomly swap it with any turf in the area + if(length(turfs)) + var/turf/loner = pick(turfs) + var/area/caster_area = get_area(center) + turf_steps[loner] = get_turf(pick(caster_area.contents)) + + return turf_steps + + +/obj/effect/cross_action + name = "cross me" + desc = "for crossing" + anchored = TRUE + +/obj/effect/cross_action/spacetime_dist + name = "spacetime distortion" + desc = "A distortion in spacetime. You can hear faint music..." + icon_state = "" + /// A flags which save people from being thrown about + var/antimagic_flags = MAGIC_RESISTANCE + var/obj/effect/cross_action/spacetime_dist/linked_dist + var/busy = FALSE + var/sound + var/walks_left = 50 //prevents the game from hanging in extreme cases (such as minigun fire) + +/obj/effect/cross_action/singularity_act() + return + +/obj/effect/cross_action/singularity_pull() + return + +/obj/effect/cross_action/spacetime_dist/Initialize(mapload, flags = MAGIC_RESISTANCE) + . = ..() + setDir(pick(GLOB.cardinals)) + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + antimagic_flags = flags + +/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM) + if(ismob(AM)) + var/mob/M = AM + if(M.can_block_magic(antimagic_flags)) + return + if(linked_dist && walks_left > 0) + flick("purplesparkles", src) + linked_dist.get_walker(AM) + walks_left-- + +/obj/effect/cross_action/spacetime_dist/proc/get_walker(atom/movable/AM) + busy = TRUE + flick("purplesparkles", src) + AM.forceMove(get_turf(src)) + playsound(get_turf(src),sound,70,FALSE) + busy = FALSE + +/obj/effect/cross_action/spacetime_dist/proc/on_entered(datum/source, atom/movable/AM) + SIGNAL_HANDLER + if(!busy) + walk_link(AM) + +/obj/effect/cross_action/spacetime_dist/attackby(obj/item/W, mob/user, params) + if(user.temporarilyRemoveItemFromInventory(W)) + walk_link(W) + else + walk_link(user) + +//ATTACK HAND IGNORING PARENT RETURN VALUE +/obj/effect/cross_action/spacetime_dist/attack_hand(mob/user, list/modifiers) + walk_link(user) + +/obj/effect/cross_action/spacetime_dist/attack_paw(mob/user, list/modifiers) + walk_link(user) + +/obj/effect/cross_action/spacetime_dist/Destroy() + busy = TRUE + linked_dist = null + return ..() diff --git a/code/modules/spells/spell_types/self/stop_time.dm b/code/modules/spells/spell_types/self/stop_time.dm new file mode 100644 index 0000000000000..36692d7d93724 --- /dev/null +++ b/code/modules/spells/spell_types/self/stop_time.dm @@ -0,0 +1,30 @@ +/datum/action/spell/timestop + name = "Stop Time" + desc = "This spell stops time for everyone except for you, \ + allowing you to move freely while your enemies and even projectiles are frozen." + button_icon_state = "time" + + school = SCHOOL_FORBIDDEN // Fucking with time is not appreciated by anyone + cooldown_time = 50 SECONDS + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "TOKI YO TOMARE!" + invocation_type = INVOCATION_SHOUT + + /// The radius / range of the time stop. + var/timestop_range = 2 + /// The duration of the time stop. + var/timestop_duration = 10 SECONDS + +/datum/action/spell/timestop/Grant(mob/grant_to) + . = ..() + if(owner) + ADD_TRAIT(owner, TRAIT_TIME_STOP_IMMUNE, REF(src)) + +/datum/action/spell/timestop/Remove(mob/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_TIME_STOP_IMMUNE, REF(src)) + return ..() + +/datum/action/spell/timestop/on_cast(mob/user, atom/target) + . = ..() + new /obj/effect/timestop(get_turf(user), timestop_range, timestop_duration, list(user)) diff --git a/code/modules/spells/spell_types/self/summon_item.dm b/code/modules/spells/spell_types/self/summon_item.dm new file mode 100644 index 0000000000000..3ce58048d0234 --- /dev/null +++ b/code/modules/spells/spell_types/self/summon_item.dm @@ -0,0 +1,154 @@ +/datum/action/spell/summonitem + name = "Instant Summons" + desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe." + button_icon_state = "summons" + + school = SCHOOL_TRANSMUTATION + cooldown_time = 10 SECONDS + + invocation = "GAR YOK" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + spell_max_level = 1 //cannot be improved + + ///The obj marked for recall + var/obj/marked_item + +/datum/action/spell/summonitem/is_valid_spell(mob/user, atom/target) + return isliving(user) + +/// Set the passed object as our marked item +/datum/action/spell/summonitem/proc/mark_item(obj/to_mark) + name = "Recall [to_mark]" + marked_item = to_mark + RegisterSignal(marked_item, COMSIG_PARENT_QDELETING, PROC_REF(on_marked_item_deleted)) + +/// Unset our current marked item +/datum/action/spell/summonitem/proc/unmark_item() + name = initial(name) + UnregisterSignal(marked_item, COMSIG_PARENT_QDELETING) + marked_item = null + +/// Signal proc for COMSIG_PARENT_QDELETING on our marked item, unmarks our item if it's deleted +/datum/action/spell/summonitem/proc/on_marked_item_deleted(datum/source) + SIGNAL_HANDLER + + if(owner) + to_chat(owner, ("You sense your marked item has been destroyed!")) + unmark_item() + +/datum/action/spell/summonitem/on_cast(mob/living/user, atom/target) + . = ..() + if(QDELETED(marked_item)) + try_link_item(user) + return + + if(marked_item == user.get_active_held_item()) + try_unlink_item(user) + return + + try_recall_item(user) + +/// If we don't have a marked item, attempts to mark the caster's held item. +/datum/action/spell/summonitem/proc/try_link_item(mob/living/caster) + var/obj/item/potential_mark = caster.get_active_held_item() + if(!potential_mark) + if(caster.get_inactive_held_item()) + to_chat(caster, ("You must hold the desired item in your hands to mark it for recall!")) + else + to_chat(caster, ("You aren't holding anything that can be marked for recall!")) + return FALSE + + var/link_message = "" + if(potential_mark.item_flags & ABSTRACT) + return FALSE + if(SEND_SIGNAL(potential_mark, COMSIG_ITEM_MARK_RETRIEVAL, src, caster) & COMPONENT_BLOCK_MARK_RETRIEVAL) + return FALSE + if(HAS_TRAIT(potential_mark, TRAIT_NODROP)) + link_message += "Though it feels redundant... " + + link_message += "You mark [potential_mark] for recall." + to_chat(caster, "[link_message]") + mark_item(potential_mark) + return TRUE + +/// If we have a marked item and it's in our hand, we will try to unlink it +/datum/action/spell/summonitem/proc/try_unlink_item(mob/living/caster) + to_chat(caster, ("You begin removing the mark on [marked_item]...")) + if(!do_after(caster, 5 SECONDS, marked_item)) + to_chat(caster, ("You decide to keep [marked_item] marked.")) + return FALSE + + to_chat(caster, ("You remove the mark on [marked_item] to use elsewhere.")) + unmark_item() + return TRUE + +/// Recalls our marked item to the caster. May bring some unexpected things along. +/datum/action/spell/summonitem/proc/try_recall_item(mob/living/caster) + var/obj/item_to_retrieve = marked_item + + if(item_to_retrieve.loc) + // I don't want to know how someone could put something + // inside itself but these are wizards so let's be safe + var/infinite_recursion = 0 + + // if it's in something, you get the whole thing. + while(!isturf(item_to_retrieve.loc) && infinite_recursion < 10) + if(isitem(item_to_retrieve.loc)) + var/obj/item/mark_loc = item_to_retrieve.loc + // Being able to summon abstract things because + // your item happened to get placed there is a no-no + if(mark_loc.item_flags & ABSTRACT) + break + + // If its on someone, properly drop it + if(ismob(item_to_retrieve.loc)) + var/mob/holding_mark = item_to_retrieve.loc + + // Items in silicons warp the whole silicon + if(issilicon(holding_mark)) + holding_mark.loc.visible_message(("[holding_mark] suddenly disappears!")) + holding_mark.forceMove(caster.loc) + holding_mark.loc.visible_message(("[holding_mark] suddenly appears!")) + item_to_retrieve = null + break + + holding_mark.dropItemToGround(item_to_retrieve) + + else if(isobj(item_to_retrieve.loc)) + var/obj/retrieved_item = item_to_retrieve.loc + // Can't bring anchored things + if(retrieved_item.anchored) + return + // Edge cases for moving certain machinery... + if(istype(retrieved_item, /obj/machinery/portable_atmospherics)) + var/obj/machinery/portable_atmospherics/atmos_item = retrieved_item + atmos_item.disconnect() + atmos_item.update_appearance() + + // Otherwise bring the whole thing with us + item_to_retrieve = retrieved_item + + infinite_recursion += 1 + + else + // Organs are usually stored in nullspace + if(isorgan(item_to_retrieve)) + var/obj/item/organ/organ = item_to_retrieve + if(organ.owner) + // If this code ever runs I will be happy + log_combat(caster, organ.owner, "magically removed [organ.name] from") + organ.Remove(organ.owner) + + if(!item_to_retrieve) + return + + item_to_retrieve.loc?.visible_message(("[item_to_retrieve] suddenly disappears!")) + + if(isitem(item_to_retrieve) && caster.put_in_hands(item_to_retrieve)) + item_to_retrieve.loc.visible_message(("[item_to_retrieve] suddenly appears in [caster]'s hand!")) + else + item_to_retrieve.forceMove(caster.drop_location()) + item_to_retrieve.loc.visible_message(("[item_to_retrieve] suddenly appears!")) + playsound(get_turf(item_to_retrieve), 'sound/magic/summonitems_generic.ogg', 50, TRUE) diff --git a/code/modules/spells/spell_types/self/voice_of_god.dm b/code/modules/spells/spell_types/self/voice_of_god.dm new file mode 100644 index 0000000000000..e465a1d6d4380 --- /dev/null +++ b/code/modules/spells/spell_types/self/voice_of_god.dm @@ -0,0 +1,50 @@ +/datum/action/spell/voice_of_god + name = "Voice of God" + desc = "Speak with an incredibly compelling voice, forcing listeners to obey your commands." + icon_icon = 'icons/hud/actions/actions_items.dmi' + button_icon_state = "voice_of_god" + sound = 'sound/magic/clockwork/invoke_general.ogg' + + cooldown_time = 120 SECONDS // Varies depending on command + invocation = "" // Handled by the VOICE OF GOD itself + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + antimagic_flags = NONE + + /// The command to deliver on cast + var/command + /// The modifier to the cooldown, after cast + var/cooldown_mod = 1 + /// The modifier put onto the power of the command + var/power_mod = 1 + /// A list of spans to apply to commands given + var/list/spans = list("colossus", "yell") + +/datum/action/spell/voice_of_god/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + command = tgui_input_text(user, "Speak with the Voice of God", "Command") + if(QDELETED(src) || QDELETED(user) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!command) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + +/datum/action/spell/voice_of_god/on_cast(mob/user, atom/target) + . = ..() + var/command_cooldown = voice_of_god(uppertext(command), user, spans, base_multiplier = power_mod) + cooldown_time = (command_cooldown * cooldown_mod) + +// "Invocation" is done by the actual voice of god proc +/datum/action/spell/voice_of_god/invocation() + return + +/datum/action/spell/voice_of_god/clown + name = "Voice of Clown" + desc = "Speak with an incredibly funny voice, startling people into obeying you for a brief moment." + sound = 'sound/misc/scary_horn.ogg' + cooldown_mod = 0.5 + power_mod = 0.1 + spans = list("clown") diff --git a/code/modules/spells/spell_types/shadow_walk.dm b/code/modules/spells/spell_types/shadow_walk.dm deleted file mode 100644 index 7dbcc2e0cda41..0000000000000 --- a/code/modules/spells/spell_types/shadow_walk.dm +++ /dev/null @@ -1,101 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/shadowwalk - name = "Shadow Walk" - desc = "Grants unlimited movement in darkness." - charge_max = 0 - clothes_req = FALSE - antimagic_allowed = TRUE - phase_allowed = TRUE - selection_type = "range" - range = -1 - include_user = TRUE - cooldown_min = 0 - overlay = null - action_icon = 'icons/hud/actions/actions_minor_antag.dmi' - action_icon_state = "ninja_cloak" - action_background_icon_state = "bg_alien" - -/obj/effect/proc_holder/spell/targeted/shadowwalk/cast(list/targets,mob/living/user = usr) - var/L = user.loc - if(istype(user.loc, /obj/effect/dummy/phased_mob/shadow)) - var/obj/effect/dummy/phased_mob/shadow/S = L - S.end_jaunt(FALSE) - return - else - var/turf/T = get_turf(user) - var/light_amount = T.get_lumcount() - if(light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) - playsound(get_turf(user), 'sound/magic/ethereal_enter.ogg', 50, 1, -1) - visible_message("[user] melts into the shadows!") - user.SetAllImmobility(0) - user.setStaminaLoss(0, 0) - var/obj/effect/dummy/phased_mob/shadow/S2 = new(get_turf(user.loc)) - user.forceMove(S2) - S2.jaunter = user - else - to_chat(user, "It isn't dark enough here!") - -/obj/effect/dummy/phased_mob/shadow - name = "darkness" - icon = 'icons/effects/effects.dmi' - icon_state = "nothing" - var/canmove = TRUE - var/mob/living/jaunter - density = FALSE - anchored = TRUE - invisibility = 60 - resistance_flags = LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/effect/dummy/phased_mob/shadow/relaymove(mob/living/user, direction) - var/turf/newLoc = get_step(src,direction) - if(isspaceturf(newLoc)) - to_chat(user, "It really would not be wise to go into space.") - return - if(newLoc.get_lumcount() > SHADOW_SPECIES_LIGHT_THRESHOLD && newLoc.is_blocked_turf()) - to_chat(user, "It wouldn't be wise to move here while incorporeal, I may become trapped.") - return - forceMove(newLoc) - check_light_level() - -/obj/effect/dummy/phased_mob/shadow/proc/check_light_level() - var/turf/T = get_turf(src) - var/light_amount = T.get_lumcount() - if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) // jaunt ends - end_jaunt(TRUE) - else if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD && (!QDELETED(jaunter))) //heal in the dark - jaunter.heal_overall_damage(1,1, 0, BODYTYPE_ORGANIC) - -/obj/effect/dummy/phased_mob/shadow/proc/end_jaunt(forced = FALSE) - if(jaunter) - if(forced) - visible_message("[jaunter] is revealed by the light!") - else - visible_message("[jaunter] emerges from the darkness!") - jaunter.forceMove(get_turf(src)) - playsound(get_turf(jaunter), 'sound/magic/ethereal_exit.ogg', 50, 1, -1) - jaunter = null - qdel(src) - -/obj/effect/dummy/phased_mob/shadow/Initialize(mapload) - . = ..() - START_PROCESSING(SSobj, src) - -/obj/effect/dummy/phased_mob/shadow/Destroy() - STOP_PROCESSING(SSobj, src) - . = ..() - -/obj/effect/dummy/phased_mob/shadow/process() - if(!jaunter) - qdel(src) - if(jaunter.loc != src) - qdel(src) - check_light_level() - -/obj/effect/dummy/phased_mob/shadow/ex_act() - return - -/obj/effect/dummy/phased_mob/shadow/bullet_act() - return BULLET_ACT_FORCE_PIERCE - -/obj/effect/dummy/phased_mob/shadow/singularity_act() - return - diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm deleted file mode 100644 index 70fea8d8c39a4..0000000000000 --- a/code/modules/spells/spell_types/shapeshift.dm +++ /dev/null @@ -1,187 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/shapeshift - name = "Shapechange" - desc = "Take on the shape of another for a time to use their natural abilities. Once you've made your choice it cannot be changed." - clothes_req = FALSE - human_req = FALSE - charge_max = 200 - cooldown_min = 50 - range = -1 - include_user = TRUE - invocation = "RAC'WA NO!" - invocation_type = INVOCATION_SHOUT - action_icon_state = "shapeshift" - - var/revert_on_death = TRUE - var/die_with_shapeshifted_form = TRUE - var/convert_damage = TRUE //If you want to convert the caster's health to the shift, and vice versa. - var/convert_damage_type = BRUTE //Since simplemobs don't have advanced damagetypes, what to convert damage back into. - - var/shapeshift_type - var/list/possible_shapes = list(/mob/living/simple_animal/mouse,\ - /mob/living/simple_animal/pet/dog/corgi,\ - /mob/living/simple_animal/hostile/carp/ranged/chaos,\ - /mob/living/simple_animal/bot/ed209,\ - /mob/living/simple_animal/hostile/poison/giant_spider/hunter/viper/wizard,\ - /mob/living/simple_animal/hostile/construct/juggernaut) - -/obj/effect/proc_holder/spell/targeted/shapeshift/cast(list/targets,mob/user = usr) - if(src in user.mob_spell_list) - user.mob_spell_list.Remove(src) - user.mind.AddSpell(src) - if(user.buckled) - user.buckled.unbuckle_mob(src,force=TRUE) - for(var/mob/living/M in targets) - if(!shapeshift_type) - var/list/animal_list = list() - for(var/path in possible_shapes) - var/mob/living/simple_animal/A = path - animal_list[initial(A.name)] = path - var/new_shapeshift_type = input(M, "Choose Your Animal Form!", "It's Morphing Time!", null) as null|anything in sort_list(animal_list) - if(shapeshift_type) - return - shapeshift_type = new_shapeshift_type - if(!shapeshift_type) //If you aren't gonna decide I am! - shapeshift_type = pick(animal_list) - shapeshift_type = animal_list[shapeshift_type] - - var/obj/shapeshift_holder/S = locate() in M - if(S) - Restore(M) - else - Shapeshift(M) - - -/obj/effect/proc_holder/spell/targeted/shapeshift/proc/Shapeshift(mob/living/caster) - var/obj/shapeshift_holder/H = locate() in caster - if(H) - to_chat(caster, "You're already shapeshifted!") - return - - var/mob/living/shape = new shapeshift_type(caster.loc) - H = new(shape,src,caster) - - clothes_req = FALSE - human_req = FALSE - -/obj/effect/proc_holder/spell/targeted/shapeshift/proc/Restore(mob/living/shape) - var/obj/shapeshift_holder/H = locate() in shape - if(!H) - return - - H.restore() - - clothes_req = initial(clothes_req) - human_req = initial(human_req) - -/obj/effect/proc_holder/spell/targeted/shapeshift/dragon - name = "Dragon Form" - desc = "Take on the shape a lesser ash drake." - invocation = "RAAAAAAAAWR!" - convert_damage = FALSE - - - shapeshift_type = /mob/living/simple_animal/hostile/megafauna/dragon/lesser - - -/obj/shapeshift_holder - name = "Shapeshift holder" - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF - var/mob/living/stored - var/mob/living/shape - var/restoring = FALSE - var/datum/soullink/shapeshift/slink - var/obj/effect/proc_holder/spell/targeted/shapeshift/source - -CREATION_TEST_IGNORE_SUBTYPES(/obj/shapeshift_holder) - -/obj/shapeshift_holder/Initialize(mapload,obj/effect/proc_holder/spell/targeted/shapeshift/source,mob/living/caster, convert_damage = FALSE) - . = ..() - src.source = source - shape = loc - if(!istype(shape)) - CRASH("shapeshift holder created outside mob/living") - stored = caster - if(stored.mind) - stored.mind.transfer_to(shape) - stored.forceMove(src) - stored.notransform = TRUE - if(convert_damage || istype(source) && source.convert_damage) - var/damage_percent = (stored.maxHealth - stored.health)/stored.maxHealth; - var/damapply = damage_percent * shape.maxHealth; - - shape.apply_damage(damapply, source.convert_damage_type, forced = TRUE); - - slink = soullink(/datum/soullink/shapeshift, stored , shape) - slink.source = src - -/obj/shapeshift_holder/Destroy() - if(!restoring) - restore() - stored = null - shape = null - . = ..() - -/obj/shapeshift_holder/Moved() - . = ..() - if(!restoring || QDELETED(src)) - restore() - -/obj/shapeshift_holder/handle_atom_del(atom/A) - if(A == stored && !restoring) - restore() - -/obj/shapeshift_holder/Exited(atom/movable/gone, direction) - . = ..() - if(stored == gone && !restoring) - restore() - -/obj/shapeshift_holder/proc/casterDeath() - //Something kills the stored caster through direct damage. - if(source.revert_on_death) - restore(death=TRUE) - else - shape.investigate_log("has been killed whilst shapeshifted.", INVESTIGATE_DEATHS) - shape.death() - -/obj/shapeshift_holder/proc/shapeDeath(death=TRUE) - //Shape dies. - if(death || istype(source) && source.die_with_shapeshifted_form) - if(death || istype(source) && source.revert_on_death) - restore(death=TRUE) - else - restore() - -/obj/shapeshift_holder/proc/restore(death=FALSE, convert_damage = TRUE) - if(!stored) //somehow this proc is getting called twice and it runtimes on the second pass because stored has been hit with qdel() - return FALSE - restoring = TRUE - qdel(slink) - stored.forceMove(get_turf(src)) - stored.notransform = FALSE - if(shape.mind) - shape.mind.transfer_to(stored) - if(death) - stored.death() - else if(convert_damage || (istype(source) && source.convert_damage)) - var/original_blood_volume = stored.blood_volume - stored.revive(full_heal = TRUE) - - var/damage_percent = (shape.maxHealth - shape.health)/shape.maxHealth; - var/damapply = stored.maxHealth * damage_percent - - stored.apply_damage(damapply, (istype(source) ? source.convert_damage_type : BRUTE), forced = TRUE) //brute is the default damage convert - stored.blood_volume = original_blood_volume - if(!QDELETED(shape)) - qdel(shape) - qdel(src) - -/datum/soullink/shapeshift - var/obj/shapeshift_holder/source - -/datum/soullink/shapeshift/ownerDies(gibbed, mob/living/owner) - if(source) - source.casterDeath(gibbed) - -/datum/soullink/shapeshift/sharerDies(gibbed, mob/living/sharer) - if(source) - source.shapeDeath(!gibbed) diff --git a/code/modules/spells/spell_types/shapeshift/_shapeshift.dm b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm new file mode 100644 index 0000000000000..d149c68230f0d --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -0,0 +1,246 @@ +/datum/action/spell/shapeshift + school = SCHOOL_TRANSMUTATION + + /// Whehter we revert to our human form on death. + var/revert_on_death = TRUE + /// Whether we die when our shapeshifted form is killed + var/die_with_shapeshifted_form = TRUE + /// Whether we convert our health from one form to another + var/convert_damage = TRUE + /// If convert damage is true, the damage type we deal when converting damage back and forth + var/convert_damage_type = BRUTE + + /// Our chosen type + var/mob/living/shapeshift_type + /// All possible types we can become + var/list/atom/possible_shapes + +/datum/action/spell/shapeshift/is_valid_spell(mob/user, atom/target) + return isliving(user) + +/datum/action/spell/shapeshift/proc/is_shifted(mob/living/cast_on) + return locate(/obj/shapeshift_holder) in cast_on + +/datum/action/spell/shapeshift/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + if(shapeshift_type) + return + + if(length(possible_shapes) == 1) + shapeshift_type = possible_shapes[1] + return + + var/list/shape_names_to_types = list() + var/list/shape_names_to_image = list() + if(!length(shape_names_to_types) || !length(shape_names_to_image)) + for(var/atom/path as anything in possible_shapes) + var/shape_name = initial(path.name) + shape_names_to_types[shape_name] = path + shape_names_to_image[shape_name] = image(icon = initial(path.icon), icon_state = initial(path.icon_state)) + + var/picked_type = show_radial_menu( + user, + user, + shape_names_to_image, + custom_check = CALLBACK(src, PROC_REF(check_menu), user), + radius = 38, + ) + + if(!picked_type) + return . | SPELL_CANCEL_CAST + + var/atom/shift_type = shape_names_to_types[picked_type] + if(!ispath(shift_type)) + return . | SPELL_CANCEL_CAST + + shapeshift_type = shift_type || pick(possible_shapes) + if(QDELETED(src) || QDELETED(owner) || !can_cast_spell(feedback = FALSE)) + return . | SPELL_CANCEL_CAST + +/datum/action/spell/shapeshift/on_cast(mob/living/user, atom/target) + . = ..() + user.buckled?.unbuckle_mob(user, force = TRUE) + + var/currently_ventcrawling = (user.movement_type & VENTCRAWLING) + + // Do the shift back or forth + if(is_shifted(user)) + restore_form(user) + else + do_shapeshift(user) + + // The shift is done, let's make sure they're in a valid state now + // If we're not ventcrawling, we don't need to mind + if(!currently_ventcrawling) + return + + // We are ventcrawling - can our new form support ventcrawling? + if(HAS_TRAIT(user, VENTCRAWLER_ALWAYS) || HAS_TRAIT(user, VENTCRAWLER_NUDE)) + return + + // Uh oh. You've shapeshifted into something that can't fit into a vent, while ventcrawling. + eject_from_vents(user) + +/// Whenever someone shapeshifts within a vent, +/// and enters a state in which they are no longer a ventcrawler, +/// they are brutally ejected from the vents. In the form of gibs. +/datum/action/spell/shapeshift/proc/eject_from_vents(mob/living/cast_on) + var/obj/machinery/atmospherics/pipe_you_die_in = cast_on.loc + var/datum/pipeline/our_pipeline + var/pipenets = pipe_you_die_in.returnPipenets() + if(islist(pipenets)) + our_pipeline = pipenets[1] + else + our_pipeline = pipenets + + to_chat(cast_on, ("Casting [src] inside of [pipe_you_die_in] quickly turns you into a bloody mush!")) + var/obj/effect/gib_type = isalien(cast_on) ? /obj/effect/gibspawner/xeno : /obj/effect/gibspawner/generic + + for(var/obj/machinery/atmospherics/components/unary/possible_vent in range(10, get_turf(cast_on))) + if(length(possible_vent.parents) && possible_vent.parents[1] == our_pipeline) + new gib_type(get_turf(possible_vent)) + playsound(possible_vent, 'sound/effects/reee.ogg', 75, TRUE) + + priority_announce("We detected a pipe blockage around [get_area(get_turf(cast_on))], please dispatch someone to investigate.", "Central Command") + cast_on.death() + qdel(cast_on) + +/datum/action/spell/shapeshift/proc/check_menu(mob/living/caster) + if(QDELETED(src)) + return FALSE + if(QDELETED(caster)) + return FALSE + + return !caster.incapacitated() + +/datum/action/spell/shapeshift/proc/do_shapeshift(mob/living/caster) + if(is_shifted(caster)) + to_chat(caster, ("You're already shapeshifted!")) + CRASH("[type] called do_shapeshift while shapeshifted.") + + var/mob/living/new_shape = new shapeshift_type(caster.loc) + Grant(new_shape) + var/obj/shapeshift_holder/new_shape_holder = new(new_shape, src, caster) + + spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB) + + return new_shape_holder + +/datum/action/spell/shapeshift/proc/restore_form(mob/living/caster) + var/obj/shapeshift_holder/current_shift = is_shifted(caster) + if(QDELETED(current_shift)) + return + + var/mob/living/restored_player = current_shift.stored + + current_shift.restore() + Grant(restored_player) + spell_requirements = initial(spell_requirements) // Miiight mess with admin stuff. + + return restored_player + +// Maybe one day, this can be a component or something +// Until then, this is what holds data between wizard and shapeshift form whenever shapeshift is cast. +/obj/shapeshift_holder + name = "Shapeshift holder" + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF + var/mob/living/stored + var/mob/living/shape + var/restoring = FALSE + var/datum/action/spell/shapeshift/source + +/obj/shapeshift_holder/Initialize(mapload, datum/action/spell/shapeshift/_source, mob/living/caster) + . = ..() + source = _source + shape = loc + if(!istype(shape)) + stack_trace("shapeshift holder created outside mob/living") + return INITIALIZE_HINT_QDEL + stored = caster + if(stored.mind) + stored.mind.transfer_to(shape) + stored.forceMove(src) + stored.notransform = TRUE + if(source.convert_damage) + var/damage_percent = (stored.maxHealth - stored.health) / stored.maxHealth; + var/damapply = damage_percent * shape.maxHealth; + + shape.apply_damage(damapply, source.convert_damage_type, forced = TRUE); + shape.blood_volume = stored.blood_volume; + + RegisterSignals(shape, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(shape_death)) + RegisterSignals(stored, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(caster_death)) + +/obj/shapeshift_holder/Destroy() + // restore_form manages signal unregistering. If restoring is TRUE, we've already unregistered the signals and we're here + // because restore() qdel'd src. + if(!restoring) + restore() + stored = null + shape = null + return ..() + +/obj/shapeshift_holder/Moved() + . = ..() + if(!restoring && !QDELETED(src)) + restore() + +/obj/shapeshift_holder/handle_atom_del(atom/A) + if(A == stored && !restoring) + restore() + +/obj/shapeshift_holder/Exited(atom/movable/gone, direction) + if(stored == gone && !restoring) + restore() + +/obj/shapeshift_holder/proc/caster_death() + SIGNAL_HANDLER + + //Something kills the stored caster through direct damage. + if(source.revert_on_death) + restore(death = TRUE) + else + shape.death() + +/obj/shapeshift_holder/proc/shape_death() + SIGNAL_HANDLER + + //Shape dies. + if(source.die_with_shapeshifted_form) + if(source.revert_on_death) + restore(death = TRUE) + else + restore() + +/obj/shapeshift_holder/proc/restore(death=FALSE) + // Destroy() calls this proc if it hasn't been called. Unregistering here prevents multiple qdel loops + // when caster and shape both die at the same time. + UnregisterSignal(shape, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH)) + UnregisterSignal(stored, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH)) + restoring = TRUE + stored.forceMove(shape.loc) + stored.notransform = FALSE + if(shape.mind) + shape.mind.transfer_to(stored) + if(death) + stored.death() + else if(source.convert_damage) + stored.revive(full_heal = TRUE, admin_revive = FALSE) + + var/damage_percent = (shape.maxHealth - shape.health)/shape.maxHealth; + var/damapply = stored.maxHealth * damage_percent + + stored.apply_damage(damapply, source.convert_damage_type, forced = TRUE) + if(source.convert_damage) + stored.blood_volume = shape.blood_volume; + + // This guard is important because restore() can also be called on COMSIG_PARENT_QDELETING for shape, as well as on death. + // This can happen in, for example, [/proc/wabbajack] where the mob hit is qdel'd. + if(!QDELETED(shape)) + QDEL_NULL(shape) + + qdel(src) + return stored diff --git a/code/modules/spells/spell_types/shapeshift/dragon.dm b/code/modules/spells/spell_types/shapeshift/dragon.dm new file mode 100644 index 0000000000000..92c0987457e99 --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/dragon.dm @@ -0,0 +1,7 @@ +/datum/action/spell/shapeshift/dragon + name = "Dragon Form" + desc = "Take on the shape a lesser ash drake." + invocation = "RAAAAAAAAWR!" + spell_requirements = NONE + + possible_shapes = list(/mob/living/simple_animal/hostile/megafauna/dragon/lesser) diff --git a/code/modules/spells/spell_types/shapeshift/polar_bear.dm b/code/modules/spells/spell_types/shapeshift/polar_bear.dm new file mode 100644 index 0000000000000..e28024f6d92b3 --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/polar_bear.dm @@ -0,0 +1,7 @@ +/datum/action/spell/shapeshift/polar_bear + name = "Polar Bear Form" + desc = "Take on the shape of a polar bear." + invocation = "RAAAAAAAAWR!" + spell_requirements = NONE + + possible_shapes = list(/mob/living/simple_animal/hostile/bear/snow) diff --git a/code/modules/spells/spell_types/shapeshift/shapechange.dm b/code/modules/spells/spell_types/shapeshift/shapechange.dm new file mode 100644 index 0000000000000..1b83b7f7f3c3e --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/shapechange.dm @@ -0,0 +1,22 @@ +/datum/action/spell/shapeshift/wizard + name = "Wild Shapeshift" + desc = "Take on the shape of another for a time to use their natural abilities. \ + Once you've made your choice, it cannot be changed." + button_icon_state = "shapeshift" + + school = SCHOOL_TRANSMUTATION + cooldown_time = 20 SECONDS + cooldown_reduction_per_rank = 3.75 SECONDS + + invocation = "RAC'WA NO!" + invocation_type = INVOCATION_SHOUT + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + possible_shapes = list( + /mob/living/simple_animal/mouse, + /mob/living/simple_animal/pet/dog/corgi, + /mob/living/simple_animal/hostile/carp/ranged/chaos, + /mob/living/simple_animal/bot/ed209, + /mob/living/simple_animal/hostile/poison/giant_spider, + /mob/living/simple_animal/hostile/construct/juggernaut/mystic, + ) diff --git a/code/modules/spells/spell_types/soultap.dm b/code/modules/spells/spell_types/soultap.dm deleted file mode 100644 index 2b26b6b180b7c..0000000000000 --- a/code/modules/spells/spell_types/soultap.dm +++ /dev/null @@ -1,35 +0,0 @@ -#define HEALTH_LOST_PER_SOUL_TAP 20 - -//SOUL TAP!// -//Trades 20 max health for a refresh on all of your spells. I was considering making it depend on the cooldowns of your spells, but I want to support "Big spell wizard" with this loadout. -//the two spells that sound most problematic with this is mindswap and lichdom, but soul tap requires clothes for mindswap and lichdom takes your soul. - -/obj/effect/proc_holder/spell/self/tap - name = "Soul Tap" - desc = "Fuel your spells using your own soul!" - school = "necromancy" //i could see why this wouldn't be necromancy but messing with souls or whatever. ectomancy? - charge_max = 10 - invocation = "AT ANY COST!" - invocation_type = INVOCATION_SHOUT - level_max = 0 - cooldown_min = 10 - - action_icon = 'icons/hud/actions/actions_spells.dmi' - action_icon_state = "soultap" - -/obj/effect/proc_holder/spell/self/tap/cast(mob/living/user = usr) - if(!user.mind.hasSoul) - to_chat(user, "You do not possess a soul to tap into!") - return - to_chat(user, "Your body feels drained and there is a burning pain in your chest.") - user.maxHealth -= HEALTH_LOST_PER_SOUL_TAP - user.health = min(user.health, user.maxHealth) - if(user.maxHealth <= 0) - to_chat(user, "Your weakened soul is completely consumed by the tap!") - user.mind.hasSoul = FALSE - for(var/obj/effect/proc_holder/spell/spell in user.mind.spell_list) - spell.charge_counter = spell.charge_max - spell.recharging = FALSE - spell.update_icon() - -#undef HEALTH_LOST_PER_SOUL_TAP diff --git a/code/modules/spells/spell_types/spacetime_distortion.dm b/code/modules/spells/spell_types/spacetime_distortion.dm deleted file mode 100644 index 7011b69db2ee7..0000000000000 --- a/code/modules/spells/spell_types/spacetime_distortion.dm +++ /dev/null @@ -1,133 +0,0 @@ -/obj/effect/proc_holder/spell/spacetime_dist - name = "Spacetime Distortion" - desc = "Entangle the strings of space-time in an area around you, randomizing the layout and making proper movement impossible. The strings vibrate..." - charge_max = 450 - var/duration = 150 - range = 7 - var/list/effects - var/ready = TRUE - centcom_cancast = FALSE - sound = 'sound/effects/magic.ogg' - cooldown_min = 300 - invocation = "ZYAR INCANTUS" - invocation_type = INVOCATION_SHOUT - clothes_req = FALSE - level_max = 0 - action_icon_state = "spacetime" - -/obj/effect/proc_holder/spell/spacetime_dist/can_cast(mob/user = usr) - if(ready) - return ..() - return FALSE - -/obj/effect/proc_holder/spell/spacetime_dist/choose_targets(mob/user = usr) - var/list/turfs = spiral_range_turfs(range, user) - if(!turfs.len) - revert_cast() - return - - ready = FALSE - var/list/turf_steps = list() - var/length = round(turfs.len * 0.5) - for(var/i in 1 to length) - turf_steps[pick_n_take(turfs)] = pick_n_take(turfs) - if(turfs.len > 0) - var/turf/loner = pick(turfs) - var/area/A = get_area(user) - turf_steps[loner] = get_turf(pick(A.contents)) - - perform(turf_steps,user=user) - -/obj/effect/proc_holder/spell/spacetime_dist/after_cast(list/targets) - addtimer(CALLBACK(src, PROC_REF(clean_turfs)), duration) - -/obj/effect/proc_holder/spell/spacetime_dist/cast(list/targets, mob/user = usr) - effects = list() - for(var/V in targets) - var/turf/T0 = V - var/turf/T1 = targets[V] - var/obj/effect/cross_action/spacetime_dist/STD0 = new /obj/effect/cross_action/spacetime_dist(T0) - var/obj/effect/cross_action/spacetime_dist/STD1 = new /obj/effect/cross_action/spacetime_dist(T1) - STD0.linked_dist = STD1 - STD0.add_overlay(T1.photograph()) - STD1.linked_dist = STD0 - STD1.add_overlay(T0.photograph()) - STD1.set_light(4, 30, "#c9fff5") - effects += STD0 - effects += STD1 - -/obj/effect/proc_holder/spell/spacetime_dist/proc/clean_turfs() - for(var/effect in effects) - qdel(effect) - effects.Cut() - effects = null - ready = TRUE - -/obj/effect/cross_action - name = "cross me" - desc = "for crossing" - anchored = TRUE - -/obj/effect/cross_action/spacetime_dist - name = "spacetime distortion" - desc = "A distortion in spacetime. You can hear faint music..." - icon_state = "" - var/obj/effect/cross_action/spacetime_dist/linked_dist - var/busy = FALSE - var/sound - var/walks_left = 50 //prevents the game from hanging in extreme cases (such as minigun fire) - -/obj/effect/cross_action/singularity_act() - return - -/obj/effect/cross_action/singularity_pull() - return - -/obj/effect/cross_action/spacetime_dist/Initialize(mapload) - . = ..() - setDir(pick(GLOB.cardinals)) - var/static/list/loc_connections = list( - COMSIG_ATOM_ENTERED = PROC_REF(on_entered), - ) - AddElement(/datum/element/connect_loc, loc_connections) - -/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM) - if(ismob(AM)) - var/mob/M = AM - if(M.anti_magic_check(major = FALSE)) - return - if(linked_dist && walks_left > 0) - flick("purplesparkles", src) - linked_dist.get_walker(AM) - walks_left-- - -/obj/effect/cross_action/spacetime_dist/proc/get_walker(atom/movable/AM) - busy = TRUE - flick("purplesparkles", src) - AM.forceMove(get_turf(src)) - playsound(get_turf(src),sound,70,0) - busy = FALSE - -/obj/effect/cross_action/spacetime_dist/proc/on_entered(datum/source, atom/movable/AM) - SIGNAL_HANDLER - - if(!busy) - walk_link(AM) - -/obj/effect/cross_action/spacetime_dist/attackby(obj/item/W, mob/user, params) - if(user.temporarilyRemoveItemFromInventory(W)) - walk_link(W) - else - walk_link(user) - -//ATTACK HAND IGNORING PARENT RETURN VALUE -/obj/effect/cross_action/spacetime_dist/attack_hand(mob/user) - walk_link(user) - -/obj/effect/cross_action/spacetime_dist/attack_paw(mob/user) - walk_link(user) - -/obj/effect/cross_action/spacetime_dist/Destroy() - busy = TRUE - linked_dist = null - return ..() diff --git a/code/modules/spells/spell_types/summonitem.dm b/code/modules/spells/spell_types/summonitem.dm deleted file mode 100644 index abf00e699a10c..0000000000000 --- a/code/modules/spells/spell_types/summonitem.dm +++ /dev/null @@ -1,110 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/summonitem - name = "Instant Summons" - desc = "This spell can be used to recall a previously marked item to your hand from anywhere in the universe." - school = "transmutation" - charge_max = 100 - clothes_req = FALSE - invocation = "GAR YOK" - invocation_type = INVOCATION_WHISPER - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = TRUE - - var/obj/marked_item - var/allow_change = TRUE - - action_icon_state = "summons" - -/obj/effect/proc_holder/spell/targeted/summonitem/cast(list/targets,mob/user = usr) - for(var/mob/living/L in targets) - var/list/hand_items = list(L.get_active_held_item(),L.get_inactive_held_item()) - var/message - - if(!marked_item && allow_change) //linking item to the spell - message = "" - for(var/obj/item/item in hand_items) - if(item.item_flags & ABSTRACT) - continue - if(SEND_SIGNAL(item, COMSIG_ITEM_MARK_RETRIEVAL) & COMPONENT_BLOCK_MARK_RETRIEVAL) - continue - if(HAS_TRAIT(item, TRAIT_NODROP)) - message += "Though it feels redundant, " - marked_item = item - message += "You mark [item] for recall." - name = "Recall [item]" - break - - if(!marked_item) - if(hand_items) - message = "You aren't holding anything that can be marked for recall." - else - message = "You must hold the desired item in your hands to mark it for recall." - - else if(marked_item && (marked_item in hand_items) && allow_change) //unlinking item to the spell - message = "You remove the mark on [marked_item] to use elsewhere." - name = "Instant Summons" - marked_item = null - - else if(marked_item && QDELETED(marked_item)) //the item was destroyed at some point - message = "You sense your marked item has been destroyed!" - name = "Instant Summons" - marked_item = null - if(!allow_change) - qdel(src) - return - - else //Getting previously marked item - var/obj/item_to_retrieve = marked_item - var/infinite_recursion = 0 //I don't want to know how someone could put something inside itself but these are wizards so let's be safe - - if(!item_to_retrieve.loc) - if(isorgan(item_to_retrieve)) // Organs are usually stored in nullspace - var/obj/item/organ/organ = item_to_retrieve - if(organ.owner) - // If this code ever runs I will be happy - log_combat(L, organ.owner, "magically removed [organ.name] from", addition="INTENT: [uppertext(L.a_intent)]") - organ.Remove(organ.owner) - else - while(!isturf(item_to_retrieve.loc) && infinite_recursion < 10) //if it's in something you get the whole thing. - if(isitem(item_to_retrieve.loc)) - var/obj/item/I = item_to_retrieve.loc - if(I.item_flags & ABSTRACT) //Being able to summon abstract things because your item happened to get placed there is a no-no - break - if(ismob(item_to_retrieve.loc)) //If its on someone, properly drop it - var/mob/M = item_to_retrieve.loc - - if(issilicon(M)) //Items in silicons warp the whole silicon - M.loc.visible_message("[M] suddenly disappears!") - M.forceMove(L.loc) - M.loc.visible_message("[M] suddenly appears!") - item_to_retrieve = null - break - M.dropItemToGround(item_to_retrieve) - - else - if(istype(item_to_retrieve.loc, /obj/machinery/portable_atmospherics/)) //Edge cases for moved machinery - var/obj/machinery/portable_atmospherics/P = item_to_retrieve.loc - P.disconnect() - P.update_icon() - - item_to_retrieve = item_to_retrieve.loc - - infinite_recursion += 1 - - if(!item_to_retrieve) - return - - if(item_to_retrieve.loc) - item_to_retrieve.loc.visible_message("The [item_to_retrieve.name] suddenly disappears!") - if(!L.put_in_hands(item_to_retrieve)) - item_to_retrieve.forceMove(L.drop_location()) - item_to_retrieve.loc.visible_message("The [item_to_retrieve.name] suddenly appears!") - playsound(get_turf(L), 'sound/magic/summonitems_generic.ogg', 50, 1) - else - item_to_retrieve.loc.visible_message("The [item_to_retrieve.name] suddenly appears in [L]'s hand!") - playsound(get_turf(L), 'sound/magic/summonitems_generic.ogg', 50, 1) - - - if(message) - to_chat(L, message) diff --git a/code/modules/spells/spell_types/taeclowndo.dm b/code/modules/spells/spell_types/taeclowndo.dm deleted file mode 100644 index 63c06359046be..0000000000000 --- a/code/modules/spells/spell_types/taeclowndo.dm +++ /dev/null @@ -1,86 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/conjure_item/summon_pie - name = "Summon Creampie" - desc = "A clown's weapon of choice. Use this to summon a fresh pie, just waiting to acquaintain itself with someone's face." - invocation_type = INVOCATION_NONE - include_user = 1 - range = -1 - clothes_req = 0 - item_type = /obj/item/food/pie/cream - - charge_max = 30 - cooldown_min = 30 - action_icon = 'icons/obj/food/piecake.dmi' - action_icon_state = "pie" - -////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/obj/effect/proc_holder/spell/aimed/banana_peel - name = "Conjure Banana Peel" - desc = "Make a banana peel appear out of thin air right under someone's feet!" - charge_type = "recharge" - charge_max = 100 - cooldown_min = 100 - clothes_req = 0 - invocation_type = INVOCATION_NONE - range = 7 - selection_type = "view" - projectile_type = null - - active_msg = "You focus, your mind reaching to the clown dimension, ready to make a peel matrialize wherever you want!" - deactive_msg = "You relax, the peel remaining right in the \"thin air\" it would appear out of." - action_icon = 'icons/obj/hydroponics/harvest.dmi' - base_icon_state = "banana_peel" - action_icon_state = "banana" - - -/obj/effect/proc_holder/spell/aimed/banana_peel/cast(list/targets, mob/user = usr) - var/target = get_turf(targets[1]) - - if(get_dist(user,target)>range) - to_chat(user, "\The [target] is too far away!") - return - - . = ..() - new /obj/item/grown/bananapeel(target) - -/obj/effect/proc_holder/spell/aimed/banana_peel/update_icon() - if(!action) - return - if(active) - action.button_icon_state = base_icon_state - else - action.button_icon_state = action_icon_state - - action.UpdateButtonIcon() - return -////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/obj/effect/proc_holder/spell/targeted/touch/megahonk - name = "Mega HoNk" - desc = "This spell channels your inner clown powers, concentrating them into one massive HONK." - hand_path = /obj/item/melee/touch_attack/megahonk - - charge_max = 100 - clothes_req = 0 - cooldown_min = 100 - - action_icon = 'icons/mecha/mecha_equipment.dmi' - action_icon_state = "mecha_honker" - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/obj/effect/proc_holder/spell/targeted/touch/bspie - name = "Bluespace Banana Pie" - desc = "An entire body would fit in there!" - hand_path = /obj/item/melee/touch_attack/bspie - - charge_max = 450 - clothes_req = 0 - cooldown_min = 450 - - action_icon = 'icons/obj/food/piecake.dmi' - action_icon_state = "frostypie" - - - - diff --git a/code/modules/spells/spell_types/telepathy.dm b/code/modules/spells/spell_types/telepathy.dm deleted file mode 100644 index f4f1c3106246b..0000000000000 --- a/code/modules/spells/spell_types/telepathy.dm +++ /dev/null @@ -1,39 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/telepathy - name = "Telepathy" - desc = "Telepathically transmits a message to the target." - charge_max = 0 - clothes_req = 0 - range = 7 - include_user = 0 - action_icon = 'icons/hud/actions/actions_revenant.dmi' - action_icon_state = "r_transmit" - action_background_icon_state = "bg_spell" - var/notice = "notice" - var/boldnotice = "boldnotice" - var/magic_check = FALSE - var/holy_check = FALSE - -/obj/effect/proc_holder/spell/targeted/telepathy/cast(list/targets, mob/living/user = usr) - for(var/mob/living/M in targets) - if(istype(M.get_item_by_slot(ITEM_SLOT_HEAD), /obj/item/clothing/head/costume/foilhat)) - to_chat(user, "It appears the target's mind is ironclad! No getting a message in there!") - return - var/msg = tgui_input_text(usr, "What do you wish to tell [M]?", "Telepathy") - if(!length(msg)) - revert_cast(user) - return - if(CHAT_FILTER_CHECK(msg)) - to_chat(user, "Your message contains forbidden words.") - return - msg = user.treat_message_min(msg) - log_directed_talk(user, M, msg, LOG_SAY, "[name]") - to_chat(user, "You transmit to [M]: [msg]") - if(!M.anti_magic_check(magic_check, holy_check)) //hear no evil - to_chat(M, "You hear something behind you talking... [msg]") - M.balloon_alert(M, "You hear a voice in your head...") - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) - continue - var/follow_rev = FOLLOW_LINK(ded, user) - var/follow_whispee = FOLLOW_LINK(ded, M) - to_chat(ded, "[follow_rev] [user] [name]: \"[msg]\" to [follow_whispee] [M]") diff --git a/code/modules/spells/spell_types/teleport/_teleport.dm b/code/modules/spells/spell_types/teleport/_teleport.dm new file mode 100644 index 0000000000000..b953105a9c6a1 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/_teleport.dm @@ -0,0 +1,146 @@ +/** + * ## Teleport Spell + * + * Teleports the caster to a turf selected by get_destinations(). + */ +/datum/action/spell/teleport + sound = 'sound/weapons/zapbang.ogg' + + school = SCHOOL_TRANSLOCATION + + /// What channel the teleport is done under. + var/teleport_channel = TELEPORT_CHANNEL_MAGIC_SELF + /// Whether we force the teleport to happen (ie, it cannot be blocked by TELEPORT_ALLOW_NONE areas or blessings or whatever) + var/force_teleport = FALSE + /// A list of flags related to determining if our destination target is valid or not. + var/destination_flags = NONE + /// The sound played on arrival, after the teleport. + var/post_teleport_sound = 'sound/weapons/zapbang.ogg' + /// The teleport mode to be used, for bypassing area protections + var/teleport_mode = TELEPORT_ALLOW_ALL + +/datum/action/spell/teleport/on_cast(mob/user, atom/target) + . = ..() + var/list/turf/destinations = get_destinations(user) + if(!length(destinations)) + CRASH("[type] failed to find a teleport destination.") + + do_teleport(user, pick(destinations), asoundout = post_teleport_sound, channel = teleport_channel, teleport_mode = teleport_mode) + +/// Gets a list of destinations that are valid +/datum/action/spell/teleport/proc/get_destinations(atom/center) + CRASH("[type] did not implement get_destinations and either has no effects or implemented the spell incorrectly.") + +/// Checks if the passed turf is a valid destination. +/datum/action/spell/teleport/proc/is_valid_destination(turf/selected) + if(isspaceturf(selected) && (destination_flags & TELEPORT_SPELL_SKIP_SPACE)) + return FALSE + if(selected.density && (destination_flags & TELEPORT_SPELL_SKIP_DENSE)) + return FALSE + if(selected.is_blocked_turf(exclude_mobs = TRUE) && (destination_flags & TELEPORT_SPELL_SKIP_BLOCKED)) + return FALSE + + return TRUE + +/** + * ### Radius Teleport Spell + * + * A subtype of teleport that will teleport the caster + * to a random turf within a radius of themselves. + */ +/datum/action/spell/teleport/radius_turf + /// The inner radius around the caster that we can teleport to + var/inner_tele_radius = 1 + /// The outer radius around the caster that we can teleport to + var/outer_tele_radius = 2 + +/datum/action/spell/teleport/radius_turf/get_destinations(atom/center) + var/list/valid_turfs = list() + var/list/possibles = RANGE_TURFS(outer_tele_radius, center) + if(inner_tele_radius > 0) + possibles -= RANGE_TURFS(inner_tele_radius, center) + + for(var/turf/nearby_turf as anything in possibles) + if(!is_valid_destination(nearby_turf)) + continue + + valid_turfs += nearby_turf + + // If there are valid turfs around us? + // Screw it, allow 'em to teleport to ANY nearby turf. + return length(valid_turfs) ? valid_turfs : possibles + +/datum/action/spell/teleport/radius_turf/is_valid_destination(turf/selected) + . = ..() + if(!.) + return FALSE + + // putting them at the edge is dumb + if(selected.x > world.maxx - outer_tele_radius || selected.x < outer_tele_radius) + return FALSE + if(selected.y > world.maxy - outer_tele_radius || selected.y < outer_tele_radius) + return FALSE + + return TRUE + +/** + * ### Area Teleport Spell + * + * A subtype of teleport that will teleport the caster + * to a random turf within a selected (or random) area. + */ +/datum/action/spell/teleport/area_teleport + force_teleport = TRUE // Forced, as the Wizard Den is TELEPORT_ALLOW_NONE and wizards couldn't escape otherwise. + destination_flags = TELEPORT_SPELL_SKIP_BLOCKED + /// The last area we chose to teleport / where we're currently teleporting to, if mid-cast + var/last_chosen_area_name + /// If FALSE, the caster can select the destination area. If TRUE, they will teleport to somewhere randomly instead. + var/randomise_selection = FALSE + /// If the invocation appends the selected area when said. Requires invocation mode shout or whisper. + var/invocation_says_area = TRUE + +/datum/action/spell/teleport/area_teleport/get_destinations(atom/center) + var/list/valid_turfs = list() + for(var/turf/possible_destination as anything in get_area_turfs(GLOB.teleportlocs[last_chosen_area_name])) + if(!is_valid_destination(possible_destination)) + continue + + valid_turfs += possible_destination + + return valid_turfs + +/datum/action/spell/teleport/area_teleport/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/area/target_area + if(randomise_selection) + target_area = pick(GLOB.teleportlocs) + else + target_area = tgui_input_list(user, "Chose an area to teleport to.", "Teleport", GLOB.teleportlocs) + + if(QDELETED(src) || QDELETED(user) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!target_area || isnull(GLOB.teleportlocs[target_area])) + return . | SPELL_CANCEL_CAST + + last_chosen_area_name = target_area + +/datum/action/spell/teleport/area_teleport/on_cast(mob/user, atom/target) + if(isliving(user)) + var/mob/living/living_cast_on = user + living_cast_on.buckled?.unbuckle_mob(user, force = TRUE) + return ..() + +/datum/action/spell/teleport/area_teleport/invocation() + var/area/last_chosen_area = GLOB.teleportlocs[last_chosen_area_name] + + if(!invocation_says_area || isnull(last_chosen_area)) + return ..() + + switch(invocation_type) + if(INVOCATION_SHOUT) + owner.say("[invocation], [uppertext(last_chosen_area.name)]!", forced = "spell ([src])") + if(INVOCATION_WHISPER) + owner.whisper("[invocation], [uppertext(last_chosen_area.name)].", forced = "spell ([src])") diff --git a/code/modules/spells/spell_types/teleport/blink.dm b/code/modules/spells/spell_types/teleport/blink.dm new file mode 100644 index 0000000000000..4ca7984dcee60 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/blink.dm @@ -0,0 +1,19 @@ +/datum/action/spell/teleport/radius_turf/blink + name = "Blink" + desc = "This spell randomly teleports you a short distance." + button_icon_state = "blink" + sound = 'sound/magic/blink.ogg' + + school = SCHOOL_FORBIDDEN + cooldown_time = 2 SECONDS + cooldown_reduction_per_rank = 0.4 SECONDS + + invocation_type = INVOCATION_NONE + + smoke_type = /obj/effect/particle_effect/smoke + smoke_amt = 0 + + inner_tele_radius = 0 + outer_tele_radius = 6 + + post_teleport_sound = 'sound/magic/blink.ogg' diff --git a/code/modules/spells/spell_types/teleport/teleport.dm b/code/modules/spells/spell_types/teleport/teleport.dm new file mode 100644 index 0000000000000..5b4a2adf6ef56 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/teleport.dm @@ -0,0 +1,49 @@ +/// The wizard's teleport SPELL +/datum/action/spell/teleport/area_teleport/wizard + name = "Teleport" + desc = "This spell teleports you to an area of your selection." + button_icon_state = "teleport" + sound = 'sound/magic/teleport_diss.ogg' + + school = SCHOOL_TRANSLOCATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + spell_requirements = NONE + invocation = "SCYAR NILA" + invocation_type = INVOCATION_SHOUT + + smoke_type = /obj/effect/particle_effect/smoke + smoke_amt = 2 + + post_teleport_sound = 'sound/magic/teleport_app.ogg' + +// Santa's teleport, themed as such +/datum/action/spell/teleport/area_teleport/wizard/santa + name = "Santa Teleport" + + invocation = "HO HO HO!" + antimagic_flags = NONE + + invocation_says_area = FALSE // Santa moves in mysterious ways + +/// Used by the wizard's teleport scroll +/datum/action/spell/teleport/area_teleport/wizard/scroll + name = "Teleport (scroll)" + cooldown_time = 0 SECONDS + + invocation = null + invocation_type = INVOCATION_NONE + teleport_mode = TELEPORT_ALLOW_WIZARD + invocation_says_area = FALSE + +/datum/action/spell/teleport/area_teleport/wizard/scroll/is_available() + return ..() && owner.is_holding(master) + +/datum/action/spell/teleport/area_teleport/wizard/scroll/pre_cast(mob/user, atom/target) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/mob/living/carbon/caster = user + if(caster.incapacitated() || !caster.is_holding(master)) + return . | SPELL_CANCEL_CAST diff --git a/code/modules/spells/spell_types/the_traps.dm b/code/modules/spells/spell_types/the_traps.dm deleted file mode 100644 index 537a252e6d158..0000000000000 --- a/code/modules/spells/spell_types/the_traps.dm +++ /dev/null @@ -1,26 +0,0 @@ -/obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps - name = "The Traps!" - desc = "Summon a number of traps around you. They will damage and enrage any enemies that step on them." - - charge_max = 250 - cooldown_min = 100 - - clothes_req = FALSE - invocation = "CAVERE INSIDIAS" - invocation_type = INVOCATION_SHOUT - range = 3 - - summon_type = list( - /obj/structure/trap/stun, - /obj/structure/trap/fire, - /obj/structure/trap/chill, - /obj/structure/trap/damage - ) - summon_lifespan = 3000 - summon_amt = 5 - - action_icon_state = "the_traps" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps/post_summon(obj/structure/trap/T, mob/user) - T.immune_minds += user.mind - T.charges = 1 diff --git a/code/modules/spells/spell_types/touch/_touch.dm b/code/modules/spells/spell_types/touch/_touch.dm new file mode 100644 index 0000000000000..e2884a28038ac --- /dev/null +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -0,0 +1,217 @@ +/datum/action/spell/touch + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED + sound = 'sound/items/welder.ogg' + invocation = "High Five!" + invocation_type = INVOCATION_SHOUT + + /// Typepath of what hand we create on initial cast. + var/obj/item/melee/touch_attack/hand_path = /obj/item/melee/touch_attack + /// Ref to the hand we currently have deployed. + var/obj/item/melee/touch_attack/attached_hand + /// The message displayed to the person upon creating the touch hand + var/draw_message = ("You channel the power of the spell to your hand.") + /// The message displayed upon willingly dropping / deleting / cancelling the touch hand before using it + var/drop_message = ("You draw the power out of your hand.") + +/datum/action/spell/touch/Destroy() + // If we have an owner, the hand is cleaned up in Remove(), which Destroy() calls. + if(!owner) + QDEL_NULL(attached_hand) + return ..() + +/datum/action/spell/touch/Remove(mob/living/remove_from) + remove_hand(remove_from) + return ..() + +/datum/action/spell/touch/update_button(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE) + . = ..() + if(!button) + return + if(attached_hand) + button.color = COLOR_GREEN + +/datum/action/spell/touch/update_stat_status(list/stat) + if(attached_hand) + stat[STAT_STATUS] = GENERATE_STAT_TEXT("[capitalize(name)] is currently active!") + +/datum/action/spell/touch/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/carbon_owner = owner + if(!(carbon_owner.mobility_flags & MOBILITY_USE)) + return FALSE + return TRUE + +/datum/action/spell/touch/is_valid_spell(mob/user, atom/target) + return iscarbon(user) + +/** + * Creates a new hand_path hand and equips it to the caster. + * + * If the equipping action fails, reverts the cooldown and returns FALSE. + * Otherwise, registers signals and returns TRUE. + */ +/datum/action/spell/touch/proc/create_hand(mob/living/carbon/cast_on) + var/obj/item/melee/touch_attack/new_hand = new hand_path(cast_on, src) + if(!cast_on.put_in_hands(new_hand, del_on_fail = TRUE)) + reset_spell_cooldown() + if (cast_on.usable_hands == 0) + to_chat(cast_on, ("You dont have any usable hands!")) + else + to_chat(cast_on, ("Your hands are full!")) + return FALSE + + attached_hand = new_hand + RegisterSignal(attached_hand, COMSIG_ITEM_AFTERATTACK, PROC_REF(on_hand_hit)) + RegisterSignal(attached_hand, COMSIG_PARENT_QDELETING, PROC_REF(on_hand_deleted)) + RegisterSignal(attached_hand, COMSIG_ITEM_DROPPED, PROC_REF(on_hand_dropped)) + to_chat(cast_on, draw_message) + return TRUE + +/** + * Unregisters any signals and deletes the hand currently summoned by the spell. + * + * If reset_cooldown_after is TRUE, we will additionally refund the cooldown of the spell. + * If reset_cooldown_after is FALSE, we will instead just start the spell's cooldown + */ +/datum/action/spell/touch/proc/remove_hand(mob/living/hand_owner, reset_cooldown_after = FALSE) + if(!QDELETED(attached_hand)) + UnregisterSignal(attached_hand, list(COMSIG_ITEM_AFTERATTACK, COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED)) + hand_owner?.temporarilyRemoveItemFromInventory(attached_hand) + QDEL_NULL(attached_hand) + + if(reset_cooldown_after) + if(hand_owner) + to_chat(hand_owner, drop_message) + reset_spell_cooldown() + else + start_cooldown() + +// Touch spells don't go on cooldown OR give off an invocation until the hand is used itself. +/datum/action/spell/touch/pre_cast(mob/user, atom/target) + return ..() | SPELL_NO_FEEDBACK | SPELL_NO_IMMEDIATE_COOLDOWN + +/datum/action/spell/touch/on_cast(mob/living/carbon/user, atom/target) + . = ..() + if(!QDELETED(attached_hand) && (attached_hand in user.held_items)) + remove_hand(user, reset_cooldown_after = TRUE) + return + + create_hand(user) + return ..() + +/** + * Signal proc for [COMSIG_ITEM_AFTERATTACK] from our attached hand. + * + * When our hand hits an atom, we can cast do_hand_hit() on them. + */ +/datum/action/spell/touch/proc/on_hand_hit(datum/source, atom/victim, mob/caster, proximity_flag, click_parameters) + SIGNAL_HANDLER + + if(!proximity_flag) + return + if(victim == caster) + return + if(!can_cast_spell(feedback = FALSE)) + return + + INVOKE_ASYNC(src, PROC_REF(do_hand_hit), source, victim, caster) + + +/** + * Calls cast_on_hand_hit() from the caster onto the victim. + */ +/datum/action/spell/touch/proc/do_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + SEND_SIGNAL(src, COMSIG_SPELL_TOUCH_HAND_HIT, victim, caster, hand) + if(!cast_on_hand_hit(hand, victim, caster)) + return + + log_combat(caster, victim, "cast the touch spell [name] on", hand) + spell_feedback() + remove_hand(caster) + + +/** + * The actual process of casting the spell on the victim from the caster. + * + * Override / extend this to implement casting effects. + * Return TRUE on a successful cast to use up the hand (delete it) + * Return FALSE to do nothing and let them keep the hand in hand + */ +/datum/action/spell/touch/proc/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return FALSE + + + +/** + * Signal proc for [COMSIG_PARENT_QDELETING] from our attached hand. + * + * If our hand is deleted for a reason unrelated to our spell, + * unlink it (clear refs) and revert the cooldown + */ +/datum/action/spell/touch/proc/on_hand_deleted(datum/source) + SIGNAL_HANDLER + + remove_hand(reset_cooldown_after = TRUE) + +/** + * Signal proc for [COMSIG_ITEM_DROPPED] from our attached hand. + * + * If our caster drops the hand, remove the hand / revert the cast + * Basically gives them an easy hotkey to lose their hand without needing to click the button + */ +/datum/action/spell/touch/proc/on_hand_dropped(datum/source, mob/living/dropper) + SIGNAL_HANDLER + + remove_hand(dropper, reset_cooldown_after = TRUE) + +/obj/item/melee/touch_attack + name = "\improper outstretched hand" + desc = "High Five?" + icon = 'icons/obj/items_and_weapons.dmi' + lefthand_file = 'icons/mob/inhands/misc/touchspell_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/touchspell_righthand.dmi' + icon_state = "latexballon" + item_state = null + item_flags = NEEDS_PERMIT | ABSTRACT + w_class = WEIGHT_CLASS_HUGE + force = 0 + throwforce = 0 + throw_range = 0 + throw_speed = 0 + /// A weakref to what spell made us. + var/datum/weakref/spell_which_made_us + +/obj/item/melee/touch_attack/Initialize(mapload, datum/action/spell/spell) + . = ..() + + if(spell) + spell_which_made_us = WEAKREF(spell) + +/obj/item/melee/touch_attack/attack(mob/target, mob/living/carbon/user) + if(!iscarbon(user)) //Look ma, no hands + return TRUE + if(!(user.mobility_flags & MOBILITY_USE)) + to_chat(user, ("You can't reach out!")) + return TRUE + return ..() + +/** + * When the hand component of a touch spell is qdel'd, (the hand is dropped or otherwise lost), + * the cooldown on the spell that made it is automatically refunded. + * + * However, if you want to consume the hand and not give a cooldown, + * such as adding a unique behavior to the hand specifically, this function will do that. + */ +/obj/item/melee/touch_attack/proc/remove_hand_with_no_refund(mob/holder) + var/datum/action/spell/touch/hand_spell = spell_which_made_us?.resolve() + if(!QDELETED(hand_spell)) + hand_spell.remove_hand(holder, reset_cooldown_after = FALSE) + return + + // We have no spell associated for some reason, just delete us as normal. + holder.temporarilyRemoveItemFromInventory(src, force = TRUE) + qdel(src) diff --git a/code/modules/spells/spell_types/touch/flesh_to_stone.dm b/code/modules/spells/spell_types/touch/flesh_to_stone.dm new file mode 100644 index 0000000000000..666521f05434b --- /dev/null +++ b/code/modules/spells/spell_types/touch/flesh_to_stone.dm @@ -0,0 +1,33 @@ +/datum/action/spell/touch/flesh_to_stone + name = "Flesh to Stone" + desc = "This spell charges your hand with the power to turn victims into inert statues for a long period of time." + button_icon_state = "statue" + sound = 'sound/magic/fleshtostone.ogg' + + school = SCHOOL_TRANSMUTATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "STAUN EI!!" + + hand_path = /obj/item/melee/touch_attack/flesh_to_stone + +/datum/action/spell/touch/flesh_to_stone/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + if(!isliving(victim)) + return FALSE + + var/mob/living/living_victim = victim + if(living_victim.can_block_magic(antimagic_flags)) + to_chat(caster, ("The spell can't seem to affect [victim]!")) + to_chat(victim, ("You feel your flesh turn to stone for a moment, then revert back!")) + return TRUE + + living_victim.Stun(4 SECONDS) + living_victim.petrify() + return TRUE + +/obj/item/melee/touch_attack/flesh_to_stone + name = "\improper petrifying touch" + desc = "That's the bottom line, because flesh to stone said so!" + icon_state = "fleshtostone" + item_state = "fleshtostone" diff --git a/code/modules/spells/spell_types/touch/smite.dm b/code/modules/spells/spell_types/touch/smite.dm new file mode 100644 index 0000000000000..80bb17d4db580 --- /dev/null +++ b/code/modules/spells/spell_types/touch/smite.dm @@ -0,0 +1,55 @@ +/datum/action/spell/touch/smite + name = "Smite" + desc = "This spell charges your hand with an unholy energy \ + that can be used to cause a touched victim to violently explode." + button_icon_state = "gib" + sound = 'sound/magic/disintegrate.ogg' + + school = SCHOOL_EVOCATION + cooldown_time = 1 MINUTES + cooldown_reduction_per_rank = 10 SECONDS + + invocation = "EI NATH!!" + sparks_amt = 4 + + hand_path = /obj/item/melee/touch_attack/smite + +/datum/action/spell/touch/smite/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + if(!isliving(victim)) + return FALSE + + do_sparks(sparks_amt, FALSE, get_turf(victim)) + for(var/mob/living/nearby_spectator in view(caster, 7)) + if(nearby_spectator == caster) + continue + nearby_spectator.flash_act(affect_silicon = FALSE) + + var/mob/living/living_victim = victim + if(living_victim.can_block_magic(antimagic_flags)) + caster.visible_message( + ("The feedback blows [caster]'s arm off!"), + ("The spell bounces from [living_victim]'s skin back into your arm!"), + ) + caster.flash_act() + var/obj/item/bodypart/to_dismember = caster.get_holding_bodypart_of_item(hand) + to_dismember?.dismember() + return TRUE + + if(ishuman(victim)) + var/mob/living/carbon/human/human_victim = victim + var/obj/item/clothing/suit/worn_suit = human_victim.wear_suit + if(istype(worn_suit, /obj/item/clothing/suit/hooded/bloated_human)) + human_victim.visible_message(("[victim]'s [worn_suit] explodes off of them into a puddle of gore!")) + human_victim.dropItemToGround(worn_suit) + qdel(worn_suit) + new /obj/effect/gibspawner(get_turf(victim)) + return TRUE + + living_victim.gib() + return TRUE + +/obj/item/melee/touch_attack/smite + name = "\improper smiting touch" + desc = "This hand of mine glows with an awesome power!" + icon_state = "disintegrate" + item_state = "disintegrate" diff --git a/code/modules/spells/spell_types/touch_attacks.dm b/code/modules/spells/spell_types/touch_attacks.dm deleted file mode 100644 index 1fb384d21c070..0000000000000 --- a/code/modules/spells/spell_types/touch_attacks.dm +++ /dev/null @@ -1,101 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/touch - var/hand_path = /obj/item/melee/touch_attack - var/obj/item/melee/touch_attack/attached_hand = null - var/drawmessage = "You channel the power of the spell to your hand." - var/dropmessage = "You draw the power out of your hand." - invocation_type = INVOCATION_NONE //you scream on connecting, not summoning - include_user = TRUE - range = -1 - //Checks - var/spell_used = FALSE - -/obj/effect/proc_holder/spell/targeted/touch/Destroy() - remove_hand() - to_chat(usr, "The power of the spell dissipates from your hand.") - ..() - -/obj/effect/proc_holder/spell/targeted/touch/proc/remove_hand() - QDEL_NULL(attached_hand) - if(!spell_used) - charge_counter = charge_max - -/obj/effect/proc_holder/spell/targeted/touch/proc/on_hand_destroy(obj/item/melee/touch_attack/hand) - if(hand != attached_hand) - CRASH("Incorrect touch spell hand.") - //Start recharging. - attached_hand = null - recharging = TRUE - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/targeted/touch/cast(list/targets,mob/user = usr) - if(!QDELETED(attached_hand)) - remove_hand() - to_chat(user, "[dropmessage]") - return - - for(var/mob/living/carbon/target in targets) - if(!attached_hand && charge_hand(target)) - recharging = FALSE - return - -/obj/effect/proc_holder/spell/targeted/touch/charge_check(mob/user,silent = FALSE) - if(!QDELETED(attached_hand)) //Charge doesn't matter when putting the hand away. - return TRUE - return ..() - -/obj/effect/proc_holder/spell/targeted/touch/proc/create_hand() - return new hand_path(null, src) - -/obj/effect/proc_holder/spell/targeted/touch/proc/charge_hand(mob/living/carbon/user) - attached_hand = create_hand() - if(!user.put_in_hands(attached_hand)) - remove_hand() - if (user.usable_hands == 0) - to_chat(user, "You dont have any usable hands!") - else - to_chat(user, "Your hands are full!") - return FALSE - spell_used = FALSE - to_chat(user, "[drawmessage]") - return TRUE - - -/obj/effect/proc_holder/spell/targeted/touch/disintegrate - name = "Disintegrate" - desc = "This spell charges your hand with vile energy that can be used to violently explode victims." - hand_path = /obj/item/melee/touch_attack/disintegrate - - school = "evocation" - charge_max = 600 - clothes_req = TRUE - cooldown_min = 200 //100 deciseconds reduction per rank - - action_icon_state = "gib" - -/obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone - name = "Flesh to Stone" - desc = "This spell charges your hand with the power to turn victims into inert statues for a long period of time." - hand_path = /obj/item/melee/touch_attack/fleshtostone - - school = "transmutation" - charge_max = 600 - clothes_req = TRUE - cooldown_min = 200 //100 deciseconds reduction per rank - - action_icon_state = "statue" - sound = 'sound/magic/fleshtostone.ogg' - -/obj/effect/proc_holder/spell/targeted/touch/mutation - clothes_req = FALSE - var/datum/mutation/parent_mutation - -CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/proc_holder/spell/targeted/touch/mutation) - -/obj/effect/proc_holder/spell/targeted/touch/mutation/Initialize(mapload, datum/mutation/_parent) - . = ..() - if(!istype(_parent)) - return INITIALIZE_HINT_QDEL - parent_mutation = _parent - -/obj/effect/proc_holder/spell/targeted/touch/mutation/create_hand() - return new hand_path(null, src, parent_mutation) diff --git a/code/modules/spells/spell_types/trigger.dm b/code/modules/spells/spell_types/trigger.dm deleted file mode 100644 index c13c96686c6df..0000000000000 --- a/code/modules/spells/spell_types/trigger.dm +++ /dev/null @@ -1,26 +0,0 @@ -/obj/effect/proc_holder/spell/pointed/trigger - name = "Trigger" - desc = "This spell triggers another spell or a few." - var/list/linked_spells = list() //those are just referenced by the trigger spell and are unaffected by it directly - var/list/starting_spells = list() //those are added on New() to contents from default spells and are deleted when the trigger spell is deleted to prevent memory leaks - -/obj/effect/proc_holder/spell/pointed/trigger/Initialize(mapload) - . = ..() - for(var/spell in starting_spells) - var/spell_to_add = text2path(spell) - new spell_to_add(src) //should result in adding to contents, needs testing - -/obj/effect/proc_holder/spell/pointed/trigger/Destroy() - for(var/spell in contents) - qdel(spell) - linked_spells = null - starting_spells = null - return ..() - -/obj/effect/proc_holder/spell/pointed/trigger/cast(list/targets, mob/user = usr) - playMagSound() - for(var/mob/living/target in targets) - for(var/obj/effect/proc_holder/spell/spell in contents) - spell.perform(list(target),0) - for(var/obj/effect/proc_holder/spell/spell in linked_spells) - spell.perform(list(target),0) diff --git a/code/modules/spells/spell_types/turf_teleport.dm b/code/modules/spells/spell_types/turf_teleport.dm deleted file mode 100644 index 15efcc345c6b0..0000000000000 --- a/code/modules/spells/spell_types/turf_teleport.dm +++ /dev/null @@ -1,38 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/turf_teleport - name = "Turf Teleport" - desc = "This spell teleports the target to the turf in range." - nonabstract_req = TRUE - - var/inner_tele_radius = 1 - var/outer_tele_radius = 2 - - var/include_space = FALSE //whether it includes space tiles in possible teleport locations - var/include_dense = FALSE //whether it includes dense tiles in possible teleport locations - var/sound1 = 'sound/weapons/zapbang.ogg' - var/sound2 = 'sound/weapons/zapbang.ogg' - -/obj/effect/proc_holder/spell/targeted/turf_teleport/cast(list/targets,mob/user = usr) - playsound(get_turf(user), sound1, 50,1) - for(var/mob/living/target in targets) - var/list/turfs = new/list() - for(var/turf/T as() in (RANGE_TURFS(outer_tele_radius, target)-RANGE_TURFS(inner_tele_radius, target))) - if(isspaceturf(T) && !include_space) - continue - if(T.density && !include_dense) - continue - if(T.x>world.maxx-outer_tele_radius || T.xworld.maxy-outer_tele_radius || T.yYou are unable to speak!") - return FALSE - return TRUE - -/obj/effect/proc_holder/spell/voice_of_god/choose_targets(mob/user = usr) - perform(user=user) -/obj/effect/proc_holder/spell/voice_of_god/perform(list/targets, recharge = 1, mob/user = usr) - command = input(user, "Speak with the Voice of God", "Command") - if(QDELETED(src) || QDELETED(user)) - return - if(!command) - revert_cast(user) - return - ..() - -/obj/effect/proc_holder/spell/voice_of_god/cast(list/targets, mob/user = usr) - playsound(get_turf(user), speech_sound, 300, 1, 5) - var/cooldown = voice_of_god(uppertext(command), user, spans, base_multiplier = power_mod) - charge_max = (cooldown * cooldown_mod) - -/obj/effect/proc_holder/spell/voice_of_god/clown - name = "Voice of Clown" - desc = "Speak with an incredibly funny voice, startling people into obeying you for a brief moment." - power_mod = 0.1 - cooldown_mod = 0.5 - spans = list("clown") - speech_sound = 'sound/spookoween/scary_horn2.ogg' diff --git a/code/modules/spells/spell_types/wizard.dm b/code/modules/spells/spell_types/wizard.dm deleted file mode 100644 index 0bb5a62728985..0000000000000 --- a/code/modules/spells/spell_types/wizard.dm +++ /dev/null @@ -1,379 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/projectile/magic_missile - name = "Magic Missile" - desc = "This spell fires several, slow moving, magic projectiles at nearby targets." - - school = "evocation" - charge_max = 200 - clothes_req = TRUE - invocation = "FORTI GY AMA" - invocation_type = INVOCATION_SHOUT - range = 7 - cooldown_min = 60 //35 deciseconds reduction per rank - max_targets = 0 - proj_type = /obj/projectile/magic/spell/magic_missile - action_icon_state = "magicm" - sound = 'sound/magic/magic_missile.ogg' - -/obj/projectile/magic/spell/magic_missile - name = "a magic missile" - icon_state = "magicm" - range = 20 - speed = 5 - trigger_range = 0 - linger = TRUE - paralyze = 60 - hitsound = 'sound/magic/mm_hit.ogg' - - trail = TRUE - trail_lifespan = 5 - trail_icon_state = "magicmd" - -/obj/projectile/magic/spell/magic_missile/New(loc, spell_level) - . = ..() - paralyze += spell_level * 10 - -/obj/effect/proc_holder/spell/targeted/genetic/mutate - name = "Mutate" - desc = "This spell causes you to turn into a hulk and gain laser vision for a short while." - - school = "transmutation" - charge_max = 400 - clothes_req = TRUE - invocation = "BIRUZ BENNAR" - invocation_type = INVOCATION_SHOUT - range = -1 - include_user = TRUE - - mutations = list(LASEREYES, HULK) - duration = 300 - cooldown_min = 300 //25 deciseconds reduction per rank - - action_icon_state = "mutate" - sound = 'sound/magic/mutate.ogg' - - -/obj/effect/proc_holder/spell/targeted/smoke - name = "Smoke" - desc = "This spell spawns a cloud of choking smoke at your location." - - school = "conjuration" - charge_max = 120 - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = -1 - include_user = TRUE - cooldown_min = 20 //25 deciseconds reduction per rank - - smoke_spread = 2 - smoke_amt = 4 - - action_icon_state = "smoke" - -/obj/effect/proc_holder/spell/targeted/smoke/lesser //Chaplain smoke book - name = "Smoke" - desc = "This spell spawns a small cloud of choking smoke at your location." - - school = "conjuration" - charge_max = 360 - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = -1 - include_user = TRUE - - smoke_spread = 1 - smoke_amt = 2 - - action_icon_state = "smoke" - -/obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - name = "Disable Tech" - desc = "This spell disables all weapons, cameras and most other technology in range." - charge_max = 400 - clothes_req = TRUE - invocation = "NEC CANTIO" - invocation_type = INVOCATION_SHOUT - range = -1 - include_user = TRUE - cooldown_min = 200 //50 deciseconds reduction per rank - - emp_heavy = 6 - emp_light = 10 - sound = 'sound/magic/disable_tech.ogg' - -/obj/effect/proc_holder/spell/targeted/turf_teleport/blink - name = "Blink" - desc = "This spell randomly teleports you a short distance." - - school = "abjuration" - charge_max = 20 - clothes_req = TRUE - invocation = "none" - invocation_type = INVOCATION_NONE - range = -1 - include_user = TRUE - cooldown_min = 5 //4 deciseconds reduction per rank - - - smoke_spread = 1 - smoke_amt = 0 - - inner_tele_radius = 0 - outer_tele_radius = 6 - - action_icon_state = "blink" - sound1 = 'sound/magic/blink.ogg' - sound2 = 'sound/magic/blink.ogg' - -/obj/effect/proc_holder/spell/targeted/turf_teleport/blink/cult - name = "quickstep" - - charge_max = 100 - clothes_req = FALSE - clothes_req = TRUE - -/obj/effect/proc_holder/spell/targeted/area_teleport/teleport - name = "Teleport" - desc = "This spell teleports you to an area of your selection." - - school = "abjuration" - charge_max = 600 - clothes_req = TRUE - invocation = "SCYAR NILA" - invocation_type = INVOCATION_SHOUT - range = -1 - include_user = TRUE - cooldown_min = 200 //100 deciseconds reduction per rank - action_icon_state = "teleport" - - smoke_spread = 1 - smoke_amt = 2 - sound1 = 'sound/magic/teleport_diss.ogg' - sound2 = 'sound/magic/teleport_app.ogg' - -/obj/effect/proc_holder/spell/targeted/area_teleport/teleport/santa - name = "Santa Teleport" - - invocation = "HO HO HO" - clothes_req = FALSE - say_destination = FALSE // Santa moves in mysterious ways - -/obj/effect/proc_holder/spell/aoe_turf/timestop - name = "Stop Time" - desc = "This spell stops time for everyone except for you, allowing you to move freely while your enemies and even projectiles are frozen." - charge_max = 500 - clothes_req = TRUE - invocation = "TOKI WO TOMARE" - invocation_type = INVOCATION_SHOUT - range = 0 - cooldown_min = 100 - action_icon_state = "time" - var/timestop_range = 2 - var/timestop_duration = 100 - -/obj/effect/proc_holder/spell/aoe_turf/timestop/cast(list/targets, mob/user = usr) - new /obj/effect/timestop/magic(get_turf(user), timestop_range, timestop_duration, list(user)) - -/obj/effect/proc_holder/spell/aoe_turf/conjure/carp - name = "Summon Carp" - desc = "This spell conjures a simple carp." - - school = "conjuration" - charge_max = 1200 - clothes_req = TRUE - invocation = "NOUK FHUNMM SACP RISSKA" - invocation_type = INVOCATION_SHOUT - range = 1 - - summon_type = list(/mob/living/simple_animal/hostile/carp) - cast_sound = 'sound/magic/summon_karp.ogg' - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/construct - name = "Artificer" - desc = "This spell conjures a construct which may be controlled by Shades." - - school = "conjuration" - charge_max = 600 - clothes_req = FALSE - invocation = "none" - invocation_type = INVOCATION_NONE - range = 0 - - summon_type = list(/obj/structure/constructshell) - - action_icon_state = "artificer" - cast_sound = 'sound/magic/summonitems_generic.ogg' - - -/obj/effect/proc_holder/spell/aoe_turf/conjure/creature - name = "Summon Creature Swarm" - desc = "This spell tears the fabric of reality, allowing horrific daemons to spill forth." - - school = "conjuration" - charge_max = 1200 - clothes_req = FALSE - invocation = "IA IA" - invocation_type = INVOCATION_SHOUT - summon_amt = 10 - range = 3 - - summon_type = list(/mob/living/simple_animal/hostile/netherworld) - cast_sound = 'sound/magic/summonitems_generic.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/conjure/creature/cult - name = "Summon Creatures (DANGEROUS)" - clothes_req = TRUE - charge_max = 5000 - summon_amt = 2 - -/obj/effect/proc_holder/spell/aoe_turf/conjure/creature/bee - name = "Lesser summon bees" - desc = "This spell magically kicks a transdimensional beehive, instantly summoning a swarm of bees to your location. These bees are NOT friendly to anyone." - charge_max = 600 - clothes_req = TRUE - invocation = "NOT THE BEES" - summon_amt = 9 - action_icon_state = "bee" - cooldown_min = 20 SECONDS - - summon_type = /mob/living/simple_animal/hostile/poison/bees/toxin - cast_sound = 'sound/voice/moth/scream_moth.ogg' - -/obj/effect/proc_holder/spell/aoe_turf/repulse - name = "Repulse" - desc = "This spell throws everything around the user away." - charge_max = 400 - clothes_req = TRUE - invocation = "GITTAH WEIGH" - invocation_type = INVOCATION_SHOUT - range = 5 - cooldown_min = 150 - selection_type = "view" - sound = 'sound/magic/repulse.ogg' - var/maxthrow = 5 - var/sparkle_path = /obj/effect/temp_visual/gravpush - var/anti_magic_check = TRUE - var/repulse_force = MOVE_FORCE_EXTREMELY_STRONG - - action_icon_state = "repulse" - -/obj/effect/proc_holder/spell/aoe_turf/repulse/cast(list/targets,mob/user = usr, var/stun_amt = 40) - var/list/thrownatoms = list() - var/atom/throwtarget - var/distfromcaster - playMagSound() - for(var/turf/T in targets) //Done this way so things don't get thrown all around hilariously. - for(var/atom/movable/AM in T) - thrownatoms += AM - - stun_amt += 10 * spell_level - maxthrow = 5 + spell_level - - for(var/am in thrownatoms) - var/atom/movable/AM = am - if(AM == user || AM.anchored) - continue - - if(ismob(AM)) - var/mob/M = AM - if(M.anti_magic_check(anti_magic_check, FALSE)) - continue - - throwtarget = get_edge_target_turf(user, get_dir(user, get_step_away(AM, user))) - distfromcaster = get_dist(user, AM) - if(distfromcaster == 0) - if(isliving(AM)) - var/mob/living/M = AM - M.Paralyze(100) - M.adjustBruteLoss(5) - to_chat(M, "You're slammed into the floor by [user]!") - else - new sparkle_path(get_turf(AM), get_dir(user, AM)) //created sparkles will disappear on their own - if(isliving(AM)) - var/mob/living/M = AM - M.Paralyze(stun_amt) - to_chat(M, "You're thrown back by [user]!") - AM.safe_throw_at(throwtarget, ((clamp((maxthrow - (clamp(distfromcaster - 2, 0, distfromcaster))), 3, maxthrow))), 1,user, force = repulse_force)//So stuff gets tossed around at the same time. - -/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno //i fixed conflicts only to find out that this is in the WIZARD file instead of the xeno file?! - name = "Tail Sweep" - desc = "Throw back attackers with a sweep of your tail." - sound = 'sound/magic/tail_swing.ogg' - charge_max = 150 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 2 - cooldown_min = 150 - invocation_type = INVOCATION_NONE - sparkle_path = /obj/effect/temp_visual/dir_setting/tailsweep - action_icon = 'icons/hud/actions/actions_xeno.dmi' - action_icon_state = "tailsweep" - action_background_icon_state = "bg_alien" - anti_magic_check = FALSE - -/obj/effect/proc_holder/spell/aoe_turf/repulse/xeno/cast(list/targets,mob/user = usr) - if(iscarbon(user)) - var/mob/living/carbon/C = user - playsound(C.loc, 'sound/voice/hiss5.ogg', 80, 1, 1) - C.spin(6,1) - ..(targets, user, 60) - -/obj/effect/proc_holder/spell/targeted/sacred_flame - name = "Sacred Flame" - desc = "Makes everyone around you more flammable, and lights yourself on fire." - charge_max = 60 - clothes_req = FALSE - invocation = "FI'RAN DADISKO" - invocation_type = INVOCATION_SHOUT - max_targets = 0 - range = 6 - include_user = TRUE - selection_type = "view" - action_icon_state = "sacredflame" - sound = 'sound/magic/fireball.ogg' - -/obj/effect/proc_holder/spell/targeted/sacred_flame/cast(list/targets, mob/user = usr) - for(var/mob/living/L in targets) - if(L.anti_magic_check(TRUE, TRUE)) - continue - L.adjust_fire_stacks(20) - if(isliving(user)) - var/mob/living/U = user - if(!U.anti_magic_check(TRUE, TRUE)) - U.IgniteMob() - -/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket - name = "Thrown Lightning" - desc = "Forged from eldrich energies, a packet of pure power, known as a spell packet will appear in your hand, that when thrown will stun the target." - clothes_req = TRUE - item_type = /obj/item/spellpacket/lightningbolt - charge_max = 10 - action_icon_state = "thrownlightning" - -/obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket/cast(list/targets, mob/user = usr) - ..() - for(var/mob/living/carbon/C in targets) - C.throw_mode_on(THROW_MODE_TOGGLE) - -/obj/item/spellpacket/lightningbolt - name = "\improper Lightning bolt Spell Packet" - desc = "Some birdseed wrapped in cloth that crackles with electricity." - icon = 'icons/obj/toy.dmi' - icon_state = "snappop" - w_class = WEIGHT_CLASS_TINY - -/obj/item/spellpacket/lightningbolt/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(!..()) - if(isliving(hit_atom)) - var/mob/living/M = hit_atom - if(!M.anti_magic_check()) - M.electrocute_act(80, src, flags = SHOCK_ILLUSION) - qdel(src) - -/obj/item/spellpacket/lightningbolt/throw_at(atom/target, range, speed, mob/thrower, spin=TRUE, diagonals_first = FALSE, datum/callback/callback, force = INFINITY, quickstart = TRUE) - . = ..() - if(ishuman(thrower)) - var/mob/living/carbon/human/H = thrower - H.say("LIGHTNINGBOLT!!", forced = "spell") diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm index 3f8bdf4d2e7ac..c5118f1964708 100644 --- a/code/modules/surgery/bodyparts/dismemberment.dm +++ b/code/modules/surgery/bodyparts/dismemberment.dm @@ -241,7 +241,7 @@ //Handle dental implants for(var/datum/action/item_action/hands_free/activate_pill/AP in owner.actions) AP.Remove(owner) - var/obj/pill = AP.target + var/obj/pill = UNLINT(AP.master) if(pill) pill.forceMove(src) diff --git a/code/modules/surgery/dental_implant.dm b/code/modules/surgery/dental_implant.dm index 0b448fa3906cc..c1e5d12d8bf83 100644 --- a/code/modules/surgery/dental_implant.dm +++ b/code/modules/surgery/dental_implant.dm @@ -22,8 +22,7 @@ user.transferItemToLoc(tool, target, TRUE) var/datum/action/item_action/hands_free/activate_pill/P = new(tool) - P.button.name = "Activate [tool.name]" - P.target = tool + name = "Activate [tool.name]" P.Grant(target) //The pill never actually goes in an inventory slot, so the owner doesn't inherit actions from it display_results(user, target, "You wedge [tool] into [target]'s [parse_zone(surgery.location)].", @@ -34,13 +33,12 @@ /datum/action/item_action/hands_free/activate_pill name = "Activate Pill" -/datum/action/item_action/hands_free/activate_pill/Trigger() - if(!..()) - return FALSE - to_chat(owner, "You grit your teeth and burst the implanted [target.name]!") +/datum/action/item_action/hands_free/activate_pill/on_activate(mob/user, atom/target) + to_chat(owner, "You grit your teeth and burst the implanted pill!") log_combat(owner, null, "swallowed an implanted pill", target) - if(target.reagents.total_volume) - target.reagents.reaction(owner, INGEST) - target.reagents.trans_to(owner, target.reagents.total_volume, transfered_by = owner) + var/obj/item/item_target = target + if(item_target.reagents.total_volume) + item_target.reagents.reaction(owner, INGEST) + item_target.reagents.trans_to(owner, item_target.reagents.total_volume, transfered_by = owner) qdel(target) return TRUE diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm index 43fc7e56dd065..adc33020f1f09 100644 --- a/code/modules/surgery/organs/augments_internal.dm +++ b/code/modules/surgery/organs/augments_internal.dm @@ -203,7 +203,7 @@ /datum/action/item_action/update_linkedsurgery name = "Update Surgical Implant" -/datum/action/item_action/update_linkedsurgery/Trigger() +/datum/action/item_action/update_linkedsurgery/on_activate(mob/user, atom/target) if(istype(target, /obj/item/organ/cyberimp/brain/linkedsurgery)) var/obj/item/organ/cyberimp/brain/linkedsurgery/I = target var/old_surgeries_amount = length(I.advanced_surgeries) diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 1e00b6aef4be7..9ef4582f05591 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -156,16 +156,16 @@ name = "Pump your blood" //You are now brea- pumping blood manually -/datum/action/item_action/organ_action/cursed_heart/Trigger() - . = ..() +/datum/action/item_action/organ_action/cursed_heart/on_activate(mob/user, atom/target) if(. && istype(target, /obj/item/organ/heart/cursed)) var/obj/item/organ/heart/cursed/cursed_heart = target - if(world.time < (cursed_heart.last_pump + (cursed_heart.pump_delay-10))) //no spam + if(world.time < cursed_heart.last_pump + (cursed_heart.pump_delay-10)) //no spam to_chat(owner, "Too soon!") return cursed_heart.last_pump = world.time + start_cooldown(cursed_heart.pump_delay-10) playsound(owner,'sound/effects/singlebeat.ogg',40,1) to_chat(owner, "Your heart beats.") diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm index b57a867f0354c..93a61ac6a85fc 100644 --- a/code/modules/surgery/organs/vocal_cords.dm +++ b/code/modules/surgery/organs/vocal_cords.dm @@ -37,9 +37,7 @@ actions_types = list(/datum/action/item_action/organ_action/use/adamantine_vocal_cords) icon_state = "adamantine_cords" -/datum/action/item_action/organ_action/use/adamantine_vocal_cords/Trigger() - if(!IsAvailable()) - return +/datum/action/item_action/organ_action/use/adamantine_vocal_cords/on_activate(mob/user, atom/target) var/message = input(owner, "Resonate a message to all nearby golems.", "Resonate") if(QDELETED(src) || QDELETED(owner) || !message) return @@ -73,9 +71,11 @@ /datum/action/item_action/organ_action/colossus/New() ..() - cords = target + if (!istype(master, /obj/item/organ/vocal_cords/colossus)) + CRASH("/obj/item/organ/vocal_cords/colossus assigned to colossus") + cords = master -/datum/action/item_action/organ_action/colossus/IsAvailable() +/datum/action/item_action/organ_action/colossus/is_available() if(world.time < cords.next_command) return FALSE if(!owner) @@ -89,12 +89,7 @@ return FALSE return TRUE -/datum/action/item_action/organ_action/colossus/Trigger() - . = ..() - if(!IsAvailable()) - if(world.time < cords.next_command) - to_chat(owner, "You must wait [DisplayTimeText(cords.next_command - world.time)] before Speaking again.") - return +/datum/action/item_action/organ_action/colossus/on_activate(mob/user, atom/target) var/command = input(owner, "Speak with the Voice of God", "Command") if(QDELETED(src) || QDELETED(owner)) return @@ -120,6 +115,8 @@ /obj/item/organ/vocal_cords/colossus/speak_with(message) var/cooldown = voice_of_god(uppertext(message), owner, spans, base_multiplier) next_command = world.time + (cooldown * cooldown_mod) + for (var/datum/action/item_action/organ_action/colossus/action in actions) + action.start_cooldown(cooldown * cooldown_mod) ////////////////////////////////////// ///////////VOICE OF GOD/////////////// @@ -143,7 +140,7 @@ message = LOWER_TEXT(message) var/list/mob/living/listeners = list() for(var/mob/living/L in hearers(8, get_turf(user))) - if(L.can_hear() && !L.anti_magic_check(FALSE, TRUE) && L.stat != DEAD) + if(L.can_hear() && !L.can_block_magic(MAGIC_RESISTANCE_HOLY) && L.stat != DEAD) if(L == user && !include_speaker) continue diff --git a/code/modules/surgery/organs/wings.dm b/code/modules/surgery/organs/wings.dm index 096446f21ba42..b2edfb685ae43 100644 --- a/code/modules/surgery/organs/wings.dm +++ b/code/modules/surgery/organs/wings.dm @@ -178,26 +178,23 @@ return ..() /datum/action/item_action/organ_action/use/bee_dash + check_flags = AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS + cooldown_time = 10 SECONDS var/jumpspeed = 1 - var/recharging_rate = 100 - var/recharging_time = 0 -/datum/action/item_action/organ_action/use/bee_dash/Trigger() +/datum/action/item_action/organ_action/use/bee_dash/on_activate(mob/user, atom/target) var/mob/living/carbon/L = owner var/obj/item/organ/wings/bee/wings = locate(/obj/item/organ/wings/bee) in L.internal_organs var/jumpdistance = wings.jumpdist - if(L.stat != CONSCIOUS || L.buckled) // Has to be conscious and unbuckled - return - if(recharging_time > world.time) - to_chat(L, "The wings aren't ready to dash yet!") + if( L.buckled) // Has to be conscious and unbuckled return var/datum/gas_mixture/environment = L.loc.return_air() if(environment && !(environment.return_pressure() > 30)) to_chat(L, "The atmosphere is too thin for you to dash!") return - var/turf/target = get_edge_target_turf(L, L.dir) //represents the user's direction + var/turf/dash_target = get_edge_target_turf(L, L.dir) //represents the user's direction var/hoppingtable = FALSE // Triggers the trip var/jumpdistancemoved = jumpdistance // temp jumpdistance var/turf/checkjump = get_turf(L) @@ -217,10 +214,10 @@ var/datum/callback/crashcallback if(hoppingtable) crashcallback = CALLBACK(src, PROC_REF(crash_into_table), get_step(checkjump, L.dir)) - if(L.throw_at(target, jumpdistancemoved, jumpspeed, spin = FALSE, diagonals_first = TRUE, callback = crashcallback, force = MOVE_FORCE_WEAK)) + if(L.throw_at(dash_target, jumpdistancemoved, jumpspeed, spin = FALSE, diagonals_first = TRUE, callback = crashcallback, force = MOVE_FORCE_WEAK)) playsound(L, 'sound/creatures/bee.ogg', 50, 1, 1) L.visible_message("[usr] dashes forward into the air!") - recharging_time = world.time + recharging_rate + start_cooldown() else to_chat(L, "Something prevents you from dashing forward!") @@ -239,7 +236,7 @@ icon_icon = 'icons/hud/actions/actions_items.dmi' button_icon_state = "flight" -/datum/action/innate/flight/Activate() +/datum/action/innate/flight/on_activate() var/mob/living/carbon/human/H = owner var/datum/species/S = H.dna.species if(S.CanFly(H)) diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index d92e2d64a9e60..7f59250919001 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -11,6 +11,7 @@ #include "achievement_validation.dm" #include "anchored_mobs.dm" #include "antag_datums.dm" +#include "antimagic_test.dm" #include "area_contents.dm" #include "armor_verification.dm" #include "armour_checks.dm" @@ -45,6 +46,7 @@ #include "language_transfer.dm" #include "merge_type.dm" #include "metabolizing.dm" +#include "mindbound_actions.dm" #include "missing_icons.dm" #include "ntnetwork_tests.dm" #include "outfit_sanity.dm" @@ -62,6 +64,10 @@ #include "siunit.dm" #include "spawn_humans.dm" #include "species_whitelists.dm" +#include "spell_invocations.dm" +#include "spell_mindswap.dm" +#include "spell_names.dm" +#include "spell_shapeshift.dm" #include "stat_mc.dm" #include "subsystem_init.dm" #include "subsystem_metric_sanity.dm" @@ -72,7 +78,7 @@ #include "timer_sanity.dm" #include "unit_test.dm" #include "walls_have_sheets.dm" -#include "wizard.dm" +#include "wizard_loadout.dm" #include "worn_icons.dm" /* diff --git a/code/modules/unit_tests/antimagic_test.dm b/code/modules/unit_tests/antimagic_test.dm new file mode 100644 index 0000000000000..4544cacea29df --- /dev/null +++ b/code/modules/unit_tests/antimagic_test.dm @@ -0,0 +1,7 @@ +/// Verifies that antag datums have banning_keys. +/datum/unit_test/antimagic_test/Run() + var/mob/living/carbon/human/consistent/priest = allocate(/mob/living/carbon/human/consistent) + var/obj/nullrod = new /obj/item/nullrod() + priest.put_in_active_hand(nullrod) + var/result = priest.can_block_magic() + TEST_ASSERT(result, "Antimagic failed despite nullrod being equipped") diff --git a/code/modules/unit_tests/mindbound_actions.dm b/code/modules/unit_tests/mindbound_actions.dm new file mode 100644 index 0000000000000..db3ce7d9f1c17 --- /dev/null +++ b/code/modules/unit_tests/mindbound_actions.dm @@ -0,0 +1,30 @@ +/** + * Tests that actions assigned to a mob's mind + * are successfuly transferred when their mind is transferred to a new mob. + */ +/datum/unit_test/actions_moved_on_mind_transfer + +/datum/unit_test/actions_moved_on_mind_transfer/Run() + + var/mob/living/carbon/human/consistent/wizard = allocate(/mob/living/carbon/human/consistent) + var/mob/living/simple_animal/pet/dog/corgi/wizard_dog = allocate(/mob/living/simple_animal/pet/dog/corgi) + wizard.mind_initialize() + + var/datum/action/spell/pointed/projectile/fireball/fireball = new(wizard.mind) + fireball.Grant(wizard) + var/datum/action/spell/aoe/magic_missile/missile = new(wizard.mind) + missile.Grant(wizard) + var/datum/action/spell/jaunt/ethereal_jaunt/jaunt = new(wizard.mind) + jaunt.Grant(wizard) + + var/datum/mind/wizard_mind = wizard.mind + wizard_mind.transfer_to(wizard_dog) + + TEST_ASSERT_EQUAL(wizard_dog.mind, wizard_mind, "Mind transfer failed to occur, which invalidates the test.") + + for(var/datum/action/spell/remaining_spell in wizard.actions) + Fail("Spell: [remaining_spell] failed to transfer minds when a mind transfer occured.") + + qdel(fireball) + qdel(missile) + qdel(jaunt) diff --git a/code/modules/unit_tests/missing_icons.dm b/code/modules/unit_tests/missing_icons.dm index bf6ac1adeb5a8..b8bd7effe04f5 100644 --- a/code/modules/unit_tests/missing_icons.dm +++ b/code/modules/unit_tests/missing_icons.dm @@ -1,4 +1,4 @@ -/// Makes sure objects actually have icons that exist! +/// Makes sure objects and datums actually have icons that exist! /datum/unit_test/missing_icons var/static/list/possible_icon_states = list() /// additional_icon_location is for downstream modularity support. @@ -19,12 +19,35 @@ /datum/unit_test/missing_icons/Run() generate_possible_icon_states_list() generate_possible_icon_states_list("icons/effects/") + generate_possible_icon_states_list("icons/hud/") if(additional_icon_location) generate_possible_icon_states_list(additional_icon_location) //Add EVEN MORE paths if needed here! //generate_possible_icon_states_list("your/folder/path/") var/list/bad_list = list() + for(var/datum/action/action_path as anything in subtypesof(/datum/action)) + var/icon = initial(action_path.icon_icon) + if(isnull(icon)) + continue + var/icon_state = initial(action_path.button_icon_state) + if(isnull(icon_state)) + continue + + if(length(bad_list) && (icon_state in bad_list[icon])) + continue + + if(icon_exists(icon, icon_state)) + continue + + bad_list[icon] += list(icon_state) + + var/match_message + if(icon_state in possible_icon_states) + for(var/file_place in possible_icon_states[icon_state]) + match_message += (match_message ? " & '[file_place]'" : " - Matching sprite found in: '[file_place]'") + + TEST_FAIL("Missing icon_state for [action_path] in '[icon]'.\n\ticon_state = \"[icon_state]\"[match_message]") for(var/obj/obj_path as anything in subtypesof(/obj)) if(ispath(obj_path, /obj/item)) var/obj/item/item_path = obj_path diff --git a/code/modules/unit_tests/spell_invocations.dm b/code/modules/unit_tests/spell_invocations.dm new file mode 100644 index 0000000000000..9463a8c7d97bf --- /dev/null +++ b/code/modules/unit_tests/spell_invocations.dm @@ -0,0 +1,26 @@ +/** + * Validates that all spells have a correct + * invocation type and invocation setup. + */ +/datum/unit_test/spell_invocations + +/datum/unit_test/spell_invocations/Run() + + var/list/types_to_test = subtypesof(/datum/action/spell) + + for(var/datum/action/spell/spell_type as anything in types_to_test) + var/spell_name = initial(spell_type.name) + var/invoke_type = initial(spell_type.invocation_type) + switch(invoke_type) + if(INVOCATION_EMOTE) + if(isnull(initial(spell_type.invocation_self_message))) + Fail("Spell: [spell_name] ([spell_type]) set emote invocation type but did not set a self message.") + if(isnull(initial(spell_type.invocation))) + Fail("Spell: [spell_name] ([spell_type]) set emote invocation type but did not set an invocation message.") + + if(INVOCATION_SHOUT, INVOCATION_WHISPER) + if(isnull(initial(spell_type.invocation))) + Fail("Spell: [spell_name] ([spell_type]) set a speaking invocation type but did not set an invocation message.") + + // INVOCATION_NONE: + // It doesn't matter what they have set for invocation text. So not it's skipped. diff --git a/code/modules/unit_tests/spell_mindswap.dm b/code/modules/unit_tests/spell_mindswap.dm new file mode 100644 index 0000000000000..ffd5342958425 --- /dev/null +++ b/code/modules/unit_tests/spell_mindswap.dm @@ -0,0 +1,41 @@ +/** + * Validates that the mind swap spell + * properly transfers minds between a caster and a target. + * + * Also checks that the mindswap spell itself was transferred over + * to the new body on cast. + */ +/datum/unit_test/mind_swap_spell + +/datum/unit_test/mind_swap_spell/Run() + + var/mob/living/carbon/human/consistent/swapper = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/consistent/to_swap = allocate(/mob/living/carbon/human/consistent) + + swapper.forceMove(run_loc_floor_bottom_left) + to_swap.forceMove(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) + + swapper.mind_initialize() + to_swap.mind_initialize() + + var/datum/mind/swapper_mind = swapper.mind + var/datum/mind/to_swap_mind = to_swap.mind + + var/datum/action/spell/pointed/mind_transfer/mind_swap = new(swapper.mind) + mind_swap.target_requires_key = FALSE + mind_swap.Grant(swapper) + + // Perform a cast from the very base - mimics a click + var/result = mind_swap.InterceptClickOn(swapper, null, to_swap) + TEST_ASSERT(result, "[mind_swap] spell: Mind swap returned \"false\" from InterceptClickOn / cast, despite having valid conditions.") + + TEST_ASSERT_EQUAL(swapper.mind, to_swap_mind, "[mind_swap] spell: Despite returning \"true\" on cast, swap failed to relocate the minds of the caster and the target.") + TEST_ASSERT_EQUAL(to_swap.mind, swapper_mind, "[mind_swap] spell: Despite returning \"true\" on cast, swap failed to relocate the minds of the target and the caster.") + + var/datum/action/spell/pointed/mind_transfer/should_be_null = locate() in swapper.actions + var/datum/action/spell/pointed/mind_transfer/should_not_be_null = locate() in to_swap.actions + + TEST_ASSERT(!isnull(should_not_be_null), "[mind_swap] spell: The spell was not transferred to the caster's new body, despite successful mind reolcation.") + TEST_ASSERT(isnull(should_be_null), "[mind_swap] spell: The spell remained on the caster's original body, despite successful mind relocation.") + + qdel(mind_swap) diff --git a/code/modules/unit_tests/spell_names.dm b/code/modules/unit_tests/spell_names.dm new file mode 100644 index 0000000000000..2683a6660faca --- /dev/null +++ b/code/modules/unit_tests/spell_names.dm @@ -0,0 +1,32 @@ +/** + * Validates that all spells have a different name. + * + * Spell names are used for debugging in some places + * as well as an option for admins giving out spells, + * so every spell should have a distinct name. + * + * If you're making a subtype with only one or two big changes, + * consider adding an adjective to the name. + * + * "Lesser Fireball" for a subtype of Fireball with a shorter cooldown. + * "Deadly Magic Missile" for a subtype of Magic Missile that does damage, etc. + */ +/datum/unit_test/spell_names + +/datum/unit_test/spell_names/Run() + + var/list/types_to_test = typesof(/datum/action/spell) + + var/list/existing_names = list() + for(var/datum/action/spell/spell_type as anything in types_to_test) + var/spell_name = initial(spell_type.name) + if(spell_name == "Spell") + continue + + if(spell_name in existing_names) + Fail("Spell: [spell_name] ([spell_type]) had a name identical to another spell. \ + This can cause confusion for admins giving out spells, and while debugging. \ + Consider giving the name an adjective if it's a subtype. (\"Greater\", \"Lesser\", \"Deadly\".)") + continue + + existing_names += spell_name diff --git a/code/modules/unit_tests/spell_shapeshift.dm b/code/modules/unit_tests/spell_shapeshift.dm new file mode 100644 index 0000000000000..8ff2db18c7a91 --- /dev/null +++ b/code/modules/unit_tests/spell_shapeshift.dm @@ -0,0 +1,20 @@ +/** + * Validates that all shapeshift type spells + * have a valid possible_shapes setup. + */ +/datum/unit_test/shapeshift_spell_validity + +/datum/unit_test/shapeshift_spell_validity/Run() + + var/list/types_to_test = subtypesof(/datum/action/spell/shapeshift) + + for(var/spell_type in types_to_test) + var/datum/action/spell/shapeshift/shift = new spell_type() + if(!LAZYLEN(shift.possible_shapes)) + Fail("Shapeshift spell: [shift] ([spell_type]) did not have any possible shapeshift options.") + + for(var/shift_type in shift.possible_shapes) + if(!ispath(shift_type, /mob/living)) + Fail("Shapeshift spell: [shift] had an invalid / non-living shift type ([shift_type]) in their possible shapes list.") + + qdel(shift) diff --git a/code/modules/unit_tests/wizard.dm b/code/modules/unit_tests/wizard_loadout.dm similarity index 82% rename from code/modules/unit_tests/wizard.dm rename to code/modules/unit_tests/wizard_loadout.dm index 60b9c7790b425..554edc4ccaad7 100644 --- a/code/modules/unit_tests/wizard.dm +++ b/code/modules/unit_tests/wizard_loadout.dm @@ -3,10 +3,12 @@ // May this never happen again. /// Test loadouts for crashes, runtimes, stack traces and infinite loops. No ASSERTs necessary. +/datum/unit_test/wizard_loadout + /datum/unit_test/wizard_loadout/Run() for(var/loadout in ALL_WIZARD_LOADOUTS) var/obj/item/spellbook/wizard_book = allocate(/obj/item/spellbook) - var/mob/living/carbon/human/wizard = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/consistent/wizard = allocate(/mob/living/carbon/human/consistent) wizard.mind_initialize() wizard.put_in_active_hand(wizard_book, forced = TRUE) wizard_book.wizard_loadout(wizard, loadout) diff --git a/code/modules/vehicles/mecha/combat/durand.dm b/code/modules/vehicles/mecha/combat/durand.dm index 1f688bb662123..bed5d91e6a7a6 100644 --- a/code/modules/vehicles/mecha/combat/durand.dm +++ b/code/modules/vehicles/mecha/combat/durand.dm @@ -49,7 +49,7 @@ var/mob/living/occupant = O var/datum/action/action = LAZYACCESSASSOC(occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/mech_defense_mode) if(action) - action.Trigger() + action.trigger() break /obj/vehicle/sealed/mecha/combat/durand/Move(direction) @@ -66,7 +66,7 @@ if(defense_mode) var/datum/action/action = LAZYACCESSASSOC(occupant_actions, M, /datum/action/vehicle/sealed/mecha/mech_defense_mode) if(action) - INVOKE_ASYNC(action, TYPE_PROC_REF(/datum/action, Trigger), FALSE) + INVOKE_ASYNC(action, TYPE_PROC_REF(/datum/action, trigger), FALSE) return ..() ///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA. @@ -209,7 +209,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/durand_shield) for(var/occupant in chassis.occupants) var/datum/action/button = chassis.occupant_actions[occupant][/datum/action/vehicle/sealed/mecha/mech_defense_mode] button.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" - button.UpdateButtonIcon() + button.update_buttons() set_light_on(chassis.defense_mode) @@ -245,7 +245,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/durand_shield) for(var/O in chassis.occupants) var/mob/living/occupant = O var/datum/action/action = LAZYACCESSASSOC(chassis.occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/mech_defense_mode) - action.Trigger(FALSE) + action.trigger() atom_integrity = 10000 /obj/durand_shield/play_attack_sound() diff --git a/code/modules/vehicles/mecha/mecha_actions.dm b/code/modules/vehicles/mecha/mecha_actions.dm index f5b572ef46df5..70e4a95f6d05d 100644 --- a/code/modules/vehicles/mecha/mecha_actions.dm +++ b/code/modules/vehicles/mecha/mecha_actions.dm @@ -19,9 +19,7 @@ name = "Eject From Mech" button_icon_state = "mech_eject" -/datum/action/vehicle/sealed/mecha/mech_eject/Trigger() - if(!owner) - return +/datum/action/vehicle/sealed/mecha/mech_eject/on_activate(mob/user, atom/target) if(!chassis || !(owner in chassis.occupants)) return chassis.container_resist(owner) @@ -30,20 +28,20 @@ name = "Toggle Internal Airtank Usage" button_icon_state = "mech_internals_off" -/datum/action/vehicle/sealed/mecha/mech_toggle_internals/Trigger() +/datum/action/vehicle/sealed/mecha/mech_toggle_internals/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return chassis.use_internal_tank = !chassis.use_internal_tank button_icon_state = "mech_internals_[chassis.use_internal_tank ? "on" : "off"]" chassis.balloon_alert(owner, "Now taking air from the [chassis.use_internal_tank ? "internal airtank" : "environment"].") chassis.log_message("Now taking air from [chassis.use_internal_tank?"internal airtank":"environment"].", LOG_MECHA) - UpdateButtonIcon() + update_buttons() /datum/action/vehicle/sealed/mecha/mech_cycle_equip name = "Cycle Equipment" button_icon_state = "mech_cycle_equip_off" -/datum/action/vehicle/sealed/mecha/mech_cycle_equip/Trigger() +/datum/action/vehicle/sealed/mecha/mech_cycle_equip/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return @@ -61,7 +59,7 @@ chassis.balloon_alert(owner, "[chassis.selected] selected.") send_byjax(chassis.occupants,"exosuit.browser","eq_list",chassis.get_equipment_list()) button_icon_state = "mech_cycle_equip_on" - UpdateButtonIcon() + update_buttons() return var/number = 0 for(var/equipment in available_equipment) @@ -77,7 +75,7 @@ chassis.balloon_alert(owner, "Switched to [chassis.selected].") button_icon_state = "mech_cycle_equip_on" send_byjax(chassis.occupants,"exosuit.browser","eq_list",chassis.get_equipment_list()) - UpdateButtonIcon() + update_buttons() return @@ -85,7 +83,7 @@ name = "Toggle Lights" button_icon_state = "mech_lights_off" -/datum/action/vehicle/sealed/mecha/mech_toggle_lights/Trigger() +/datum/action/vehicle/sealed/mecha/mech_toggle_lights/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return if(!(chassis.mecha_flags & HAS_LIGHTS)) @@ -99,13 +97,13 @@ chassis.set_light_on(chassis.mecha_flags & LIGHTS_ON) chassis.balloon_alert(owner, "Toggled lights [(chassis.mecha_flags & LIGHTS_ON) ? "on" : "off"].") chassis.log_message("Toggled lights [(chassis.mecha_flags & LIGHTS_ON)?"on":"off"].", LOG_MECHA) - UpdateButtonIcon() + update_buttons() /datum/action/vehicle/sealed/mecha/mech_view_stats name = "View Stats" button_icon_state = "mech_view_stats" -/datum/action/vehicle/sealed/mecha/mech_view_stats/Trigger() +/datum/action/vehicle/sealed/mecha/mech_view_stats/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return var/datum/browser/popup = new(owner , "exosuit") @@ -117,7 +115,7 @@ name = "Toggle Strafing. Disabled when Alt is held." button_icon_state = "strafe" -/datum/action/vehicle/sealed/mecha/strafe/Trigger() +/datum/action/vehicle/sealed/mecha/strafe/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return @@ -138,7 +136,7 @@ for(var/occupant in occupants) var/datum/action/action = LAZYACCESSASSOC(occupant_actions, occupant, /datum/action/vehicle/sealed/mecha/strafe) - action?.UpdateButtonIcon() + action?.update_buttons() //////////////////////////////////////// Specific Ability Actions /////////////////////////////////////////////// //Need to be granted by the mech type, Not default abilities. @@ -147,20 +145,20 @@ name = "Toggle an energy shield that blocks all attacks from the faced direction at a heavy power cost." button_icon_state = "mech_defense_mode_off" -/datum/action/vehicle/sealed/mecha/mech_defense_mode/Trigger(forced_state = FALSE) +/datum/action/vehicle/sealed/mecha/mech_defense_mode/on_activate(mob/user, atom/target) SEND_SIGNAL(chassis, COMSIG_MECHA_ACTION_TRIGGER, owner, args) //Signal sent to the mech, to be handed to the shield. See durand.dm for more details /datum/action/vehicle/sealed/mecha/mech_overload_mode name = "Toggle leg actuators overload" button_icon_state = "mech_overload_off" -/datum/action/vehicle/sealed/mecha/mech_overload_mode/Trigger(forced_state = null) +/datum/action/vehicle/sealed/mecha/mech_overload_mode/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return - if(!isnull(forced_state)) + /*if(!isnull(forced_state)) chassis.leg_overload_mode = forced_state - else - chassis.leg_overload_mode = !chassis.leg_overload_mode + else Don't know what to do about this part*/ + chassis.leg_overload_mode = !chassis.leg_overload_mode button_icon_state = "mech_overload_[chassis.leg_overload_mode ? "on" : "off"]" chassis.log_message("Toggled leg actuators overload.", LOG_MECHA) if(chassis.leg_overload_mode) @@ -171,13 +169,13 @@ chassis.movedelay = initial(chassis.movedelay) chassis.step_energy_drain = chassis.normal_step_energy_drain chassis.balloon_alert(owner, "Disabled leg actuators overload.") - UpdateButtonIcon() + update_buttons() /datum/action/vehicle/sealed/mecha/mech_smoke name = "Smoke" button_icon_state = "mech_smoke" -/datum/action/vehicle/sealed/mecha/mech_smoke/Trigger() +/datum/action/vehicle/sealed/mecha/mech_smoke/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return if(!TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_SMOKE) && chassis.smoke_charges>0) @@ -185,12 +183,12 @@ chassis.smoke_charges-- TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_SMOKE, chassis.smoke_cooldown) - /datum/action/vehicle/sealed/mecha/mech_zoom name = "Zoom" button_icon_state = "mech_zoom_off" -/datum/action/vehicle/sealed/mecha/mech_zoom/Trigger() +/datum/action/vehicle/sealed/mecha/mech_zoom/on_activate(mob/user, atom/target) + if(!owner || !chassis || !(owner in chassis.occupants)) return if(owner.client) @@ -203,13 +201,13 @@ SEND_SOUND(owner, sound('sound/mecha/imag_enh.ogg',volume=50)) else owner.client.view_size.resetToDefault() //Let's not let this stack shall we? - UpdateButtonIcon() + update_buttons() /datum/action/vehicle/sealed/mecha/mech_switch_damtype name = "Reconfigure arm microtool arrays" button_icon_state = "mech_damtype_brute" -/datum/action/vehicle/sealed/mecha/mech_switch_damtype/Trigger() +/datum/action/vehicle/sealed/mecha/mech_switch_damtype/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return var/new_damtype @@ -226,16 +224,16 @@ chassis.damtype = new_damtype button_icon_state = "mech_damtype_[new_damtype]" playsound(chassis, 'sound/mecha/mechmove01.ogg', 50, TRUE) - UpdateButtonIcon() + update_buttons() /datum/action/vehicle/sealed/mecha/mech_toggle_phasing name = "Toggle Phasing" button_icon_state = "mech_phasing_off" -/datum/action/vehicle/sealed/mecha/mech_toggle_phasing/Trigger() +/datum/action/vehicle/sealed/mecha/mech_toggle_phasing/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return chassis.phasing = !chassis.phasing button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]" chassis.balloon_alert(owner, "[chassis.phasing ? "Enabled" : "Disabled"] phasing") - UpdateButtonIcon() + update_buttons() diff --git a/code/modules/vehicles/mecha/working/clarke.dm b/code/modules/vehicles/mecha/working/clarke.dm index 7fa2dee48e11e..f917d08e60cdd 100644 --- a/code/modules/vehicles/mecha/working/clarke.dm +++ b/code/modules/vehicles/mecha/working/clarke.dm @@ -101,10 +101,10 @@ /datum/action/vehicle/sealed/mecha/mech_search_ruins name = "Search for Ruins" - button_icon_state = "mech_search_ruins" + button_icon_state = "mech_search_ruins" //This is missing from code itself COOLDOWN_DECLARE(search_cooldown) -/datum/action/vehicle/sealed/mecha/mech_search_ruins/Trigger() +/datum/action/vehicle/sealed/mecha/mech_search_ruins/on_activate(mob/user, atom/target) if(!owner || !chassis || !(owner in chassis.occupants)) return if(!COOLDOWN_FINISHED(src, search_cooldown)) @@ -114,10 +114,10 @@ return var/mob/living/living_owner = owner button_icon_state = "mech_search_ruins_cooldown" - UpdateButtonIcon() + update_buttons() COOLDOWN_START(src, search_cooldown, SEARCH_COOLDOWN) addtimer(VARSET_CALLBACK(src, button_icon_state, "mech_search_ruins"), SEARCH_COOLDOWN) - addtimer(CALLBACK(src, PROC_REF(UpdateButtonIcon)), SEARCH_COOLDOWN) + addtimer(CALLBACK(src, PROC_REF(update_buttons)), SEARCH_COOLDOWN) var/obj/pinpointed_ruin for(var/obj/effect/landmark/ruin/ruin_landmark as anything in GLOB.ruin_landmarks) if(ruin_landmark.z != chassis.z) diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm index d97d774d2fa84..873985297cd6a 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -94,7 +94,7 @@ /datum/action/vehicle check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_INCAPACITATED | AB_CHECK_CONSCIOUS icon_icon = 'icons/hud/actions/actions_vehicle.dmi' - button_icon_state = "vehicle_eject" + button_icon_state = null var/obj/vehicle/vehicle_target /datum/action/vehicle/sealed @@ -106,8 +106,8 @@ desc = "Climb out of your vehicle!" button_icon_state = "car_eject" -/datum/action/vehicle/sealed/climb_out/Trigger() - if(..() && istype(vehicle_entered_target)) +/datum/action/vehicle/sealed/climb_out/on_activate(mob/user, atom/target) + if(istype(vehicle_entered_target)) vehicle_entered_target.mob_try_exit(owner, owner) /datum/action/vehicle/ridden @@ -118,7 +118,7 @@ desc = "Take your key out of the vehicle's ignition" button_icon_state = "car_removekey" -/datum/action/vehicle/sealed/remove_key/Trigger() +/datum/action/vehicle/sealed/remove_key/on_activate(mob/user, atom/target) vehicle_entered_target.remove_key(owner) //CLOWN CAR ACTION DATUMS @@ -127,31 +127,29 @@ desc = "Honk your classy horn." button_icon_state = "car_horn" var/hornsound = 'sound/items/carhorn.ogg' - var/last_honk_time - -/datum/action/vehicle/sealed/horn/Trigger() - if(world.time - last_honk_time > 20) - vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks!") - to_chat(owner, "You press the vehicle's horn.") + cooldown_time = 2 SECONDS + +/datum/action/vehicle/sealed/horn/on_activate(mob/user, atom/target) + vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks!") + to_chat(owner, "You press the vehicle's horn.") + playsound(vehicle_entered_target, hornsound, 75) + start_cooldown() + +/datum/action/vehicle/sealed/horn/clowncar/on_activate(mob/user, atom/target) + vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks!") + to_chat(owner, "You press the vehicle's horn.") + start_cooldown() + if(vehicle_target.inserted_key) + vehicle_target.inserted_key.attack_self(owner) //The key plays a sound + else playsound(vehicle_entered_target, hornsound, 75) - last_honk_time = world.time - -/datum/action/vehicle/sealed/horn/clowncar/Trigger() - if(world.time - last_honk_time > 20) - vehicle_entered_target.visible_message("[vehicle_entered_target] loudly honks!") - to_chat(owner, "You press the vehicle's horn.") - last_honk_time = world.time - if(vehicle_target.inserted_key) - vehicle_target.inserted_key.attack_self(owner) //The key plays a sound - else - playsound(vehicle_entered_target, hornsound, 75) /datum/action/vehicle/sealed/DumpKidnappedMobs name = "Dump kidnapped mobs" desc = "Dump all objects and people in your car on the floor." button_icon_state = "car_dump" -/datum/action/vehicle/sealed/DumpKidnappedMobs/Trigger() +/datum/action/vehicle/sealed/DumpKidnappedMobs/on_activate(mob/user, atom/target) vehicle_entered_target.visible_message("[vehicle_entered_target] starts dumping the people inside of it.") vehicle_entered_target.DumpSpecificMobs(VEHICLE_CONTROL_KIDNAPPED) @@ -161,7 +159,7 @@ desc = "Press one of those colorful buttons on your display panel!" button_icon_state = "car_rtd" -/datum/action/vehicle/sealed/RollTheDice/Trigger() +/datum/action/vehicle/sealed/RollTheDice/on_activate(mob/user, atom/target) if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target C.RollTheDice(owner) @@ -171,7 +169,7 @@ desc = "Destroy them with their own fodder" button_icon_state = "car_cannon" -/datum/action/vehicle/sealed/Cannon/Trigger() +/datum/action/vehicle/sealed/Cannon/on_activate(mob/user, atom/target) if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target if(C.cannonbusy) @@ -182,63 +180,61 @@ name = "Thank the Clown car Driver" desc = "They're just doing their job." button_icon_state = "car_thanktheclown" - var/last_thank_time + cooldown_time = 6 SECONDS -/datum/action/vehicle/sealed/Thank/Trigger() +/datum/action/vehicle/sealed/Thank/on_activate(mob/user, atom/target) if(istype(vehicle_entered_target, /obj/vehicle/sealed/car/clowncar)) var/obj/vehicle/sealed/car/clowncar/C = vehicle_entered_target - if(world.time >= last_thank_time + 60) - var/mob/living/carbon/human/clown = pick(C.return_drivers()) - owner.say("Thank you for the fun ride, [clown.name]!") - last_thank_time = world.time - C.ThanksCounter() + var/mob/living/carbon/human/clown = pick(C.return_drivers()) + owner.say("Thank you for the fun ride, [clown.name]!") + start_cooldown() + C.ThanksCounter() /datum/action/vehicle/ridden/scooter/skateboard/ollie name = "Ollie" desc = "Get some air! Land on a table to do a gnarly grind." button_icon_state = "skateboard_ollie" ///Cooldown to next jump - var/next_ollie + cooldown_time = 0.5 SECONDS -/datum/action/vehicle/ridden/scooter/skateboard/ollie/Trigger() - if(world.time > next_ollie) - var/obj/vehicle/ridden/scooter/skateboard/V = vehicle_target - if (V.grinding) - return - var/mob/living/L = owner - var/turf/landing_turf = get_step(V.loc, V.dir) - var/multiplier = 1 - if(HAS_TRAIT(L, TRAIT_PROSKATER)) - multiplier = 0.3 //70% reduction - L.adjustStaminaLoss(V.instability * multiplier * 2) - if (L.getStaminaLoss() >= 100) - playsound(src, 'sound/effects/bang.ogg', 20, TRUE) - V.unbuckle_mob(L) - L.throw_at(landing_turf, 2, 2) - L.Paralyze(multiplier * 40) - V.visible_message("[L] misses the landing and falls on [L.p_their()] face!") - else - L.spin(4, 1) - animate(L, pixel_y = -6, time = 4) - animate(V, pixel_y = -6, time = 3) - playsound(V, 'sound/vehicles/skateboard_ollie.ogg', 50, TRUE) - passtable_on(L, VEHICLE_TRAIT) - V.pass_flags |= PASSTABLE - L.Move(landing_turf, vehicle_target.dir) - passtable_off(L, VEHICLE_TRAIT) - V.pass_flags &= ~PASSTABLE - if(locate(/obj/structure/table) in V.loc.contents) - V.grinding = TRUE - V.icon_state = "[V.board_icon]-grind" - addtimer(CALLBACK(V, TYPE_PROC_REF(/obj/vehicle/ridden/scooter/skateboard, grind)), 2) - next_ollie = world.time + 5 +/datum/action/vehicle/ridden/scooter/skateboard/ollie/on_activate(mob/user, atom/target) + var/obj/vehicle/ridden/scooter/skateboard/V = vehicle_target + if (V.grinding) + return + var/mob/living/L = owner + var/turf/landing_turf = get_step(V.loc, V.dir) + var/multiplier = 1 + if(HAS_TRAIT(L, TRAIT_PROSKATER)) + multiplier = 0.3 //70% reduction + L.adjustStaminaLoss(V.instability * multiplier * 2) + if (L.getStaminaLoss() >= 100) + playsound(src, 'sound/effects/bang.ogg', 20, TRUE) + V.unbuckle_mob(L) + L.throw_at(landing_turf, 2, 2) + L.Paralyze(multiplier * 40) + V.visible_message("[L] misses the landing and falls on [L.p_their()] face!") + else + L.spin(4, 1) + animate(L, pixel_y = -6, time = 4) + animate(V, pixel_y = -6, time = 3) + playsound(V, 'sound/vehicles/skateboard_ollie.ogg', 50, TRUE) + passtable_on(L, VEHICLE_TRAIT) + V.pass_flags |= PASSTABLE + L.Move(landing_turf, vehicle_target.dir) + passtable_off(L, VEHICLE_TRAIT) + V.pass_flags &= ~PASSTABLE + if(locate(/obj/structure/table) in V.loc.contents) + V.grinding = TRUE + V.icon_state = "[V.board_icon]-grind" + addtimer(CALLBACK(V, TYPE_PROC_REF(/obj/vehicle/ridden/scooter/skateboard, grind)), 2) + start_cooldown() /datum/action/vehicle/ridden/scooter/skateboard/kflip name = "Kick Flip" desc = "Do a sweet kickflip to dismount... in style." button_icon_state = "skateboard_ollie" -/datum/action/vehicle/ridden/scooter/skateboard/kflip/Trigger() +/datum/action/vehicle/ridden/scooter/skateboard/kflip/on_activate(mob/user, atom/target) var/obj/vehicle/ridden/scooter/skateboard/V = vehicle_target var/mob/living/L = owner var/multiplier = 1 diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm index bf9b9c1a86d30..19c1053c7eef4 100644 --- a/code/modules/wiremod/shell/brain_computer_interface.dm +++ b/code/modules/wiremod/shell/brain_computer_interface.dm @@ -137,7 +137,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/circuit_component/bci_action) name = "Action" icon_icon = 'icons/hud/actions/actions_items.dmi' check_flags = AB_CHECK_CONSCIOUS - button_icon_state = "bci_power" + button_icon_state = "power_green" var/obj/item/circuit_component/bci_action/circuit_component @@ -152,7 +152,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/circuit_component/bci_action) return ..() -/datum/action/innate/bci_action/Activate() +/datum/action/innate/bci_action/on_activate() circuit_component.signal.set_output(COMPONENT_SIGNAL) /obj/item/circuit_component/bci_core @@ -282,12 +282,16 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/circuit_component/bci_action) src.circuit_component = circuit_component - button.maptext_x = 2 - button.maptext_y = 0 - update_maptext() + update_buttons() START_PROCESSING(SSobj, src) +/datum/action/innate/bci_charge_action/create_button() + var/atom/movable/screen/movable/action_button/button = ..() + button.maptext_x = 2 + button.maptext_y = 0 + return button + /datum/action/innate/bci_charge_action/Destroy() circuit_component.charge_action = null circuit_component = null @@ -296,7 +300,7 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/circuit_component/bci_action) return ..() -/datum/action/innate/bci_charge_action/Trigger() +/datum/action/innate/bci_charge_action/on_activate(mob/user, atom/target) var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell if (isnull(cell)) @@ -306,9 +310,14 @@ CREATION_TEST_IGNORE_SUBTYPES(/obj/item/circuit_component/bci_action) to_chat(owner, "You can recharge it by using a cyborg recharging station.") /datum/action/innate/bci_charge_action/process(delta_time) - update_maptext() + update_buttons() -/datum/action/innate/bci_charge_action/proc/update_maptext() +/datum/action/innate/bci_charge_action/update_button(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE) + . = ..() + if(!.) + return + if(status_only) + return var/obj/item/stock_parts/cell/cell = circuit_component.parent.cell button.maptext = cell ? MAPTEXT("[cell.percent()]%") : "" diff --git a/code/modules/xenoarchaeology/traits/xenoartifact_minors.dm b/code/modules/xenoarchaeology/traits/xenoartifact_minors.dm index 7a32176d15f95..c073e8d0dc226 100644 --- a/code/modules/xenoarchaeology/traits/xenoartifact_minors.dm +++ b/code/modules/xenoarchaeology/traits/xenoartifact_minors.dm @@ -153,8 +153,8 @@ log_game("[key_name_admin(man)] took control of the sentient [X]. [X] located at [AREACOORD(X)]") man.forceMove(X) man.set_anchored(TRUE) - var/obj/effect/proc_holder/spell/targeted/xeno_senitent_action/P = new /obj/effect/proc_holder/spell/targeted/xeno_senitent_action(,X) - man.AddSpell(P) + var/datum/action/xeno_senitent_action/P = new /datum/action/xeno_senitent_action(X) + P.Grant(man) //show little guy his traits to_chat(man, "Your traits are: \n") for(var/datum/xenoartifact_trait/T in X.traits) @@ -163,32 +163,26 @@ playsound(get_turf(X), 'sound/items/haunted/ghostitemattack.ogg', 50, TRUE) qdel(S) -/obj/effect/proc_holder/spell/targeted/xeno_senitent_action //Lets sentience target goober +/datum/action/xeno_senitent_action //Lets sentience target goober name = "Activate" desc = "Select a target to activate your traits on." - range = 1 - charge_max = 0 SECONDS - clothes_req = 0 - include_user = 0 - action_icon = 'icons/hud/actions/actions_revenant.dmi' - action_icon_state = "r_transmit" - action_background_icon_state = "bg_spell" - var/obj/item/xenoartifact/xeno - -CREATION_TEST_IGNORE_SUBTYPES(/obj/effect/proc_holder/spell/targeted/xeno_senitent_action) - -/obj/effect/proc_holder/spell/targeted/xeno_senitent_action/Initialize(mapload, var/obj/item/xenoartifact/Z) + icon_icon = 'icons/hud/actions/actions_revenant.dmi' + button_icon_state = "r_transmit" + background_icon_state = "bg_spell" + requires_target = TRUE + +/datum/action/xeno_senitent_action/New(master) . = ..() - xeno = Z - range = Z.max_range+1 + if (istype(master, /obj/item/xenoartifact)) + CRASH("Xeno artifact action assigned to a non-xeno artifact") -/obj/effect/proc_holder/spell/targeted/xeno_senitent_action/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - if(!xeno) - return - for(var/atom/M in targets) - xeno.true_target += xeno.process_target(M) - xeno.default_activate(xeno.charge_req+10) - charge_max = xeno.cooldown+xeno.cooldownmod +/datum/action/xeno_senitent_action/on_activate(mob/user, atom/target) + . = ..() + var/obj/item/xenoartifact/xeno = master + xeno.true_target += xeno.process_target(target) + xeno.default_activate(xeno.charge_req+10) + cooldown_time = xeno.cooldown+xeno.cooldownmod + start_cooldown() /datum/xenoartifact_trait/minor/sentient/Destroy(force, ...) . = ..() diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi new file mode 100644 index 0000000000000..9676a833588eb Binary files /dev/null and b/icons/effects/eldritch.dmi differ diff --git a/icons/effects/mouse_pointers/barn_target.dmi b/icons/effects/mouse_pointers/barn_target.dmi new file mode 100644 index 0000000000000..475a2b88b192a Binary files /dev/null and b/icons/effects/mouse_pointers/barn_target.dmi differ diff --git a/icons/effects/mouse_pointers/blind_target.dmi b/icons/effects/mouse_pointers/blind_target.dmi new file mode 100644 index 0000000000000..1d33fc7a4c2bd Binary files /dev/null and b/icons/effects/mouse_pointers/blind_target.dmi differ diff --git a/icons/effects/mouse_pointers/cluwne.dmi b/icons/effects/mouse_pointers/cluwne.dmi new file mode 100644 index 0000000000000..343e25482d25c Binary files /dev/null and b/icons/effects/mouse_pointers/cluwne.dmi differ diff --git a/icons/effects/mouse_pointers/compromise_target.dmi b/icons/effects/mouse_pointers/compromise_target.dmi new file mode 100644 index 0000000000000..3baa48c9c98e8 Binary files /dev/null and b/icons/effects/mouse_pointers/compromise_target.dmi differ diff --git a/icons/effects/mouse_pointers/cool_sword.dmi b/icons/effects/mouse_pointers/cool_sword.dmi new file mode 100644 index 0000000000000..a34fa56d87d3c Binary files /dev/null and b/icons/effects/mouse_pointers/cool_sword.dmi differ diff --git a/icons/effects/mouse_pointers/cult_target.dmi b/icons/effects/mouse_pointers/cult_target.dmi new file mode 100644 index 0000000000000..650feb3361343 Binary files /dev/null and b/icons/effects/mouse_pointers/cult_target.dmi differ diff --git a/icons/effects/mouse_pointers/examine_pointer.dmi b/icons/effects/mouse_pointers/examine_pointer.dmi new file mode 100644 index 0000000000000..41a9cec13b07f Binary files /dev/null and b/icons/effects/mouse_pointers/examine_pointer.dmi differ diff --git a/icons/effects/mouse_pointers/honorbound.dmi b/icons/effects/mouse_pointers/honorbound.dmi new file mode 100644 index 0000000000000..8c19973933b23 Binary files /dev/null and b/icons/effects/mouse_pointers/honorbound.dmi differ diff --git a/icons/effects/mouse_pointers/lace.dmi b/icons/effects/mouse_pointers/lace.dmi new file mode 100644 index 0000000000000..68aad755c627e Binary files /dev/null and b/icons/effects/mouse_pointers/lace.dmi differ diff --git a/icons/effects/mouse_pointers/light_drag.dmi b/icons/effects/mouse_pointers/light_drag.dmi new file mode 100644 index 0000000000000..a66d55b662b23 Binary files /dev/null and b/icons/effects/mouse_pointers/light_drag.dmi differ diff --git a/icons/effects/mouse_pointers/mecha_mouse-disable.dmi b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi new file mode 100644 index 0000000000000..48924c58c2928 Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse-disable.dmi differ diff --git a/icons/effects/mouse_pointers/mecha_mouse.dmi b/icons/effects/mouse_pointers/mecha_mouse.dmi new file mode 100644 index 0000000000000..4b46a44684068 Binary files /dev/null and b/icons/effects/mouse_pointers/mecha_mouse.dmi differ diff --git a/icons/effects/mouse_pointers/mindswap_target.dmi b/icons/effects/mouse_pointers/mindswap_target.dmi new file mode 100644 index 0000000000000..32ccda154db9a Binary files /dev/null and b/icons/effects/mouse_pointers/mindswap_target.dmi differ diff --git a/icons/effects/mouse_pointers/moon_target.dmi b/icons/effects/mouse_pointers/moon_target.dmi new file mode 100644 index 0000000000000..d89cab42aa124 Binary files /dev/null and b/icons/effects/mouse_pointers/moon_target.dmi differ diff --git a/icons/effects/mouse_pointers/overload_machine_target.dmi b/icons/effects/mouse_pointers/overload_machine_target.dmi new file mode 100644 index 0000000000000..8bc67cdab6c9f Binary files /dev/null and b/icons/effects/mouse_pointers/overload_machine_target.dmi differ diff --git a/icons/effects/mouse_pointers/override_machine_target.dmi b/icons/effects/mouse_pointers/override_machine_target.dmi new file mode 100644 index 0000000000000..77dbb4ba32bca Binary files /dev/null and b/icons/effects/mouse_pointers/override_machine_target.dmi differ diff --git a/icons/effects/mouse_pointers/scan_target.dmi b/icons/effects/mouse_pointers/scan_target.dmi new file mode 100644 index 0000000000000..bc3e2eaf16bbb Binary files /dev/null and b/icons/effects/mouse_pointers/scan_target.dmi differ diff --git a/icons/effects/mouse_pointers/screen_drag.dmi b/icons/effects/mouse_pointers/screen_drag.dmi new file mode 100644 index 0000000000000..48e4fce85f7a4 Binary files /dev/null and b/icons/effects/mouse_pointers/screen_drag.dmi differ diff --git a/icons/effects/mouse_pointers/supplypod_down_target.dmi b/icons/effects/mouse_pointers/supplypod_down_target.dmi new file mode 100644 index 0000000000000..53a3bee0a7872 Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_down_target.dmi differ diff --git a/icons/effects/mouse_pointers/supplypod_pickturf.dmi b/icons/effects/mouse_pointers/supplypod_pickturf.dmi new file mode 100644 index 0000000000000..3ca1131e1a856 Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf.dmi differ diff --git a/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi new file mode 100644 index 0000000000000..113fe47540c38 Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_pickturf_down.dmi differ diff --git a/icons/effects/mouse_pointers/supplypod_target.dmi b/icons/effects/mouse_pointers/supplypod_target.dmi new file mode 100644 index 0000000000000..94401d7a8ae96 Binary files /dev/null and b/icons/effects/mouse_pointers/supplypod_target.dmi differ diff --git a/icons/effects/mouse_pointers/swap_target.dmi b/icons/effects/mouse_pointers/swap_target.dmi new file mode 100644 index 0000000000000..39278dd77687c Binary files /dev/null and b/icons/effects/mouse_pointers/swap_target.dmi differ diff --git a/icons/effects/mouse_pointers/throw_target.dmi b/icons/effects/mouse_pointers/throw_target.dmi new file mode 100644 index 0000000000000..660eafbf2b19a Binary files /dev/null and b/icons/effects/mouse_pointers/throw_target.dmi differ diff --git a/icons/effects/mouse_pointers/volt_target.dmi b/icons/effects/mouse_pointers/volt_target.dmi new file mode 100644 index 0000000000000..43ff78599c0a0 Binary files /dev/null and b/icons/effects/mouse_pointers/volt_target.dmi differ diff --git a/icons/effects/mouse_pointers/weapon_pointer.dmi b/icons/effects/mouse_pointers/weapon_pointer.dmi new file mode 100644 index 0000000000000..b5070062c0bb6 Binary files /dev/null and b/icons/effects/mouse_pointers/weapon_pointer.dmi differ diff --git a/icons/effects/mouse_pointers/wrap_target.dmi b/icons/effects/mouse_pointers/wrap_target.dmi new file mode 100644 index 0000000000000..2e9a338c9ec23 Binary files /dev/null and b/icons/effects/mouse_pointers/wrap_target.dmi differ diff --git a/icons/hud/64x16_actions.dmi b/icons/hud/64x16_actions.dmi new file mode 100644 index 0000000000000..812d888846ebd Binary files /dev/null and b/icons/hud/64x16_actions.dmi differ diff --git a/icons/hud/actions.dmi b/icons/hud/actions.dmi new file mode 100644 index 0000000000000..19f8a470ddec0 Binary files /dev/null and b/icons/hud/actions.dmi differ diff --git a/icons/hud/actions/actions_heretic.dmi b/icons/hud/actions/actions_ecult.dmi similarity index 100% rename from icons/hud/actions/actions_heretic.dmi rename to icons/hud/actions/actions_ecult.dmi diff --git a/icons/hud/actions/actions_mecha.dmi b/icons/hud/actions/actions_mecha.dmi index b97094cb95324..6f1dac29bda76 100644 Binary files a/icons/hud/actions/actions_mecha.dmi and b/icons/hud/actions/actions_mecha.dmi differ diff --git a/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi index 1e34ba1144290..9f97ee81606b9 100644 Binary files a/icons/hud/screen_gen.dmi and b/icons/hud/screen_gen.dmi differ diff --git a/icons/hud/style/screen_midnight.dmi b/icons/hud/style/screen_midnight.dmi index 7e43349ee6d1b..2c3d4115ffbf8 100644 Binary files a/icons/hud/style/screen_midnight.dmi and b/icons/hud/style/screen_midnight.dmi differ diff --git a/icons/hud/style/screen_operative.dmi b/icons/hud/style/screen_operative.dmi index 3c30a229de04f..0102fd05c7351 100644 Binary files a/icons/hud/style/screen_operative.dmi and b/icons/hud/style/screen_operative.dmi differ diff --git a/icons/hud/style/screen_plasmafire.dmi b/icons/hud/style/screen_plasmafire.dmi index 6932d9b2f9c82..41674c5a31e30 100644 Binary files a/icons/hud/style/screen_plasmafire.dmi and b/icons/hud/style/screen_plasmafire.dmi differ diff --git a/icons/hud/style/screen_retro.dmi b/icons/hud/style/screen_retro.dmi index 5c1c7b9d2a53a..7ddfa44d78cf0 100644 Binary files a/icons/hud/style/screen_retro.dmi and b/icons/hud/style/screen_retro.dmi differ diff --git a/icons/hud/style/screen_slimecore.dmi b/icons/hud/style/screen_slimecore.dmi index 3a158e29436b1..8a4f3bed27282 100644 Binary files a/icons/hud/style/screen_slimecore.dmi and b/icons/hud/style/screen_slimecore.dmi differ diff --git a/icons/hud/style/screen_trasenknox.dmi b/icons/hud/style/screen_trasenknox.dmi index cbb6de11fd3d0..59480d4f722b4 100644 Binary files a/icons/hud/style/screen_trasenknox.dmi and b/icons/hud/style/screen_trasenknox.dmi differ diff --git a/icons/hud/unused/actions_genetic.dmi b/icons/hud/unused/actions_genetic.dmi index 2d608c0fe6b0a..bc41e919a0cab 100644 Binary files a/icons/hud/unused/actions_genetic.dmi and b/icons/hud/unused/actions_genetic.dmi differ diff --git a/icons/mob/effects/genetics.dmi b/icons/mob/effects/genetics.dmi new file mode 100644 index 0000000000000..76d5224b3c7e7 Binary files /dev/null and b/icons/mob/effects/genetics.dmi differ diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi index 500d712390dd6..ed6ac1f7ea95d 100644 Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ diff --git a/sound/effects/wounds/sizzle1.ogg b/sound/effects/wounds/sizzle1.ogg new file mode 100644 index 0000000000000..4a3d2290181cf Binary files /dev/null and b/sound/effects/wounds/sizzle1.ogg differ diff --git a/sound/magic/magic_block.ogg b/sound/magic/magic_block.ogg new file mode 100644 index 0000000000000..8e737f36fdab0 Binary files /dev/null and b/sound/magic/magic_block.ogg differ diff --git a/sound/magic/magic_block_holy.ogg b/sound/magic/magic_block_holy.ogg new file mode 100644 index 0000000000000..7b494f923ab26 Binary files /dev/null and b/sound/magic/magic_block_holy.ogg differ diff --git a/sound/magic/magic_block_mind.ogg b/sound/magic/magic_block_mind.ogg new file mode 100644 index 0000000000000..7525dca3f2c32 Binary files /dev/null and b/sound/magic/magic_block_mind.ogg differ diff --git a/sound/misc/scary_horn.ogg b/sound/misc/scary_horn.ogg new file mode 100644 index 0000000000000..fb2805bd2cdb8 Binary files /dev/null and b/sound/misc/scary_horn.ogg differ diff --git a/tgui/packages/tgui/interfaces/Spellbook.js b/tgui/packages/tgui/interfaces/Spellbook.js new file mode 100644 index 0000000000000..0c84d63c8fbe6 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Spellbook.js @@ -0,0 +1,581 @@ +import { multiline } from 'common/string'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Dimmer, Divider, Icon, NoticeBox, ProgressBar, Section, Stack } from '../components'; +import { Window } from '../layouts'; +const TAB2NAME = [ + { + title: 'Enscribed Name', + blurb: + "This book answers only to its owner, and of course, must have one. The permanence of the pact between a spellbook and its owner ensures such a powerful artifact cannot fall into enemy hands, or be used in ways that break the Federation's rules such as bartering spells.", + component: () => EnscribedName, + noScrollable: 2, + }, + { + title: 'Table of Contents', + blurb: null, + component: () => TableOfContents, + }, + { + title: 'Offensive', + blurb: 'Spells and items geared towards debilitating and destroying.', + }, + { + title: 'Defensive', + blurb: "Spells and items geared towards improving your survivability or reducing foes' ability to attack.", + }, + { + title: 'Mobility', + blurb: 'Spells and items geared towards improving your ability to move. It is a good idea to take at least one.', + }, + { + title: 'Assistance', + blurb: + 'Spells and items geared towards bringing in outside forces to aid you or improving upon your other items and abilities.', + }, + { + title: 'Challenges', + blurb: + 'The Wizard Federation is looking for shows of power. Arming the station against you will increase the danger, but will grant you more charges for your spellbook.', + locked: true, + noScrollable: 1, + }, + { + title: 'Rituals', + blurb: 'These powerful spells change the very fabric of reality. Not always in your favour.', + }, + { + title: 'Loadouts', + blurb: + 'The Wizard Federation accepts that sometimes, choosing is hard. You can choose from some approved wizard loadouts here.', + component: () => Loadouts, + noScrollable: 2, + }, + { + title: 'Randomize', + blurb: "If you didn't like the loadouts offered, you can embrace chaos. Not recommended for newer wizards.", + component: () => Randomize, + }, +]; +const BUYWORD2ICON = { + Learn: 'plus', + Summon: 'hat-wizard', + Cast: 'meteor', +}; +const EnscribedName = (props, context) => { + const { act, data } = useBackend(context); + const { owner } = data; + return ( + <> + + {owner} + + + + ); +}; +const lineHeightToc = '34.6px'; +const TableOfContents = (props, context) => { + const { act, data } = useBackend(context); + const [tabIndex, setTabIndex] = useLocalState(context, 'tab-index', 1); + return ( + +