From 6d148fdf9ea2aacd41eb731a11be446f344cf397 Mon Sep 17 00:00:00 2001 From: tattax <71668564+tattax@users.noreply.github.com> Date: Thu, 6 Apr 2023 20:32:39 -0300 Subject: [PATCH 01/75] commit 1 - get me out --- code/__DEFINES/actions.dm | 20 + code/__DEFINES/antagonists.dm | 27 +- code/__DEFINES/atom_hud.dm | 30 - code/__DEFINES/bloodsuckers.dm | 87 +- code/__DEFINES/colors.dm | 7 + code/__DEFINES/cult.dm | 4 + .../signals_atom/signals_atom_x_act.dm | 2 +- code/__DEFINES/dcs/signals/signals_datum.dm | 7 + .../dcs/signals/signals_mob/signals_carbon.dm | 2 +- .../dcs/signals/signals_mob/signals_living.dm | 3 + code/__DEFINES/dcs/signals/signals_object.dm | 18 +- code/__DEFINES/dcs/signals/signals_spell.dm | 2 +- code/__DEFINES/flags.dm | 1 + code/__DEFINES/lighting.dm | 3 + code/__DEFINES/magic.dm | 109 +++ code/__DEFINES/misc.dm | 10 + code/__DEFINES/mobs.dm | 1 + code/__DEFINES/power.dm | 5 + code/__DEFINES/security.dm | 74 ++ code/__DEFINES/status_effects.dm | 96 +- code/__DEFINES/text.dm | 7 +- code/__DEFINES/traits.dm | 44 + code/__DEFINES/vv.dm | 1 + code/__DEFINES/{yogs_defines}/antagonists.dm | 2 +- code/__HELPERS/_lists.dm | 2 + code/__HELPERS/hallucinations.dm | 250 +++++ code/__HELPERS/maths.dm | 157 +++ code/__HELPERS/roundend.dm | 7 +- code/__HELPERS/unsorted.dm | 4 - code/_onclick/click.dm | 4 +- code/_onclick/cyborg.dm | 15 +- code/_onclick/hud/_defines.dm | 13 + code/_onclick/hud/action_button.dm | 4 +- code/_onclick/hud/devil.dm | 1 - code/_onclick/hud/hud.dm | 9 - code/_onclick/hud/human.dm | 43 +- code/_onclick/hud/monkey.dm | 7 - code/_onclick/other_mobs.dm | 22 +- code/controllers/subsystem/pai.dm | 8 +- .../subsystem/processing/antag_hud.dm | 4 + code/controllers/subsystem/statpanel.dm | 50 +- code/datums/action.dm | 884 ----------------- code/datums/actions/action.dm | 256 +++++ code/datums/actions/cooldown_action.dm | 221 +++++ code/datums/actions/innate_action.dm | 84 ++ code/datums/actions/item_action.dm | 33 + code/datums/actions/items/adjust.dm | 7 + code/datums/actions/{ => items}/beam_rifle.dm | 1 - code/datums/actions/items/clockcult.dm | 32 + code/datums/actions/items/cult_dagger.dm | 37 + code/datums/actions/items/hands_free.dm | 8 + code/datums/actions/items/organ_action.dm | 24 + code/datums/actions/items/set_internals.dm | 12 + code/datums/actions/items/stealth_box.dm | 55 ++ code/datums/actions/items/toggles.dm | 159 ++++ code/datums/actions/items/vortex_recall.dm | 12 + code/datums/actions/mobs/language_menu.dm | 13 + code/datums/actions/mobs/small_sprite.dm | 50 + code/datums/ai_laws.dm | 4 +- code/datums/brain_damage/creepy_trauma.dm | 15 +- code/datums/brain_damage/imaginary_friend.dm | 2 +- code/datums/brain_damage/mild.dm | 57 +- code/datums/brain_damage/phobia.dm | 14 +- code/datums/brain_damage/severe.dm | 61 +- code/datums/brain_damage/split_personality.dm | 8 +- code/datums/components/igniter.dm | 2 +- code/datums/components/mood.dm | 31 +- code/datums/components/spooky.dm | 14 +- code/datums/components/stationloving.dm | 10 +- code/datums/components/storage/storage.dm | 92 +- code/datums/components/wet_floor.dm | 4 +- .../diseases/advance/symptoms/confusion.dm | 2 +- .../datums/diseases/advance/symptoms/dizzy.dm | 2 +- code/datums/diseases/advance/symptoms/fire.dm | 8 +- .../diseases/advance/symptoms/hallucigen.dm | 2 +- .../diseases/advance/symptoms/narcolepsy.dm | 4 +- .../diseases/advance/symptoms/sensory.dm | 21 +- code/datums/diseases/anxiety.dm | 28 +- code/datums/diseases/brainrot.dm | 2 +- code/datums/diseases/decloning.dm | 4 +- code/datums/diseases/heart_failure.dm | 4 +- code/datums/diseases/jitters.dm | 6 +- code/datums/diseases/plague.dm | 2 +- code/datums/diseases/rhumba_beat.dm | 2 +- code/datums/diseases/sleepy.dm | 4 +- code/datums/diseases/transformation.dm | 2 +- code/datums/diseases/tuberculosis.dm | 4 +- code/datums/emotes.dm | 96 +- code/datums/helper_datums/teleport.dm | 2 +- code/datums/hud.dm | 520 +++++++--- code/datums/martial/cqc.dm | 4 +- code/datums/martial/flying_fang.dm | 10 +- code/datums/martial/hunterfu.dm | 10 +- code/datums/martial/plasma_fist.dm | 10 +- code/datums/martial/psychotic_brawl.dm | 4 +- code/datums/martial/sleeping_carp.dm | 2 +- code/datums/martial/wrestling.dm | 2 +- code/datums/mind.dm | 59 +- .../mood_events/generic_negative_events.dm | 2 +- code/datums/mutations.dm | 13 +- code/datums/mutations/actions.dm | 110 --- code/datums/mutations/antenna.dm | 116 ++- code/datums/mutations/autotomy.dm | 42 + code/datums/mutations/body.dm | 12 +- code/datums/mutations/cold.dm | 30 +- code/datums/mutations/fire_breath.dm | 96 ++ code/datums/mutations/hulk.dm | 2 +- code/datums/mutations/speech.dm | 2 +- code/datums/mutations/telepathy.dm | 10 + code/datums/mutations/touch.dm | 50 +- code/datums/mutations/touchfar.dm | 6 +- code/datums/mutations/void_magnet.dm | 43 + .../status_effects/{ => buffs}/buffs.dm | 25 +- .../status_effects/debuffs/confusion.dm | 49 + .../status_effects/{ => debuffs}/debuffs.dm | 269 ++++-- .../status_effects/debuffs/dizziness.dm | 79 ++ .../status_effects/debuffs/drowsiness.dm | 42 + .../status_effects/debuffs/drugginess.dm | 36 + code/datums/status_effects/debuffs/drunk.dm | 210 ++++ .../status_effects/debuffs/hallucination.dm | 117 +++ .../status_effects/debuffs/jitteriness.dm | 62 ++ .../{ => debuffs}/knuckleroot.dm | 0 .../status_effects/debuffs/speech_debuffs.dm | 216 +++++ code/datums/status_effects/neutral.dm | 10 +- code/datums/status_effects/status_effect.dm | 143 ++- code/datums/traits/negative.dm | 6 +- code/datums/weakrefs.dm | 87 +- code/datums/wounds/bones.dm | 2 +- code/game/alternate_appearance.dm | 103 +- code/game/area/areas.dm | 4 +- code/game/area/areas/shuttles.dm | 3 + code/game/atoms.dm | 40 +- code/game/atoms_movable.dm | 35 +- code/game/data_huds.dm | 156 +-- .../gamemodes/bloodsuckers/bloodsucker.dm | 8 +- .../gamemodes/bloodsuckers/traitorsuckers.dm | 2 +- code/game/gamemodes/brother/traitor_bro.dm | 10 - code/game/gamemodes/clock_cult/clock_cult.dm | 15 +- code/game/gamemodes/cult/cult.dm | 19 +- code/game/gamemodes/devil/game_mode.dm | 20 - code/game/gamemodes/dynamic/dynamic.dm | 2 +- .../dynamic/dynamic_rulesets_latejoin.dm | 2 +- code/game/gamemodes/dynamic/readme.md | 2 +- .../gamemodes/eldritch_cult/eldritch_cult.dm | 11 - code/game/gamemodes/objective.dm | 4 +- code/game/machinery/_machinery.dm | 2 +- code/game/machinery/computer/arcade.dm | 4 +- code/game/machinery/computer/security.dm | 2 +- code/game/machinery/decontamination.dm | 2 +- code/game/machinery/doors/airlock.dm | 6 +- code/game/machinery/launch_pad.dm | 16 +- code/game/machinery/newscaster.dm | 18 +- .../machinery/telecomms/computers/message.dm | 2 +- .../mecha/equipment/tools/medical_tools.dm | 2 +- .../mecha/equipment/weapons/melee_weapons.dm | 12 +- code/game/mecha/equipment/weapons/weapons.dm | 10 +- code/game/mecha/mecha.dm | 14 +- code/game/mecha/mecha_actions.dm | 22 +- code/game/mecha/medical/odysseus.dm | 6 +- code/game/mecha/working/clarke.dm | 6 +- code/game/objects/buckling.dm | 23 +- code/game/objects/effects/anomalies.dm | 2 +- code/game/objects/effects/blessing.dm | 2 +- code/game/objects/effects/decals/cleanable.dm | 21 +- .../fluid_spread/effects_foam.dm | 2 +- code/game/objects/effects/forcefields.dm | 43 +- code/game/objects/effects/phased_mob.dm | 73 +- .../temporary_visuals/miscellaneous.dm | 24 +- code/game/objects/items.dm | 90 +- code/game/objects/items/RCD.dm | 4 + code/game/objects/items/RCL.dm | 10 + code/game/objects/items/cards_ids.dm | 5 +- code/game/objects/items/chromosome.dm | 8 +- code/game/objects/items/cigs_lighters.dm | 10 +- code/game/objects/items/crayons.dm | 2 +- code/game/objects/items/defib.dm | 6 +- code/game/objects/items/devices/PDA/PDA.dm | 2 +- .../items/devices/busterarm/_buster.dm | 2 +- code/game/objects/items/devices/flashlight.dm | 2 +- code/game/objects/items/devices/multitool.dm | 10 +- code/game/objects/items/devices/paicard.dm | 4 +- code/game/objects/items/granters.dm | 4 +- code/game/objects/items/granters/_granters.dm | 106 +++ .../granters/crafting/_crafting_granter.dm | 11 + .../items/granters/crafting/bone_notes.dm | 20 + .../objects/items/granters/crafting/cannon.dm | 19 + .../items/granters/crafting/desserts.dm | 20 + .../items/granters/crafting/pipegun.dm | 19 + .../items/granters/magic/_spell_granter.dm | 93 ++ .../objects/items/granters/magic/barnyard.dm | 34 + .../objects/items/granters/magic/blind.dm | 19 + .../objects/items/granters/magic/charge.dm | 20 + .../objects/items/granters/magic/fireball.dm | 27 + .../objects/items/granters/magic/forcewall.dm | 20 + .../objects/items/granters/magic/knock.dm | 19 + .../game/objects/items/granters/magic/mime.dm | 28 + .../objects/items/granters/magic/mindswap.dm | 57 ++ .../items/granters/magic/sacred_flame.dm | 14 + .../objects/items/granters/magic/smoke.dm | 26 + .../items/granters/magic/summon_item.dm | 19 + .../granters/martial_arts/_martial_arts.dm | 24 + .../items/granters/martial_arts/cqc.dm | 29 + .../granters/martial_arts/plasma_fist.dm | 35 + .../granters/martial_arts/sleeping_carp.dm | 35 + code/game/objects/items/granters/origami.dm | 33 + code/game/objects/items/holy_weapons.dm | 12 +- code/game/objects/items/implants/implant.dm | 19 +- .../items/implants/implant_abductor.dm | 1 - .../objects/items/implants/implant_chem.dm | 1 - .../objects/items/implants/implant_clown.dm | 2 +- .../objects/items/implants/implant_exile.dm | 2 +- .../items/implants/implant_explosive.dm | 4 + .../items/implants/implant_krav_maga.dm | 1 - .../items/implants/implant_mindshield.dm | 7 +- .../items/implants/implant_mindshieldtot.dm | 1 - .../objects/items/implants/implant_misc.dm | 5 +- .../objects/items/implants/implant_spell.dm | 58 +- .../objects/items/implants/implant_track.dm | 2 +- code/game/objects/items/melee/misc.dm | 4 +- code/game/objects/items/robot/robot_items.dm | 28 +- .../objects/items/robot/robot_upgrades.dm | 2 +- code/game/objects/items/scrolls.dm | 96 +- code/game/objects/items/signs.dm | 9 + code/game/objects/items/stacks/bscrystal.dm | 2 +- .../items/stacks/sheets/sheet_types.dm | 51 +- code/game/objects/items/storage/backpack.dm | 13 + .../game/objects/items/storage/uplink_kits.dm | 6 +- code/game/objects/items/stunbaton.dm | 10 +- code/game/objects/items/tanks/jetpack.dm | 2 +- code/game/objects/items/tanks/watertank.dm | 3 + code/game/objects/items/tools/weldingtool.dm | 7 +- code/game/objects/items/twohanded.dm | 14 +- code/game/objects/structures.dm | 2 +- .../objects/structures/beds_chairs/chair.dm | 2 +- .../objects/structures/beds_chairs/pew.dm | 2 +- .../structures/crates_lockers/crates.dm | 5 + code/game/objects/structures/spirit_board.dm | 2 +- code/game/objects/structures/watercloset.dm | 4 +- code/game/turfs/simulated/chasm.dm | 2 +- code/game/turfs/simulated/lava.dm | 2 +- code/game/turfs/turf.dm | 3 + code/modules/VR/vr_sleeper.dm | 5 +- code/modules/admin/admin.dm | 12 +- code/modules/admin/admin_verbs.dm | 121 ++- code/modules/admin/fun_balloon.dm | 6 +- code/modules/admin/team_panel.dm | 37 +- code/modules/admin/verbs/adminjump.dm | 2 +- code/modules/admin/verbs/bluespacearty.dm | 4 +- code/modules/admin/verbs/debug.dm | 32 +- code/modules/admin/verbs/randomverbs.dm | 51 +- .../antagonists/_common/antag_datum.dm | 158 ++- code/modules/antagonists/_common/antag_hud.dm | 145 +-- .../antagonists/_common/antag_spawner.dm | 11 +- code/modules/antagonists/abductor/abductor.dm | 21 +- .../abductor/equipment/abduction_gear.dm | 6 +- .../antagonists/abductor/equipment/gland.dm | 10 +- code/modules/antagonists/blob/blob.dm | 4 +- .../blob/blobstrains/blazing_oil.dm | 2 +- .../antagonists/blob/structures/_blob.dm | 2 +- .../bloodsuckers/bloodsucker_assets.dm | 15 + .../bloodsuckers/bloodsucker_daylight.dm | 192 ---- .../bloodsuckers/bloodsucker_flaws.dm | 114 +-- .../bloodsuckers/bloodsucker_frenzy.dm | 27 +- .../bloodsuckers/bloodsucker_hud.dm | 98 ++ .../bloodsuckers/bloodsucker_integration.dm | 21 +- .../bloodsuckers/bloodsucker_mobs.dm | 1 + .../bloodsuckers/bloodsucker_objectives.dm | 18 +- .../antagonists/bloodsuckers/bloodsuckers.dm | 637 +++++-------- .../bloodsuckers/bloodsuckers_objects.dm | 268 +++--- .../antagonists/bloodsuckers/clans/_clan.dm | 260 +++++ .../bloodsuckers/clans/clan_flavortext.dm | 48 + .../bloodsuckers/clans/clan_gangrel.dm | 23 + .../bloodsuckers/clans/clan_lasombra.dm | 43 + .../bloodsuckers/clans/clan_toreador.dm | 31 + .../bloodsuckers/clans/clan_tzimisce.dm | 30 + .../bloodsuckers/powers/_powers.dm | 49 +- .../antagonists/bloodsuckers/powers/cloak.dm | 14 +- .../bloodsuckers/powers/distress.dm | 2 +- .../antagonists/bloodsuckers/powers/feed.dm | 461 ++++----- .../bloodsuckers/powers/fortitude.dm | 15 +- .../bloodsuckers/powers/gangrel.dm | 163 ++-- .../antagonists/bloodsuckers/powers/gohome.dm | 11 +- .../bloodsuckers/powers/masquerade.dm | 90 +- .../bloodsuckers/powers/olfaction.dm | 8 +- .../bloodsuckers/powers/recuperate.dm | 4 +- .../powers/targeted/_powers_targeted.dm | 10 +- .../bloodsuckers/powers/targeted/brawn.dm | 20 +- .../bloodsuckers/powers/targeted/haste.dm | 15 +- .../bloodsuckers/powers/targeted/lasombra.dm | 103 ++ .../bloodsuckers/powers/targeted/lunge.dm | 140 +-- .../bloodsuckers/powers/targeted/mesmerize.dm | 2 +- .../bloodsuckers/powers/targeted/trespass.dm | 2 +- .../bloodsuckers/powers/targeted/tzimisce.dm | 21 +- .../bloodsuckers/powers/vassal_fold.dm | 91 ++ .../antagonists/bloodsuckers/powers/veil.dm | 4 +- .../structures/bloodsucker_coffin.dm | 157 ++- .../structures/bloodsucker_crypt.dm | 725 ++++++++------ .../structures/bloodsucker_life.dm | 357 +++---- .../structures/bloodsucker_recipes.dm | 42 + .../bloodsuckers/subsystem_sunlight.dm | 90 ++ .../antagonists/bloodsuckers/vassal.dm | 224 ----- .../vassal/bloodsucker_conversion.dm | 135 +++ .../bloodsuckers/vassal/favorite_vassal.dm | 23 + .../bloodsuckers/vassal/revenge_vassal.dm | 106 +++ .../antagonists/bloodsuckers/vassal/vassal.dm | 378 ++++++++ .../antagonists/brainwashing/brainwashing.dm | 19 +- code/modules/antagonists/brother/brother.dm | 4 +- .../changeling/cellular_emporium.dm | 40 +- .../antagonists/changeling/changeling.dm | 133 ++- .../changeling/changeling_power.dm | 38 +- .../antagonists/changeling/powers/absorb.dm | 10 +- .../changeling/powers/fakedeath.dm | 4 +- .../antagonists/changeling/powers/headcrab.dm | 2 +- .../changeling/powers/lesserform.dm | 2 +- .../antagonists/changeling/powers/shriek.dm | 4 +- .../changeling/powers/tiny_prick.dm | 10 +- .../clock_helpers/hierophant_network.dm | 2 +- .../clock_items/clock_weapons/_call_weapon.dm | 2 +- .../clockcult/clock_items/clockwork_armor.dm | 2 +- .../clockcult/clock_items/clockwork_slab.dm | 6 +- .../clockcult/clock_items/judicial_visor.dm | 4 +- .../clock_scriptures/scripture_scripts.dm | 2 +- .../clock_structures/ocular_warden.dm | 2 +- .../clock_structures/taunting_trail.dm | 7 +- .../antagonists/clockcult/clockcult.dm | 6 +- code/modules/antagonists/creep/creep.dm | 18 +- code/modules/antagonists/cult/blood_magic.dm | 117 +-- code/modules/antagonists/cult/cult.dm | 102 +- code/modules/antagonists/cult/cult_comms.dm | 362 ++++--- code/modules/antagonists/cult/cult_items.dm | 8 +- code/modules/antagonists/cult/runes.dm | 8 +- code/modules/antagonists/demon/demons.dm | 7 +- code/modules/antagonists/devil/devil.dm | 14 +- code/modules/antagonists/devil/imp/imp.dm | 1 + .../devil/sintouched/sintouched.dm | 21 +- .../antagonists/disease/disease_mob.dm | 6 +- .../eldritch_cult/eldritch_antag.dm | 21 +- .../eldritch_cult/eldritch_effects.dm | 2 +- .../eldritch_cult/eldritch_items.dm | 2 +- .../eldritch_cult/eldritch_knowledge.dm | 2 +- .../eldritch_cult/eldritch_magic.dm | 900 ++++++++++-------- .../eldritch_cult/eldritch_monster_antag.dm | 2 - .../eldritch_cult/eldritch_transmutations.dm | 2 +- .../eldritch_cult/knowledge/ash_lore.dm | 12 +- .../eldritch_cult/knowledge/rust_lore.dm | 4 +- .../transmutations/ash_transmutations.dm | 9 +- code/modules/antagonists/fugitive/fugitive.dm | 18 +- .../antagonists/fugitive/fugitive_outfits.dm | 11 +- code/modules/antagonists/fugitive/hunter.dm | 19 +- code/modules/antagonists/golems/golem.dm | 18 +- code/modules/antagonists/hivemind/hivemind.dm | 20 +- code/modules/antagonists/hivemind/vessel.dm | 20 +- code/modules/antagonists/horror/horror.dm | 17 +- .../horror/horror_abilities_and_upgrades.dm | 14 +- .../monsterhunter/monsterhunter.dm | 10 +- code/modules/antagonists/ninja/ninja.dm | 13 +- code/modules/antagonists/nukeop/nukeop.dm | 16 +- code/modules/antagonists/revenant/revenant.dm | 27 +- .../revenant/revenant_abilities.dm | 342 +++---- .../antagonists/revenant/revenant_blight.dm | 2 +- .../antagonists/revolution/revolution.dm | 21 +- code/modules/antagonists/santa/santa.dm | 3 +- .../antagonists/slaughter/slaughter_antag.dm | 1 + .../antagonists/slaughter/slaughterevent.dm | 11 +- .../antagonists/traitor/datum_traitor.dm | 48 +- .../traitor/equipment/Malf_Modules.dm | 176 ++-- .../traitor/equipment/module_picker.dm | 2 +- .../antagonists/traitor/syndicate_contract.dm | 14 +- .../antagonists/wizard/equipment/artefact.dm | 14 +- .../antagonists/wizard/equipment/soulstone.dm | 21 +- .../antagonists/wizard/equipment/spellbook.dm | 733 -------------- .../equipment/spellbook_entries/_entry.dm | 232 +++++ .../equipment/spellbook_entries/assistance.dm | 107 +++ .../equipment/spellbook_entries/challenges.dm | 10 + .../equipment/spellbook_entries/defensive.dm | 148 +++ .../equipment/spellbook_entries/mobility.dm | 45 + .../equipment/spellbook_entries/offensive.dm | 115 +++ .../equipment/spellbook_entries/summons.dm | 87 ++ .../wizard/equipment/wizard_spellbook.dm | 329 +++++++ code/modules/antagonists/wizard/wizard.dm | 76 +- .../antagonists/zombie/abilities/spit.dm | 2 +- code/modules/antagonists/zombie/zombie.dm | 13 +- code/modules/assembly/flash.dm | 26 +- code/modules/assembly/igniter.dm | 2 +- code/modules/assembly/signaler.dm | 17 + .../atmospherics/gasmixtures/reactions.dm | 2 +- .../atmospherics/machinery/datum_pipeline.dm | 3 +- code/modules/awaymissions/cordon.dm | 16 +- .../awaymissions/mission_code/Academy.dm | 57 +- .../awaymissions/mission_code/snowdin.dm | 2 +- code/modules/cargo/bounty.dm | 2 +- code/modules/cargo/centcom_podlauncher.dm | 2 +- code/modules/cargo/exports.dm | 2 +- code/modules/cargo/supplypod.dm | 2 +- code/modules/client/client_defines.dm | 3 + code/modules/client/verbs/suicide.dm | 2 +- code/modules/clothing/chameleon.dm | 24 +- code/modules/clothing/clothing.dm | 2 +- code/modules/clothing/glasses/_glasses.dm | 90 +- .../clothing/glasses/engine_goggles.dm | 2 +- code/modules/clothing/glasses/hud.dm | 12 +- code/modules/clothing/head/hardhat.dm | 9 +- code/modules/clothing/head/helmet.dm | 2 +- code/modules/clothing/head/misc.dm | 4 + code/modules/clothing/masks/gasmask.dm | 6 +- code/modules/clothing/masks/hailer.dm | 3 + code/modules/clothing/neck/bodycamera.dm | 2 +- code/modules/clothing/shoes/bananashoes.dm | 2 +- code/modules/clothing/shoes/magboots.dm | 2 +- code/modules/clothing/shoes/miscellaneous.dm | 38 +- .../modules/clothing/spacesuits/chronosuit.dm | 4 +- code/modules/clothing/spacesuits/hardsuit.dm | 8 +- code/modules/clothing/spacesuits/plasmamen.dm | 2 +- .../modules/clothing/suits/reactive_armour.dm | 2 +- code/modules/clothing/suits/toggles.dm | 6 +- code/modules/clothing/suits/wiz_robe.dm | 7 + code/modules/clothing/under/miscellaneous.dm | 2 +- code/modules/events/anomaly.dm | 4 +- code/modules/events/brand_intelligence.dm | 2 +- code/modules/events/portal_storm.dm | 4 +- code/modules/events/tzimisce.dm | 23 +- code/modules/events/wizard/aid.dm | 65 +- code/modules/events/wizard/shuffle.dm | 33 +- code/modules/fields/timestop.dm | 18 +- code/modules/flufftext/Hallucination.dm | 91 +- .../food_and_drinks/drinks/drinks/bottle.dm | 2 +- .../food_and_drinks/food/snacks_pastry.dm | 2 +- code/modules/holodeck/area_copy.dm | 5 +- code/modules/hydroponics/grown/flowers.dm | 2 +- code/modules/hydroponics/grown/towercap.dm | 4 +- code/modules/instruments/items.dm | 13 +- code/modules/jobs/job_types/_job.dm | 24 +- code/modules/jobs/job_types/curator.dm | 2 +- code/modules/jobs/job_types/mime.dm | 82 +- .../jobs/job_types/research_director.dm | 2 + code/modules/jobs/job_types/scientist.dm | 3 + .../jobs/job_types/security_officer.dm | 2 +- code/modules/mapping/minimap.dm | 2 +- .../mining/equipment/regenerative_core.dm | 2 +- code/modules/mining/fulton.dm | 2 +- .../mining/lavaland/necropolis_chests.dm | 41 +- code/modules/mining/ores_coins.dm | 2 +- code/modules/mob/dead/emote.dm | 4 +- code/modules/mob/dead/observer/observer.dm | 4 +- code/modules/mob/emote.dm | 15 +- code/modules/mob/living/blood.dm | 4 + code/modules/mob/living/brain/brain.dm | 3 - code/modules/mob/living/carbon/alien/alien.dm | 6 +- .../carbon/alien/humanoid/alien_powers.dm | 517 +++++----- .../carbon/alien/humanoid/caste/drone.dm | 56 +- .../carbon/alien/humanoid/caste/praetorian.dm | 56 +- .../carbon/alien/humanoid/caste/sentinel.dm | 6 +- .../living/carbon/alien/humanoid/humanoid.dm | 38 +- .../mob/living/carbon/alien/humanoid/queen.dm | 172 ++-- .../carbon/alien/humanoid/update_icons.dm | 9 +- .../mob/living/carbon/alien/larva/larva.dm | 9 +- .../mob/living/carbon/alien/larva/powers.dm | 164 ++-- code/modules/mob/living/carbon/alien/life.dm | 3 - .../modules/mob/living/carbon/alien/organs.dm | 64 +- .../modules/mob/living/carbon/alien/screen.dm | 2 +- code/modules/mob/living/carbon/carbon.dm | 115 ++- .../mob/living/carbon/carbon_defense.dm | 29 +- .../mob/living/carbon/carbon_defines.dm | 2 - code/modules/mob/living/carbon/emote.dm | 16 +- code/modules/mob/living/carbon/human/death.dm | 3 - code/modules/mob/living/carbon/human/emote.dm | 10 +- .../mob/living/carbon/human/examine.dm | 27 +- code/modules/mob/living/carbon/human/human.dm | 5 +- .../mob/living/carbon/human/human_defense.dm | 2 +- .../mob/living/carbon/human/inventory.dm | 53 +- code/modules/mob/living/carbon/human/life.dm | 4 +- code/modules/mob/living/carbon/human/say.dm | 5 +- .../mob/living/carbon/human/species.dm | 10 +- .../carbon/human/species_types/abductors.dm | 4 +- .../carbon/human/species_types/golems.dm | 102 +- .../carbon/human/species_types/jellypeople.dm | 10 +- .../carbon/human/species_types/plasmamen.dm | 2 +- .../carbon/human/species_types/podpeople.dm | 2 +- .../human/species_types/shadowpeople.dm | 27 +- .../carbon/human/species_types/vampire.dm | 2 +- .../mob/living/carbon/human/status_procs.dm | 11 - code/modules/mob/living/carbon/inventory.dm | 27 +- code/modules/mob/living/carbon/life.dm | 167 +--- .../modules/mob/living/carbon/status_procs.dm | 20 - code/modules/mob/living/damage_procs.dm | 40 +- code/modules/mob/living/emote.dm | 18 +- code/modules/mob/living/life.dm | 2 - code/modules/mob/living/living.dm | 67 +- code/modules/mob/living/living_defense.dm | 114 +-- code/modules/mob/living/living_defines.dm | 11 - code/modules/mob/living/login.dm | 3 - code/modules/mob/living/say.dm | 45 +- code/modules/mob/living/silicon/ai/ai.dm | 2 +- .../ai/decentralized/projects/induction.dm | 75 +- .../mob/living/silicon/ai/freelook/eye.dm | 10 +- .../mob/living/silicon/pai/pai_defense.dm | 8 +- .../mob/living/silicon/pai/software.dm | 8 +- .../mob/living/silicon/robot/robot_defense.dm | 2 +- code/modules/mob/living/silicon/silicon.dm | 17 +- .../mob/living/simple_animal/bot/bot.dm | 12 +- .../mob/living/simple_animal/bot/ed209bot.dm | 6 +- .../mob/living/simple_animal/bot/honkbot.dm | 8 +- .../mob/living/simple_animal/bot/secbot.dm | 8 +- .../mob/living/simple_animal/constructs.dm | 175 ++-- .../living/simple_animal/eldritch_demons.dm | 86 +- .../simple_animal/friendly/drone/_drone.dm | 2 +- .../simple_animal/friendly/drone/inventory.dm | 7 + .../simple_animal/friendly/spiderbot.dm | 2 +- .../simple_animal/guardian/types/dextrous.dm | 11 + .../simple_animal/guardian/types/fire.dm | 2 +- .../simple_animal/guardian/types/lightning.dm | 2 +- .../simple_animal/guardian/types/support.dm | 2 +- .../simple_animal/hostile/giant_spider.dm | 347 ++++--- .../simple_animal/hostile/jungle/seedling.dm | 2 +- .../hostile/megafauna/colossus.dm | 60 +- .../hostile/mining_mobs/basilisk.dm | 2 +- .../hostile/mining_mobs/elites/legionnaire.dm | 2 +- .../hostile/mining_mobs/goldgrub.dm | 8 +- .../living/simple_animal/hostile/statue.dm | 97 +- .../living/simple_animal/hostile/wizard.dm | 78 +- .../mob/living/simple_animal/parrot.dm | 2 +- .../mob/living/simple_animal/simple_animal.dm | 9 +- code/modules/mob/living/status_procs.dm | 161 ++++ code/modules/mob/living/taste.dm | 2 +- code/modules/mob/login.dm | 1 + code/modules/mob/mob.dm | 144 ++- code/modules/mob/mob_defines.dm | 27 +- code/modules/mob/mob_movement.dm | 8 +- code/modules/mob/say.dm | 12 +- code/modules/mob/status_procs.dm | 26 - .../computers/item/computer.dm | 4 +- .../suit/n_suit_verbs/ninja_adrenaline.dm | 2 +- code/modules/pai/actions.dm | 62 ++ code/modules/pai/camera.dm | 58 ++ code/modules/pai/candidate.dm | 35 + code/modules/pai/card.dm | 259 +++++ code/modules/pai/death.dm | 19 + code/modules/pai/debug.dm | 45 + code/modules/pai/defense.dm | 90 ++ code/modules/pai/door_jack.dm | 129 +++ code/modules/pai/hud.dm | 267 ++++++ code/modules/pai/login.dm | 10 + code/modules/pai/pai.dm | 412 ++++++++ code/modules/pai/personality.dm | 56 ++ code/modules/pai/say.dm | 2 + code/modules/pai/shell.dm | 169 ++++ code/modules/pai/software.dm | 242 +++++ code/modules/paperwork/contract.dm | 2 +- code/modules/paperwork/paper.dm | 2 +- code/modules/paperwork/paperplane.dm | 2 +- code/modules/paperwork/ticketmachine.dm | 4 +- .../pool/components/swimming_felinid.dm | 5 +- code/modules/power/singularity/emitter.dm | 6 +- code/modules/power/singularity/singularity.dm | 2 +- code/modules/power/solar.dm | 2 +- code/modules/power/supermatter/supermatter.dm | 11 +- code/modules/power/tracker.dm | 1 + .../projectiles/attachments/laser_sight.dm | 49 + .../modules/projectiles/attachments/scopes.dm | 24 + code/modules/projectiles/gun.dm | 6 +- .../projectiles/guns/ballistic/automatic.dm | 2 +- .../projectiles/guns/energy/special.dm | 5 +- code/modules/projectiles/guns/magic/wand.dm | 2 +- .../projectiles/guns/misc/beam_rifle.dm | 10 +- code/modules/projectiles/projectile.dm | 6 +- code/modules/projectiles/projectile/beams.dm | 4 +- .../projectile/bullets/_incendiary.dm | 2 +- .../projectile/bullets/revolver.dm | 2 +- code/modules/projectiles/projectile/magic.dm | 200 +++- .../projectiles/projectile/reusable/arrow.dm | 2 +- .../projectile/special/hallucination.dm | 6 +- .../projectile/special/mindflayer.dm | 4 +- .../chemistry/reagents/alcohol_reagents.dm | 195 ++-- .../reagents/cat2_medicine_reagents.dm | 4 +- .../chemistry/reagents/drink_reagents.dm | 76 +- .../chemistry/reagents/drug_reagents.dm | 99 +- .../chemistry/reagents/food_reagents.dm | 25 +- .../chemistry/reagents/medicine_reagents.dm | 182 ++-- .../chemistry/reagents/other_reagents.dm | 83 +- .../reagents/pyrotechnic_reagents.dm | 6 +- .../chemistry/reagents/toxin_reagents.dm | 26 +- .../chemistry/recipes/pyrotechnics.dm | 2 +- .../reagents/reagent_containers/blood_pack.dm | 69 +- .../nanites/nanite_programs/healing.dm | 2 +- .../research/nanites/nanite_programs/rogue.dm | 14 +- .../nanites/nanite_programs/suppression.dm | 6 +- .../nanites/nanite_programs/utility.dm | 2 +- .../nanites/nanite_programs/weapon.dm | 4 +- .../xenobiology/crossbreeding/_misc.dm | 2 +- .../xenobiology/crossbreeding/_mobs.dm | 36 +- .../crossbreeding/_status_effects.dm | 31 +- .../xenobiology/crossbreeding/_structures.dm | 2 +- .../xenobiology/crossbreeding/burning.dm | 13 +- code/modules/shuttle/arrivals.dm | 2 +- code/modules/shuttle/assault_pod.dm | 2 +- code/modules/shuttle/emergency.dm | 6 +- code/modules/spells/spell.dm | 876 ++++++++--------- code/modules/spells/spell_types/aimed.dm | 181 ---- .../spell_types/aoe_spell/_aoe_spell.dm | 57 ++ .../spell_types/aoe_spell/area_conversion.dm | 25 + .../spells/spell_types/aoe_spell/knock.dm | 20 + .../spell_types/aoe_spell/magic_missile.dm | 47 + .../spells/spell_types/aoe_spell/repulse.dm | 87 ++ .../spell_types/aoe_spell/sacred_flame.dm | 39 + .../spells/spell_types/area_teleport.dm | 92 -- code/modules/spells/spell_types/barnyard.dm | 51 - code/modules/spells/spell_types/bloodcrawl.dm | 41 - code/modules/spells/spell_types/charge.dm | 103 -- code/modules/spells/spell_types/cone/_cone.dm | 123 +++ .../modules/spells/spell_types/cone_spells.dm | 117 --- code/modules/spells/spell_types/conjure.dm | 100 -- .../spells/spell_types/conjure/_conjure.dm | 50 + .../spells/spell_types/conjure/bees.dm | 18 + .../spells/spell_types/conjure/carp.dm | 13 + .../spells/spell_types/conjure/constructs.dm | 20 + .../spells/spell_types/conjure/creatures.dm | 15 + .../spells/spell_types/conjure/cult_turfs.dm | 29 + .../spells/spell_types/conjure/ed_swarm.dm | 21 + .../spell_types/conjure/invisible_chair.dm | 34 + .../spell_types/conjure/invisible_wall.dm | 26 + .../spells/spell_types/conjure/link_worlds.dm | 15 + .../spells/spell_types/conjure/presents.dm | 14 + .../spells/spell_types/conjure/soulstone.dm | 26 + .../spells/spell_types/conjure/the_traps.dm | 35 + .../spell_types/conjure_item/_conjure_item.dm | 46 + .../spell_types/conjure_item/infinite_guns.dm | 41 + .../spell_types/conjure_item/invisible_box.dm | 42 + .../conjure_item/lightning_packet.dm | 38 + .../spell_types/conjure_item/snowball.dm | 8 + .../spells/spell_types/construct_spells.dm | 336 ------- code/modules/spells/spell_types/devil.dm | 2 +- code/modules/spells/spell_types/emplosion.dm | 18 - .../spells/spell_types/ethereal_jaunt.dm | 84 -- code/modules/spells/spell_types/explosion.dm | 16 - code/modules/spells/spell_types/forcewall.dm | 41 - code/modules/spells/spell_types/genetic.dm | 44 - code/modules/spells/spell_types/godhand.dm | 203 ---- code/modules/spells/spell_types/hivemind.dm | 28 +- .../spells/spell_types/infinite_guns.dm | 29 - .../spells/spell_types/inflict_handler.dm | 67 -- .../spells/spell_types/jaunt/_jaunt.dm | 83 ++ .../spells/spell_types/jaunt/bloodcrawl.dm | 315 ++++++ .../spell_types/jaunt/ethereal_jaunt.dm | 256 +++++ .../spells/spell_types/jaunt/shadow_walk.dm | 82 ++ code/modules/spells/spell_types/lichdom.dm | 160 ---- code/modules/spells/spell_types/lightning.dm | 86 -- .../spell_types/list_target/_list_target.dm | 41 + .../spell_types/list_target/telepathy.dm | 51 + .../{curse.dm => madness_curse.dm} | 0 code/modules/spells/spell_types/mime.dm | 249 ----- .../spells/spell_types/mind_transfer.dm | 99 -- .../spells/spell_types/personality_commune.dm | 32 - .../spells/spell_types/pointed/_pointed.dm | 181 ++++ .../spell_types/pointed/abyssal_gaze.dm | 53 ++ .../spells/spell_types/pointed/barnyard.dm | 55 ++ .../spells/spell_types/pointed/blind.dm | 55 +- .../spells/spell_types/pointed/dominate.dm | 49 + .../spells/spell_types/pointed/finger_guns.dm | 48 + .../spells/spell_types/pointed/fireball.dm | 23 + .../spell_types/pointed/lightning_bolt.dm | 43 + .../spell_types/pointed/mind_transfer.dm | 133 +++ .../spells/spell_types/pointed/pointed.dm | 105 -- .../spells/spell_types/pointed/spell_cards.dm | 82 ++ code/modules/spells/spell_types/projectile.dm | 134 --- .../projectile/_basic_projectile.dm | 29 + .../spell_types/projectile/juggernaut.dm | 12 + code/modules/spells/spell_types/rod_form.dm | 52 - code/modules/spells/spell_types/santa.dm | 16 - .../spells/spell_types/self/basic_heal.dm | 27 + .../modules/spells/spell_types/self/charge.dm | 58 ++ .../spells/spell_types/self/disable_tech.dm | 30 + .../spells/spell_types/self/forcewall.dm | 66 ++ .../spells/spell_types/self/lichdom.dm | 83 ++ .../spells/spell_types/self/lightning.dm | 128 +++ .../spells/spell_types/self/mime_vow.dm | 24 + .../modules/spells/spell_types/self/mutate.dm | 49 + .../spells/spell_types/self/night_vision.dm | 39 + .../spell_types/self/personality_commune.dm | 54 ++ .../spells/spell_types/self/rod_form.dm | 160 ++++ code/modules/spells/spell_types/self/smoke.dm | 37 + .../spells/spell_types/self/soultap.dm | 62 ++ .../spell_types/self/spacetime_distortion.dm | 168 ++++ .../spells/spell_types/self/stop_time.dm | 30 + .../spells/spell_types/self/summonitem.dm | 154 +++ .../spells/spell_types/self/voice_of_god.dm | 50 + .../modules/spells/spell_types/shadow_walk.dm | 4 +- .../spell_types/shapeshift/_shapeshift.dm | 244 +++++ .../spells/spell_types/shapeshift/dragon.dm | 7 + .../spell_types/shapeshift/polar_bear.dm | 7 + .../spell_types/shapeshift/shapechange.dm | 22 + code/modules/spells/spell_types/soultap.dm | 33 - .../spell_types/spacetime_distortion.dm | 124 --- code/modules/spells/spell_types/summonitem.dm | 118 --- code/modules/spells/spell_types/telepathy.dm | 32 - .../spells/spell_types/teleport/_teleport.dm | 145 +++ .../spells/spell_types/teleport/blink.dm | 19 + .../spells/spell_types/teleport/teleport.dm | 51 + code/modules/spells/spell_types/the_traps.dm | 26 - .../spells/spell_types/touch/_touch.dm | 271 ++++++ .../spell_types/touch/duffelbag_curse.dm | 85 ++ .../spell_types/touch/flesh_to_stone.dm | 33 + .../modules/spells/spell_types/touch/smite.dm | 55 ++ .../spells/spell_types/touch_attacks.dm | 116 --- code/modules/spells/spell_types/trigger.dm | 30 - .../spells/spell_types/turf_teleport.dm | 44 - .../spells/spell_types/voice_of_god.dm | 45 - code/modules/spells/spell_types/wizard.dm | 2 +- code/modules/surgery/organs/augment_legs.dm | 8 +- code/modules/surgery/organs/augments_arms.dm | 2 +- code/modules/surgery/organs/augments_chest.dm | 16 +- code/modules/surgery/organs/augments_eyes.dm | 4 +- code/modules/surgery/organs/ears.dm | 8 +- code/modules/surgery/organs/heart.dm | 4 +- code/modules/surgery/organs/lungs.dm | 6 +- code/modules/surgery/organs/stomach.dm | 12 +- code/modules/surgery/organs/vocal_cords.dm | 2 +- code/modules/surgery/surgery_step.dm | 2 +- code/modules/swarmers/swarmer.dm | 2 +- .../tgui_alert.dm => tgui_input/alert.dm} | 0 .../tgui_input_list.dm => tgui_input/list.dm} | 0 code/modules/tgui_input/number.dm | 149 +++ code/modules/tgui_input/text.dm | 144 +++ code/modules/vehicles/vehicle_actions.dm | 2 +- icons/effects/mouse_pointers/barn_target.dmi | Bin 0 -> 575 bytes .../{ => mouse_pointers}/cult_target.dmi | Bin .../mouse_pointers/mindswap_target.dmi | Bin 0 -> 653 bytes .../overload_machine_target.dmi | Bin 0 -> 433 bytes .../override_machine_target.dmi | Bin 0 -> 359 bytes icons/mob/actions.dmi | Bin 10665 -> 11426 bytes icons/mob/actions/actions_bloodsucker.dmi | Bin 22159 -> 22094 bytes .../actions/actions_gangrel_bloodsucker.dmi | Bin 12771 -> 13202 bytes .../actions/actions_lasombra_bloodsucker.dmi | Bin 3643 -> 5255 bytes icons/mob/actions/actions_mime.dmi | Bin 0 -> 3557 bytes .../actions/actions_tzimisce_bloodsucker.dmi | Bin 1330 -> 1327 bytes icons/mob/bloodsucker_clan_icons.dmi | Bin 0 -> 18057 bytes icons/mob/bloodsucker_mobs.dmi | Bin 7441 -> 7539 bytes icons/mob/mob.dmi | Bin 304545 -> 217418 bytes icons/mob/nonhuman-player/cult.dmi | Bin 0 -> 140789 bytes icons/mob/radial.dmi | Bin 20187 -> 20885 bytes icons/mob/screen_gen.dmi | Bin 93805 -> 120844 bytes icons/obj/hydroponics/equipment.dmi | Bin 31982 -> 33686 bytes icons/obj/vamp_obj.dmi | Bin 21508 -> 23360 bytes strings/steve.json | 21 + tgui/packages/tgui/index.js | 1 + .../tgui/interfaces/AntagInfoBlob.tsx | 203 ++++ .../tgui/interfaces/AntagInfoBloodsucker.tsx | 266 ++++++ .../tgui/interfaces/AntagInfoChangeling.tsx | 210 ++++ .../tgui/interfaces/AntagInfoDemon.tsx | 126 +++ .../tgui/interfaces/AntagInfoGeneric.tsx | 57 ++ .../interfaces/AntagInfoRevengeVassal.tsx | 150 +++ .../tgui/interfaces/KindredArchives.js | 65 -- tgui/packages/tgui/interfaces/KindredBook.tsx | 42 + .../tgui/interfaces/NumberInputModal.tsx | 118 +++ tgui/packages/tgui/interfaces/PsiWeb.js | 2 +- tgui/packages/tgui/interfaces/Spellbook.js | 369 +++---- .../tgui/interfaces/TextInputModal.tsx | 121 +++ .../tgui/interfaces/common/InputButtons.tsx | 75 ++ .../tgui/interfaces/common/Loader.tsx | 15 + .../tgui/styles/themes/darkspawn.scss | 60 ++ yogstation.dme | 214 +++-- yogstation/code/datums/antagonists/vampire.dm | 7 +- .../code/datums/diseases/cluwnification.dm | 8 +- .../code/datums/martial/explosive_fist.dm | 8 +- yogstation/code/datums/mutations.dm | 4 +- .../code/datums/status_effects/neutral.dm | 2 +- yogstation/code/game/area/areas/centcom.dm | 3 +- .../gamemodes/battle_royale/battleroyale.dm | 2 +- .../game/gamemodes/darkspawn/darkspawn.dm | 10 - .../code/game/gamemodes/darkspawn/veil.dm | 10 +- .../game/gamemodes/shadowling/shadowling.dm | 10 - .../code/game/gamemodes/vampire/vampire.dm | 11 - .../game/gamemodes/vampire/vampire_other.dm | 2 +- .../game/gamemodes/vampire/vampire_powers.dm | 8 +- .../game/objects/items/holotool/holotool.dm | 4 +- .../structures/beds_chairs/electric_bed.dm | 2 +- .../modules/antagonists/_common/antag_menu.dm | 28 + .../antagonists/darkspawn/darkspawn.dm | 115 +-- .../darkspawn_abilities/__psi_web.dm | 75 +- .../darkspawn/darkspawn_abilities/_divulge.dm | 2 +- .../darkspawn_abilities/_sacrament.dm | 2 +- .../darkspawn_abilities/crawling_shadows.dm | 2 +- .../darkspawn_abilities/devour_will.dm | 2 +- .../darkspawn/darkspawn_abilities/pass.dm | 2 +- .../darkspawn_abilities/silver_tongue.dm | 2 +- .../darkspawn_abilities/veil_mind.dm | 4 +- .../darkspawn/darkspawn_ability.dm | 2 +- .../code/modules/antagonists/gang/gang.dm | 30 +- .../code/modules/antagonists/gang/gang_hud.dm | 20 +- .../antagonists/hijacked_ai/hijacked_ai.dm | 13 +- .../antagonists/infiltrator/infiltrator.dm | 18 +- .../antagonists/infiltrator/items/hardsuit.dm | 1 + .../antagonists/shadowling/shadowling.dm | 13 +- .../shadowling/shadowling_abilities.dm | 16 +- .../modules/antagonists/shadowling/thrall.dm | 8 +- .../antagonists/slaughter/slaughter.dm | 99 +- .../antagonists/traitor/datum_mindslave.dm | 37 +- .../modules/guardian/abilities/_ability.dm | 4 +- .../guardian/abilities/major/healing.dm | 4 +- .../modules/guardian/abilities/major/time.dm | 2 +- .../guardian/abilities/special/pocket.dm | 4 +- .../carbon/human/species_types/plantpeople.dm | 2 +- .../species_types/preternis/preternis.dm | 8 +- yogstation/code/modules/mob/living/emote.dm | 14 +- .../hostile/retaliate/king_of_goats.dm | 2 +- yogstation/code/modules/mob/mob.dm | 14 +- .../chemistry/reagents/alcohol_reagents.dm | 4 +- yogstation/icons/mob/antag_hud.dmi | Bin 0 -> 8043 bytes yogstation/icons/mob/hud.dmi | Bin 19204 -> 10490 bytes 808 files changed, 24168 insertions(+), 13632 deletions(-) create mode 100644 code/__DEFINES/actions.dm create mode 100644 code/__DEFINES/magic.dm create mode 100644 code/__DEFINES/security.dm create mode 100644 code/__HELPERS/hallucinations.dm create mode 100644 code/__HELPERS/maths.dm create mode 100644 code/controllers/subsystem/processing/antag_hud.dm delete mode 100644 code/datums/action.dm create mode 100644 code/datums/actions/action.dm create mode 100644 code/datums/actions/cooldown_action.dm create mode 100644 code/datums/actions/innate_action.dm create mode 100644 code/datums/actions/item_action.dm create mode 100644 code/datums/actions/items/adjust.dm rename code/datums/actions/{ => items}/beam_rifle.dm (99%) create mode 100644 code/datums/actions/items/clockcult.dm create mode 100644 code/datums/actions/items/cult_dagger.dm create mode 100644 code/datums/actions/items/hands_free.dm create mode 100644 code/datums/actions/items/organ_action.dm create mode 100644 code/datums/actions/items/set_internals.dm create mode 100644 code/datums/actions/items/stealth_box.dm create mode 100644 code/datums/actions/items/toggles.dm create mode 100644 code/datums/actions/items/vortex_recall.dm create mode 100644 code/datums/actions/mobs/language_menu.dm create mode 100644 code/datums/actions/mobs/small_sprite.dm delete mode 100644 code/datums/mutations/actions.dm create mode 100644 code/datums/mutations/autotomy.dm create mode 100644 code/datums/mutations/fire_breath.dm create mode 100644 code/datums/mutations/telepathy.dm create mode 100644 code/datums/mutations/void_magnet.dm rename code/datums/status_effects/{ => buffs}/buffs.dm (97%) create mode 100644 code/datums/status_effects/debuffs/confusion.dm rename code/datums/status_effects/{ => debuffs}/debuffs.dm (83%) create mode 100644 code/datums/status_effects/debuffs/dizziness.dm create mode 100644 code/datums/status_effects/debuffs/drowsiness.dm create mode 100644 code/datums/status_effects/debuffs/drugginess.dm create mode 100644 code/datums/status_effects/debuffs/drunk.dm create mode 100644 code/datums/status_effects/debuffs/hallucination.dm create mode 100644 code/datums/status_effects/debuffs/jitteriness.dm rename code/datums/status_effects/{ => debuffs}/knuckleroot.dm (100%) create mode 100644 code/datums/status_effects/debuffs/speech_debuffs.dm create mode 100644 code/game/objects/items/granters/_granters.dm create mode 100644 code/game/objects/items/granters/crafting/_crafting_granter.dm create mode 100644 code/game/objects/items/granters/crafting/bone_notes.dm create mode 100644 code/game/objects/items/granters/crafting/cannon.dm create mode 100644 code/game/objects/items/granters/crafting/desserts.dm create mode 100644 code/game/objects/items/granters/crafting/pipegun.dm create mode 100644 code/game/objects/items/granters/magic/_spell_granter.dm create mode 100644 code/game/objects/items/granters/magic/barnyard.dm create mode 100644 code/game/objects/items/granters/magic/blind.dm create mode 100644 code/game/objects/items/granters/magic/charge.dm create mode 100644 code/game/objects/items/granters/magic/fireball.dm create mode 100644 code/game/objects/items/granters/magic/forcewall.dm create mode 100644 code/game/objects/items/granters/magic/knock.dm create mode 100644 code/game/objects/items/granters/magic/mime.dm create mode 100644 code/game/objects/items/granters/magic/mindswap.dm create mode 100644 code/game/objects/items/granters/magic/sacred_flame.dm create mode 100644 code/game/objects/items/granters/magic/smoke.dm create mode 100644 code/game/objects/items/granters/magic/summon_item.dm create mode 100644 code/game/objects/items/granters/martial_arts/_martial_arts.dm create mode 100644 code/game/objects/items/granters/martial_arts/cqc.dm create mode 100644 code/game/objects/items/granters/martial_arts/plasma_fist.dm create mode 100644 code/game/objects/items/granters/martial_arts/sleeping_carp.dm create mode 100644 code/game/objects/items/granters/origami.dm create mode 100644 code/modules/antagonists/bloodsuckers/bloodsucker_assets.dm delete mode 100644 code/modules/antagonists/bloodsuckers/bloodsucker_daylight.dm create mode 100644 code/modules/antagonists/bloodsuckers/bloodsucker_hud.dm create mode 100644 code/modules/antagonists/bloodsuckers/clans/_clan.dm create mode 100644 code/modules/antagonists/bloodsuckers/clans/clan_flavortext.dm create mode 100644 code/modules/antagonists/bloodsuckers/clans/clan_gangrel.dm create mode 100644 code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm create mode 100644 code/modules/antagonists/bloodsuckers/clans/clan_toreador.dm create mode 100644 code/modules/antagonists/bloodsuckers/clans/clan_tzimisce.dm create mode 100644 code/modules/antagonists/bloodsuckers/powers/targeted/lasombra.dm create mode 100644 code/modules/antagonists/bloodsuckers/powers/vassal_fold.dm create mode 100644 code/modules/antagonists/bloodsuckers/subsystem_sunlight.dm delete mode 100644 code/modules/antagonists/bloodsuckers/vassal.dm create mode 100644 code/modules/antagonists/bloodsuckers/vassal/bloodsucker_conversion.dm create mode 100644 code/modules/antagonists/bloodsuckers/vassal/favorite_vassal.dm create mode 100644 code/modules/antagonists/bloodsuckers/vassal/revenge_vassal.dm create mode 100644 code/modules/antagonists/bloodsuckers/vassal/vassal.dm delete mode 100644 code/modules/antagonists/wizard/equipment/spellbook.dm create mode 100644 code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm create mode 100644 code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm create mode 100644 code/modules/antagonists/wizard/equipment/spellbook_entries/challenges.dm create mode 100644 code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm create mode 100644 code/modules/antagonists/wizard/equipment/spellbook_entries/mobility.dm create mode 100644 code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm create mode 100644 code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm create mode 100644 code/modules/antagonists/wizard/equipment/wizard_spellbook.dm create mode 100644 code/modules/pai/actions.dm create mode 100644 code/modules/pai/camera.dm create mode 100644 code/modules/pai/candidate.dm create mode 100644 code/modules/pai/card.dm create mode 100644 code/modules/pai/death.dm create mode 100644 code/modules/pai/debug.dm create mode 100644 code/modules/pai/defense.dm create mode 100644 code/modules/pai/door_jack.dm create mode 100644 code/modules/pai/hud.dm create mode 100644 code/modules/pai/login.dm create mode 100644 code/modules/pai/pai.dm create mode 100644 code/modules/pai/personality.dm create mode 100644 code/modules/pai/say.dm create mode 100644 code/modules/pai/shell.dm create mode 100644 code/modules/pai/software.dm delete mode 100644 code/modules/spells/spell_types/aimed.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/_aoe_spell.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/area_conversion.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/knock.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/magic_missile.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/repulse.dm create mode 100644 code/modules/spells/spell_types/aoe_spell/sacred_flame.dm delete mode 100644 code/modules/spells/spell_types/area_teleport.dm delete mode 100644 code/modules/spells/spell_types/barnyard.dm delete mode 100644 code/modules/spells/spell_types/bloodcrawl.dm delete mode 100644 code/modules/spells/spell_types/charge.dm create mode 100644 code/modules/spells/spell_types/cone/_cone.dm delete mode 100644 code/modules/spells/spell_types/cone_spells.dm delete mode 100644 code/modules/spells/spell_types/conjure.dm create mode 100644 code/modules/spells/spell_types/conjure/_conjure.dm create mode 100644 code/modules/spells/spell_types/conjure/bees.dm create mode 100644 code/modules/spells/spell_types/conjure/carp.dm create mode 100644 code/modules/spells/spell_types/conjure/constructs.dm create mode 100644 code/modules/spells/spell_types/conjure/creatures.dm create mode 100644 code/modules/spells/spell_types/conjure/cult_turfs.dm create mode 100644 code/modules/spells/spell_types/conjure/ed_swarm.dm create mode 100644 code/modules/spells/spell_types/conjure/invisible_chair.dm create mode 100644 code/modules/spells/spell_types/conjure/invisible_wall.dm create mode 100644 code/modules/spells/spell_types/conjure/link_worlds.dm create mode 100644 code/modules/spells/spell_types/conjure/presents.dm create mode 100644 code/modules/spells/spell_types/conjure/soulstone.dm create mode 100644 code/modules/spells/spell_types/conjure/the_traps.dm create mode 100644 code/modules/spells/spell_types/conjure_item/_conjure_item.dm create mode 100644 code/modules/spells/spell_types/conjure_item/infinite_guns.dm create mode 100644 code/modules/spells/spell_types/conjure_item/invisible_box.dm create mode 100644 code/modules/spells/spell_types/conjure_item/lightning_packet.dm create mode 100644 code/modules/spells/spell_types/conjure_item/snowball.dm delete mode 100644 code/modules/spells/spell_types/construct_spells.dm delete mode 100644 code/modules/spells/spell_types/emplosion.dm delete mode 100644 code/modules/spells/spell_types/ethereal_jaunt.dm delete mode 100644 code/modules/spells/spell_types/explosion.dm delete mode 100644 code/modules/spells/spell_types/forcewall.dm delete mode 100644 code/modules/spells/spell_types/genetic.dm delete mode 100644 code/modules/spells/spell_types/godhand.dm delete mode 100644 code/modules/spells/spell_types/infinite_guns.dm delete mode 100644 code/modules/spells/spell_types/inflict_handler.dm create mode 100644 code/modules/spells/spell_types/jaunt/_jaunt.dm create mode 100644 code/modules/spells/spell_types/jaunt/bloodcrawl.dm create mode 100644 code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm create mode 100644 code/modules/spells/spell_types/jaunt/shadow_walk.dm delete mode 100644 code/modules/spells/spell_types/lichdom.dm delete mode 100644 code/modules/spells/spell_types/lightning.dm create mode 100644 code/modules/spells/spell_types/list_target/_list_target.dm create mode 100644 code/modules/spells/spell_types/list_target/telepathy.dm rename code/modules/spells/spell_types/{curse.dm => madness_curse.dm} (100%) delete mode 100644 code/modules/spells/spell_types/mime.dm delete mode 100644 code/modules/spells/spell_types/mind_transfer.dm delete mode 100644 code/modules/spells/spell_types/personality_commune.dm create mode 100644 code/modules/spells/spell_types/pointed/_pointed.dm create mode 100644 code/modules/spells/spell_types/pointed/abyssal_gaze.dm create mode 100644 code/modules/spells/spell_types/pointed/barnyard.dm create mode 100644 code/modules/spells/spell_types/pointed/dominate.dm create mode 100644 code/modules/spells/spell_types/pointed/finger_guns.dm create mode 100644 code/modules/spells/spell_types/pointed/fireball.dm create mode 100644 code/modules/spells/spell_types/pointed/lightning_bolt.dm create mode 100644 code/modules/spells/spell_types/pointed/mind_transfer.dm delete mode 100644 code/modules/spells/spell_types/pointed/pointed.dm create mode 100644 code/modules/spells/spell_types/pointed/spell_cards.dm delete mode 100644 code/modules/spells/spell_types/projectile.dm create mode 100644 code/modules/spells/spell_types/projectile/_basic_projectile.dm create mode 100644 code/modules/spells/spell_types/projectile/juggernaut.dm delete mode 100644 code/modules/spells/spell_types/rod_form.dm delete mode 100644 code/modules/spells/spell_types/santa.dm create mode 100644 code/modules/spells/spell_types/self/basic_heal.dm create mode 100644 code/modules/spells/spell_types/self/charge.dm create mode 100644 code/modules/spells/spell_types/self/disable_tech.dm create mode 100644 code/modules/spells/spell_types/self/forcewall.dm create mode 100644 code/modules/spells/spell_types/self/lichdom.dm create mode 100644 code/modules/spells/spell_types/self/lightning.dm create mode 100644 code/modules/spells/spell_types/self/mime_vow.dm create mode 100644 code/modules/spells/spell_types/self/mutate.dm create mode 100644 code/modules/spells/spell_types/self/night_vision.dm create mode 100644 code/modules/spells/spell_types/self/personality_commune.dm create mode 100644 code/modules/spells/spell_types/self/rod_form.dm create mode 100644 code/modules/spells/spell_types/self/smoke.dm create mode 100644 code/modules/spells/spell_types/self/soultap.dm create mode 100644 code/modules/spells/spell_types/self/spacetime_distortion.dm create mode 100644 code/modules/spells/spell_types/self/stop_time.dm create mode 100644 code/modules/spells/spell_types/self/summonitem.dm create mode 100644 code/modules/spells/spell_types/self/voice_of_god.dm create mode 100644 code/modules/spells/spell_types/shapeshift/_shapeshift.dm create mode 100644 code/modules/spells/spell_types/shapeshift/dragon.dm create mode 100644 code/modules/spells/spell_types/shapeshift/polar_bear.dm create mode 100644 code/modules/spells/spell_types/shapeshift/shapechange.dm delete mode 100644 code/modules/spells/spell_types/soultap.dm delete mode 100644 code/modules/spells/spell_types/spacetime_distortion.dm delete mode 100644 code/modules/spells/spell_types/summonitem.dm delete mode 100644 code/modules/spells/spell_types/telepathy.dm create mode 100644 code/modules/spells/spell_types/teleport/_teleport.dm create mode 100644 code/modules/spells/spell_types/teleport/blink.dm create mode 100644 code/modules/spells/spell_types/teleport/teleport.dm delete mode 100644 code/modules/spells/spell_types/the_traps.dm create mode 100644 code/modules/spells/spell_types/touch/_touch.dm create mode 100644 code/modules/spells/spell_types/touch/duffelbag_curse.dm create mode 100644 code/modules/spells/spell_types/touch/flesh_to_stone.dm create mode 100644 code/modules/spells/spell_types/touch/smite.dm delete mode 100644 code/modules/spells/spell_types/touch_attacks.dm delete mode 100644 code/modules/spells/spell_types/trigger.dm delete mode 100644 code/modules/spells/spell_types/turf_teleport.dm delete mode 100644 code/modules/spells/spell_types/voice_of_god.dm rename code/modules/{tgui/tgui_alert.dm => tgui_input/alert.dm} (100%) rename code/modules/{tgui/tgui_input_list.dm => tgui_input/list.dm} (100%) create mode 100644 code/modules/tgui_input/number.dm create mode 100644 code/modules/tgui_input/text.dm create mode 100644 icons/effects/mouse_pointers/barn_target.dmi rename icons/effects/{ => mouse_pointers}/cult_target.dmi (100%) create mode 100644 icons/effects/mouse_pointers/mindswap_target.dmi create mode 100644 icons/effects/mouse_pointers/overload_machine_target.dmi create mode 100644 icons/effects/mouse_pointers/override_machine_target.dmi create mode 100644 icons/mob/actions/actions_mime.dmi create mode 100644 icons/mob/bloodsucker_clan_icons.dmi create mode 100644 icons/mob/nonhuman-player/cult.dmi create mode 100644 strings/steve.json create mode 100644 tgui/packages/tgui/interfaces/AntagInfoBlob.tsx create mode 100644 tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx create mode 100644 tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx create mode 100644 tgui/packages/tgui/interfaces/AntagInfoDemon.tsx create mode 100644 tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx create mode 100644 tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx delete mode 100644 tgui/packages/tgui/interfaces/KindredArchives.js create mode 100644 tgui/packages/tgui/interfaces/KindredBook.tsx create mode 100644 tgui/packages/tgui/interfaces/NumberInputModal.tsx create mode 100644 tgui/packages/tgui/interfaces/TextInputModal.tsx create mode 100644 tgui/packages/tgui/interfaces/common/InputButtons.tsx create mode 100644 tgui/packages/tgui/interfaces/common/Loader.tsx create mode 100644 tgui/packages/tgui/styles/themes/darkspawn.scss create mode 100644 yogstation/code/modules/antagonists/_common/antag_menu.dm create mode 100644 yogstation/icons/mob/antag_hud.dmi diff --git a/code/__DEFINES/actions.dm b/code/__DEFINES/actions.dm new file mode 100644 index 000000000000..e052d7088a08 --- /dev/null +++ b/code/__DEFINES/actions.dm @@ -0,0 +1,20 @@ +///Action button checks if hands are unusable +#define AB_CHECK_HANDS_BLOCKED (1<<0) +///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) +///Action button checks if user is conscious +#define AB_CHECK_CONSCIOUS (1<<3) + +///Action button triggered with right click +#define TRIGGER_SECONDARY_ACTION (1<<0) + +// Defines for formatting cooldown actions for the stat panel. +/// The stat panel the action is displayed in. +#define PANEL_DISPLAY_PANEL "panel" +/// The status shown in the stat panel. +/// Can be stuff like "ready", "on cooldown", "active", "charges", "charge cost", etc. +#define PANEL_DISPLAY_STATUS "status" +/// The name shown in the stat panel. +#define PANEL_DISPLAY_NAME "name" \ No newline at end of file diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 6400d4231af6..7f5d198bbe4e 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -122,8 +122,33 @@ #define TIER_3 6 #define TIER_ASCEND 7 -//Bloodsuckers +///Whether a mob is a Bloodsucker #define IS_BLOODSUCKER(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/bloodsucker)) +///Whether a mob is a Vassal #define IS_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal)) +///Whether a mob is a Favorite Vassal +#define IS_FAVORITE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/favorite)) +///Whether a mob is a Revenge Vassal +#define IS_REVENGE_VASSAL(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/vassal/revenge)) +///Whether a mob is a Monster Hunter #define IS_MONSTERHUNTER(mob) (mob?.mind?.has_antag_datum(/datum/antagonist/monsterhunter)) +/// The Classic Wizard wizard loadout. +#define WIZARD_LOADOUT_CLASSIC "loadout_classic" +/// Mjolnir's Power wizard loadout. +#define WIZARD_LOADOUT_MJOLNIR "loadout_hammer" +/// Fantastical Army wizard loadout. +#define WIZARD_LOADOUT_WIZARMY "loadout_army" +/// Soul Tapper wizard loadout. +#define WIZARD_LOADOUT_SOULTAP "loadout_tap" +/// Convenient list of all wizard loadouts for unit testing. +#define ALL_WIZARD_LOADOUTS list( \ + WIZARD_LOADOUT_CLASSIC, \ + WIZARD_LOADOUT_MJOLNIR, \ + WIZARD_LOADOUT_WIZARMY, \ + WIZARD_LOADOUT_SOULTAP, \ +) + +/// Used in logging spells for roundend results +#define LOG_SPELL_TYPE "type" +#define LOG_SPELL_AMOUNT "amount" \ No newline at end of file diff --git a/code/__DEFINES/atom_hud.dm b/code/__DEFINES/atom_hud.dm index 11027d0b7f13..1b77cee4f3ca 100644 --- a/code/__DEFINES/atom_hud.dm +++ b/code/__DEFINES/atom_hud.dm @@ -63,36 +63,6 @@ #define DATA_HUD_AI_DETECT 9 #define DATA_HUD_SECURITY_MEDICAL 10 -//antag HUD defines -#define ANTAG_HUD_CULT 11 -#define ANTAG_HUD_REV 12 -#define ANTAG_HUD_OPS 13 -#define ANTAG_HUD_WIZ 14 -#define ANTAG_HUD_SHADOW 15 -#define ANTAG_HUD_TRAITOR 16 -#define ANTAG_HUD_NINJA 17 -#define ANTAG_HUD_CHANGELING 18 -#define ANTAG_HUD_ABDUCTOR 19 -#define ANTAG_HUD_DEVIL 20 -#define ANTAG_HUD_SINTOUCHED 21 -#define ANTAG_HUD_SOULLESS 22 -#define ANTAG_HUD_CLOCKWORK 23 -#define ANTAG_HUD_BROTHER 24 -#define ANTAG_HUD_HIVE 25 -#define ANTAG_HUD_OBSESSED 26 -#define ANTAG_HUD_FUGITIVE 27 -#define ANTAG_HUD_VAMPIRE 28 -#define ANTAG_HUD_DARKSPAWN 29 -#define ANTAG_HUD_CAPITALIST 30 -#define ANTAG_HUD_COMMUNIST 31 -#define ANTAG_HUD_HERETIC 32 -#define ANTAG_HUD_MINDSLAVE 33 -#define ANTAG_HUD_ZOMBIE 34 -#define ANTAG_HUD_INFILTRATOR 35 -#define ANTAG_HUD_BLOODSUCKER 36 -#define ANTAG_HUD_MHUNTER 37 -#define ANTAG_HUD_BRAINWASHED 38 - // Notification action types #define NOTIFY_JUMP "jump" #define NOTIFY_ATTACK "attack" diff --git a/code/__DEFINES/bloodsuckers.dm b/code/__DEFINES/bloodsuckers.dm index d8be795f7d80..920c10e3441e 100644 --- a/code/__DEFINES/bloodsuckers.dm +++ b/code/__DEFINES/bloodsuckers.dm @@ -3,10 +3,10 @@ */ /// Determines Bloodsucker regeneration rate #define BS_BLOOD_VOLUME_MAX_REGEN 700 -/// Cost to torture someone, in blood -#define TORTURE_BLOOD_COST "15" +/// Cost to torture someone halfway, in blood. Called twice for full cost +#define TORTURE_BLOOD_HALF_COST 8 /// Cost to convert someone after successful torture, in blood -#define TORTURE_CONVERSION_COST "50" +#define TORTURE_CONVERSION_COST 50 /// Deals with constant processes off of LifeTick() #define COMSIG_LIVING_BIOLOGICAL_LIFE "biological_life" /// Once blood is this low, will enter Frenzy @@ -16,6 +16,37 @@ /// You have special interactions with Bloodsuckers #define TRAIT_BLOODSUCKER_HUNTER "bloodsucker_hunter" +///Drinks blood the normal Bloodsucker way. +#define BLOODSUCKER_DRINK_NORMAL "bloodsucker_drink_normal" +///Drinks blood but is snobby, refusing to drink from mindless +#define BLOODSUCKER_DRINK_SNOBBY "bloodsucker_drink_snobby" +///Drinks blood from disgusting creatures without Humanity consequences. +#define BLOODSUCKER_DRINK_INHUMANELY "bloodsucker_drink_imhumanely" + +///Controls blood, just like all Bloodsuckers do +#define BLOODSUCKER_CONTROL_BLOOD 1 +///Controls shadows and blood, as it creates and destroys. Able to make shadow rituals +#define BLOODSUCKER_CONTROL_SHADOWS 2 +///Controls metal and blood to make art works to buff mood and other benefits +#define BLOODSUCKER_CONTROL_METAL 4 +///Controls the inner machinations of one's being, being able to use the vassalrack and sculpt flesh monsters +#define BLOODSUCKER_CONTROL_FLESH 8 + +#define BLOODSUCKER_RANK_UP_NORMAL "bloodsucker_rank_up_normal" + +#define DANGER_LEVEL_FIRST_WARNING 1 +#define DANGER_LEVEL_SECOND_WARNING 2 +#define DANGER_LEVEL_THIRD_WARNING 3 +#define DANGER_LEVEL_SOL_ROSE 4 +#define DANGER_LEVEL_SOL_ENDED 5 + +///If someone passes all checks and can be vassalized +#define VASSALIZATION_ALLOWED "allowed" +///If someone has to accept vassalization +#define VASSALIZATION_DISLOYAL "disloyal" +///If someone is not allowed under any circimstances to become a Vassal +#define VASSALIZATION_BANNED "banned" + /** * Cooldown defines * Used in Cooldowns Bloodsuckers use to prevent spamming @@ -28,6 +59,7 @@ /** * Clan defines */ +#define CLAN_NONE "Caitiff" #define CLAN_BRUJAH "Brujah Clan" #define CLAN_NOSFERATU "Nosferatu Clan" #define CLAN_TREMERE "Tremere Clan" @@ -38,6 +70,10 @@ #define CLAN_LASOMBRA "Lasombra Clan" #define CLAN_TZIMISCE "Tzimisce Clan" +#define TREMERE_VASSAL "tremere_vassal" +#define FAVORITE_VASSAL "favorite_vassal" +#define REVENGE_VASSAL "revenge_vassal" + #define TRIPLECHEST_MONSTER "Triple Chest (300 Blood)" #define ARMMY_MONSTER "Armmy (100 Blood)" #define CALCIUM_MONSTER "Calcium (150 Blood)" @@ -60,16 +96,18 @@ /// This Power can be purchased by Bloodsuckers #define BLOODSUCKER_CAN_BUY (1<<0) +/// This is a Default Power that all Bloodsuckers get. +#define BLOODSUCKER_DEFAULT_POWER (1<<1) /// This Power can be purchased by Lasombra Bloodsuckers -#define LASOMBRA_CAN_BUY (1<<1) +#define LASOMBRA_CAN_BUY (1<<2) /// This Power can be purchased by Gangrel Bloodsuckers -#define GANGREL_CAN_BUY (1<<2) +#define GANGREL_CAN_BUY (1<<3) /// This Power can be purchased by Vassals -#define VASSAL_CAN_BUY (1<<3) +#define VASSAL_CAN_BUY (1<<4) /// This Power can be purchased by Monster Hunters -#define HUNTER_CAN_BUY (1<<4) +#define HUNTER_CAN_BUY (1<<5) /// This Power can be purchased by Tzimisce Bloodsuckers -#define TZIMISCE_CAN_BUY (1<<5) +#define TZIMISCE_CAN_BUY (1<<6) /// This Power is a Toggled Power #define BP_AM_TOGGLE (1<<0) @@ -80,6 +118,35 @@ /// This Power doesn't cost bloot to run while unconscious #define BP_AM_COSTLESS_UNCONSCIOUS (1<<3) -// Signals +/** + * Signals + */ +///Called when a Bloodsucker ranks up: (datum/bloodsucker_datum, mob/owner, mob/target) +#define BLOODSUCKER_RANK_UP "bloodsucker_rank_up" + +///Called when a Bloodsucker attempts to make a Vassal into their Favorite. +#define BLOODSUCKER_PRE_MAKE_FAVORITE "bloodsucker_pre_make_favorite" +///Called when a Bloodsucker makes a Vassal into their Favorite Vassal: (datum/vassal_datum, mob/master) +#define BLOODSUCKER_MAKE_FAVORITE "bloodsucker_make_favorite" +///Called when a new Vassal is successfully made: (datum/bloodsucker_datum) +#define BLOODSUCKER_MADE_VASSAL "bloodsucker_made_vassal" + +///Called on Bloodsucker's LifeTick() +#define BLOODSUCKER_HANDLE_LIFE "bloodsucker_handle_life" +///Called when a Bloodsucker exits Torpor. +#define BLOODSUCKER_EXIT_TORPOR "bloodsucker_exit_torpor" +///Called when a Bloodsucker reaches Final Death. +#define BLOODSUCKER_FINAL_DEATH "bloodsucker_final_death" -#define COMSIG_BLOODSUCKER_RANKS_SPENT \ No newline at end of file +///Whether the Bloodsucker should not be dusted when arriving Final Death +#define DONT_DUST (1<<0) + +/** + * Sol signals + */ +#define COMSIG_SOL_RANKUP_BLOODSUCKERS "comsig_sol_rankup_bloodsuckers" +#define COMSIG_SOL_RISE_TICK "comsig_sol_rise_tick" +#define COMSIG_SOL_NEAR_START "comsig_sol_near_start" +#define COMSIG_SOL_END "comsig_sol_end" +///Sent when a warning for Sol is meant to go out: (danger_level, vampire_warning_message, vassal_warning_message) +#define COMSIG_SOL_WARNING_GIVEN "comsig_sol_warning_given" diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index bfb5972c8901..fb32be991ecf 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -66,3 +66,10 @@ /// The default color for admin say, used as a fallback when the preference is not enabled #define DEFAULT_ASAY_COLOR "#996600" + +/* + * Antag Specific Colors +*/ + +#define COLOR_CHANGELING_CHEMICALS "#DD66DD" +#define COLOR_DARKSPAWN_PSI "#7264FF" \ No newline at end of file diff --git a/code/__DEFINES/cult.dm b/code/__DEFINES/cult.dm index 054d6345dbd6..871c6c5d00e2 100644 --- a/code/__DEFINES/cult.dm +++ b/code/__DEFINES/cult.dm @@ -27,3 +27,7 @@ //misc #define SOULS_TO_REVIVE 3 #define BLOODCULT_EYE "f00" +//soulstone & construct themes +#define THEME_CULT "cult" +#define THEME_WIZARD "wizard" +#define THEME_HOLY "holy" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm index 771a84c4d9e4..ad6d7b1e86df 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_x_act.dm @@ -8,7 +8,7 @@ #define COMSIG_ATOM_EMP_ACT "atom_emp_act" ///from base of atom/fire_act(): (exposed_temperature, exposed_volume) #define COMSIG_ATOM_FIRE_ACT "atom_fire_act" -///from base of atom/bullet_act(): (/obj/projectile, def_zone) +///from base of atom/bullet_act(): (/obj/item/projectile, def_zone) #define COMSIG_ATOM_BULLET_ACT "atom_bullet_act" ///from base of atom/CheckParts(): (list/parts_list, datum/crafting_recipe/R) #define COMSIG_ATOM_CHECKPARTS "atom_checkparts" diff --git a/code/__DEFINES/dcs/signals/signals_datum.dm b/code/__DEFINES/dcs/signals/signals_datum.dm index 2cebc161d57e..9b3067bc8a45 100644 --- a/code/__DEFINES/dcs/signals/signals_datum.dm +++ b/code/__DEFINES/dcs/signals/signals_datum.dm @@ -13,3 +13,10 @@ #define COMSIG_ELEMENT_ATTACH "element_attach" /// fires on the target datum when an element is attached to it (/datum/element) #define COMSIG_ELEMENT_DETACH "element_detach" + +// Antagonist signals +/// 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" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm index 01c460d4a834..fca27b58a864 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_carbon.dm @@ -111,7 +111,7 @@ ///Called when a monkey turns into a human, from /mob/living/carbon/proc/finish_humanize(species) #define COMSIG_MONKEY_HUMANIZE "monkey_humanize" -///From mob/living/carbon/human/suicide() +///From mob/living/carbon/human/handle_suicide(); on yog: mob/living/carbon/human/verb/suicide() #define COMSIG_HUMAN_SUICIDE_ACT "human_suicide_act" ///from base of /mob/living/carbon/regenerate_limbs(): (excluded_limbs) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm index b878fb218234..cbec9632211d 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_living.dm @@ -135,3 +135,6 @@ #define COMSIG_LIVING_BEFRIENDED "living_befriended" /// From /mob/living/unfriend() : (mob/living/old_friend) #define COMSIG_LIVING_UNFRIENDED "living_unfriended" + +///from mind/transfer_to. Sent after the mind has been transferred: (mob/previous_body) +#define COMSIG_MIND_TRANSFERRED "mind_transferred" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 77a76f9fc773..397062b90685 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -335,23 +335,23 @@ ///called in /obj/item/gun/process_fire (user, target, params, zone_override) #define COMSIG_GRENADE_ARMED "grenade_armed" -// /obj/projectile signals (sent to the firer) +// /obj/item/projectile signals (sent to the firer) -///from base of /obj/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, Angle, hit_limb) +///from base of /obj/item/projectile/proc/on_hit(), like COMSIG_PROJECTILE_ON_HIT but on the projectile itself and with the hit limb (if any): (atom/movable/firer, atom/target, Angle, hit_limb) #define COMSIG_PROJECTILE_SELF_ON_HIT "projectile_self_on_hit" -///from base of /obj/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) +///from base of /obj/item/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) #define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" -///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/original_target) +///from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/original_target) #define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" -///from base of /obj/projectile/proc/fire(): (obj/projectile, atom/firer, atom/original_target) +///from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/firer, atom/original_target) #define COMSIG_PROJECTILE_FIRER_BEFORE_FIRE "projectile_firer_before_fire" -///from the base of /obj/projectile/proc/fire(): () +///from the base of /obj/item/projectile/proc/fire(): () #define COMSIG_PROJECTILE_FIRE "projectile_fire" ///sent to targets during the process_hit proc of projectiles #define COMSIG_PROJECTILE_PREHIT "com_proj_prehit" -///from the base of /obj/projectile/Range(): () +///from the base of /obj/item/projectile/Range(): () #define COMSIG_PROJECTILE_RANGE "projectile_range" -///from the base of /obj/projectile/on_range(): () +///from the base of /obj/item/projectile/on_range(): () #define COMSIG_PROJECTILE_RANGE_OUT "projectile_range_out" ///from [/obj/item/proc/tryEmbed] sent when trying to force an embed (mainly for projectiles and eating glass) #define COMSIG_EMBED_TRY_FORCE "item_try_embed" @@ -371,7 +371,7 @@ ///from /datum/action/vehicle/sealed/headlights/vim/Trigger(): (headlights_on) #define COMSIG_VIM_HEADLIGHTS_TOGGLED "vim_headlights_toggled" -// /obj/vehicle/sealed/mecha signals +// /obj/mecha signals /// sent if you attach equipment to mecha #define COMSIG_MECHA_EQUIPMENT_ATTACHED "mecha_equipment_attached" diff --git a/code/__DEFINES/dcs/signals/signals_spell.dm b/code/__DEFINES/dcs/signals/signals_spell.dm index 8c81adeb8f52..445a464873cb 100644 --- a/code/__DEFINES/dcs/signals/signals_spell.dm +++ b/code/__DEFINES/dcs/signals/signals_spell.dm @@ -33,7 +33,7 @@ // Spell type signals // Pointed projectiles -// Sent from /datum/action/cooldown/spell/pointed/projectile/fire_projectile() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on, obj/projectile/to_fire) +// Sent from /datum/action/cooldown/spell/pointed/projectile/fire_projectile() to the caster: (datum/action/cooldown/spell/spell, atom/cast_on, obj/item/projectile/to_fire) #define COMSIG_MOB_SPELL_PROJECTILE "mob_spell_projectile" /// Sent from /datum/action/cooldown/spell/pointed/projectile/on_cast_hit: (atom/firer, atom/target, atom/hit, angle, hit_limb) #define COMSIG_SPELL_PROJECTILE_HIT "spell_projectile_hit" diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index 64fa83df4f0d..eaedd0b6dd35 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -70,6 +70,7 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 /// If megafauna can be spawned by natural random generation #define MEGAFAUNA_SPAWN_ALLOWED (1<<5) /// Are you forbidden from teleporting to the area? (centcom, mobs, wizard, hand teleporter) +#define NOTELEPORT (1<<6) /* These defines are used specifically with the atom/pass_flags bitmask diff --git a/code/__DEFINES/lighting.dm b/code/__DEFINES/lighting.dm index 05b2b9a13013..c29a03011689 100644 --- a/code/__DEFINES/lighting.dm +++ b/code/__DEFINES/lighting.dm @@ -33,6 +33,9 @@ /// Set to zero to disable soft lighting. Luminosity changes then work if it's lit at all. #define LIGHTING_SOFT_THRESHOLD 0 +/// The amount of lumcount on a tile for it to be considered dark (used to determine reading and nyctophobia) +#define LIGHTING_TILE_IS_DARK 0.2 + /// If I were you I'd leave this alone. #define LIGHTING_BASE_MATRIX \ list \ diff --git a/code/__DEFINES/magic.dm b/code/__DEFINES/magic.dm new file mode 100644 index 000000000000..7eeae378337c --- /dev/null +++ b/code/__DEFINES/magic.dm @@ -0,0 +1,109 @@ +// 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" +/// Psychic school. Not true magic, but psychic spells only benefit themselves. +#define SCHOOL_PSYCHIC "psychic" +/// 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" +/// Blood magic, involves vampirism, draining blood, etc. +#define SCHOOL_SANGUINE "sanguine" + +// Invocation types - what does the wizard need to do to invoke (cast) the spell? +/// Allows being able to cast the spell without saying or doing anything. +#define INVOCATION_NONE "none" +/// Forces the wizard to shout the invocation to cast the spell. +#define INVOCATION_SHOUT "shout" +/// Forces the wizard to whisper the invocation to cast the spell. +#define INVOCATION_WHISPER "whisper" +/// Forces the wizard to emote to cast the spell. +#define INVOCATION_EMOTE "emote" + +// 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 requires being on the station z-level to be cast. +#define SPELL_REQUIRES_STATION (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_STATION" = SPELL_REQUIRES_STATION, + "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, 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, +))*/ + +/** + * Checks if our mob is jaunting actively (within a phased mob object) + * Used in jaunting spells specifically to determine whether they should be entering or exiting jaunt + * + * If you want to use this in non-jaunt related code, it is preferable + * to instead check for trait [TRAIT_MAGICALLY_PHASED] instead of using this + * as it encompasses more states in which a mob may be "incorporeal from magic" + */ +#define is_jaunting(atom) (istype(atom.loc, /obj/effect/dummy/phased_mob)) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 5dba720d72b1..63197107cf01 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -400,13 +400,23 @@ GLOBAL_LIST_INIT(donor_pdas, list(PDA_COLOR_NORMAL, PDA_COLOR_TRANSPARENT, PDA_C #define STACK_CHECK_ADJACENT "adjacent" //checks if there is an object of the result type within one tile //text files +/// File location for brain damage traumas #define BRAIN_DAMAGE_FILE "traumas.json" +/// File location for AI ion laws #define ION_FILE "ion_laws.json" +/// File location for pirate names #define PIRATE_NAMES_FILE "pirates.json" +/// File location for redpill questions #define REDPILL_FILE "redpill.json" +/// File location for wanted posters messages #define WANTED_FILE "wanted_message.json" +/// File location for really dumb suggestions memes +#define VISTA_FILE "steve.json" +/// File location for flesh wound descriptions #define FLESH_SCAR_FILE "wounds/flesh_scar_desc.json" +/// File location for bone wound descriptions #define BONE_SCAR_FILE "wounds/bone_scar_desc.json" +/// File location for scar wound descriptions #define SCAR_LOC_FILE "wounds/scar_loc.json" //Fullscreen overlay resolution in tiles. diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index ec37e27982dc..a1b8fa4e6387 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -18,6 +18,7 @@ #define MOVE_INTENT_RUN "run" //Blood volumes, in cL +#define BLOOD_VOLUME_MAX_LETHAL 2150 // The lethal amount for a good semaritan, based off IRL data about vampires #define BLOOD_VOLUME_GENERIC 560 // The default amount of blood in a blooded creature, in cL, based off IRL data about humans #define BLOOD_VOLUME_MONKEY 325 // Based on IRL data bout Chimpanzees #define BLOOD_VOLUME_XENO 700 // Based off data from my asshole diff --git a/code/__DEFINES/power.dm b/code/__DEFINES/power.dm index f38b380aec9c..58edfeb91baa 100644 --- a/code/__DEFINES/power.dm +++ b/code/__DEFINES/power.dm @@ -6,3 +6,8 @@ #define TESLA_MINI_POWER 869130 //Multiplier of all power consumed. #define POWER_MOD 1 + +///conversion ratio from joules to watts +#define WATTS / 0.002 +///conversion ratio from watts to joules +#define JOULES * 0.002 diff --git a/code/__DEFINES/security.dm b/code/__DEFINES/security.dm new file mode 100644 index 000000000000..a385d843398e --- /dev/null +++ b/code/__DEFINES/security.dm @@ -0,0 +1,74 @@ + +// CATEGORY HEADERS + +/// Fingerpints detected +#define DETSCAN_CATEGORY_FINGERS "Prints" +/// Displays any bloodprints found and their uefi +#define DETSCAN_CATEGORY_BLOOD "Blood" +/// Clothing and glove fibers +#define DETSCAN_CATEGORY_FIBER "Fibers" +/// Liquids detected +#define DETSCAN_CATEGORY_DRINK "Reagents" +/// ID Access +#define DETSCAN_CATEGORY_ACCESS "ID Access" + +// The categories below do not have hard rules on what info is displayed, and are for categorizing info thematically. + +/// Generic extra information category +#define DETSCAN_CATEGORY_NOTES "Additional Notes" +/// Attributes that might be illegal, but don't have ties to syndicate/aren't exclusively produced by them +#define DETSCAN_CATEGORY_ILLEGAL "Illegal Tech" +/// The emags and other in-house technology from the syndicate +#define DETSCAN_CATEGORY_SYNDIE "Syndicate Tech" +/// praise be +#define DETSCAN_CATEGORY_HOLY "Holy Data" +/// The mode that the items in, what kind of item is dispensed, etc +#define DETSCAN_CATEGORY_SETTINGS "Active Settings" + +// If your category is not in this list it WILL NOT BE DISPLAYED +/// defines the order categories are displayed, with the original categories, then custom ones, then finally the extra info. +#define DETSCAN_DEFAULT_ORDER(...) list(\ + DETSCAN_CATEGORY_FINGERS, \ + DETSCAN_CATEGORY_BLOOD, \ + DETSCAN_CATEGORY_FIBER, \ + DETSCAN_CATEGORY_DRINK, \ + DETSCAN_CATEGORY_ACCESS, \ + DETSCAN_CATEGORY_SETTINGS, \ + DETSCAN_CATEGORY_HOLY, \ + DETSCAN_CATEGORY_ILLEGAL, \ + DETSCAN_CATEGORY_SYNDIE, \ + DETSCAN_CATEGORY_NOTES, \ +) + +/// the order departments show up in for the id scan (its sorted by red to blue on the color wheel) +#define DETSCAN_ACCESS_ORDER(...) list(\ + REGION_SECURITY, \ + REGION_ENGINEERING, \ + REGION_SUPPLY, \ + REGION_GENERAL, \ + REGION_MEDBAY, \ + REGION_COMMAND, \ + REGION_RESEARCH, \ + REGION_CENTCOM, \ +) + +/// if any categories list has this entry, it will be hidden +#define DETSCAN_BLOCK "DETSCAN_BLOCK" + +/// Wanted statuses +#define WANTED_ARREST "Arrest" +#define WANTED_DISCHARGED "Discharged" +#define WANTED_NONE "None" +#define WANTED_PAROLE "Parole" +#define WANTED_PRISONER "Incarcerated" +#define WANTED_SUSPECT "Suspected" + +/// List of available wanted statuses +#define WANTED_STATUSES(...) list(\ + WANTED_NONE, \ + WANTED_SUSPECT, \ + WANTED_ARREST, \ + WANTED_PRISONER, \ + WANTED_PAROLE, \ + WANTED_DISCHARGED, \ +) diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 0d00b6c713ac..5793d256fd65 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -9,6 +9,12 @@ #define STATUS_EFFECT_REFRESH 3 // if it only allows one, and new instances just instead refresh the timer +///Processing flags - used to define the speed at which the status will work +///This is fast - 0.2s between ticks (I believe!) +#define STATUS_EFFECT_FAST_PROCESS 0 +///This is slower and better for more intensive status effects - 1s between ticks +#define STATUS_EFFECT_NORMAL_PROCESS 1 + /////////// // BUFFS // /////////// @@ -32,7 +38,7 @@ #define STATUS_EFFECT_EXERCISED /datum/status_effect/exercised //Prevents heart disease -#define STATUS_EFFECT_HIPPOCRATIC_OATH /datum/status_effect/hippocraticOath //Gives you an aura of healing as well as regrowing the Rod of Asclepius if lost +#define STATUS_EFFECT_HIPPOCRATIC_OATH /datum/status_effect/hippocratic_oath //Gives you an aura of healing as well as regrowing the Rod of Asclepius if lost #define STATUS_EFFECT_GOOD_MUSIC /datum/status_effect/good_music @@ -172,3 +178,91 @@ #define IS_IN_STASIS(mob) (mob.life_tickrate == 0) #define LIFETICK_SKIP(living, tick) (living.life_tickrate && (tick % living.life_tickrate) == 0) + +// Status effect application helpers. +// These are macros for easier use of adjust_timed_status_effect and set_timed_status_effect. +// +// adjust_x: +// - Adds duration to a status effect +// - Removes duration if a negative duration is passed. +// - Ex: adjust_stutter(10 SECONDS) adds ten seconds of stuttering. +// - Ex: adjust_jitter(-5 SECONDS) removes five seconds of jittering, or just removes jittering if less than five seconds exist. +// +// adjust_x_up_to: +// - Will only add (or remove) duration of a status effect up to the second parameter +// - If the duration will result in going beyond the second parameter, it will stop exactly at that parameter +// - The second parameter cannot be negative. +// - Ex: adjust_stutter_up_to(20 SECONDS, 10 SECONDS) adds ten seconds of stuttering. +// +// set_x: +// - Set the duration of a status effect to the exact number. +// - Setting duration to zero seconds is effectively the same as just using remove_status_effect, or qdelling the effect. +// - Ex: set_stutter(10 SECONDS) sets the stuttering to ten seconds, regardless of whether they had more or less existing stutter. +// +// set_x_if_lower: +// - Will only set the duration of that effect IF any existing duration is lower than what was passed. +// - Ex: set_stutter_if_lower(10 SECONDS) will set stuttering to ten seconds if no stuttering or less than ten seconds of stuttering exists +// - Ex: set_jitter_if_lower(20 SECONDS) will do nothing if more than twenty seconds of jittering already exists + +#define adjust_stutter(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter) +#define adjust_stutter_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter, up_to) +#define set_stutter(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter) +#define set_stutter_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter, TRUE) + +#define adjust_derpspeech(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech) +#define adjust_derpspeech_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech, up_to) +#define set_derpspeech(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech) +#define set_derpspeech_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/stutter/derpspeech, TRUE) + +#define adjust_slurring(duration) adjust_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk) +#define adjust_slurring_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk, up_to) +#define set_slurring(duration) set_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk) +#define set_slurring_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/speech/slurring/drunk, TRUE) + +#define adjust_dizzy(duration) adjust_timed_status_effect(duration, /datum/status_effect/dizziness) +#define adjust_dizzy_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/dizziness, up_to) +#define set_dizzy(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness) +#define set_dizzy_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/dizziness, TRUE) + +#define adjust_jitter(duration) adjust_timed_status_effect(duration, /datum/status_effect/jitter) +#define adjust_jitter_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/jitter, up_to) +#define set_jitter(duration) set_timed_status_effect(duration, /datum/status_effect/jitter) +#define set_jitter_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/jitter, TRUE) + +#define adjust_confusion(duration) adjust_timed_status_effect(duration, /datum/status_effect/confusion) +#define adjust_confusion_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/confusion, up_to) +#define set_confusion(duration) set_timed_status_effect(duration, /datum/status_effect/confusion) +#define set_confusion_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/confusion, TRUE) + +#define adjust_drugginess(duration) adjust_timed_status_effect(duration, /datum/status_effect/drugginess) +#define adjust_drugginess_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/drugginess, up_to) +#define set_drugginess(duration) set_timed_status_effect(duration, /datum/status_effect/drugginess) +#define set_drugginess_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/drugginess, TRUE) + +#define adjust_silence(duration) adjust_timed_status_effect(duration, /datum/status_effect/silenced) +#define adjust_silence_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/silenced, up_to) +#define set_silence(duration) set_timed_status_effect(duration, /datum/status_effect/silenced) +#define set_silence_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/silenced, TRUE) + +#define adjust_hallucinations(duration) adjust_timed_status_effect(duration, /datum/status_effect/hallucination) +#define adjust_hallucinations_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/hallucination, up_to) +#define set_hallucinations(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination) +#define set_hallucinations_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/hallucination, TRUE) + +#define adjust_drowsiness(duration) adjust_timed_status_effect(duration, /datum/status_effect/drowsiness) +#define adjust_drowsiness_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/drowsiness, up_to) +#define set_drowsiness(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness) +#define set_drowsiness_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/drowsiness, TRUE) + +#define adjust_pacifism(duration) adjust_timed_status_effect(/datum/status_effect/pacify, duration) +#define set_pacifism(duration) set_timed_status_effect(/datum/status_effect/pacify, duration) + +#define adjust_eye_blur(duration) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur) +#define adjust_eye_blur_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/eye_blur, up_to) +#define set_eye_blur(duration) set_timed_status_effect(duration, /datum/status_effect/eye_blur) +#define set_eye_blur_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/eye_blur, TRUE) + +#define adjust_temp_blindness(duration) adjust_timed_status_effect(duration, /datum/status_effect/temporary_blindness) +#define adjust_temp_blindness_up_to(duration, up_to) adjust_timed_status_effect(duration, /datum/status_effect/temporary_blindness, up_to) +#define set_temp_blindness(duration) set_timed_status_effect(duration, /datum/status_effect/temporary_blindness) +#define set_temp_blindness_if_lower(duration) set_timed_status_effect(duration, /datum/status_effect/temporary_blindness, TRUE) diff --git a/code/__DEFINES/text.dm b/code/__DEFINES/text.dm index 8a314eba02de..f61312e58803 100644 --- a/code/__DEFINES/text.dm +++ b/code/__DEFINES/text.dm @@ -2,4 +2,9 @@ #define MAPTEXT(text) {"[##text]"} /// Macro from Lummox used to get height from a MeasureText proc -#define WXH_TO_HEIGHT(x) text2num(copytext(x, findtextEx(x, "x") + 1)) \ No newline at end of file +#define WXH_TO_HEIGHT(x) text2num(copytext(x, findtextEx(x, "x") + 1)) + +/* + * Uses MAPTEXT to format antag points into a more appealing format + */ +#define FORMAT_ANTAG_TEXT(value, color) MAPTEXT("
[round(value)]
") diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index ee69e73fe874..81b03042251b 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -114,6 +114,17 @@ //Remember to update code/datums/traits/ folder if you're adding/removing/renaming traits. //mob traits +/// Forces the user to stay unconscious. +#define TRAIT_KNOCKEDOUT "knockedout" +/// Prevents voluntary movement. +#define TRAIT_IMMOBILIZED "immobilized" +/// Prevents voluntary standing or staying up on its own. +#define TRAIT_FLOORED "floored" +/// Forces user to stay standing +#define TRAIT_FORCED_STANDING "forcedstanding" +/// Prevents usage of manipulation appendages (picking, holding or using items, manipulating storage). +#define TRAIT_HANDS_BLOCKED "handsblocked" +#define TRAIT_INCAPACITATED "incapacitated" #define TRAIT_BLIND "blind" #define TRAIT_MUTE "mute" #define TRAIT_EMOTEMUTE "emotemute" @@ -231,6 +242,17 @@ #define TRAIT_BADMAIL "badmail" //Your mail is going to be worse than average #define TRAIT_SHORT_TELOMERES "short_telomeres" //You cannot be CLOONED #define TRAIT_LONG_TELOMERES "long_telomeres" //You get CLOONED faster!!! +/// Immune to being afflicted by time stop (spell) +#define TRAIT_TIME_STOP_IMMUNE "time_stop_immune" +/// This mob has no soul +#define TRAIT_NO_SOUL "no_soul" +/// 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" /// This person is crying #define TRAIT_CRYING "crying" @@ -303,6 +325,10 @@ #define STATION_TRAIT "station-trait" #define ATTACHMENT_TRAIT "attachment-trait" #define GLASSES_TRAIT "glasses" +/// A trait given by a specific status effect (not sure why we need both but whatever!) +#define TRAIT_STATUS_EFFECT(effect_id) "[effect_id]-trait" +/// trait associated to being held in a chokehold +#define CHOKEHOLD_TRAIT "chokehold" // unique trait sources, still defines #define CLONING_POD_TRAIT "cloning-pod" @@ -346,8 +372,12 @@ #define STARGAZER_TRAIT "stargazer" #define RANDOM_BLACKOUTS "random_blackouts" #define MADE_UNCLONEABLE "made-uncloneable" +/// Source trait for Bloodsuckers-related traits #define BLOODSUCKER_TRAIT "bloodsucker_trait" +/// Source trait during a Frenzy #define FRENZY_TRAIT "frenzy_trait" +/// Source trait while Feeding +#define FEED_TRAIT "feed_trait" #define HORROR_TRAIT "horror" #define HOLDER_TRAIT "holder_trait" #define SINFULDEMON_TRAIT "sinfuldemon" @@ -363,3 +393,17 @@ #define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint" #define STATION_TRAIT_PDA_GLITCHED "station_trait_pda_glitched" #define STATION_TRAIT_STATION_ADRIFT "station_trait_station_adrift" + +//important_recursive_contents traits +/* + * Used for movables that need to be updated, via COMSIG_ENTER_AREA and COMSIG_EXIT_AREA, when transitioning areas. + * Use [/atom/movable/proc/become_area_sensitive(trait_source)] to properly enable it. How you remove it isn't as important. + */ +#define TRAIT_AREA_SENSITIVE "area-sensitive" +///every hearing sensitive atom has this trait +#define TRAIT_HEARING_SENSITIVE "hearing_sensitive" +///every object that is currently the active storage of some client mob has this trait +#define TRAIT_ACTIVE_STORAGE "active_storage" + +///Organ traits +#define TRAIT_BALLMER_SCIENTIST "ballmer_scientist" diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 9139c6bbb8f5..95d7909b7bd3 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -67,6 +67,7 @@ #define VV_HK_ADDCOMPONENT "addcomponent" #define VV_HK_MODIFY_TRAITS "modtraits" #define VV_HK_VIEW_REFERENCES "viewreferences" +#define VV_HK_WEAKREF_RESOLVE "weakref_resolve" // /atom #define VV_HK_MODIFY_TRANSFORM "atom_transform" #define VV_HK_ADD_REAGENT "addreagent" diff --git a/code/__DEFINES/{yogs_defines}/antagonists.dm b/code/__DEFINES/{yogs_defines}/antagonists.dm index 7afc8ce41334..6007a5e48b67 100644 --- a/code/__DEFINES/{yogs_defines}/antagonists.dm +++ b/code/__DEFINES/{yogs_defines}/antagonists.dm @@ -9,4 +9,4 @@ #define NOT_DOMINATING -1 #define MAX_LEADERS_GANG 3 -#define INITIAL_DOM_ATTEMPTS 3 +#define INITIAL_DOM_ATTEMPTS 3 \ No newline at end of file diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 2898017fb27a..1e29638e0d64 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -18,6 +18,8 @@ #define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null) #define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V; #define LAZYLEN(L) length(L) +///Sets a list to null +#define LAZYNULL(L) L = null ///Accesses an associative list, returns null if nothing is found #define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null ///Qdel every item in the list before setting the list to null diff --git a/code/__HELPERS/hallucinations.dm b/code/__HELPERS/hallucinations.dm new file mode 100644 index 000000000000..67c1bc7a3d46 --- /dev/null +++ b/code/__HELPERS/hallucinations.dm @@ -0,0 +1,250 @@ +/// A global list of all ongoing hallucinations, primarily for easy access to be able to stop (delete) hallucinations. +GLOBAL_LIST_EMPTY(all_ongoing_hallucinations) + +/// What typepath of the hallucination +#define HALLUCINATION_ARG_TYPE 1 +/// Where the hallucination came from, for logging +#define HALLUCINATION_ARG_SOURCE 2 + +/// Onwards from this index, it's the arglist that gets passed into the hallucination created. +#define HALLUCINATION_ARGLIST 3 + +/// Biotypes which cannot hallucinate for balance and logic reasons (not code) +#define NO_HALLUCINATION_BIOTYPES MOB_ROBOTIC || MOB_SPIRIT || MOB_EPIC + +// Macro wrapper for _cause_hallucination so we can cheat in named arguments, like AddComponent. +/** + * Causes a hallucination of a certain type to the mob. + * + * First argument is always the type of halllucination, a /datum/hallucination, required. + * second argument is always the key source of the hallucination, used for admin logging, required. + * + * Additionally, named arguments are supported for passing them forward to the created hallucination's new(). + */ +#define cause_hallucination(arguments...) _cause_hallucination(list(##arguments)) + +/// Unless you need this for an explicit reason, use the cause_hallucination wrapper. +/mob/living/proc/_cause_hallucination(list/raw_args) + if(!length(raw_args)) + CRASH("cause_hallucination called with no arguments.") + + var/datum/hallucination/hallucination_type = raw_args[HALLUCINATION_ARG_TYPE] // first arg is the type always + if(!ispath(hallucination_type)) + CRASH("cause_hallucination was given a non-hallucination type.") + + var/hallucination_source = raw_args[HALLUCINATION_ARG_SOURCE] // and second arg, the source + var/datum/hallucination/new_hallucination + + if(length(raw_args) >= HALLUCINATION_ARGLIST) + var/list/passed_args = raw_args.Copy(HALLUCINATION_ARGLIST) + passed_args.Insert(HALLUCINATION_ARG_TYPE, src) + + new_hallucination = new hallucination_type(arglist(passed_args)) + else + new_hallucination = new hallucination_type(src) + + // For some reason, we qdel'd in New, maybe something went wrong. + if(QDELETED(new_hallucination)) + return + // It's not guaranteed that the hallucination passed can successfully be initiated. + // This means there may be cases where someone should have a hallucination but nothing happens, + // notably if you pass a randomly picked hallucination type into this. + // Maybe there should be a separate proc to reroll on failure? + if(!new_hallucination.start()) + qdel(new_hallucination) + return + + investigate_log("was afflicted with a hallucination of type [hallucination_type] by: [hallucination_source]. \ + ([new_hallucination.feedback_details])", INVESTIGATE_HALLUCINATIONS) + return new_hallucination + +/** + * Emits a hallucinating pulse around the passed atom. + * Affects everyone in the passed radius who can view the center, + * except for those with TRAIT_MADNESS_IMMUNE, or those who are blind. + * + * center - required, the center of the pulse + * radius - the radius around that the pulse reaches + * hallucination_duration - how much hallucination is added by the pulse. reduced based on distance to the center. + * hallucination_max_duration - a cap on how much hallucination can be added + * optional_messages - optional list of messages passed. Those affected by pulses will be given one of the messages in said list. + */ +/proc/visible_hallucination_pulse(atom/center, radius = 7, hallucination_duration = 50 SECONDS, hallucination_max_duration, list/optional_messages) + for(var/mob/living/nearby_living in view(center, radius)) +// if(HAS_TRAIT(nearby_living, TRAIT_MADNESS_IMMUNE) || (nearby_living.mind && HAS_TRAIT(nearby_living.mind, TRAIT_MADNESS_IMMUNE))) +// continue + + if(nearby_living.mob_biotypes & NO_HALLUCINATION_BIOTYPES) + continue + + if(is_blind(nearby_living)) + continue + + // Everyone else gets hallucinations. + var/dist = sqrt(1 / max(1, get_dist(nearby_living, center))) + nearby_living.adjust_hallucinations_up_to(hallucination_duration * dist, hallucination_max_duration) + if(length(optional_messages)) + to_chat(nearby_living, pick(optional_messages)) + +/// Global weighted list of all hallucinations that can show up randomly. +GLOBAL_LIST_INIT(random_hallucination_weighted_list, generate_hallucination_weighted_list()) + +/// Generates the global weighted list of random hallucinations. +/proc/generate_hallucination_weighted_list() + var/list/weighted_list = list() + + for(var/datum/hallucination/hallucination_type as anything in typesof(/datum/hallucination)) + if(hallucination_type == initial(hallucination_type.abstract_hallucination_parent)) + continue + var/weight = initial(hallucination_type.random_hallucination_weight) + if(weight <= 0) + continue + + weighted_list[hallucination_type] = weight + + return weighted_list + +/// Debug proc for getting the total weight of the random_hallucination_weighted_list +/proc/debug_hallucination_weighted_list() + var/total_weight = 0 + for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list) + total_weight += GLOB.random_hallucination_weighted_list[hallucination_type] + + to_chat(usr, span_boldnotice("The total weight of the hallucination weighted list is [total_weight].")) + return total_weight + +/// Debug verb for getting the weight of each distinct type within the random_hallucination_weighted_list +/client/proc/debug_hallucination_weighted_list_per_type() + set name = "Show Hallucination Weights" + set category = "Debug" + + var/header = "Type Weight Percent" + + var/total_weight = debug_hallucination_weighted_list() + var/list/all_weights = list() + var/datum/hallucination/last_type + var/last_type_weight = 0 + for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list) + var/this_weight = GLOB.random_hallucination_weighted_list[hallucination_type] + // Last_type is the abstract parent of the last hallucination type we iterated over + if(last_type) + // If this hallucination is the same path as the last type (subtype), add it to the total of the last type weight + if(ispath(hallucination_type, last_type)) + last_type_weight += this_weight + continue + + // Otherwise we moved onto the next hallucination subtype so we can stop + else + all_weights["[last_type] [last_type_weight] / [total_weight] [round(100 * (last_type_weight / total_weight), 0.01)]% chance"] = last_type_weight + + // Set last_type to the abstract parent of this hallucination + last_type = initial(hallucination_type.abstract_hallucination_parent) + // If last_type is the base hallucination it has no distinct subtypes so we can total it up immediately + if(last_type == /datum/hallucination) + all_weights["[hallucination_type] [this_weight] / [total_weight] [round(100 * (this_weight / total_weight), 0.01)]% chance"] = this_weight + last_type = null + + // Otherwise we start the weight sum for the next entry here + else + last_type_weight = this_weight + + // Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text + all_weights = sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE) + + var/page_style = "" + var/page_contents = "[page_style][header][jointext(assoc_to_keys(all_weights), "")]
" + var/datum/browser/popup = new(mob, "hallucinationdebug", "Hallucination Weights", 600, 400) + popup.set_content(page_contents) + popup.open() + +/// Gets a random subtype of the passed hallucination type that has a random_hallucination_weight > 0. +/// If no subtype is passed, it will get any random hallucination subtype that is not abstract and has weight > 0. +/// This can be used instead of picking from the global weighted list to just get a random valid hallucination. +/proc/get_random_valid_hallucination_subtype(passed_type = /datum/hallucination) + if(!ispath(passed_type, /datum/hallucination)) + CRASH("get_random_valid_hallucination_subtype - get_random_valid_hallucination_subtype passed not a hallucination subtype.") + + for(var/datum/hallucination/hallucination_type as anything in shuffle(subtypesof(passed_type))) + if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type) + continue + if(initial(hallucination_type.random_hallucination_weight) <= 0) + continue + + return hallucination_type + + return null + +/// Helper to give the passed mob the ability to select a hallucination from the list of all hallucination subtypes. +/proc/select_hallucination_type(mob/user, message = "Select a hallucination subtype", title = "Choose Hallucination") + var/static/list/hallucinations + if(!hallucinations) + hallucinations = typesof(/datum/hallucination) + for(var/datum/hallucination/hallucination_type as anything in hallucinations) + if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type) + hallucinations -= hallucination_type + + var/chosen = tgui_input_list(user, message, title, hallucinations) + if(!chosen || !ispath(chosen, /datum/hallucination)) + return null + + return chosen + +/// Helper to give the passed mob the ability to create a delusion hallucination (even a custom one). +/// Returns a list of arguments - pass these to _cause_hallucination to cause the desired hallucination +/proc/create_delusion(mob/user) + var/static/list/delusions + if(!delusions) + delusions = typesof(/datum/hallucination/delusion) + for(var/datum/hallucination/delusion_type as anything in delusions) + if(initial(delusion_type.abstract_hallucination_parent) == delusion_type) + delusions -= delusion_type + + var/chosen = tgui_input_list(user, "Select a delusion type. Custom will allow for custom icon entry.", "Select Delusion", delusions) + if(!chosen || !ispath(chosen, /datum/hallucination/delusion)) + return + + var/list/delusion_args = list() + var/static/list/options = list("Yes", "No") + var/duration = tgui_input_number(user, "How long should it last in seconds?", "Delusion: Duration", max_value = INFINITY, min_value = 1, default = 30) + var/affects_us = (tgui_alert(user, "Should they see themselves as the delusion?", "Delusion: Affects us", options) == "Yes") + var/affects_others = (tgui_alert(user, "Should they see everyone else delusion?", "Delusion: Affects others", options) == "Yes") + var/skip_nearby = (tgui_alert(user, "Should the delusion only affect people outside of their view?", "Delusion: Skip in view", options) == "Yes") + var/play_wabbajack = (tgui_alert(user, "Play the wabbajack sound when it happens?", "Delusion: Wabbajack sound", options) == "Yes") + + delusion_args = list( + chosen, + "forced delusion", + duration = duration * 1 SECONDS, + affects_us = affects_us, + affects_others = affects_others, + skip_nearby = skip_nearby, + play_wabbajack = play_wabbajack, + ) + +/* if(ispath(chosen, /datum/hallucination/delusion/custom)) + var/custom_icon_file = input(user, "Pick file for custom delusion:", "Custom Delusion: File") as null|file + if(!custom_icon_file) + return + + var/custom_icon_state = tgui_input_text(user, "What icon state do you wanna use from the file?", "Custom Delusion: Icon State") + if(!custom_icon_state) + return + + var/custom_name = tgui_input_text(user, "What name should it show up as? (Can be empty)", "Custom Delusion: Name") + + delusion_args += list( + custom_icon_file = custom_icon_file, + custom_icon_state = custom_icon_state, + custom_name = custom_name, + ) */ + + return delusion_args + +/// Lines the bubblegum hallucinatoin uses when it pops up +#define BUBBLEGUM_HALLUCINATION_LINES list( \ + span_colossus("I AM IMMORTAL."), \ + span_colossus("I SHALL TAKE YOUR WORLD."), \ + span_colossus("I SEE YOU."), \ + span_colossus("YOU CANNOT ESCAPE ME FOREVER."), \ + span_colossus("NOTHING CAN HOLD ME."), \ + ) diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm new file mode 100644 index 000000000000..5b5144e0cc2d --- /dev/null +++ b/code/__HELPERS/maths.dm @@ -0,0 +1,157 @@ +///Calculate the angle between two movables and the west|east coordinate +/proc/get_angle(atom/movable/start, atom/movable/end)//For beams. + if(!start || !end) + return 0 + var/dy =(32 * end.y + end.pixel_y) - (32 * start.y + start.pixel_y) + var/dx =(32 * end.x + end.pixel_x) - (32 * start.x + start.pixel_x) + if(!dy) + return (dx >= 0) ? 90 : 270 + . = arctan(dx/dy) + if(dy < 0) + . += 180 + else if(dx < 0) + . += 360 + +/// Angle between two arbitrary points and horizontal line same as [/proc/get_angle] +/proc/get_angle_raw(start_x, start_y, start_pixel_x, start_pixel_y, end_x, end_y, end_pixel_x, end_pixel_y) + var/dy = (32 * end_y + end_pixel_y) - (32 * start_y + start_pixel_y) + var/dx = (32 * end_x + end_pixel_x) - (32 * start_x + start_pixel_x) + if(!dy) + return (dx >= 0) ? 90 : 270 + . = arctan(dx/dy) + if(dy < 0) + . += 180 + else if(dx < 0) + . += 360 + +///for getting the angle when animating something's pixel_x and pixel_y +/proc/get_pixel_angle(y, x) + if(!y) + return (x >= 0) ? 90 : 270 + . = arctan(x/y) + if(y < 0) + . += 180 + else if(x < 0) + . += 360 + +/** + * Get a list of turfs in a line from `starting_atom` to `ending_atom`. + * + * Uses the ultra-fast [Bresenham Line-Drawing Algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm). + */ +/proc/get_line(atom/starting_atom, atom/ending_atom) + var/current_x_step = starting_atom.x//start at x and y, then add 1 or -1 to these to get every turf from starting_atom to ending_atom + var/current_y_step = starting_atom.y + var/starting_z = starting_atom.z + + var/list/line = list(get_turf(starting_atom))//get_turf(atom) is faster than locate(x, y, z) + + var/x_distance = ending_atom.x - current_x_step //x distance + var/y_distance = ending_atom.y - current_y_step + + var/abs_x_distance = abs(x_distance)//Absolute value of x distance + var/abs_y_distance = abs(y_distance) + + var/x_distance_sign = SIGN(x_distance) //Sign of x distance (+ or -) + var/y_distance_sign = SIGN(y_distance) + + var/x = abs_x_distance >> 1 //Counters for steps taken, setting to distance/2 + var/y = abs_y_distance >> 1 //Bit-shifting makes me l33t. It also makes get_line() unnessecarrily fast. + + if(abs_x_distance >= abs_y_distance) //x distance is greater than y + for(var/distance_counter in 0 to (abs_x_distance - 1))//It'll take abs_x_distance steps to get there + y += abs_y_distance + + if(y >= abs_x_distance) //Every abs_y_distance steps, step once in y direction + y -= abs_x_distance + current_y_step += y_distance_sign + + current_x_step += x_distance_sign //Step on in x direction + line += locate(current_x_step, current_y_step, starting_z)//Add the turf to the list + else + for(var/distance_counter in 0 to (abs_y_distance - 1)) + x += abs_x_distance + + if(x >= abs_y_distance) + x -= abs_y_distance + current_x_step += x_distance_sign + + current_y_step += y_distance_sign + line += locate(current_x_step, current_y_step, starting_z) + return line + +///Format a power value in W, kW, MW, or GW. +/proc/display_power(powerused) + if(powerused < 1000) //Less than a kW + return "[powerused] W" + else if(powerused < 1000000) //Less than a MW + return "[round((powerused * 0.001),0.01)] kW" + else if(powerused < 1000000000) //Less than a GW + return "[round((powerused * 0.000001),0.001)] MW" + return "[round((powerused * 0.000000001),0.0001)] GW" + +///Format an energy value in J, kJ, MJ, or GJ. 1W = 1J/s. +/proc/display_joules(units) + if (units < 1000) // Less than a kJ + return "[round(units, 0.1)] J" + else if (units < 1000000) // Less than a MJ + return "[round(units * 0.001, 0.01)] kJ" + else if (units < 1000000000) // Less than a GJ + return "[round(units * 0.000001, 0.001)] MJ" + return "[round(units * 0.000000001, 0.0001)] GJ" + +/proc/joules_to_energy(joules) + return joules * (1 SECONDS) / SSmachines.wait + +/proc/energy_to_joules(energy_units) + return energy_units * SSmachines.wait / (1 SECONDS) + +///Format an energy value measured in Power Cell units. +/proc/display_energy(units) + // APCs process every (SSmachines.wait * 0.1) seconds, and turn 1 W of + // excess power into watts when charging cells. + // With the current configuration of wait=20 and CELLRATE=0.002, this + // means that one unit is 1 kJ. + return display_joules(energy_to_joules(units) WATTS) + +///chances are 1:value. anyprob(1) will always return true +/proc/anyprob(value) + return (rand(1,value) == value) + +///counts the number of bits in Byond's 16-bit width field, in constant time and memory! +/proc/bit_count(bit_field) + var/temp = bit_field - ((bit_field >> 1) & 46811) - ((bit_field >> 2) & 37449) //0133333 and 0111111 respectively + temp = ((temp + (temp >> 3)) & 29127) % 63 //070707 + return temp + +/// Returns the name of the mathematical tuple of same length as the number arg (rounded down). +/proc/make_tuple(number) + var/static/list/units_prefix = list("", "un", "duo", "tre", "quattuor", "quin", "sex", "septen", "octo", "novem") + var/static/list/tens_prefix = list("", "decem", "vigin", "trigin", "quadragin", "quinquagin", "sexagin", "septuagin", "octogin", "nongen") + var/static/list/one_to_nine = list("monuple", "double", "triple", "quadruple", "quintuple", "sextuple", "septuple", "octuple", "nonuple") + number = round(number) + switch(number) + if(0) + return "empty tuple" + if(1 to 9) + return one_to_nine[number] + if(10 to 19) + return "[units_prefix[(number%10)+1]]decuple" + if(20 to 99) + return "[units_prefix[(number%10)+1]][tens_prefix[round((number % 100)/10)+1]]tuple" + if(100) + return "centuple" + else //It gets too tedious to use latin prefixes from here. + return "[number]-tuple" + +/// Takes a value, and a threshold it has to at least match +/// returns the correctly signed value max'd to the threshold +/proc/at_least(new_value, threshold) + var/sign = SIGN(new_value) + // SIGN will return 0 if the value is 0, so we just go to the positive threshold + if(!sign) + return threshold + if(sign == 1) + return max(new_value, threshold) + if(sign == -1) + return min(new_value, threshold * -1) diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 3043e9f19315..c29dcc9ce49b 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -189,10 +189,9 @@ CHECK_TICK // Add AntagHUD to everyone, see who was really evil the whole time! - for(var/datum/atom_hud/antag/H in GLOB.huds) - for(var/m in GLOB.player_list) - var/mob/M = m - H.add_hud_to(M) + for(var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antagonist_hud in GLOB.active_alternate_appearances) + for(var/mob/player as anything in GLOB.player_list) + antagonist_hud.show_to(player) CHECK_TICK diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index 317008337d0e..888cc6b68c9b 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -579,10 +579,6 @@ Turf and target are separate in case you want to teleport some distance from a t var/dy = abs(B.y - A.y) return get_dir(A, B) & (rand() * (dx+dy) < dy ? 3 : 12) -//chances are 1:value. anyprob(1) will always return true -/proc/anyprob(value) - return (rand(1,value)==value) - /proc/view_or_range(distance = world.view , center = usr , type) switch(type) if("view") diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index d5286f1bc99f..d069496710ec 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -116,9 +116,9 @@ var/obj/mecha/M = loc return M.click_action(A,src,params) - if(restrained()) + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow - RestrainedClickOn(A) + UnarmedAttack(A, FALSE) return if(in_throw_mode) diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index bf3595d0ae77..607f4a532891 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -63,8 +63,16 @@ return if(W) - // buckled cannot prevent machine interlinking but stops arm movement - if( buckled || incapacitated()) + if(incapacitated()) + return + + //while buckled, you can still connect to and control things like doors, but you can't use your modules + if(buckled) + to_chat(src, span_warning("You can't use modules while buckled to [buckled]!")) + return + + //if your "hands" are blocked you shouldn't be able to use modules + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) return if(W == A) @@ -170,7 +178,10 @@ change attack_robot() above to the proper function */ /mob/living/silicon/robot/UnarmedAttack(atom/A) + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + return A.attack_robot(src) + /mob/living/silicon/robot/RangedAttack(atom/A) A.attack_robot(src) diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 1c5632b74e01..7d975fba0e9f 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -197,3 +197,16 @@ #define ui_ghost_chem "SOUTH: 22,CENTER+3:-8" #define ui_ghost_nanite "SOUTH: 6,CENTER+3:8" #define ui_ghost_wound "SOUTH: 22,CENTER+3:8" + +// 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" +///Inserted first in the list +#define SCRN_OBJ_INSERT_FIRST "first" diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm index 239bfb99cc62..bcba929210ff 100644 --- a/code/_onclick/hud/action_button.dm +++ b/code/_onclick/hud/action_button.dm @@ -177,7 +177,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.UpdateButtons(status_only) //This is the proc used to update all the action buttons. /mob/proc/update_action_buttons(reload_screen, skip_observers=FALSE) @@ -196,7 +196,7 @@ else for(var/datum/action/A in actions) var/is_owner = (A.owner == src) - A.UpdateButtonIcon() + A.UpdateButtons() var/atom/movable/screen/movable/action_button/B = A.button if(B.ordered) button_number++ diff --git a/code/_onclick/hud/devil.dm b/code/_onclick/hud/devil.dm index 5ef6c0b2afdb..3e4b39832322 100644 --- a/code/_onclick/hud/devil.dm +++ b/code/_onclick/hud/devil.dm @@ -41,7 +41,6 @@ zone_select.icon = ui_style zone_select.update_icon(mymob) - lingchemdisplay = new /atom/movable/screen/ling/chems() devilsouldisplay = new /atom/movable/screen/devil/soul_counter infodisplay += devilsouldisplay diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm index da66d0b0b3ee..f97748308210 100644 --- a/code/_onclick/hud/hud.dm +++ b/code/_onclick/hud/hud.dm @@ -25,13 +25,6 @@ GLOBAL_LIST_INIT(available_ui_styles, list( var/inventory_shown = FALSE //Equipped item inventory var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons) - var/atom/movable/screen/ling/chems/lingchemdisplay - var/atom/movable/screen/ling/sting/lingstingdisplay - - var/atom/movable/screen/bloodsucker/blood_counter/blood_display - var/atom/movable/screen/bloodsucker/rank_counter/vamprank_display - var/atom/movable/screen/bloodsucker/sunlight_counter/sunlight_display - var/atom/movable/screen/blobpwrdisplay var/atom/movable/screen/alien_plasma_display @@ -107,9 +100,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list( stamina = null healthdoll = null internals = null - lingchemdisplay = null devilsouldisplay = null - lingstingdisplay = null blobpwrdisplay = null alien_plasma_display = null alien_queen_finder = null diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm index ea233cc41b85..e586a6bb5598 100644 --- a/code/_onclick/hud/human.dm +++ b/code/_onclick/hud/human.dm @@ -62,12 +62,10 @@ /atom/movable/screen/devil/soul_counter/proc/clear() invisibility = INVISIBILITY_ABSTRACT -/atom/movable/screen/ling - invisibility = INVISIBILITY_ABSTRACT - /atom/movable/screen/ling/sting name = "current sting" screen_loc = ui_lingstingdisplay + invisibility = INVISIBILITY_ABSTRACT /atom/movable/screen/ling/sting/Click() if(isobserver(usr)) @@ -80,31 +78,6 @@ icon_state = "power_display" screen_loc = ui_lingchemdisplay -/atom/movable/screen/bloodsucker - icon = 'icons/mob/actions/actions_bloodsucker.dmi' - invisibility = INVISIBILITY_ABSTRACT - -/atom/movable/screen/bloodsucker/proc/clear() - invisibility = INVISIBILITY_ABSTRACT - -/atom/movable/screen/bloodsucker/proc/update_counter() - invisibility = 0 - -/atom/movable/screen/bloodsucker/blood_counter - name = "Blood Consumed" - icon_state = "blood_display" - screen_loc = ui_blood_display - -/atom/movable/screen/bloodsucker/rank_counter - name = "Bloodsucker Rank" - icon_state = "rank" - screen_loc = ui_vamprank_display - -/atom/movable/screen/bloodsucker/sunlight_counter - name = "Solar Flare Timer" - icon_state = "sunlight_night" - screen_loc = ui_sunlight_display - /datum/hud/human/New(mob/living/carbon/human/owner) ..() owner.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness) @@ -322,23 +295,9 @@ pull_icon.screen_loc = ui_above_intent static_inventory += pull_icon - lingchemdisplay = new /atom/movable/screen/ling/chems() - infodisplay += lingchemdisplay - - lingstingdisplay = new /atom/movable/screen/ling/sting() - infodisplay += lingstingdisplay - devilsouldisplay = new /atom/movable/screen/devil/soul_counter infodisplay += devilsouldisplay - //bloodsuckers - blood_display = new /atom/movable/screen/bloodsucker/blood_counter - infodisplay += blood_display - vamprank_display = new /atom/movable/screen/bloodsucker/rank_counter - infodisplay += vamprank_display - sunlight_display = new /atom/movable/screen/bloodsucker/sunlight_counter - infodisplay += sunlight_display - zone_select = new /atom/movable/screen/zone_sel() zone_select.icon = ui_style zone_select.update_icon(mymob) diff --git a/code/_onclick/hud/monkey.dm b/code/_onclick/hud/monkey.dm index f9a73ed66bdf..ca06be2a8013 100644 --- a/code/_onclick/hud/monkey.dm +++ b/code/_onclick/hud/monkey.dm @@ -90,13 +90,6 @@ pull_icon.screen_loc = ui_above_movement static_inventory += pull_icon - lingchemdisplay = new /atom/movable/screen/ling/chems() - infodisplay += lingchemdisplay - - lingstingdisplay = new /atom/movable/screen/ling/sting() - infodisplay += lingstingdisplay - - zone_select = new /atom/movable/screen/zone_sel() zone_select.icon = ui_style zone_select.update_icon(mymob) diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 20a26acf9d29..45aa05ea2801 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -5,6 +5,10 @@ Otherwise pretty standard. */ /mob/living/carbon/human/UnarmedAttack(atom/A, proximity) + if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED)) + if(src == A) + check_self_for_injuries() + return if(HAS_TRAIT(A, TRAIT_NOINTERACT)) to_chat(A, span_notice("You can't touch things!")) return @@ -87,14 +91,6 @@ return ui_interact(user) return FALSE -/* -/mob/living/carbon/human/RestrainedClickOn(var/atom/A) ---carbons will handle this - return -*/ - -/mob/living/carbon/RestrainedClickOn(atom/A) - return 0 - /mob/living/carbon/human/RangedAttack(atom/A, mouseparams) . = ..() if(gloves) @@ -177,9 +173,6 @@ attack_paw(user) return -/mob/living/carbon/alien/RestrainedClickOn(atom/A) - return - // Babby aliens /mob/living/carbon/alien/larva/UnarmedAttack(atom/A) A.attack_larva(src) @@ -199,9 +192,6 @@ /atom/proc/attack_slime(mob/user) return -/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) - return - /* Drones @@ -212,10 +202,6 @@ /atom/proc/attack_drone(mob/living/simple_animal/drone/user) attack_hand(user) //defaults to attack_hand. Override it when you don't want drones to do same stuff as humans. -/mob/living/simple_animal/slime/RestrainedClickOn(atom/A) - return - - /* True Devil */ diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm index fd82007f6587..89a682c86b4f 100644 --- a/code/controllers/subsystem/pai.dm +++ b/code/controllers/subsystem/pai.dm @@ -6,12 +6,12 @@ SUBSYSTEM_DEF(pai) var/list/candidates = list() var/ghost_spam = FALSE var/spam_delay = 100 - var/list/pai_card_list = list() + var/list/paicard_list = list() /datum/controller/subsystem/pai/Topic(href, href_list[]) if(href_list["download"]) var/datum/paiCandidate/candidate = locate(href_list["candidate"]) in candidates - var/obj/item/paicard/card = locate(href_list["device"]) in pai_card_list + var/obj/item/paicard/card = locate(href_list["device"]) in paicard_list if(card.pai) return if(istype(card, /obj/item/paicard) && istype(candidate, /datum/paiCandidate)) @@ -27,8 +27,6 @@ SUBSYSTEM_DEF(pai) card.setPersonality(pai) - SSticker.mode.update_cult_icons_removed(card.pai.mind) - candidates -= candidate usr << browse(null, "window=findPai") @@ -71,7 +69,7 @@ SUBSYSTEM_DEF(pai) if("submit") if(candidate) candidate.ready = 1 - for(var/obj/item/paicard/p in pai_card_list) + for(var/obj/item/paicard/p in paicard_list) if(!p.pai) p.alertUpdate() usr << browse(null, "window=paiRecruit") diff --git a/code/controllers/subsystem/processing/antag_hud.dm b/code/controllers/subsystem/processing/antag_hud.dm new file mode 100644 index 000000000000..aaf10a5e2ecb --- /dev/null +++ b/code/controllers/subsystem/processing/antag_hud.dm @@ -0,0 +1,4 @@ +PROCESSING_SUBSYSTEM_DEF(antag_hud) + name = "Antag HUDs" + flags = SS_NO_INIT + wait = 2 SECONDS diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index ad805770d59b..c9bc6817a1ee 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -68,9 +68,22 @@ SUBSYSTEM_DEF(statpanels) set_SDQL2_tab(target) if(target.mob) var/mob/target_mob = target.mob - if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list))) - if(num_fires % default_wait == 0) - set_spells_tab(target, target_mob) + + // Handle the action panels of the stat panel + var/update_actions = FALSE + // We're on a spell tab, update the tab so we can see cooldowns progressing and such + if(target.stat_tab in target.spell_tabs) + update_actions = TRUE + // We're not on a spell tab per se, but we have cooldown actions, and we've yet to + // set up our spell tabs at all + if(!length(target.spell_tabs) && locate(/datum/action/cooldown) in target_mob.actions) + update_actions = TRUE + + if(update_actions && num_fires % default_wait == 0) + set_action_tabs(target, target_mob) + + // Handle the examined turf of the stat panel + if(target_mob?.listed_turf && num_fires % default_wait == 0) if(!target_mob.TurfAdjacent(target_mob.listed_turf)) target << output("", "statbrowser:remove_listedturf") @@ -105,18 +118,15 @@ SUBSYSTEM_DEF(statpanels) sdql2A += sdql2B target << output(url_encode(json_encode(sdql2A)), "statbrowser:update_sdql2") -/datum/controller/subsystem/statpanels/proc/set_spells_tab(client/target, mob/target_mob) - var/list/proc_holders = target_mob.get_proc_holders() +/// Set up the various action tabs. +/datum/controller/subsystem/statpanels/proc/set_action_tabs(client/target, mob/target_mob) + var/list/actions = target_mob.get_actions_for_statpanel() target.spell_tabs.Cut() - for(var/proc_holder_list as anything in proc_holders) - target.spell_tabs |= proc_holder_list[1] - - var/proc_holders_encoded = "" - if(length(proc_holders)) - proc_holders_encoded = url_encode(json_encode(proc_holders)) + for(var/action_data in actions) + target.spell_tabs |= action_data[1] - target << output("[url_encode(json_encode(target.spell_tabs))];[proc_holders_encoded]", "statbrowser:update_spells") + target << output("[url_encode(json_encode(target.spell_tabs))];[actions]", "statbrowser:update_spells") /datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob) var/list/overrides = list() @@ -183,10 +193,22 @@ SUBSYSTEM_DEF(statpanels) return TRUE var/mob/target_mob = target.mob - if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(target_mob.mob_spell_list) || length(target_mob.mind?.spell_list))) - set_spells_tab(target, target_mob) + + // Handle actions + + var/update_actions = FALSE + if(target.stat_tab in target.spell_tabs) + update_actions = TRUE + + if(!length(target.spell_tabs) && locate(/datum/action/cooldown) in target_mob.actions) + update_actions = TRUE + + if(update_actions) + set_action_tabs(target, target_mob) return TRUE + // Handle turfs + if(target_mob?.listed_turf) if(!target_mob.TurfAdjacent(target_mob.listed_turf)) target << output("", "statbrowser:remove_listedturf") diff --git a/code/datums/action.dm b/code/datums/action.dm deleted file mode 100644 index a83257a9ba4d..000000000000 --- a/code/datums/action.dm +++ /dev/null @@ -1,884 +0,0 @@ -#define AB_CHECK_RESTRAINED 1 -#define AB_CHECK_STUN 2 -#define AB_CHECK_LYING 4 -#define AB_CHECK_CONSCIOUS 8 - -/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/mob/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/mob/actions.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/syndicate = FALSE // are these buttons only for syndicates? - var/atom/movable/screen/cooldown_overlay/cooldown_overlay - -/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 - -/datum/action/Destroy() - if(owner) - Remove(owner) - target = null - qdel(button) - button = null - return ..() - -/datum/action/proc/Grant(mob/M) - if(M) - M.originalactions += src - if(owner) - if(owner == M) - return - Remove(owner) - owner = M - if(syndicate) - if(!is_syndicate(M) && !is_clockcult(M)) // if a syndicate check is failed; don't generate button on the hud at all unless you are a clock cultist as well. - Hopek - return - - //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_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 - for(var/mob/dead/observer/O in M.observers) - O?.client.screen += button - M.update_action_buttons() // Now push the owners buttons back - else - Remove(owner) - -/datum/action/proc/Remove(mob/M) - if(M) - if(M.client) - M.client.screen -= button - for(var/mob/dead/observer/O in M.observers) - O?.client.screen -= button - M.originalactions -= src - M.actions -= src - M.update_action_buttons() - owner = null - if(button) - 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/Process() - return - -/datum/action/proc/IsAvailable() - if(!owner) - return FALSE - if(check_flags & AB_CHECK_RESTRAINED) - if(owner.restrained()) - return FALSE - if(check_flags & AB_CHECK_STUN) - if(isliving(owner)) - var/mob/living/L = owner - if(L.IsParalyzed() || L.IsStun()) - return FALSE - if(check_flags & AB_CHECK_LYING) - if(isliving(owner)) - var/mob/living/L = owner - if(!(L.mobility_flags & MOBILITY_STAND)) - return FALSE - if(check_flags & AB_CHECK_CONSCIOUS) - if(owner.stat) - return FALSE - return TRUE - -/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE) - if(button) - if(!status_only) - button.name = name - button.desc = desc - if(owner && 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 = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0) - else - button.color = rgb(255,255,255,255) - return 1 - -/datum/action/proc/ApplyIcon(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 - - -//Presets for item actions -/datum/action/item_action - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_LYING|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 0 - if(target) - var/obj/item/I = target - I.ui_action_click(owner, src) - return 1 - -/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 appearence - ..() - 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_laser_sight - name = "Toggle Laser Sight" - icon_icon = 'icons/obj/guns/attachment.dmi' - button_icon_state = "laser_sight" - var/obj/item/attachment/laser_sight/att - -/datum/action/item_action/toggle_laser_sight/Trigger() - if(!att) - if(istype(target, /obj/item/gun)) - var/obj/item/gun/parent_gun = target - for(var/obj/item/attachment/A in parent_gun.current_attachments) - if(istype(A, /obj/item/attachment/laser_sight)) - att = A - break - att?.toggle_on() - UpdateButtonIcon() - -/datum/action/item_action/toggle_laser_sight/UpdateButtonIcon(status_only = FALSE, force) - button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]" - ..() - -/datum/action/item_action/change_laser_sight_color - name = "Change Laser Sight Color" - icon_icon = 'icons/obj/guns/attachment.dmi' - button_icon_state = "laser_sight" - var/obj/item/attachment/laser_sight/att - -/datum/action/item_action/change_laser_sight_color/Trigger() - if(!att) - if(istype(target, /obj/item/gun)) - var/obj/item/gun/H = target - for(var/obj/item/attachment/A in H.current_attachments) - if(istype(A, /obj/item/attachment/laser_sight)) - att = A - break - if(att && owner) - var/C = input(owner, "Select Laser Color", "Select laser color", att.laser_color) as null|color - if(!C || QDELETED(att)) - return - att.laser_color = C - UpdateButtonIcon() - -/datum/action/item_action/change_laser_sight_color/UpdateButtonIcon(status_only = FALSE, force) - button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]" - ..() - -/datum/action/item_action/toggle_infrared_sight - name = "Toggle Infrared" - icon_icon = 'icons/obj/guns/attachment.dmi' - button_icon_state = "ifr_sight" - var/obj/item/attachment/scope/infrared/att - -/datum/action/item_action/toggle_infrared_sight/Trigger() - if(!att) - if(istype(target, /obj/item/gun)) - var/obj/item/gun/parent_gun = target - for(var/obj/item/attachment/A in parent_gun.current_attachments) - if(istype(A, /obj/item/attachment/scope/infrared)) - att = A - break - att?.toggle_on() - UpdateButtonIcon() - -/datum/action/item_action/toggle_infrared_sight/UpdateButtonIcon(status_only = FALSE, force) - button_icon_state = "ifr_sight[att?.is_on ? "_on" : ""]" - ..() - -/datum/action/item_action/toggle_hood - name = "Toggle Hood" - -/datum/action/item_action/toggle_firemode - name = "Toggle Firemode" - -/datum/action/item_action/toggle_bodycam - name = "Toggle Bodycamera" - -/datum/action/item_action/rcl_col - name = "Change Cable Color" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "rcl_rainbow" - -/datum/action/item_action/rcl_gui - name = "Toggle Fast Wiring Gui" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "rcl_gui" - -/datum/action/item_action/startchainsaw - name = "Pull The Starting Cord" - -/datum/action/item_action/charge_hammer - name = "Charge the Blast Pads" - -/datum/action/item_action/charge_hammer/Trigger() - var/obj/item/twohanded/vxtvulhammer/V = target - if(istype(V)) - V.charge_hammer(owner) - -/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 - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS - 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/hardhat/weldhat/H = target - if(istype(H)) - H.toggle_welding_screen(owner) - -/datum/action/item_action/toggle_headphones - name = "Toggle Headphones" - desc = "UNTZ UNTZ UNTZ" - -/datum/action/item_action/toggle_headphones/Trigger() - var/obj/item/clothing/ears/headphones/H = target - if(istype(H)) - H.toggle(owner) - -/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/mob/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/mob/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 - icon_icon = 'icons/mob/actions/actions_clockcult.dmi' - background_icon_state = "bg_clock" - buttontooltipstyle = "clockcult" - -/datum/action/item_action/clock/IsAvailable() - if(!is_servant_of_ratvar(owner)) - return 0 - return ..() - -/datum/action/item_action/clock/toggle_visor - name = "Create Judicial Marker" - desc = "Allows you to create a stunning Judicial Marker at any location in view. Click again to disable." - -/datum/action/item_action/clock/toggle_visor/IsAvailable() - if(!is_servant_of_ratvar(owner)) - return 0 - if(istype(target, /obj/item/clothing/glasses/judicial_visor)) - var/obj/item/clothing/glasses/judicial_visor/V = target - if(V.recharging) - 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 - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS - name = "Toggle Helmet Flashlight" - -/datum/action/item_action/toggle_helmet_mode - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS - name = "Toggle Helmet Mode" - -/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/hatsky_voiceline - name = "Press Voice Button" - desc = "Engage the voice box on your Hatsky to hear a classic line from the real Officer Beepsky!" - -/datum/action/item_action/nano_picket_sign - name = "Retext Nano Picket Sign" - var/obj/item/picket_sign/S - -/datum/action/item_action/nano_picket_sign/New(Target) - ..() - if(istype(Target, /obj/item/picket_sign)) - S = Target - -/datum/action/item_action/nano_picket_sign/Trigger() - if(istype(S)) - S.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_wings - name = "Toggle Wings" - -/datum/action/item_action/toggle_human_head - name = "Toggle Human Head" - -/datum/action/item_action/toggle_helmet - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS - name = "Toggle Helmet" - -/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/mob/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, span_notice("[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/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/mob/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(iscultist(M)) - ..() - button.screen_loc = "6:157,4:-2" - button.moved = "6:157,4:-2" - else - Remove(owner) - -/datum/action/item_action/cult_dagger/Trigger() - for(var/obj/item/H in owner.held_items) //In case we were already holding another dagger - if(istype(H, /obj/item/melee/cultblade/dagger)) - H.attack_self(owner) - return - var/obj/item/I = target - if(owner.can_equip(I, SLOT_HANDS)) - owner.temporarilyRemoveItemFromInventory(I) - owner.put_in_hands(I) - I.attack_self(owner) - else - if (owner.get_num_arms() <= 0) - to_chat(owner, span_warning("You don't have any usable hands!")) - else - to_chat(owner, span_warning("Your hands are full!")) - -/datum/action/item_action/agent_box - name = "Deploy Box" - desc = "Find inner peace, here, in the box." - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS - background_icon_state = "bg_agent" - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "deploy_box" - var/cooldown = 0 - var/obj/structure/closet/cardboard/agent/box - -/datum/action/item_action/agent_box/Trigger() - if(!..()) - return FALSE - if(QDELETED(box)) - if(cooldown < world.time - 100) - box = new(owner.drop_location()) - owner.forceMove(box) - cooldown = world.time - owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) - else - owner.forceMove(box.drop_location()) - owner.playsound_local(box, 'sound/misc/box_deploy.ogg', 50, TRUE) - QDEL_NULL(box) - -/datum/action/item_action/visegrip - name = "Vise Grip" - desc = "Remotely detonate marked targets. People become rooted for 1 second. Animals become rooted for 6 seconds and take hefty damage." - icon_icon = 'icons/effects/effects.dmi' - button_icon_state = "leghold" - -/datum/action/item_action/reach - name = "Reach" - desc = "Mark those standing on blood for 10 seconds." - icon_icon = 'icons/effects/effects.dmi' - button_icon_state = "rshield" - -/datum/action/item_action/band - name = "Band" - desc = "Summon all your thralls to your location." - icon_icon = 'icons/mob/actions/actions_cult.dmi' - button_icon_state = "horde" - -/datum/action/item_action/gambit - name = "Gambit" - desc = "Throw out your cane. If the target is weak enough to finish off, teleport to them and do it, recovering your cane in the process." - icon_icon = 'icons/mob/actions/actions_cult.dmi' - button_icon_state = "horde" - -//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() - if(next_use_time > world.time) - return FALSE - return ..() - -/datum/action/cooldown/proc/StartCooldown() - next_use_time = world.time + cooldown_time - button.maptext = "[round(cooldown_time/10, 0.1)]" - UpdateButtonIcon() - START_PROCESSING(SSfastprocess, src) - -/datum/action/cooldown/process() - if(!owner) - button.maptext = "" - STOP_PROCESSING(SSfastprocess, src) - var/timeleft = max(next_use_time - world.time, 0) - if(timeleft == 0) - button.maptext = "" - UpdateButtonIcon() - STOP_PROCESSING(SSfastprocess, src) - else - button.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/mob/actions/actions_minor_antag.dmi' - button_icon_state = "art_summon" - - -/datum/action/item_action/dash - name = "Dash" - desc = "Momentarily maximizes the jets of the shoes, allowing the user to dash a short distance." - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "thrust" - -/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/Trigger() - if(!..()) - return FALSE - if(ismob(owner)) - var/mob/M = owner - var/datum/language_holder/H = M.get_language_holder() - H.open_language_menu(usr) - -/datum/action/item_action/wheelys - name = "Toggle Wheely-Heel's Wheels" - desc = "Pops out or in your wheely-heel's wheels." - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "wheelys" - -/datum/action/item_action/airshoes - name = "Toggle thrust on air shoes." - desc = "Switch between walking and hovering." - icon_icon = 'icons/mob/actions/actions_items.dmi' - button_icon_state = "airshoes_a" - - -/datum/action/item_action/kindleKicks - name = "Activate Kindle Kicks" - desc = "Kick you feet together, activating the lights in your Kindle Kicks." - icon_icon = 'icons/mob/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/mob/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/mob/actions/actions_xeno.dmi' - button_icon_state = "smallqueen" - background_icon_state = "bg_alien" - 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/stalwart - 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/megafauna/spacedragon - small_icon = 'icons/mob/carp.dmi' - small_icon_state = "carp" - -/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/mob/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 000000000000..3917ea8552cb --- /dev/null +++ b/code/datums/actions/action.dm @@ -0,0 +1,256 @@ +/** + * # 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 target the action is attached to. If the target datum is deleted, the action is as well. + /// Set in New() via the proc link_to(). PLEASE set a target if you're making an action + var/datum/target + /// 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 + /// Flags that will determine of the owner / user of the action can... use the action + var/check_flags = NONE + /// 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/mob/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/mob/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() + +/datum/action/New(Target) + link_to(Target) + +/// Links the passed target to our action, registering any relevant signals +/datum/action/proc/link_to(Target) + target = Target + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE) + + if(isatom(target)) + RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(update_icon_on_signal)) + + if(istype(target, /datum/mind)) + RegisterSignal(target, COMSIG_MIND_TRANSFERRED, PROC_REF(on_target_mind_swapped)) + +/datum/action/Destroy() + if(owner) + Remove(owner) + 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 == target) + qdel(src) + +/// 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) + 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)) + + GiveAction(grant_to) + +/// 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 + HideFrom(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(target == owner) + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref)) + owner = null + +/// Actually triggers the effects of the action. +/// Called when the on-screen button is clicked, for example. +/datum/action/proc/Trigger(trigger_flags) + if(!IsAvailable()) + return FALSE + if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER) + return FALSE + return TRUE + +/// Whether our action is currently available to use or not +/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_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.mobility_flags & MOBILITY_STAND)) + return FALSE + if((check_flags & AB_CHECK_CONSCIOUS) && owner.stat != CONSCIOUS) + return FALSE + return TRUE + +/datum/action/proc/UpdateButtons(status_only, force) + for(var/datum/hud/hud in viewers) + var/atom/movable/screen/movable/button = viewers[hud] + UpdateButton(button, status_only, force) + +/datum/action/proc/UpdateButton(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 + + ApplyIcon(button, force) + + var/available = IsAvailable() + if(available) + button.color = rgb(255,255,255,255) + else + button.color = 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/ApplyIcon(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 + +/// Gives our action to the passed viewer. +/// Puts our action in their actions list and shows them the button. +/datum/action/proc/GiveAction(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 + ShowTo(viewer) + +/// Adds our action button to the screen of the passed viewer. +/datum/action/proc/ShowTo(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 = CreateButton() + SetId(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/HideFrom(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/CreateButton() + 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/SetId(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 + + UpdateButtons() + +/// Signal proc for COMSIG_MIND_TRANSFERRED - for minds, transfers our action to our new mob on mind transfer +/datum/action/proc/on_target_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) diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm new file mode 100644 index 000000000000..48c366a021fd --- /dev/null +++ b/code/datums/actions/cooldown_action.dm @@ -0,0 +1,221 @@ +/// Preset for an action that has a cooldown. +/datum/action/cooldown + check_flags = NONE + transparent_when_unavailable = FALSE + + /// The actual next time this ability can be used + var/next_use_time = 0 + /// The stat panel this action shows up in the stat panel in. If null, will not show up. + var/panel + /// The default cooldown applied when StartCooldown() is called + var/cooldown_time = 0 + /// Whether or not you want the cooldown for the ability to display in text form + var/text_cooldown = TRUE + /// Setting for intercepting clicks before activating the ability + var/click_to_activate = FALSE + /// What icon to replace our mouse cursor with when active. Optional, Requires click_to_activate + var/ranged_mousepointer + /// The cooldown added onto the user's next click. Requires click_to_activate + var/click_cd_override = CLICK_CD_CLICK_ABILITY + /// If TRUE, we will unset after using our click intercept. Requires click_to_activate + var/unset_after_click = TRUE + /// Shares cooldowns with other cooldown abilities of the same value, not active if null + var/shared_cooldown + +/datum/action/cooldown/CreateButton() + var/atom/movable/screen/movable/action_button/button = ..() + button.maptext = "" + button.maptext_x = 8 + button.maptext_y = 0 + button.maptext_width = 24 + button.maptext_height = 12 + return button + +/datum/action/cooldown/IsAvailable() + return ..() && (next_use_time <= world.time) + +/datum/action/cooldown/Remove(mob/living/remove_from) + if(click_to_activate && remove_from.click_intercept == src) + unset_click_ability(remove_from, refund_cooldown = FALSE) + return ..() + +/// Starts a cooldown time to be shared with similar abilities +/// Will use default cooldown time if an override is not specified +/datum/action/cooldown/proc/StartCooldown(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(shared_cooldown) + for(var/datum/action/cooldown/shared_ability in owner.actions - src) + if(shared_cooldown != shared_ability.shared_cooldown) + continue + shared_ability.StartCooldownSelf(override_cooldown_time) + + StartCooldownSelf(override_cooldown_time) + +/// Starts a cooldown time for this ability only +/// Will use default cooldown time if an override is not specified +/datum/action/cooldown/proc/StartCooldownSelf(override_cooldown_time) + if(isnum(override_cooldown_time)) + next_use_time = world.time + override_cooldown_time + else + next_use_time = world.time + cooldown_time + UpdateButtons() + START_PROCESSING(SSfastprocess, src) + +/datum/action/cooldown/Trigger(trigger_flags, atom/target) + . = ..() + if(!.) + return FALSE + if(!owner) + return FALSE + + var/mob/user = usr || owner + + // If our cooldown action is a click_to_activate 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(click_to_activate) + if(target) + // For automatic / mob handling + return InterceptClickOn(user, null, target) + + var/datum/action/cooldown/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 click_to_activate action: + // We can just continue on and use the action + // the target is the user of the action (often, the owner) + return PreActivate(user) + +/// Intercepts client owner clicks to activate the ability +/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller, params, atom/target) + if(!IsAvailable()) + return FALSE + if(!target) + return FALSE + // The actual action begins here + if(!PreActivate(target)) + return FALSE + + // And if we reach here, the action was complete successfully + if(unset_after_click) + StartCooldown() + unset_click_ability(caller, refund_cooldown = FALSE) + caller.next_click = world.time + click_cd_override + + return TRUE + +/// For signal calling +/datum/action/cooldown/proc/PreActivate(atom/target) + if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) + return + . = Activate(target) + // There is a possibility our action (or owner) is qdeleted in Activate(). + if(!QDELETED(src) && !QDELETED(owner)) + SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_FINISHED, src) + +/// To be implemented by subtypes +/datum/action/cooldown/proc/Activate(atom/target) + return + +/** + * Set our action as the click override on the passed mob. + */ +/datum/action/cooldown/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() + UpdateButtons() + 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/cooldown/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() + UpdateButtons() + return TRUE + +/datum/action/cooldown/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE) + . = ..() + if(!button) + return + var/time_left = max(next_use_time - world.time, 0) + if(text_cooldown) + button.maptext = MAPTEXT("[round(time_left/10, 0.1)]") + if(!owner || time_left == 0) + button.maptext = "" + if(IsAvailable() && (button.our_hud.mymob.click_intercept == src)) + button.color = COLOR_GREEN + +/datum/action/cooldown/process() + if(!owner || (next_use_time - world.time) <= 0) + UpdateButtons() + STOP_PROCESSING(SSfastprocess, src) + return + + UpdateButtons() + +/datum/action/cooldown/Grant(mob/M) + ..() + if(!owner) + return + UpdateButtons() + if(next_use_time > world.time) + START_PROCESSING(SSfastprocess, src) + +/// Formats the action to be returned to the stat panel. +/datum/action/cooldown/proc/set_statpanel_format() + if(!panel) + return null + + var/time_remaining = max(next_use_time - world.time, 0) + var/time_remaining_in_seconds = round(time_remaining / 10, 0.1) + var/cooldown_time_in_seconds = round(cooldown_time / 10, 0.1) + + var/list/stat_panel_data = list() + + // Pass on what panel we should be displayed in. + stat_panel_data[PANEL_DISPLAY_PANEL] = panel + // Also pass on the name of the spell, with some spacing + stat_panel_data[PANEL_DISPLAY_NAME] = " - [name]" + + // No cooldown time at all, just show the ability + if(cooldown_time_in_seconds <= 0) + stat_panel_data[PANEL_DISPLAY_STATUS] = "" + + // It's a toggle-active ability, show if it's active + else if(click_to_activate && owner.click_intercept == src) + stat_panel_data[PANEL_DISPLAY_STATUS] = "ACTIVE" + + // It's on cooldown, show the cooldown + else if(time_remaining_in_seconds > 0) + stat_panel_data[PANEL_DISPLAY_STATUS] = "CD - [time_remaining_in_seconds]s / [cooldown_time_in_seconds]s" + + // It's not on cooldown, show that it is ready + else + stat_panel_data[PANEL_DISPLAY_STATUS] = "READY" + + SEND_SIGNAL(src, COMSIG_ACTION_SET_STATPANEL, stat_panel_data) + + return stat_panel_data \ No newline at end of file diff --git a/code/datums/actions/innate_action.dm b/code/datums/actions/innate_action.dm new file mode 100644 index 000000000000..3c697220a61a --- /dev/null +++ b/code/datums/actions/innate_action.dm @@ -0,0 +1,84 @@ +//Preset for general and toggled actions +/datum/action/innate + check_flags = NONE + /// Whether we're active or not, if we're a innate - toggle action. + var/active = FALSE + /// Whether we're a click action or not, if we're a innate - click action. + var/click_action = FALSE + /// If we're a click action, the mouse pointer we use + var/ranged_mousepointer + /// 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/Trigger(trigger_flags) + if(!..()) + return FALSE + // We're a click action, trigger just sets it as active or not + if(click_action) + if(owner.click_intercept == src) + unset_ranged_ability(owner, disable_text) + else + set_ranged_ability(owner, enable_text) + return TRUE + + // We're not a click action (we're a toggle or otherwise) + else + if(active) + Deactivate() + else + Activate() + + return TRUE + +/datum/action/innate/proc/Activate() + return + +/datum/action/innate/proc/Deactivate() + return + +/** + * This is gross, but a somewhat-required bit of copy+paste until action code becomes slightly more sane. + * Anything that uses these functions should eventually be moved to use cooldown actions. + * (Either that, or the click ability of cooldown actions should be moved down a type.) + * + * If you're adding something that uses these, rethink your choice in subtypes. + */ + +/// Sets this action as the active ability for the passed mob +/datum/action/innate/proc/set_ranged_ability(mob/living/on_who, text_to_show) + if(ranged_mousepointer) + on_who.client?.mouse_override_icon = ranged_mousepointer + on_who.update_mouse_pointer() + if(text_to_show) + to_chat(on_who, text_to_show) + on_who.click_intercept = src + +/// Removes this action as the active ability of the passed mob +/datum/action/innate/proc/unset_ranged_ability(mob/living/on_who, text_to_show) + if(ranged_mousepointer) + on_who.client?.mouse_override_icon = initial(owner.client?.mouse_pointer_icon) + on_who.update_mouse_pointer() + if(text_to_show) + to_chat(on_who, text_to_show) + on_who.click_intercept = null + +/// Handles whenever a mob clicks on something +/datum/action/innate/proc/InterceptClickOn(mob/living/caller, params, atom/clicked_on) + if(!IsAvailable()) + unset_ranged_ability(caller) + return FALSE + if(!clicked_on) + return FALSE + + return do_ability(caller, clicked_on) + +/// Actually goes through and does the click ability +/datum/action/innate/proc/do_ability(mob/living/caller, params, atom/clicked_on) + return FALSE + +/datum/action/innate/Remove(mob/removed_from) + if(removed_from.click_intercept == src) + unset_ranged_ability(removed_from) + return ..() \ No newline at end of file diff --git a/code/datums/actions/item_action.dm b/code/datums/actions/item_action.dm new file mode 100644 index 000000000000..1d0cac944f54 --- /dev/null +++ b/code/datums/actions/item_action.dm @@ -0,0 +1,33 @@ +//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/Trigger(trigger_flags) + . = ..() + 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) + var/obj/item/item_target = target + if(button_icon && button_icon_state) + // If set, use the custom icon that we set instead + // of the item appearence + ..() + else if((target && 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 \ No newline at end of file diff --git a/code/datums/actions/items/adjust.dm b/code/datums/actions/items/adjust.dm new file mode 100644 index 000000000000..f913bac5ba61 --- /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(Target) + ..() + var/obj/item/item_target = target + name = "Adjust [item_target.name]" \ No newline at end of file diff --git a/code/datums/actions/beam_rifle.dm b/code/datums/actions/items/beam_rifle.dm similarity index 99% rename from code/datums/actions/beam_rifle.dm rename to code/datums/actions/items/beam_rifle.dm index 3af5d13690d0..964ea3cfe200 100644 --- a/code/datums/actions/beam_rifle.dm +++ b/code/datums/actions/items/beam_rifle.dm @@ -1,4 +1,3 @@ - /datum/action/item_action/zoom_speed_action name = "Toggle Zooming Speed" icon_icon = 'icons/mob/actions/actions_spells.dmi' diff --git a/code/datums/actions/items/clockcult.dm b/code/datums/actions/items/clockcult.dm new file mode 100644 index 000000000000..c5e962c9a2a7 --- /dev/null +++ b/code/datums/actions/items/clockcult.dm @@ -0,0 +1,32 @@ +/datum/action/item_action/clock + icon_icon = 'icons/mob/actions/actions_clockcult.dmi' + background_icon_state = "bg_clock" + buttontooltipstyle = "clockcult" + +/datum/action/item_action/clock/IsAvailable() + if(!is_servant_of_ratvar(owner)) + return FALSE + return ..() + +/datum/action/item_action/clock/toggle_visor + name = "Create Judicial Marker" + desc = "Allows you to create a stunning Judicial Marker at any location in view. Click again to disable." + +/datum/action/item_action/clock/toggle_visor/IsAvailable() + if(!is_servant_of_ratvar(owner)) + return FALSE + if(istype(target, /obj/item/clothing/glasses/judicial_visor)) + var/obj/item/clothing/glasses/judicial_visor/visor = target + if(visor.recharging) + return FALSE + 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 \ No newline at end of file diff --git a/code/datums/actions/items/cult_dagger.dm b/code/datums/actions/items/cult_dagger.dm new file mode 100644 index 000000000000..337846220e3b --- /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/mob/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(iscultist(M)) + ..() + button.screen_loc = "6:157,4:-2" + button.moved = "6:157,4:-2" + else + Remove(owner) + +/datum/action/item_action/cult_dagger/Trigger() + for(var/obj/item/H in owner.held_items) //In case we were already holding another dagger + if(istype(H, /obj/item/melee/cultblade/dagger)) + H.attack_self(owner) + return + var/obj/item/I = target + + if(owner.can_equip(I, SLOT_HANDS)) + owner.temporarilyRemoveItemFromInventory(I) + owner.put_in_hands(I) + I.attack_self(owner) + return + + if(!isliving(owner)) + to_chat(owner, span_warning("You lack the necessary living force for this action.")) + return + + if (owner.get_num_arms() <= 0) + to_chat(owner, span_warning("You don't have any usable hands!")) + else + to_chat(owner, span_warning("Your hands are full!")) \ No newline at end of file diff --git a/code/datums/actions/items/hands_free.dm b/code/datums/actions/items/hands_free.dm new file mode 100644 index 000000000000..a9cc3ff30b20 --- /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" \ No newline at end of file diff --git a/code/datums/actions/items/organ_action.dm b/code/datums/actions/items/organ_action.dm new file mode 100644 index 000000000000..f376c1e9a54d --- /dev/null +++ b/code/datums/actions/items/organ_action.dm @@ -0,0 +1,24 @@ +/datum/action/item_action/organ_action + check_flags = AB_CHECK_CONSCIOUS + +/datum/action/item_action/organ_action/IsAvailable() + var/obj/item/organ/attached_organ = target + 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) + ..() + name = "Toggle [target.name]" + button.name = 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 = target + name = "Toggle [organ_target.name]" \ No newline at end of file diff --git a/code/datums/actions/items/set_internals.dm b/code/datums/actions/items/set_internals.dm new file mode 100644 index 000000000000..acc1b1ae12d5 --- /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/UpdateButtons(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(target == carbon_owner.internal) + button.icon_state = "template_active" \ No newline at end of file diff --git a/code/datums/actions/items/stealth_box.dm b/code/datums/actions/items/stealth_box.dm new file mode 100644 index 000000000000..311eaa45076d --- /dev/null +++ b/code/datums/actions/items/stealth_box.dm @@ -0,0 +1,55 @@ +///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/mob/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(trigger_flags) + . = ..() + if(!.) + return FALSE + 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, span_warning("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(span_suicide("[owner] falls out of [box]! It looks like [owner.p_they()] committed suicide!")) + owner.throw_at(get_turf(owner)) + return OXYLOSS \ No newline at end of file diff --git a/code/datums/actions/items/toggles.dm b/code/datums/actions/items/toggles.dm new file mode 100644 index 000000000000..2a887e4e32dc --- /dev/null +++ b/code/datums/actions/items/toggles.dm @@ -0,0 +1,159 @@ +/datum/action/item_action/toggle + +/datum/action/item_action/toggle/New(Target) + ..() + var/obj/item/item_target = target + name = "Toggle [item_target.name]" + +/datum/action/item_action/toggle_light + name = "Toggle Light" + +/datum/action/item_action/toggle_hood + name = "Toggle Hood" + +/datum/action/item_action/toggle_firemode + name = "Toggle Firemode" + +/datum/action/item_action/toggle_bodycam + name = "Toggle Bodycamera" + +/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_mister + name = "Toggle Mister" + +/datum/action/item_action/toggle_paddles + name = "Toggle Paddles" + +/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/hardhat/weldhat/H = target + if(istype(H)) + H.toggle_welding_screen(owner) + +/datum/action/item_action/toggle_headphones + name = "Toggle Headphones" + desc = "UNTZ UNTZ UNTZ" + +/datum/action/item_action/toggle_headphones/Trigger() + var/obj/item/clothing/ears/headphones/H = target + if(istype(H)) + H.toggle(owner) + +/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/mob/actions/actions_items.dmi' + button_icon_state = "vortex_ff_on" + +/datum/action/item_action/toggle_unfriendly_fire/Trigger() + if(..()) + UpdateButtons() + +/datum/action/item_action/toggle_unfriendly_fire/UpdateButtons(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/toggle_research_scanner + name = "Toggle Research Scanner" + icon_icon = 'icons/mob/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, span_notice("[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/jetpack_stabilization + name = "Toggle Jetpack Stabilization" + +/datum/action/item_action/jetpack_stabilization/IsAvailable() + var/obj/item/tank/jetpack/linked_jetpack = target + if(!istype(linked_jetpack) || !linked_jetpack.on) + return FALSE + 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_wings + name = "Toggle Wings" + +/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_jetpack + name = "Toggle Jetpack" + +/datum/action/item_action/wheelys + name = "Toggle Wheely-Heel's Wheels" + desc = "Pops out or in your wheely-heel's wheels." + icon_icon = 'icons/mob/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/mob/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/mob/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 + +/datum/action/item_action/equip_unequip_TED_Gun + name = "Equip/Unequip TED Gun" \ No newline at end of file diff --git a/code/datums/actions/items/vortex_recall.dm b/code/datums/actions/items/vortex_recall.dm new file mode 100644 index 000000000000..f0298cfca6be --- /dev/null +++ b/code/datums/actions/items/vortex_recall.dm @@ -0,0 +1,12 @@ +/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/mob/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/hierophant_club = target + if(hierophant_club.teleporting) + return FALSE + return ..() \ No newline at end of file diff --git a/code/datums/actions/mobs/language_menu.dm b/code/datums/actions/mobs/language_menu.dm new file mode 100644 index 000000000000..e952d6f6b1e3 --- /dev/null +++ b/code/datums/actions/mobs/language_menu.dm @@ -0,0 +1,13 @@ +/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/Trigger(trigger_flags) + . = ..() + if(!.) + return + + var/datum/language_holder/owner_holder = owner.get_language_holder() + owner_holder.open_language_menu(usr) \ No newline at end of file diff --git a/code/datums/actions/mobs/small_sprite.dm b/code/datums/actions/mobs/small_sprite.dm new file mode 100644 index 000000000000..5d0c55e5c95a --- /dev/null +++ b/code/datums/actions/mobs/small_sprite.dm @@ -0,0 +1,50 @@ +//Small sprites +/datum/action/small_sprite + name = "Toggle Giant Sprite" + desc = "Others will always see you as giant" + icon_icon = 'icons/mob/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/mob/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_alert" + +/datum/action/small_sprite/megafauna/stalwart + 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/megafauna/spacedragon + small_icon = 'icons/mob/carp.dmi' + small_icon_state = "carp" + +/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 \ No newline at end of file diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm index 2b94c521103e..eb26fd62ce39 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -385,9 +385,9 @@ inherent = templaws.inherent if(3) - pick_weighted_lawset() + pickweighted_lawset() -/datum/ai_laws/proc/pick_weighted_lawset() +/datum/ai_laws/proc/pickweighted_lawset() var/datum/ai_laws/lawtype var/list/law_weights = CONFIG_GET(keyed_list/law_weight) while(!lawtype && law_weights.len) diff --git a/code/datums/brain_damage/creepy_trauma.dm b/code/datums/brain_damage/creepy_trauma.dm index 2b43e370b6eb..bd63fc699216 100644 --- a/code/datums/brain_damage/creepy_trauma.dm +++ b/code/datums/brain_damage/creepy_trauma.dm @@ -77,10 +77,9 @@ return if(prob(25)) // 25% chances to be nervous and stutter. if(prob(50)) // 12.5% chance (previous check taken into account) of doing something suspicious. - addtimer(CALLBACK(src, .proc/on_failed_social_interaction), rand(1, 3) SECONDS) - else if(!owner.stuttering) + addtimer(CALLBACK(src, PROC_REF(on_failed_social_interaction)), rand(1, 3) SECONDS) + if(owner.set_stutter_if_lower(3 SECONDS)) to_chat(owner, span_warning("Being near [obsession] makes you nervous and you begin to stutter...")) - owner.stuttering = max(3, owner.stuttering) /datum/brain_trauma/special/obsessed/on_hug(mob/living/hugger, mob/living/hugged) if(hugged == obsession) @@ -91,17 +90,17 @@ return switch(rand(1, 100)) if(1 to 40) - INVOKE_ASYNC(owner, /mob.proc/emote, pick("blink", "blink_r")) - owner.blur_eyes(10) + INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), pick("blink", "blink_r")) + owner.blur_eyes(10 SECONDS) to_chat(owner, span_userdanger("You sweat profusely and have a hard time focusing...")) if(41 to 80) - INVOKE_ASYNC(owner, /mob.proc/emote, "pale") + INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "pale") shake_camera(owner, 15, 1) owner.adjustStaminaLoss(70) to_chat(owner, span_userdanger("You feel your heart lurching in your chest...")) if(81 to 100) - INVOKE_ASYNC(owner, /mob.proc/emote, "cough") - owner.dizziness += 10 + INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "cough") + owner.adjust_dizzy(20 SECONDS) owner.adjust_disgust(5) to_chat(owner, span_userdanger("You gag and swallow a bit of bile...")) diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 7b12b3e0cf1b..710f784bc9a9 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -225,7 +225,7 @@ name = "Hide" desc = "Hide yourself from your owner's sight." button_icon_state = "hide" - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/imaginary_hide/Activate() var/mob/camera/imaginary_friend/I = owner diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm index bc34d343a2e5..c9cb2a2776ca 100644 --- a/code/datums/brain_damage/mild.dm +++ b/code/datums/brain_damage/mild.dm @@ -13,11 +13,16 @@ lose_text = "" /datum/brain_trauma/mild/hallucinations/on_life() - owner.hallucination = min(owner.hallucination + 10, 50) + if(owner.stat != CONSCIOUS || owner.IsSleeping() || owner.IsUnconscious()) + return +// if(HAS_TRAIT(owner, TRAIT_RDS_SUPPRESSED)) +// return + + owner.adjust_hallucinations_up_to(10 SECONDS, 100 SECONDS) ..() /datum/brain_trauma/mild/hallucinations/on_lose() - owner.hallucination = 0 + owner.remove_status_effect(/datum/status_effect/hallucination) ..() /datum/brain_trauma/mild/reality_dissociation @@ -31,13 +36,13 @@ /datum/brain_trauma/mild/reality_dissociation/on_life() if(owner.reagents.has_reagent(/datum/reagent/toxin/mindbreaker, needs_metabolizing = TRUE)) - owner.hallucination = 0 + owner.remove_status_effect(/datum/status_effect/hallucination) else if(prob(2)) - owner.hallucination += rand(10, 25) + owner.adjust_hallucinations(rand(10, 25)) ..() /datum/brain_trauma/mild/reality_dissociation/on_lose() - owner.hallucination = 0 + owner.remove_status_effect(/datum/status_effect/hallucination) ..() /datum/brain_trauma/mild/stuttering @@ -48,11 +53,11 @@ lose_text = "" /datum/brain_trauma/mild/stuttering/on_life() - owner.stuttering = min(owner.stuttering + 5, 25) + owner.adjust_stutter_up_to(5 SECONDS, 50 SECONDS) ..() /datum/brain_trauma/mild/stuttering/on_lose() - owner.stuttering = 0 + owner.remove_status_effect(/datum/status_effect/speech/stutter) ..() /datum/brain_trauma/mild/dumbness @@ -68,7 +73,7 @@ ..() /datum/brain_trauma/mild/dumbness/on_life() - owner.derpspeech = min(owner.derpspeech + 5, 25) + owner.adjust_derpspeech_up_to(5 SECONDS, 50 SECONDS) if(prob(3)) owner.emote("drool") else if(owner.stat == CONSCIOUS && prob(3)) @@ -77,7 +82,7 @@ /datum/brain_trauma/mild/dumbness/on_lose() REMOVE_TRAIT(owner, TRAIT_DUMB, TRAUMA_TRAIT) - owner.derpspeech = 0 + owner.remove_status_effect(/datum/status_effect/speech/stutter/derpspeech) SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "dumb") ..() @@ -109,12 +114,12 @@ if(1) owner.vomit() if(2,3) - owner.dizziness += 10 + owner.adjust_dizzy(20 SECONDS) if(4,5) - owner.confused += 10 + owner.adjust_confusion(10 SECONDS) owner.blur_eyes(10) if(6 to 9) - owner.slurring += 30 + owner.adjust_slurring(1 MINUTES) if(10) to_chat(owner, span_notice("You forget for a moment what you were doing.")) owner.Stun(20) @@ -238,13 +243,7 @@ return new_word + jointext(shuffled, "") + word[length(word)] /datum/brain_trauma/mild/expressive_aphasia/handle_speech(datum/source, list/speech_args) - if(ishuman(source)) - var/mob/living/carbon/human/H = source - if(H.drunkenness > 10) - return - var/message = speech_args[SPEECH_MESSAGE] - if(message) var/list/message_split = splittext(message, " ") var/list/new_message = list() @@ -252,29 +251,27 @@ for(var/word in message_split) var/suffix = "" var/suffix_foundon = 0 - for(var/potential_suffix in list(".", ",", ";", "!", ":", "?")) + for(var/potential_suffix in list("." , "," , ";" , "!" , ":" , "?")) suffix_foundon = findtext(word, potential_suffix, -length(potential_suffix)) if(suffix_foundon) - suffix = copytext(word,suffix_foundon,length(word)+1) + suffix = potential_suffix break if(suffix_foundon) word = copytext(word, 1, suffix_foundon) - word = html_decode(word) - if((length(word) < 8) || (lowertext(word) in common_words) || is_simple(word)) + if(lowertext(word) in common_words) new_message += word + suffix else - var/new_word = "" - if(prob(70)) - new_word += stutter(word) - if(prob(40)) - new_word += pick(stutter(lowertext(word)), partial_shuffle(lowertext(word)) + suffix) + if(prob(30) && message_split.len > 2) + new_message += pick("uh","erm") + break else - new_word += partial_shuffle(word) + suffix - new_message += new_word - break + var/list/charlist = text2charlist(word) + charlist.len = round(charlist.len * 0.5, 1) + shuffle_inplace(charlist) + new_message += jointext(charlist, "") + suffix message = jointext(new_message, " ") diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm index ba8dcb3dd379..4493fd85afb9 100644 --- a/code/datums/brain_damage/phobia.dm +++ b/code/datums/brain_damage/phobia.dm @@ -117,22 +117,22 @@ if(1) to_chat(owner, span_warning("You are paralyzed with fear!")) owner.Stun(70) - owner.Jitter(8) + owner.adjust_jitter(8 SECONDS) if(2) owner.emote("scream") - owner.Jitter(5) + owner.adjust_jitter(5 SECONDS) owner.say("AAAAH!!", forced = "phobia") if(reason) owner.pointed(reason) if(3) to_chat(owner, span_warning("You shut your eyes in terror!")) - owner.Jitter(5) + owner.adjust_jitter(5 SECONDS) owner.blind_eyes(10) if(4) - owner.dizziness += 10 - owner.confused += 10 - owner.Jitter(10) - owner.stuttering += 10 + owner.adjust_dizzy(10 SECONDS) + owner.adjust_confusion(10 SECONDS) + owner.adjust_jitter(10 SECONDS) + owner.adjust_stutter(10 SECONDS) // Defined phobia types for badminry, not included in the RNG trauma pool to avoid diluting. diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm index 5bbf42734843..f57bdac83ed0 100644 --- a/code/datums/brain_damage/severe.dm +++ b/code/datums/brain_damage/severe.dm @@ -123,21 +123,24 @@ gain_text = "" lose_text = "" -/datum/brain_trauma/severe/narcolepsy/on_life() - ..() +/datum/brain_trauma/severe/narcolepsy/on_life(delta_time, times_fired) if(owner.IsSleeping()) return + var/sleep_chance = 1 + var/drowsy = !!owner.has_status_effect(/datum/status_effect/drowsiness) if(owner.m_intent == MOVE_INTENT_RUN) sleep_chance += 2 - if(owner.drowsyness) + if(drowsy) sleep_chance += 3 - if(prob(sleep_chance)) + + if(DT_PROB(0.5 * sleep_chance, delta_time)) to_chat(owner, span_warning("You fall asleep.")) - owner.Sleeping(60) - else if(!owner.drowsyness && prob(sleep_chance * 2)) + owner.Sleeping(6 SECONDS) + + else if(!drowsy && DT_PROB(sleep_chance, delta_time)) to_chat(owner, span_warning("You feel tired...")) - owner.drowsyness += 10 + owner.adjust_drowsiness(20 SECONDS) /datum/brain_trauma/severe/monophobia name = "Monophobia" @@ -178,38 +181,33 @@ return var/high_stress = (stress > 60) //things get psychosomatic from here on - switch(rand(1,6)) + switch(rand(1, 6)) if(1) - if(!high_stress) - to_chat(owner, span_warning("You feel sick...")) - else + if(high_stress) to_chat(owner, span_warning("You feel really sick at the thought of being alone!")) - addtimer(CALLBACK(owner, /mob/living/carbon.proc/vomit, high_stress), 50) //blood vomit if high stress - if(2) - if(!high_stress) - to_chat(owner, span_warning("You can't stop shaking...")) - owner.dizziness += 20 - owner.confused += 20 - owner.Jitter(20) else + to_chat(owner, span_warning("You feel sick...")) + addtimer(CALLBACK(owner, TYPE_PROC_REF(/mob/living/carbon, vomit), high_stress), 50) //blood vomit if high stress + if(2) + if(high_stress) to_chat(owner, span_warning("You feel weak and scared! If only you weren't alone...")) - owner.dizziness += 20 - owner.confused += 20 - owner.Jitter(20) owner.adjustStaminaLoss(50) + else + to_chat(owner, span_warning("You can't stop shaking...")) + + owner.adjust_dizzy(40 SECONDS) + owner.adjust_confusion(20 SECONDS) + owner.set_jitter_if_lower(40 SECONDS) if(3, 4) - if(!high_stress) - to_chat(owner, span_warning("You feel really lonely...")) - else + if(high_stress) to_chat(owner, span_warning("You're going mad with loneliness!")) - owner.hallucination += 30 + owner.adjust_hallucinations(60 SECONDS) + else + to_chat(owner, span_warning("You feel really lonely...")) if(5) - if(!high_stress) - to_chat(owner, span_warning("Your heart skips a beat.")) - owner.adjustOxyLoss(8) - else + if(high_stress) if(prob(15) && ishuman(owner)) var/mob/living/carbon/human/H = owner H.set_heartattack(TRUE) @@ -217,7 +215,12 @@ else to_chat(owner, span_userdanger("You feel your heart lurching in your chest...")) owner.adjustOxyLoss(8) + else + to_chat(owner, span_warning("Your heart skips a beat.")) + owner.adjustOxyLoss(8) + else + //No effect return /datum/brain_trauma/severe/discoordination diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm index 1ff8cc062dca..ca5599897772 100644 --- a/code/datums/brain_damage/split_personality.dm +++ b/code/datums/brain_damage/split_personality.dm @@ -19,12 +19,12 @@ /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/cooldown/personality_commune/stranger_spell = new(src) + 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/cooldown/spell/personality_commune/owner_spell = new(src) + owner_spell.Grant(owner_backseat) /datum/brain_trauma/severe/split_personality/proc/get_ghost() diff --git a/code/datums/components/igniter.dm b/code/datums/components/igniter.dm index 7177cb0378be..3663e9e09733 100644 --- a/code/datums/components/igniter.dm +++ b/code/datums/components/igniter.dm @@ -33,4 +33,4 @@ if(isliving(target)) var/mob/living/L = target L.adjust_fire_stacks(fire_stacks) - L.IgniteMob() \ No newline at end of file + L.ignite_mob() diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm index 1514a7996046..eb42b06f2dc7 100644 --- a/code/datums/components/mood.dm +++ b/code/datums/components/mood.dm @@ -39,11 +39,11 @@ msg += span_notice("My mental status: ") //Long term switch(sanity) if(SANITY_GREAT to INFINITY) - msg += "My mind feels like a temple!\n" + msg += "[span_nicegreen("My mind feels like a temple!")]\n" if(SANITY_NEUTRAL to SANITY_GREAT) - msg += "I have been feeling great lately!\n" + msg += "[span_nicegreen("I have been feeling great lately!")]\n" if(SANITY_DISTURBED to SANITY_NEUTRAL) - msg += "I have felt quite decent lately.\n" + msg += "[span_nicegreen("I have felt quite decent lately.")]\n" if(SANITY_UNSTABLE to SANITY_DISTURBED) msg += "[span_warning("I'm feeling a little bit unhinged...")]\n" if(SANITY_CRAZY to SANITY_UNSTABLE) @@ -54,23 +54,23 @@ msg += span_notice("My current mood: ") //Short term switch(mood_level) if(1) - msg += "I wish I was dead!\n" + msg += "[span_boldwarning("I wish I was dead!")]\n" if(2) - msg += "I feel terrible...\n" + msg += "[span_boldwarning("I feel terrible...")]\n" if(3) - msg += "I feel very upset.\n" + msg += "[span_boldwarning("I feel very upset.")]\n" if(4) - msg += "I'm a bit sad.\n" + msg += "[span_boldwarning("I'm a bit sad.")]\n" if(5) - msg += "I'm alright.\n" + msg += "[span_nicegreen("I'm alright.")]\n" if(6) - msg += "I feel pretty okay.\n" + msg += "[span_nicegreen("I feel pretty okay.")]\n" if(7) - msg += "I feel pretty good.\n" + msg += "[span_nicegreen("I feel pretty good.")]\n" if(8) - msg += "I feel amazing!\n" + msg += "[span_nicegreen("II feel amazing!")]\n" if(9) - msg += "I love life!\n" + msg += "[span_nicegreen("I love life!")]\n" msg += span_notice("Moodlets:\n")//All moodlets if(mood_events.len) @@ -78,7 +78,7 @@ var/datum/mood_event/event = mood_events[i] msg += event.description else - msg += "I don't have much of a reaction to anything right now.\n" + msg += "[span_nicegreen("I don't have much of a reaction to anything right now.")]\n" to_chat(user || parent, examine_block(msg)) /datum/component/mood/proc/update_mood() //Called whenever a mood event is added or removed @@ -139,9 +139,14 @@ highest_absolute_mood = absmood if(!conflicting_moodies.len) //no special icons- go to the normal icon states + var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker) //bloodsucker edit if(sanity < 25) screen_obj.icon_state = "mood_insane" + if(IS_BLOODSUCKER(owner) && bloodsuckerdatum.my_clan?.get_clan() == CLAN_TOREADOR) + screen_obj.add_overlay("teeth_insane") else + if(IS_BLOODSUCKER(owner) && bloodsuckerdatum.my_clan?.get_clan() == CLAN_TOREADOR) + screen_obj.add_overlay("teeth[mood_level]") screen_obj.icon_state = "mood[mood_level]" screen_obj_sanity.icon_state = "sanity[sanity_level]" return diff --git a/code/datums/components/spooky.dm b/code/datums/components/spooky.dm index b1dd475a367b..817e089ef267 100644 --- a/code/datums/components/spooky.dm +++ b/code/datums/components/spooky.dm @@ -9,8 +9,8 @@ var/mob/living/carbon/human/U = user if(!istype(U.dna.species, /datum/species/skeleton)) U.adjustStaminaLoss(35) //Extra Damage - U.Jitter(35) - U.stuttering = 20 + U.adjust_jitter(35 SECONDS) + U.adjust_stutter(20 SECONDS) if(U.getStaminaLoss() > 95) to_chat(U, "Your ears weren't meant for this spectral sound.") spectral_change(U) @@ -23,20 +23,20 @@ if(istype(H.dna.species, /datum/species/zombie)) H.adjustStaminaLoss(25) H.Paralyze(15) //zombies can't resist the doot - C.Jitter(35) - C.stuttering = 20 + C.adjust_jitter(35 SECONDS) + C.adjust_stutter(20 SECONDS) if((!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly))) C.adjustStaminaLoss(25) //boneless humanoids don't lose the will to live to_chat(C, "DOOT") spectral_change(H) else //the sound will spook monkeys. - C.Jitter(15) - C.stuttering = 20 + C.adjust_jitter(15 SECONDS) + C.adjust_stutter(20 SECONDS) /datum/component/spooky/proc/spectral_change(mob/living/carbon/human/H, mob/user) if((H.getStaminaLoss() > 95) && (!istype(H.dna.species, /datum/species/skeleton)) && (!istype(H.dna.species, /datum/species/golem)) && (!istype(H.dna.species, /datum/species/android)) && (!istype(H.dna.species, /datum/species/jelly))) - H.Paralyze(20) + H.Paralyze(2 SECONDS) H.set_species(/datum/species/skeleton) H.visible_message(span_warning("[H] has given up on life as a mortal.")) var/T = get_turf(H) diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm index 3ca70da2939e..e3a4205d19c7 100644 --- a/code/datums/components/stationloving.dm +++ b/code/datums/components/stationloving.dm @@ -7,10 +7,10 @@ /datum/component/stationloving/Initialize(inform_admins = FALSE, allow_death = FALSE) if(!ismovable(parent)) return COMPONENT_INCOMPATIBLE - RegisterSignals(parent, list(COMSIG_MOVABLE_Z_CHANGED), .proc/check_in_bounds) - RegisterSignals(parent, list(COMSIG_MOVABLE_SECLUDED_LOCATION), .proc/relocate) - RegisterSignals(parent, list(COMSIG_PARENT_PREQDELETED), .proc/check_deletion) - RegisterSignals(parent, list(COMSIG_ITEM_IMBUE_SOUL), .proc/check_soul_imbue) + RegisterSignals(parent, list(COMSIG_MOVABLE_Z_CHANGED), PROC_REF(check_in_bounds)) + RegisterSignals(parent, list(COMSIG_MOVABLE_SECLUDED_LOCATION), PROC_REF(relocate)) + RegisterSignals(parent, list(COMSIG_PARENT_PREQDELETED), PROC_REF(check_deletion)) + RegisterSignals(parent, list(COMSIG_ITEM_IMBUE_SOUL), PROC_REF(check_soul_imbue)) src.inform_admins = inform_admins src.allow_death = allow_death check_in_bounds() // Just in case something is being created outside of station/centcom @@ -47,7 +47,7 @@ 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) return disallow_soul_imbue /datum/component/stationloving/proc/in_bounds() diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 49273b5fe9e4..854d512d4663 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -50,7 +50,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 //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. var/screen_max_columns = 7 //These two determine maximum screen sizes. @@ -70,42 +70,42 @@ closer = new(null, src) orient2hud() - RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) - RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) - RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) - RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) - - RegisterSignal(parent, COMSIG_TOPIC, .proc/topic_handle) - - RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) - - RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) - RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) - RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) - RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) - - RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) - RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) - RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) - - RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_move) - - RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) - RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) - RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) + RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, PROC_REF(on_check)) + RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, PROC_REF(check_locked)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, PROC_REF(signal_show_attempt)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, PROC_REF(signal_insertion_attempt)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, PROC_REF(signal_can_insert)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, PROC_REF(signal_take_type)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, PROC_REF(signal_fill_type)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, PROC_REF(set_locked)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, PROC_REF(signal_take_obj)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, PROC_REF(signal_quick_empty)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, PROC_REF(signal_hide_attempt)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, PROC_REF(close_all)) + RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, PROC_REF(signal_return_inv)) + + RegisterSignal(parent, COMSIG_TOPIC, PROC_REF(topic_handle)) + + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(attackby)) + + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand)) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, PROC_REF(on_attack_hand)) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, PROC_REF(emp_act)) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, PROC_REF(show_to_ghost)) + RegisterSignal(parent, COMSIG_ATOM_ENTERED, PROC_REF(refresh_mob_views)) + RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(_remove_and_refresh)) + RegisterSignal(parent, COMSIG_ATOM_CANREACH, PROC_REF(canreach_react)) + + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(preattack_intercept)) + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(attack_self)) + RegisterSignal(parent, COMSIG_ITEM_PICKUP, PROC_REF(signal_on_pickup)) + + RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, PROC_REF(close_all)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) + + RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(on_alt_click)) + RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, PROC_REF(mousedrop_onto)) + RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, PROC_REF(mousedrop_receive)) update_actions() @@ -157,17 +157,17 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches) return "\n\t[span_notice("[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) - RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) - if(I.obj_flags & IN_INVENTORY) - var/mob/M = I.loc - if(!istype(M)) - return - modeswitch_action.Grant(M) + 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)) + 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/components/wet_floor.dm b/code/datums/components/wet_floor.dm index f71ba5859d05..7bc20065863d 100644 --- a/code/datums/components/wet_floor.dm +++ b/code/datums/components/wet_floor.dm @@ -67,9 +67,9 @@ T.add_overlay(intended) current_overlay = intended -/datum/component/wet_floor/proc/AfterSlip(mob/living/L) +/datum/component/wet_floor/proc/AfterSlip(mob/living/slipped) if(highest_strength == TURF_WET_LUBE) - L.confused = max(L.confused, 8) + slipped.set_confusion_if_lower(8 SECONDS) /datum/component/wet_floor/proc/update_flags() var/intensity diff --git a/code/datums/diseases/advance/symptoms/confusion.dm b/code/datums/diseases/advance/symptoms/confusion.dm index dafa7e688fc1..c59ccb6e789b 100644 --- a/code/datums/diseases/advance/symptoms/confusion.dm +++ b/code/datums/diseases/advance/symptoms/confusion.dm @@ -57,7 +57,7 @@ Bonus to_chat(M, span_warning("[pick("Your head hurts.", "Your mind blanks for a moment.")]")) else to_chat(M, span_userdanger("You can't think straight!")) - M.confused = min(100 * power, M.confused + 8) + M.adjust_confusion_up_to(100 * power, 800 * power) if(brain_damage) M.adjustOrganLoss(ORGAN_SLOT_BRAIN,3 * power, 80) M.updatehealth() diff --git a/code/datums/diseases/advance/symptoms/dizzy.dm b/code/datums/diseases/advance/symptoms/dizzy.dm index 8fa08acc78bc..386be9445e9d 100644 --- a/code/datums/diseases/advance/symptoms/dizzy.dm +++ b/code/datums/diseases/advance/symptoms/dizzy.dm @@ -52,6 +52,6 @@ Bonus to_chat(M, span_warning("[pick("You feel dizzy.", "Your head spins.")]")) else to_chat(M, span_userdanger("A wave of dizziness washes over you!")) - M.Dizzy(5) + M.adjust_dizzy(5 SECONDS) if(power >= 2) M.set_drugginess(5) diff --git a/code/datums/diseases/advance/symptoms/fire.dm b/code/datums/diseases/advance/symptoms/fire.dm index 97530befaafd..adfba6292278 100644 --- a/code/datums/diseases/advance/symptoms/fire.dm +++ b/code/datums/diseases/advance/symptoms/fire.dm @@ -60,12 +60,12 @@ Bonus to_chat(M, span_warning("[pick("You feel hot.", "You hear a crackling noise.", "You smell smoke.")]")) if(4) Firestacks_stage_4(M, A) - M.IgniteMob() + M.ignite_mob() to_chat(M, span_userdanger("Your skin bursts into flames!")) M.emote("scream") if(5) Firestacks_stage_5(M, A) - M.IgniteMob() + M.ignite_mob() to_chat(M, span_userdanger("Your skin erupts into an inferno!")) M.emote("scream") @@ -146,7 +146,7 @@ Bonus to_chat(M, span_warning("[pick("Your veins boil.", "You feel hot.", "You smell meat cooking.")]")) if(4) Alkali_fire_stage_4(M, A) - M.IgniteMob() + M.ignite_mob() to_chat(M, span_userdanger("Your sweat bursts into flames!")) M.emote("scream") if(5) @@ -155,7 +155,7 @@ Bonus explosion(get_turf(M),0,0,2 * explosion_power) Alkali_fire_stage_5(M, A) Alkali_fire_stage_5(M, A) - M.IgniteMob() + M.ignite_mob() to_chat(M, span_userdanger("Your skin erupts into an inferno!")) M.emote("scream") diff --git a/code/datums/diseases/advance/symptoms/hallucigen.dm b/code/datums/diseases/advance/symptoms/hallucigen.dm index 20e6bfd8961f..adaec72a1f32 100644 --- a/code/datums/diseases/advance/symptoms/hallucigen.dm +++ b/code/datums/diseases/advance/symptoms/hallucigen.dm @@ -66,4 +66,4 @@ Bonus else if(prob(base_message_chance)) to_chat(M, span_userdanger("[pick("Oh, your head...", "Your head pounds.", "They're everywhere! Run!", "Something in the shadows...")]")) - M.hallucination += (45 * power) + M.adjust_hallucinations(45 * power) diff --git a/code/datums/diseases/advance/symptoms/narcolepsy.dm b/code/datums/diseases/advance/symptoms/narcolepsy.dm index 322bed7af642..09b892e0906f 100644 --- a/code/datums/diseases/advance/symptoms/narcolepsy.dm +++ b/code/datums/diseases/advance/symptoms/narcolepsy.dm @@ -46,7 +46,7 @@ Bonus //this ticks even when on cooldown switch(sleep_level) //Works sorta like morphine if(10 to 19) - M.drowsyness += 1 + M.adjust_drowsiness(2 SECONDS) if(20 to INFINITY) M.Sleeping(30, 0) sleep_level = 0 @@ -87,7 +87,7 @@ Bonus if(5) if(prob(25)) to_chat(M, span_warning("[pick("So tired...","You feel very sleepy.","You have a hard time keeping your eyes open.","You try to stay awake.")]")) - M.drowsyness = max(M.drowsyness, 2) + M.adjust_drowsiness(rand(14, 7) SECONDS) sleepy_ticks += rand(10,14) if(stamina) M.adjustStaminaLoss(30) diff --git a/code/datums/diseases/advance/symptoms/sensory.dm b/code/datums/diseases/advance/symptoms/sensory.dm index 1fe061eb4a25..be603032d9a6 100644 --- a/code/datums/diseases/advance/symptoms/sensory.dm +++ b/code/datums/diseases/advance/symptoms/sensory.dm @@ -36,23 +36,22 @@ if(A.stage >= 3) - M.dizziness = max(0, M.dizziness - 2) - M.drowsyness = max(0, M.drowsyness - 2) - M.slurring = max(0, M.slurring - 2) - M.confused = max(0, M.confused - 2) + M.adjust_dizzy(-4 SECONDS) + M.adjust_drowsiness(-4 SECONDS) + M.adjust_slurring(-1 SECONDS) + M.adjust_confusion(-2 SECONDS) if(purge_alcohol) M.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.drunkenness = max(H.drunkenness - 5, 0) + M.adjust_drunk_effect(-5) if(A.stage >= 4) - M.drowsyness = max(0, M.drowsyness - 2) + M.adjust_drowsiness(-4 SECONDS) if(M.reagents.has_reagent(/datum/reagent/toxin/mindbreaker)) M.reagents.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) if(M.reagents.has_reagent(/datum/reagent/toxin/histamine)) M.reagents.remove_reagent(/datum/reagent/toxin/histamine, 5) - M.hallucination = max(0, M.hallucination - 10) + + M.adjust_hallucinations(-20 SECONDS) if(A.stage >= 5) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, -3) @@ -60,12 +59,14 @@ var/mob/living/carbon/C = M if(prob(10)) if(trauma_heal_severe) - C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_LOBOTOMY) + C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_SURGERY) else C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC) + + /datum/symptom/sensory_restoration name = "Sensory Restoration" desc = "The virus stimulates the production and replacement of sensory tissues, causing the host to regenerate eyes and ears when damaged." diff --git a/code/datums/diseases/anxiety.dm b/code/datums/diseases/anxiety.dm index 22a453894f1a..eb3128ec9fb2 100644 --- a/code/datums/diseases/anxiety.dm +++ b/code/datums/diseases/anxiety.dm @@ -11,31 +11,33 @@ desc = "If left untreated subject will regurgitate butterflies." severity = DISEASE_SEVERITY_MINOR -/datum/disease/anxiety/stage_act() - ..() +/datum/disease/anxiety/stage_act(delta_time, times_fired) + . = ..() + if(!.) + return + switch(stage) if(2) //also changes say, see say.dm - if(prob(5)) + if(DT_PROB(2.5, delta_time)) to_chat(affected_mob, span_notice("You feel anxious.")) if(3) - if(prob(10)) + if(DT_PROB(5, delta_time)) to_chat(affected_mob, span_notice("Your stomach flutters.")) - if(prob(5)) + if(DT_PROB(2.5, delta_time)) to_chat(affected_mob, span_notice("You feel panicky.")) - if(prob(2)) + if(DT_PROB(1, delta_time)) to_chat(affected_mob, span_danger("You're overtaken with panic!")) - affected_mob.confused += (rand(2,3)) + affected_mob.adjust_confusion(rand(2 SECONDS, 3 SECONDS)) if(4) - if(prob(10)) + if(DT_PROB(5, delta_time)) to_chat(affected_mob, span_danger("You feel butterflies in your stomach.")) - if(prob(5)) + if(DT_PROB(2.5, delta_time)) affected_mob.visible_message(span_danger("[affected_mob] stumbles around in a panic."), \ span_userdanger("You have a panic attack!")) - affected_mob.confused += (rand(6,8)) - affected_mob.jitteriness += (rand(6,8)) - if(prob(2)) + affected_mob.adjust_confusion(rand(6 SECONDS, 8 SECONDS)) + affected_mob.adjust_jitter(rand(12 SECONDS, 16 SECONDS)) + if(DT_PROB(1, delta_time)) affected_mob.visible_message(span_danger("[affected_mob] coughs up butterflies!"), \ span_userdanger("You cough up butterflies!")) new /mob/living/simple_animal/butterfly(affected_mob.loc) new /mob/living/simple_animal/butterfly(affected_mob.loc) - return \ No newline at end of file diff --git a/code/datums/diseases/brainrot.dm b/code/datums/diseases/brainrot.dm index 00ce32ff569e..9e231e9aad0a 100644 --- a/code/datums/diseases/brainrot.dm +++ b/code/datums/diseases/brainrot.dm @@ -54,6 +54,6 @@ if(prob(1)) affected_mob.emote("snore") if(prob(15)) - affected_mob.stuttering += 3 + affected_mob.adjust_stutter(3 SECONDS) return diff --git a/code/datums/diseases/decloning.dm b/code/datums/diseases/decloning.dm index 1ee7e82204c4..6347bf614149 100644 --- a/code/datums/diseases/decloning.dm +++ b/code/datums/diseases/decloning.dm @@ -43,7 +43,7 @@ affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1, 170) affected_mob.adjustCloneLoss(2) if(prob(15)) - affected_mob.stuttering += 3 + affected_mob.adjust_stutter(3 SECONDS) if(5) if(prob(2)) affected_mob.emote("itch") @@ -56,4 +56,4 @@ affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2, 170) if(affected_mob.cloneloss >= 100) affected_mob.visible_message(span_danger("[affected_mob] skin turns to dust!"), "Your skin turns to dust!") - affected_mob.dust() \ No newline at end of file + affected_mob.dust() diff --git a/code/datums/diseases/heart_failure.dm b/code/datums/diseases/heart_failure.dm index f48ec0702b2b..c1043a8596e9 100644 --- a/code/datums/diseases/heart_failure.dm +++ b/code/datums/diseases/heart_failure.dm @@ -33,7 +33,7 @@ to_chat(H, span_warning("You feel [pick("discomfort", "pressure", "a burning sensation", "pain")] in your chest.")) if(prob(2)) to_chat(H, span_warning("You feel dizzy.")) - H.confused += 6 + H.adjust_confusion(6 SECONDS) if(prob(3)) to_chat(H, span_warning("You feel [pick("full", "nauseated", "sweaty", "weak", "tired", "short on breath", "uneasy")].")) if(3 to 4) @@ -49,7 +49,7 @@ H.losebreath += 4 if(prob(3)) to_chat(H, span_danger("You feel very weak and dizzy...")) - H.confused += 8 + H.adjust_confusion(8 SECONDS) H.adjustStaminaLoss(40) H.emote("cough") if(5) diff --git a/code/datums/diseases/jitters.dm b/code/datums/diseases/jitters.dm index 3dc80b31ec09..7101d8288bb0 100644 --- a/code/datums/diseases/jitters.dm +++ b/code/datums/diseases/jitters.dm @@ -17,15 +17,15 @@ switch(stage) if(1) if(prob(10)) - affected_mob.Jitter(2) + affected_mob.adjust_jitter(2 SECONDS) if(2) if(prob(20)) - affected_mob.Jitter(2) + affected_mob.adjust_jitter(2 SECONDS) if(prob(10)) to_chat(affected_mob, span_notice("You feel ants in your legs.")) if(3) if(prob(40)) - affected_mob.Jitter(2) + affected_mob.adjust_jitter(2 SECONDS) if(prob(20)) to_chat(affected_mob, span_danger("You feel a million pricks on your legs!")) if((affected_mob.mobility_flags & MOBILITY_MOVE) && prob(15)) diff --git a/code/datums/diseases/plague.dm b/code/datums/diseases/plague.dm index 543b3b8146de..465194a5251e 100644 --- a/code/datums/diseases/plague.dm +++ b/code/datums/diseases/plague.dm @@ -25,7 +25,7 @@ if(4) if(prob(2)) to_chat(affected_mob, span_userdanger("You can't keep steady!")) - affected_mob.Dizzy(5) + affected_mob.adjust_dizzy(5) if(prob(2)) to_chat(affected_mob, span_danger("You can barely breathe!")) affected_mob.adjustOxyLoss(5) diff --git a/code/datums/diseases/rhumba_beat.dm b/code/datums/diseases/rhumba_beat.dm index db8266bf04f8..488a15820e46 100644 --- a/code/datums/diseases/rhumba_beat.dm +++ b/code/datums/diseases/rhumba_beat.dm @@ -33,7 +33,7 @@ if(prob(20)) if (prob(50)) affected_mob.adjust_fire_stacks(2) - affected_mob.IgniteMob() + affected_mob.ignite_mob() else affected_mob.emote("gasp") to_chat(affected_mob, span_danger("You feel a burning beat inside...")) diff --git a/code/datums/diseases/sleepy.dm b/code/datums/diseases/sleepy.dm index 8e61dd03c574..0ca951fa8a04 100644 --- a/code/datums/diseases/sleepy.dm +++ b/code/datums/diseases/sleepy.dm @@ -46,7 +46,7 @@ affected_mob.blind_eyes(6) if(prob(3)) to_chat(affected_mob, span_danger("Your legs feel weak.")) - affected_mob.confused = max(affected_mob.confused, 4) + affected_mob.set_confusion_if_lower(4 SECONDS) affected_mob.adjustStaminaLoss(10) if(prob(3)) to_chat(affected_mob, span_danger("Your eyes feel strained.")) @@ -54,7 +54,7 @@ if(prob(3)) to_chat(affected_mob, span_warning("[pick("So tired...","You feel very sleepy.","You have a hard time keeping your eyes open.","You try to stay awake.")]")) affected_mob.blur_eyes(6) - affected_mob.confused = max(affected_mob.confused, 4) + affected_mob.set_confusion_if_lower(4 SECONDS) affected_mob.adjustStaminaLoss(15) if(prob(5)) to_chat(affected_mob, span_danger("Just a little nap...")) diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index 5f1c86f4260e..47371ceb3ebf 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -133,7 +133,7 @@ if(3) if(prob(4)) to_chat(affected_mob, span_danger("You feel a stabbing pain in your head.")) - affected_mob.confused += 10 + affected_mob.adjust_confusion(1 SECONDS) if(4) if(prob(3)) affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") diff --git a/code/datums/diseases/tuberculosis.dm b/code/datums/diseases/tuberculosis.dm index 25816c909b0b..b168f18a443f 100644 --- a/code/datums/diseases/tuberculosis.dm +++ b/code/datums/diseases/tuberculosis.dm @@ -27,7 +27,7 @@ if(4) if(prob(2)) to_chat(affected_mob, span_userdanger("You see four of everything")) - affected_mob.Dizzy(5) + affected_mob.adjust_dizzy(5) if(prob(2)) to_chat(affected_mob, span_danger("You feel a sharp pain from your lower chest!")) affected_mob.adjustOxyLoss(5) @@ -46,7 +46,7 @@ affected_mob.AdjustSleeping(100) if(prob(2)) to_chat(affected_mob, span_userdanger("You feel your mind relax and your thoughts drift!")) - affected_mob.confused = min(100, affected_mob.confused + 8) + affected_mob.adjust_confusion_up_to(8 SECONDS, 100 SECONDS) if(prob(10)) affected_mob.vomit(20) if(prob(3)) diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm index ec929a5ff729..280b8c16183b 100644 --- a/code/datums/emotes.dm +++ b/code/datums/emotes.dm @@ -2,29 +2,52 @@ #define EMOTE_AUDIBLE 2 /datum/emote - var/key = "" //What calls the emote - var/key_third_person = "" //This will also call the emote - var/message = "" //Message displayed when emote is used - var/message_mime = "" //Message displayed if the user is a mime - var/message_alien = "" //Message displayed if the user is a grown alien - var/message_larva = "" //Message displayed if the user is an alien larva - var/message_robot = "" //Message displayed if the user is a robot - var/message_AI = "" //Message displayed if the user is an AI - var/message_monkey = "" //Message displayed if the user is a monkey - var/message_ipc = "" // Message to display if the user is an IPC - var/message_simple = "" //Message to display if the user is a simple_animal - var/message_param = "" //Message to display if a param was given - var/emote_type = EMOTE_VISIBLE //Whether the emote is visible or audible - var/restraint_check = FALSE //Checks if the mob is restrained before performing the emote - var/muzzle_ignore = FALSE //Will only work if the emote is EMOTE_AUDIBLE - var/list/mob_type_allowed_typecache = /mob //Types that are allowed to use that emote - var/list/mob_type_blacklist_typecache //Types that are NOT allowed to use that emote + /// What calls the emote. + var/key = "" + /// This will also call the emote. + var/key_third_person = "" + /// Message displayed when emote is used. + var/message = "" + /// Message displayed if the user is a mime. + var/message_mime = "" + /// Message displayed if the user is a grown alien. + var/message_alien = "" + /// Message displayed if the user is an alien larva. + var/message_larva = "" + /// Message displayed if the user is a robot. + var/message_robot = "" + /// Message displayed if the user is an AI. + var/message_AI = "" + /// Message displayed if the user is a monkey. + var/message_monkey = "" + /// Message to display if the user is an IPC + var/message_ipc = "" + /// Message to display if the user is a simple_animal or basic mob. + var/message_simple = "" + /// Message with %t at the end to allow adding params to the message, like for mobs doing an emote relatively to something else. + var/message_param = "" + /// Whether the emote is visible and/or audible bitflag + var/emote_type = EMOTE_VISIBLE + /// Checks if the mob can use its hands before performing the emote. + var/hands_use_check = FALSE + /// Will only work if the emote is EMOTE_AUDIBLE. + var/muzzle_ignore = FALSE + /// Types that are allowed to use that emote. + var/list/mob_type_allowed_typecache = /mob + /// Types that are NOT allowed to use that emote. + var/list/mob_type_blacklist_typecache + /// Types that can use this emote regardless of their state. var/list/mob_type_ignore_stat_typecache + /// In which state can you use this emote? (Check stat.dm for a full list of them) var/stat_allowed = CONSCIOUS - var/sound //Sound to play when emote is called - var/vary = FALSE //used for the honk borg emote - var/only_forced_audio = FALSE //can only code call this event instead of the player. - var/cooldown = 0.4 SECONDS + /// Sound to play when emote is called. + var/sound + /// Used for the honk borg emote. + var/vary = FALSE + /// Can only code call this event instead of the player. + var/only_forced_audio = FALSE + /// The cooldown between the uses of the emote. + var/cooldown = 0.8 SECONDS /datum/emote/New() if (ispath(mob_type_allowed_typecache)) @@ -129,8 +152,17 @@ /datum/emote/proc/select_param(mob/user, params) return replacetext(message_param, "%t", params) +/** + * Check to see if the user is allowed to run the emote. + * + * Arguments: + * * user - Person that is trying to send the emote. + * * status_check - Bool that says whether we should check their stat or not. + * * intentional - Bool that says whether the emote was forced (FALSE) or not (TRUE). + * + * Returns a bool about whether or not the user can run the emote. + */ /datum/emote/proc/can_run_emote(mob/user, status_check = TRUE, intentional = FALSE) - . = TRUE if(!is_type_in_typecache(user, mob_type_allowed_typecache)) return FALSE if(is_type_in_typecache(user, mob_type_blacklist_typecache)) @@ -147,24 +179,16 @@ if(DEAD) to_chat(user, span_notice("You cannot [key] while dead.")) return FALSE - if(restraint_check) - if(isliving(user)) - var/mob/living/L = user - if(L.IsParalyzed() || L.IsStun()) - if(!intentional) - return FALSE - to_chat(user, span_notice("You cannot [key] while stunned.")) - return FALSE - if(restraint_check && user.restrained()) + if(hands_use_check && HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) if(!intentional) return FALSE - to_chat(user, span_notice("You cannot [key] while restrained.")) + to_chat(user, span_warning("You cannot use your hands to [key] right now!")) return FALSE - if(isliving(user)) - var/mob/living/L = user - if(HAS_TRAIT(L, TRAIT_EMOTEMUTE)) - return FALSE + if(HAS_TRAIT(user, TRAIT_EMOTEMUTE)) + return FALSE + + return TRUE /** * Allows the intrepid coder to send a basic emote diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index d47e3876f6fe..8e9d24bdad52 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -46,7 +46,7 @@ var/mob/living/MM = teleatom to_chat(MM, span_warning("The bluespace interface on your bag of holding interferes with the teleport!")) MM.adjust_disgust(20+(precision/10)) //20-30 disgust, pretty nasty - MM.confused += (10 + precision/20) //10-15 confusion, little wobbly + MM.adjust_confusion(10 + precision/20) //10-15 confusion, little wobbly // if effects are not specified and not explicitly disabled, sparks if ((!effectin || !effectout) && !no_effects) diff --git a/code/datums/hud.dm b/code/datums/hud.dm index fcd6187cec1f..4d963080ef27 100644 --- a/code/datums/hud.dm +++ b/code/datums/hud.dm @@ -2,6 +2,10 @@ GLOBAL_LIST_EMPTY(all_huds) +///gets filled by each /datum/atom_hud/New(). +///associative list of the form: list(hud category = list(all global atom huds that use that category)) +GLOBAL_LIST_EMPTY(huds_by_category) + //GLOBAL HUD LIST GLOBAL_LIST_INIT(huds, list( DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(), @@ -14,172 +18,412 @@ GLOBAL_LIST_INIT(huds, list( DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(), DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(), DATA_HUD_SECURITY_MEDICAL = new/datum/atom_hud/data/human/security/advanced/hos(), - ANTAG_HUD_CULT = new/datum/atom_hud/antag(), - ANTAG_HUD_REV = new/datum/atom_hud/antag(), - ANTAG_HUD_OPS = new/datum/atom_hud/antag(), - ANTAG_HUD_WIZ = new/datum/atom_hud/antag(), - ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(), - ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_DEVIL = new/datum/atom_hud/antag(), - ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(), - ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_HIVE = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(), - ANTAG_HUD_VAMPIRE = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_DARKSPAWN = new/datum/atom_hud/antag(), - ANTAG_HUD_CAPITALIST = new/datum/atom_hud/antag(), - ANTAG_HUD_COMMUNIST = new/datum/atom_hud/antag(), - ANTAG_HUD_HERETIC = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_MINDSLAVE = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_ZOMBIE = new/datum/atom_hud/antag(), - ANTAG_HUD_INFILTRATOR = new/datum/atom_hud/antag(), - ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag(), - ANTAG_HUD_MHUNTER = new/datum/atom_hud/antag/hidden(), - ANTAG_HUD_BRAINWASHED = new/datum/atom_hud/antag/hidden() )) /datum/atom_hud - var/list/atom/hudatoms = list() //list of all atoms which display this hud - var/list/hudusers = list() //list with all mobs who can see the hud - var/list/hud_icons = list() //these will be the indexes for the atom's hud_list + ///associative list of the form: list(z level = list(hud atom)). + ///tracks what hud atoms for this hud exists in what z level so we can only give users + ///the hud images that they can actually see. + var/list/atom/hud_atoms = list() + + ///associative list of the form: list(z level = list(hud user client mobs)). + ///tracks mobs that can "see" us + // by z level so when they change z's we can adjust what images they see from this hud. + var/list/hud_users = list() + + ///used for signal tracking purposes, associative list of the form: list(hud atom = TRUE) that isnt separated by z level + var/list/atom/hud_atoms_all_z_levels = list() + + ///used for signal tracking purposes, associative list of the form: list(hud user = number of times this hud was added to this user). + ///that isnt separated by z level + var/list/mob/hud_users_all_z_levels = list() - var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them - var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet + ///these will be the indexes for the atom's hud_list + var/list/hud_icons = list() - var/do_silicon_check = FALSE // true for medical and sec advanced and diagnostic basic HUDs + ///mobs associated with the next time this hud can be added to them + var/list/next_time_allowed = list() + ///mobs that have triggered the cooldown and are queued to see the hud, but do not yet + var/list/queued_to_see = list() + /// huduser = list(atoms with their hud hidden) - aka everyone hates targeted invisiblity + var/list/hud_exceptions = list() + ///whether or not this atom_hud type updates the global huds_by_category list. + ///some subtypes cant work like this since theyre supposed to "belong" to + ///one target atom each. it will still go in the other global hud lists. + var/uses_global_hud_category = TRUE /datum/atom_hud/New() GLOB.all_huds += src + for(var/z_level in 1 to world.maxz) + hud_atoms += list(list()) + hud_users += list(list()) + + RegisterSignal(SSdcs, COMSIG_GLOB_NEW_Z, PROC_REF(add_z_level_huds)) + + if(uses_global_hud_category) + for(var/hud_icon in hud_icons) + GLOB.huds_by_category[hud_icon] += list(src) /datum/atom_hud/Destroy() - for(var/v in hudusers) - remove_hud_from(v) - for(var/v in hudatoms) - remove_from_hud(v) + for(var/mob/mob as anything in hud_users_all_z_levels) + hide_from(mob) + + for(var/atom/atom as anything in hud_atoms_all_z_levels) + remove_atom_from_hud(atom) + + if(uses_global_hud_category) + for(var/hud_icon in hud_icons) + LAZYREMOVEASSOC(GLOB.huds_by_category, hud_icon, src) + GLOB.all_huds -= src return ..() -/datum/atom_hud/proc/remove_hud_from(mob/M, absolute = FALSE) - if(!M || !hudusers[M]) - return - if (absolute || !--hudusers[M]) - UnregisterSignal(M, COMSIG_PARENT_QDELETING) - var/M_is_silicon = FALSE - if(do_silicon_check && istype(M, /mob/living/silicon)) - M_is_silicon = TRUE - hudusers -= M - if(next_time_allowed[M]) - next_time_allowed -= M - if(queued_to_see[M]) - queued_to_see -= M +/datum/atom_hud/proc/add_z_level_huds() + SIGNAL_HANDLER + hud_atoms += list(list()) + hud_users += list(list()) + +///returns a list of all hud atoms in the given z level and linked lower z levels (because hud users in higher z levels can see below) +/datum/atom_hud/proc/get_hud_atoms_for_z_level(z_level) + if(z_level <= 0) + return FALSE + if(z_level > length(hud_atoms)) + stack_trace("get_hud_atoms_for_z_level() was given a z level index out of bounds of hud_atoms!") + return FALSE + + . = list() + . += hud_atoms[z_level] + + var/max_number_of_linked_z_levels_i_care_to_support_here = 10 + + while(max_number_of_linked_z_levels_i_care_to_support_here) + var/lower_z_level_exists = SSmapping.level_trait(z_level, ZTRAIT_DOWN) + + if(lower_z_level_exists) + z_level-- + . += hud_atoms[z_level] + max_number_of_linked_z_levels_i_care_to_support_here-- + continue + else - for(var/atom/A in hudatoms) - if(M_is_silicon) - if (istype(A, /mob)) - var/mob/B = A - if(B.digitalcamo) - continue - remove_from_single_hud(M, A) - -/datum/atom_hud/proc/remove_from_hud(atom/A) //this is called when some stops existing or needs HUD removed - if(!A) + break + +///returns a list of all hud users in the given z level and linked upper z levels (because hud users in higher z levels can see below) +/datum/atom_hud/proc/get_hud_users_for_z_level(z_level) + if(z_level > length(hud_users) || z_level <= 0) + stack_trace("get_hud_atoms_for_z_level() was given a z level index [z_level] out of bounds 1->[length(hud_users)] of hud_atoms!") return FALSE - var/has_camo = FALSE - if(do_silicon_check && istype(A, /mob)) - var/mob/B = A - if(B.digitalcamo) - has_camo = TRUE - for(var/mob/M in hudusers) - if(has_camo) - if(istype(M, /mob/living/silicon)) - continue - remove_from_single_hud(M, A) - hudatoms -= A - return TRUE -/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client this is used to Hide HUD A from mob M - if(!M || !M.client || !A) - return - for(var/i in hud_icons) - M.client.images -= A.hud_list[i] + . = list() + . += hud_users[z_level] -/datum/atom_hud/proc/add_hud_to(mob/M) // this is called when something activates HUD - if(!M) + var/max_number_of_linked_z_levels_i_care_to_support_here = 10 + + while(max_number_of_linked_z_levels_i_care_to_support_here) + var/upper_level_exists = SSmapping.level_trait(z_level, ZTRAIT_UP) + + if(upper_level_exists) + z_level++ + . += hud_users[z_level] + max_number_of_linked_z_levels_i_care_to_support_here-- + continue + + else + break + +///show this hud to the passed in user +/datum/atom_hud/proc/show_to(mob/new_viewer) + if(!new_viewer) return - if(!hudusers[M]) - hudusers[M] = 1 - RegisterSignal(M, COMSIG_PARENT_QDELETING, .proc/unregister_mob) - var/M_is_silicon = FALSE - if(do_silicon_check && istype(M, /mob/living/silicon)) - M_is_silicon = TRUE - if(next_time_allowed[M] > world.time) - if(!queued_to_see[M]) - addtimer(CALLBACK(src, .proc/show_hud_images_after_cooldown, M, M_is_silicon), next_time_allowed[M] - world.time) - queued_to_see[M] = TRUE + + if(!hud_users_all_z_levels[new_viewer]) + hud_users_all_z_levels[new_viewer] = 1 + + RegisterSignal(new_viewer, COMSIG_PARENT_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud users and hud atoms use these signals + + var/turf/their_turf = get_turf(new_viewer) + if(!their_turf) + return + hud_users[their_turf.z][new_viewer] = TRUE + + if(next_time_allowed[new_viewer] > world.time) + if(!queued_to_see[new_viewer]) + addtimer(CALLBACK(src, PROC_REF(show_hud_images_after_cooldown), new_viewer), next_time_allowed[new_viewer] - world.time) + queued_to_see[new_viewer] = TRUE + else - next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN - for(var/atom/A in hudatoms) - if(M_is_silicon) - if(istype(A, /mob)) - var/mob/B = A - if(B.digitalcamo) - continue - add_to_single_hud(M, A) + next_time_allowed[new_viewer] = world.time + ADD_HUD_TO_COOLDOWN + for(var/atom/hud_atom_to_add as anything in get_hud_atoms_for_z_level(their_turf.z)) + add_atom_to_single_mob_hud(new_viewer, hud_atom_to_add) else - hudusers[M]++ // the hell does this do? + hud_users_all_z_levels[new_viewer] += 1 //increment the number of times this hud has been added to this hud user -/datum/atom_hud/proc/unregister_mob(datum/source, force) - SIGNAL_HANDLER - remove_hud_from(source, TRUE) - -/datum/atom_hud/proc/show_hud_images_after_cooldown(M, M_is_silicon) - if(queued_to_see[M]) - queued_to_see -= M - next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN - for(var/atom/A in hudatoms) - if(M_is_silicon) - if(istype(A, /mob)) - var/mob/B = A - if(B.digitalcamo) - continue - add_to_single_hud(M, A) - -/datum/atom_hud/proc/add_to_hud(atom/A) // something new starts existing or is in a need of a HUD - if(!A) +///Hides the images in this hud from former_viewer +///If absolute is set to true, this will forcefully remove the hud, even if sources in theory remain +/datum/atom_hud/proc/hide_from(mob/former_viewer, absolute = FALSE) + if(!former_viewer || !hud_users_all_z_levels[former_viewer]) + return + + hud_users_all_z_levels[former_viewer] -= 1//decrement number of sources for this hud on this user (bad way to track i know) + + if (absolute || hud_users_all_z_levels[former_viewer] <= 0)//if forced or there arent any sources left, remove the user + + if(!hud_atoms_all_z_levels[former_viewer])//make sure we arent unregistering changes on a mob thats also a hud atom for this hud + UnregisterSignal(former_viewer, COMSIG_PARENT_QDELETING) + + hud_users_all_z_levels -= former_viewer + + if(next_time_allowed[former_viewer]) + next_time_allowed -= former_viewer + + var/turf/their_turf = get_turf(former_viewer) + if(their_turf) + hud_users[their_turf.z] -= former_viewer + + if(queued_to_see[former_viewer]) + queued_to_see -= former_viewer + else if (their_turf) + for(var/atom/hud_atom as anything in get_hud_atoms_for_z_level(their_turf.z)) + remove_atom_from_single_hud(former_viewer, hud_atom) + +/// add new_hud_atom to this hud +/datum/atom_hud/proc/add_atom_to_hud(atom/new_hud_atom) + if(!new_hud_atom) return FALSE - hudatoms |= A - var/has_camo = FALSE - if(do_silicon_check && istype(A, /mob)) - var/mob/B = A - if(B.digitalcamo) - has_camo = TRUE - for(var/mob/M in hudusers) - if(has_camo) - if(istype(M, /mob/living/silicon)) - continue - if(!queued_to_see[M]) - add_to_single_hud(M, A) + + // No matter where or who you are, you matter to me :) + RegisterSignal(new_hud_atom, COMSIG_PARENT_QDELETING, PROC_REF(unregister_atom), override = TRUE) //both hud atoms and hud users use these signals + hud_atoms_all_z_levels[new_hud_atom] = TRUE + + var/turf/atom_turf = get_turf(new_hud_atom) + if(!atom_turf) + return TRUE + + hud_atoms[atom_turf.z] |= new_hud_atom + + for(var/mob/mob_to_show as anything in get_hud_users_for_z_level(atom_turf.z)) + if(!queued_to_see[mob_to_show]) + add_atom_to_single_mob_hud(mob_to_show, new_hud_atom) + return TRUE + +/// remove this atom from this hud completely +/datum/atom_hud/proc/remove_atom_from_hud(atom/hud_atom_to_remove) + if(!hud_atom_to_remove || !hud_atoms_all_z_levels[hud_atom_to_remove]) + return FALSE + + //make sure we arent unregistering a hud atom thats also a hud user mob + if(!hud_users_all_z_levels[hud_atom_to_remove]) + UnregisterSignal(hud_atom_to_remove, COMSIG_PARENT_QDELETING) + + for(var/mob/mob_to_remove as anything in hud_users_all_z_levels) + remove_atom_from_single_hud(mob_to_remove, hud_atom_to_remove) + + hud_atoms_all_z_levels -= hud_atom_to_remove + + var/turf/atom_turf = get_turf(hud_atom_to_remove) + if(!atom_turf) + return TRUE + + hud_atoms[atom_turf.z] -= hud_atom_to_remove + + return TRUE + +///adds a newly active hud category's image on a hud atom to every mob that could see it +/datum/atom_hud/proc/add_single_hud_category_on_atom(atom/hud_atom, hud_category_to_add) + if(!hud_atom?.active_hud_list?[hud_category_to_add] || QDELING(hud_atom) || !(hud_category_to_add in hud_icons)) + return FALSE + + if(!hud_atoms_all_z_levels[hud_atom]) + add_atom_to_hud(hud_atom) + return TRUE + + var/turf/atom_turf = get_turf(hud_atom) + if(!atom_turf) + return FALSE + + for(var/mob/hud_user as anything in get_hud_users_for_z_level(atom_turf.z)) + if(!hud_user.client) + continue + if(!hud_exceptions[hud_user] || !(hud_atom in hud_exceptions[hud_user])) + hud_user.client.images |= hud_atom.active_hud_list[hud_category_to_add] + return TRUE -/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client this is used to show hud A to mob M - if(!M || !M.client || !A) +///removes the image or images in hud_atom.hud_list[hud_category_to_remove] from every mob that can see it but leaves every other image +///from that atom there. +/datum/atom_hud/proc/remove_single_hud_category_on_atom(atom/hud_atom, hud_category_to_remove) + if(QDELETED(hud_atom) || !(hud_category_to_remove in hud_icons) || !hud_atoms_all_z_levels[hud_atom]) + return FALSE + + if(!hud_atom.active_hud_list) + remove_atom_from_hud(hud_atom) + return TRUE + + var/turf/atom_turf = get_turf(hud_atom) + if(!atom_turf) + return FALSE + + for(var/mob/hud_user as anything in get_hud_users_for_z_level(atom_turf.z)) + if(!hud_user.client) + continue + hud_user.client.images -= hud_atom.active_hud_list[hud_category_to_remove]//by this point it shouldnt be in active_hud_list + + return TRUE + +///when a hud atom or hud user changes z levels this makes sure it gets the images it needs and removes the images it doesnt need. +///because of how signals work we need the same proc to handle both use cases because being a hud atom and being a hud user arent mutually exclusive +/datum/atom_hud/proc/on_atom_or_user_z_level_changed(atom/movable/moved_atom, turf/old_turf, turf/new_turf) + SIGNAL_HANDLER + if(old_turf) + if(hud_users_all_z_levels[moved_atom]) + hud_users[old_turf.z] -= moved_atom + + remove_all_atoms_from_single_hud(moved_atom, get_hud_atoms_for_z_level(old_turf.z)) + + if(hud_atoms_all_z_levels[moved_atom]) + hud_atoms[old_turf.z] -= moved_atom + + //this wont include moved_atom since its removed + remove_atom_from_all_huds(get_hud_users_for_z_level(old_turf.z), moved_atom) + + if(new_turf) + if(hud_users_all_z_levels[moved_atom]) + hud_users[new_turf.z][moved_atom] = TRUE //hud users is associative, hud atoms isnt + + add_all_atoms_to_single_mob_hud(moved_atom, get_hud_atoms_for_z_level(new_turf.z)) + + if(hud_atoms_all_z_levels[moved_atom]) + hud_atoms[new_turf.z] |= moved_atom + + add_atom_to_all_mob_huds(get_hud_users_for_z_level(new_turf.z), moved_atom) + +/// add just hud_atom's hud images (that are part of this atom_hud) to requesting_mob's client.images list +/datum/atom_hud/proc/add_atom_to_single_mob_hud(mob/requesting_mob, atom/hud_atom) //unsafe, no sanity apart from client + if(!requesting_mob || !requesting_mob.client || !hud_atom) + return + + for(var/hud_category in (hud_icons & hud_atom.active_hud_list)) + if(!hud_exceptions[requesting_mob] || !(hud_atom in hud_exceptions[requesting_mob])) + requesting_mob.client.images |= hud_atom.active_hud_list[hud_category] + +/// all passed in hud_atoms's hud images (that are part of this atom_hud) to requesting_mob's client.images list +/// optimization of [/datum/atom_hud/proc/add_atom_to_single_mob_hud] for hot cases, we assert that no nulls will be passed in via the list +/datum/atom_hud/proc/add_all_atoms_to_single_mob_hud(mob/requesting_mob, list/atom/hud_atoms) //unsafe, no sanity apart from client + if(!requesting_mob || !requesting_mob.client) + return + + // Hud entries this mob ignores + var/list/mob_exceptions = hud_exceptions[requesting_mob] + + for(var/hud_category in hud_icons) + for(var/atom/hud_atom as anything in hud_atoms) + if(mob_exceptions && (hud_atom in hud_exceptions[requesting_mob])) + continue + var/image/output = hud_atom.active_hud_list?[hud_category] + // byond throws a fit if you try to add null to the images list + if(!output) + continue + requesting_mob.client.images |= output + +/// add just hud_atom's hud images (that are part of this atom_hud) to all the requesting_mobs's client.images list +/// optimization of [/datum/atom_hud/proc/add_atom_to_single_mob_hud] for hot cases, we assert that no nulls will be passed in via the list +/datum/atom_hud/proc/add_atom_to_all_mob_huds(list/mob/requesting_mobs, atom/hud_atom) //unsafe, no sanity apart from client + if(!hud_atom?.active_hud_list) + return + + var/list/images_to_add = list() + for(var/hud_category in (hud_icons & hud_atom.active_hud_list)) + images_to_add |= hud_atom.active_hud_list[hud_category] + + // Cache for sonic speed, lists are structs + var/list/exceptions = hud_exceptions + for(var/mob/requesting_mob as anything in requesting_mobs) + if(!requesting_mob.client) + continue + if(!exceptions[requesting_mob] || !(hud_atom in exceptions[requesting_mob])) + requesting_mob.client.images |= images_to_add + +/// remove every hud image for this hud on atom_to_remove from client_mob's client.images list +/datum/atom_hud/proc/remove_atom_from_single_hud(mob/client_mob, atom/atom_to_remove) + if(!client_mob || !client_mob.client || !atom_to_remove?.active_hud_list) + return + for(var/hud_image in hud_icons) + client_mob.client.images -= atom_to_remove.active_hud_list[hud_image] + +/// remove every hud image for this hud pulled from atoms_to_remove from client_mob's client.images list +/// optimization of [/datum/atom_hud/proc/remove_atom_from_single_hud] for hot cases, we assert that no nulls will be passed in via the list +/datum/atom_hud/proc/remove_all_atoms_from_single_hud(mob/client_mob, list/atom/atoms_to_remove) + if(!client_mob || !client_mob.client) + return + for(var/hud_image in hud_icons) + for(var/atom/atom_to_remove as anything in atoms_to_remove) + client_mob.client.images -= atom_to_remove.active_hud_list?[hud_image] + +/// remove every hud image for this hud on atom_to_remove from client_mobs's client.images list +/// optimization of [/datum/atom_hud/proc/remove_atom_from_single_hud] for hot cases, we assert that no nulls will be passed in via the list +/datum/atom_hud/proc/remove_atom_from_all_huds(list/mob/client_mobs, atom/atom_to_remove) + if(!atom_to_remove?.active_hud_list) + return + + var/list/images_to_remove = list() + for(var/hud_image in hud_icons) + images_to_remove |= atom_to_remove.active_hud_list[hud_image] + + for(var/mob/client_mob as anything in client_mobs) + if(!client_mob.client) + continue + client_mob.client.images -= images_to_remove + +/datum/atom_hud/proc/unregister_atom(datum/source, force) + SIGNAL_HANDLER + hide_from(source, TRUE) + remove_atom_from_hud(source) + +/datum/atom_hud/proc/hide_single_atomhud_from(mob/hud_user, atom/hidden_atom) + + if(hud_users_all_z_levels[hud_user]) + remove_atom_from_single_hud(hud_user, hidden_atom) + + if(!hud_exceptions[hud_user]) + hud_exceptions[hud_user] = list(hidden_atom) + else + hud_exceptions[hud_user] += hidden_atom + +/datum/atom_hud/proc/unhide_single_atomhud_from(mob/hud_user, atom/hidden_atom) + hud_exceptions[hud_user] -= hidden_atom + + var/turf/hud_atom_turf = get_turf(hidden_atom) + + if(!hud_atom_turf) return - for(var/i in hud_icons) - if(A.hud_list[i]) - M.client.images |= A.hud_list[i] + + if(hud_users[hud_atom_turf.z][hud_user]) + add_atom_to_single_mob_hud(hud_user, hidden_atom) + +/datum/atom_hud/proc/show_hud_images_after_cooldown(mob/queued_hud_user) + if(!queued_to_see[queued_hud_user]) + return + + queued_to_see -= queued_hud_user + next_time_allowed[queued_hud_user] = world.time + ADD_HUD_TO_COOLDOWN + + var/turf/user_turf = get_turf(queued_hud_user) + if(!user_turf) + return + + for(var/atom/hud_atom_to_show as anything in get_hud_atoms_for_z_level(user_turf.z)) + add_atom_to_single_mob_hud(queued_hud_user, hud_atom_to_show) //MOB PROCS -/mob/proc/reload_huds() // used when you need to readd all aplicable huds to user (for instance on reconnect) +/mob/proc/reload_huds() + var/turf/our_turf = get_turf(src) + if(!our_turf) + return + for(var/datum/atom_hud/hud in GLOB.all_huds) - if(hud && hud.hudusers[src]) - for(var/atom/A in hud.hudatoms) - hud.add_to_single_hud(src, A) + if(hud?.hud_users_all_z_levels[src]) + for(var/atom/hud_atom as anything in hud.get_hud_atoms_for_z_level(our_turf.z)) + hud.add_atom_to_single_mob_hud(src, hud_atom) /mob/dead/new_player/reload_huds() return diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm index de1e704a73d3..05e5fa7a3fd2 100644 --- a/code/datums/martial/cqc.dm +++ b/code/datums/martial/cqc.dm @@ -200,7 +200,7 @@ if(check_streak(A,D)) //if a combo is made no grab upgrade is done return TRUE if(D.grabbedby(A)) - D.Stun(15) + D.Stun(1.5 SECONDS) if(A.grab_state < 1) restraining = FALSE return TRUE @@ -252,7 +252,7 @@ playsound(get_turf(D), 'sound/weapons/cqchit1.ogg', 50, 1, -1) if(I && D.temporarilyRemoveItemFromInventory(I)) A.put_in_hands(I) - D.Jitter(2) + D.adjust_jitter(2 SECONDS) D.apply_damage(A.get_punchdamagehigh()/2, STAMINA) //5 damage else D.visible_message(span_danger("[A] grabs at [D]'s arm, but misses!"), \ diff --git a/code/datums/martial/flying_fang.dm b/code/datums/martial/flying_fang.dm index 47f8921b6bbf..aecde50d16eb 100644 --- a/code/datums/martial/flying_fang.dm +++ b/code/datums/martial/flying_fang.dm @@ -159,7 +159,7 @@ button_icon_state = "lizard_tackle" background_icon_state = "bg_default" desc = "Prepare to jump at a target, with a successful hit stunning them and preventing you from moving for a few seconds." - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_LYING | AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_LYING | AB_CHECK_CONSCIOUS var/datum/martial_art/flyingfang/linked_martial /datum/action/innate/lizard_leap/New() @@ -171,7 +171,7 @@ return ..() /datum/action/innate/lizard_leap/process() - UpdateButtonIcon() //keep the button updated + UpdateButtons() //keep the button updated /datum/action/innate/lizard_leap/IsAvailable() . = ..() @@ -200,12 +200,12 @@ A.Immobilize(30 SECONDS) //prevents you from breaking out of your pounce A.throw_at(target, get_dist(A,target)+1, 1, A, FALSE, TRUE, callback = CALLBACK(src, .proc/leap_end, A)) Deactivate() - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/lizard_leap/proc/leap_end(mob/living/carbon/human/A) A.SetImmobilized(1 SECONDS) linked_martial.leaping = FALSE - UpdateButtonIcon() + UpdateButtons() /datum/martial_art/flyingfang/handle_throw(atom/hit_atom, mob/living/carbon/human/A) if(!leaping) @@ -231,7 +231,7 @@ A.Paralyze(6 SECONDS, 1) if(leaping) leaping = FALSE - linked_leap.UpdateButtonIcon() + linked_leap.UpdateButtons() linked_leap.Deactivate(TRUE) return TRUE diff --git a/code/datums/martial/hunterfu.dm b/code/datums/martial/hunterfu.dm index 04cb6687a31d..e5ad59ae96ac 100644 --- a/code/datums/martial/hunterfu.dm +++ b/code/datums/martial/hunterfu.dm @@ -1,7 +1,7 @@ #define BODYSLAM_COMBO "GH" -#define STAKESTAB_COMBO "HH" +#define STAKESTAB_COMBO "HDH" #define NECKSNAP_COMBO "GDH" -#define HOLYKICK_COMBO "DG" +#define HOLYKICK_COMBO "DHG" // From CQC.dm /datum/martial_art/hunterfu @@ -206,7 +206,7 @@ D.grabbedby(A, 1) if(old_grab_state == GRAB_PASSIVE) D.drop_all_held_items() - A.grab_state = GRAB_AGGRESSIVE // Instant agressive grab + A.setGrabState(GRAB_AGGRESSIVE) // Instant agressive grab log_combat(A, D, "grabbed (Hunter-Fu)") D.visible_message( span_warning("[A] violently grabs [D]!"), @@ -224,8 +224,8 @@ to_chat(usr, span_notice("You try to remember some of the basics of Hunter-Fu.")) to_chat(usr, span_notice("Body Slam: Grab Harm. Slam opponent into the ground, knocking you both down.")) - to_chat(usr, span_notice("Stake Stab: Harm Harm. Stabs opponent with your bare fist, as strong as a Stake.")) + to_chat(usr, span_notice("Stake Stab: Harm Disarm Harm. Stabs opponent with your bare fist, as strong as a Stake.")) to_chat(usr, span_notice("Neck Snap: Grab Disarm Harm. Snaps an opponents neck, knocking them out.")) - to_chat(usr, span_notice("Holy Kick: Disarm Grab. Splashes the user with Holy Water, removing Cult Spells, while dealing stamina damage.")) + to_chat(usr, span_notice("Holy Kick: Disarm Harm Grab. Splashes the user with Holy Water, removing Cult Spells, while dealing stamina damage.")) to_chat(usr, span_notice("In addition, by having your throw mode on, you take a defensive position, allowing you to block and sometimes even counter attacks done to you.")) diff --git a/code/datums/martial/plasma_fist.dm b/code/datums/martial/plasma_fist.dm index 2f9f1c61a88a..4f58ace634e6 100644 --- a/code/datums/martial/plasma_fist.dm +++ b/code/datums/martial/plasma_fist.dm @@ -35,11 +35,11 @@ /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) - var/list/turfs = list() - for(var/turf/T in range(1,A)) - turfs.Add(T) - R.cast(turfs) + + var/datum/action/cooldown/spell/aoe/repulse/tornado_spell = new(src) + tornado_spell.cast(A) + qdel(tornado_spell) + log_combat(A, D, "tornado sweeped(Plasma Fist)") return diff --git a/code/datums/martial/psychotic_brawl.dm b/code/datums/martial/psychotic_brawl.dm index 0b30bb3fc457..2fab944781cf 100644 --- a/code/datums/martial/psychotic_brawl.dm +++ b/code/datums/martial/psychotic_brawl.dm @@ -41,10 +41,10 @@ log_combat(A, D, "grabbed", addition="aggressively") D.visible_message(span_warning("[A] violently grabs [D]!"), \ span_userdanger("[A] violently grabs you!")) - A.grab_state = GRAB_AGGRESSIVE //Instant aggressive grab + A.setGrabState(GRAB_AGGRESSIVE) //Instant aggressive grab else log_combat(A, D, "grabbed", addition="passively") - A.grab_state = GRAB_PASSIVE + A.setGrabState(GRAB_PASSIVE) if(4) A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) atk_verb = "headbutts" diff --git a/code/datums/martial/sleeping_carp.dm b/code/datums/martial/sleeping_carp.dm index 34ed8ba8a36c..0f303e307b86 100644 --- a/code/datums/martial/sleeping_carp.dm +++ b/code/datums/martial/sleeping_carp.dm @@ -118,7 +118,7 @@ D.grabbedby(A, 1) if(old_grab_state == GRAB_PASSIVE) D.drop_all_held_items() - A.grab_state = GRAB_AGGRESSIVE //Instant agressive grab if on grab intent + A.setGrabState(GRAB_AGGRESSIVE) //Instant agressive grab if on grab intent log_combat(A, D, "grabbed", addition="aggressively") D.visible_message(span_warning("[A] violently grabs [D]!"), \ span_userdanger("[A] violently grabs you!")) diff --git a/code/datums/martial/wrestling.dm b/code/datums/martial/wrestling.dm index 6c958038a3ab..6fd3fcddd16e 100644 --- a/code/datums/martial/wrestling.dm +++ b/code/datums/martial/wrestling.dm @@ -440,7 +440,7 @@ A.start_pulling(D) D.visible_message(span_danger("[A] gets [D] in a cinch!"), \ span_userdanger("[A] gets [D] in a cinch!")) - D.Stun(rand(60,100)) + D.Stun(rand(6, 10) SECONDS) log_combat(A, D, "cinched") return 1 diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 737800201d60..518b3bb1fccb 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -45,21 +45,20 @@ var/list/restricted_roles = list() var/list/datum/objective/objectives = list() - var/list/spell_list = list() // Wizard mode & "Give Spell" badmin button. - var/linglink var/datum/martial_art/martial_art var/static/default_martial_art = new/datum/martial_art var/miming = FALSE // Mime's vow of silence var/list/antag_datums var/antag_hud_icon_state = null //this mind's ANTAG_HUD should have this icon_state - var/datum/atom_hud/antag/antag_hud = null //this mind's antag HUD + var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud = null //this mind's antag HUD var/damnation_type = 0 var/datum/mind/soulOwner //who owns the soul. Under normal circumstances, this will point to src var/hasSoul = TRUE // If false, renders the character unable to sell their soul. var/holy_role = NONE //is this person a chaplain or admin role allowed to use bibles, Any rank besides 'NONE' allows for this. - var/mob/living/enslaved_to //If this mind's master is another mob (i.e. adamantine golems) + ///If this mind's master is another mob (i.e. adamantine golems). Weakref of a /living. + var/datum/weakref/enslaved_to var/datum/language_holder/language_holder var/unconvertable = FALSE var/late_joiner = FALSE @@ -138,12 +137,13 @@ if(new_character.mind) //disassociate any mind currently in our new body's mind variable new_character.mind.current = null - var/datum/atom_hud/antag/hud_to_transfer = antag_hud//we need this because leave_hud() will clear this list var/mob/living/old_current = current if(current) current.transfer_observers_to(new_character) //transfer anyone observing the old character to the new one current = new_character //associate ourself with our new body + QDEL_NULL(antag_hud) new_character.mind = src //and associate our new body with ourself + antag_hud = new_character.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/antagonist_hud, "combo_hud", src) 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) @@ -155,8 +155,6 @@ var/mob/living/carbon/human/H = C H.AddComponent(/datum/component/mood) // Yogs End - transfer_antag_huds(hud_to_transfer) //inherit the antag HUD - transfer_actions(new_character) transfer_martial_arts(new_character) transfer_parasites() RegisterSignal(new_character, COMSIG_GLOB_MOB_DEATH, .proc/set_death_time) @@ -252,7 +250,6 @@ /datum/mind/proc/remove_brother() if(src in SSticker.mode.brothers) remove_antag_datum(/datum/antagonist/brother) - SSticker.mode.update_brother_icons_removed(src) /datum/mind/proc/remove_nukeop() var/datum/antagonist/nukeop/nuke = has_antag_datum(/datum/antagonist/nukeop,TRUE) @@ -291,7 +288,6 @@ remove_wizard() remove_cultist() remove_rev() - SSticker.mode.update_cult_icons_removed(src) /datum/mind/proc/equip_traitor(employer = "The Syndicate", silent = FALSE, datum/antagonist/uplink_owner) if(!current) @@ -394,7 +390,7 @@ add_antag_datum(N,converter.nuke_team) - enslaved_to = creator + enslaved_to = WEAKREF(creator) current.faction |= creator.faction creator.faction |= current.faction @@ -706,30 +702,9 @@ add_antag_datum(head) special_role = ROLE_REV_HEAD -/datum/mind/proc/AddSpell(obj/effect/proc_holder/spell/S) - spell_list += S - S.action.Grant(current) - S.on_gain(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 - S.on_lose(current) - qdel(S) - current?.client << output(null, "statbrowser:check_spells") - -/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 @@ -739,12 +714,6 @@ 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) - //Check if there is a specific spell in mind /datum/mind/proc/CheckSpell(var/obj/effect/proc_holder/spell/spell) if(!spell) return @@ -754,22 +723,6 @@ return TRUE return FALSE -/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) - S.on_gain(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, /obj/effect/proc_holder/spell.proc/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/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm index 5a6a18837802..776fb7454288 100644 --- a/code/datums/mood_events/generic_negative_events.dm +++ b/code/datums/mood_events/generic_negative_events.dm @@ -30,7 +30,7 @@ /datum/mood_event/creampie description = "I've been creamed. Tastes like pie flavor.\n" mood_change = -2 - timeout = 1800 + timeout = 3 MINUTES /datum/mood_event/slipped description = "I slipped. I should be more careful next time...\n" diff --git a/code/datums/mutations.dm b/code/datums/mutations.dm index e505ef838e28..c31abed39f8e 100644 --- a/code/datums/mutations.dm +++ b/code/datums/mutations.dm @@ -12,7 +12,8 @@ var/text_gain_indication = "" var/text_lose_indication = "" var/static/list/list/mutable_appearance/visual_indicators = list() - var/obj/effect/proc_holder/spell/power + /// The path of action we grant to our user on mutation gain + var/datum/action/cooldown/spell/power_path var/layer_used = MUTATIONS_LAYER //which mutation layer to use var/list/species_allowed = list() //to restrict mutation to only certain species var/health_req //minimum health required to acquire the mutation @@ -81,9 +82,9 @@ 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) - addtimer(CALLBACK(src, .proc/modify, 5)) //gonna want children calling ..() to run first + addtimer(CALLBACK(src, PROC_REF(modify), 0.5 SECONDS)) //gonna want children calling ..() to run first /datum/mutation/human/proc/get_visual_indicator() return @@ -109,8 +110,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) return 0 return 1 diff --git a/code/datums/mutations/actions.dm b/code/datums/mutations/actions.dm deleted file mode 100644 index a2bf1dc56b4a..000000000000 --- a/code/datums/mutations/actions.dm +++ /dev/null @@ -1,110 +0,0 @@ -/datum/mutation/human/telepathy - name = "Telepathy" - desc = "A rare mutation that allows the user to telepathically communicate to others." - quality = POSITIVE - text_gain_indication = span_notice("You can hear your own voice echoing in your mind!") - text_lose_indication = span_notice("You don't hear your mind echo anymore.") - difficulty = 12 - power = /obj/effect/proc_holder/spell/targeted/telepathy - instability = 10 - energy_coeff = 1 - -/datum/mutation/human/firebreath - name = "Fire Breath" - desc = "An ancient mutation that gives lizards breath of fire." - quality = POSITIVE - difficulty = 12 - locked = TRUE - text_gain_indication = span_notice("Your throat is burning!") - text_lose_indication = span_notice("Your throat is cooling down.") - power = /obj/effect/proc_holder/spell/aimed/firebreath - instability = 30 - energy_coeff = 1 - power_coeff = 1 - conflicts = list(/datum/mutation/human/cryokinesis) - -/datum/mutation/human/firebreath/modify() - if(power) - var/obj/effect/proc_holder/spell/aimed/firebreath/S = power - S.strength = min(GET_MUTATION_POWER(src), S.strengthMax) - -/obj/effect/proc_holder/spell/aimed/firebreath - name = "Fire Breath" - desc = "You can breathe fire at a target." - school = "evocation" - charge_max = 450 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 20 - projectile_type = /obj/item/projectile/magic/aoe/fireball/firebreath - base_icon_state = "fireball" - action_icon_state = "fireball0" - sound = 'sound/magic/demon_dies.ogg' //horrifying lizard noises - active_msg = "You build up heat in your mouth." - deactive_msg = "You swallow the flame." - var/strength = 1 - var/const/strengthMax = 3 - -/obj/effect/proc_holder/spell/aimed/firebreath/before_cast(list/targets) - . = ..() - if(iscarbon(usr)) - var/mob/living/carbon/C = usr - if(C.is_mouth_covered()) - C.adjust_fire_stacks(2) - C.IgniteMob() - to_chat(C,span_warning("Something in front of your mouth caught fire!")) - return FALSE - -/obj/effect/proc_holder/spell/aimed/firebreath/ready_projectile(obj/item/projectile/P, atom/target, mob/user, iteration) - if(!istype(P, /obj/item/projectile/magic/aoe/fireball)) - return - var/obj/item/projectile/magic/aoe/fireball/F = P - F.exp_fire += (strength - 1) * 4 // +0, +2 if empowered - -obj/effect/proc_holder/spell/aimed/firebreath/fire_projectile(mob/user) - . = ..() - message_admins("[ADMIN_LOOKUPFLW(user)] has shot firebreath at [ADMIN_VERBOSEJMP(user)]") - -/obj/item/projectile/magic/aoe/fireball/firebreath - name = "fire breath" - exp_heavy = 0 - exp_light = 0 - exp_flash = 0 - exp_fire= 3 - -/datum/mutation/human/void - name = "Void Magnet" - desc = "A rare genome that attracts odd forces not usually observed." - quality = MINOR_NEGATIVE //upsides and downsides - text_gain_indication = span_notice("You feel a heavy, dull force just beyond the walls watching you.") - instability = 30 - power = /obj/effect/proc_holder/spell/self/void - energy_coeff = 1 - synchronizer_coeff = 1 - -/datum/mutation/human/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 - owner.apply_status_effect(STATUS_EFFECT_VOIDED) - -/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 - antimagic_allowed = TRUE - charge_max = 600 - invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!" - invocation_type = "shout" - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "void_magnet" - -/obj/effect/proc_holder/spell/self/void/can_cast(mob/living/user = usr) - . = ..() - if(!isturf(user.loc)) - return FALSE - -/obj/effect/proc_holder/spell/self/void/cast(mob/living/user = usr) - . = ..() - user.apply_status_effect(STATUS_EFFECT_VOIDED) diff --git a/code/datums/mutations/antenna.dm b/code/datums/mutations/antenna.dm index 22209cec639e..a211e2ec9748 100644 --- a/code/datums/mutations/antenna.dm +++ b/code/datums/mutations/antenna.dm @@ -34,60 +34,80 @@ quality = POSITIVE text_gain_indication = span_notice("You hear distant voices at the corners of your mind.") text_lose_indication = span_notice("The distant voices fade.") - power = /obj/effect/proc_holder/spell/targeted/mindread + power_path = /datum/action/cooldown/spell/pointed/mindread instability = 40 difficulty = 8 locked = TRUE -/obj/effect/proc_holder/spell/targeted/mindread +/datum/action/cooldown/spell/pointed/mindread name = "Mindread" desc = "Read the target's mind." - charge_max = 50 - range = 7 - clothes_req = FALSE - antimagic_allowed = TRUE - action_icon_state = "mindread" - -/obj/effect/proc_holder/spell/targeted/mindread/cast(list/targets, mob/living/carbon/human/user = usr) - for(var/mob/living/M in targets) - if(usr.anti_magic_check(FALSE, FALSE, TRUE, 0) || M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - to_chat(usr, span_warning("As you reach out with your mind, you're suddenly stopped by a vision of a massive tinfoil wall that stretches beyond visible range. It seems you've been foiled.")) - return - if(M.stat == DEAD) - to_chat(user, span_boldnotice("[M] is dead!")) - return - if(M.mind) - to_chat(user, span_boldnotice("You plunge into [M]'s mind...")) - if(prob(20)) - to_chat(M, span_danger("You feel something foreign enter your mind."))//chance to alert the read-ee - var/list/recent_speech = list() - var/list/say_log = list() - var/log_source = M.logging - for(var/log_type in log_source)//this whole loop puts the read-ee's say logs into say_log in an easy to access way - var/nlog_type = text2num(log_type) - if(nlog_type & LOG_SAY) - var/list/reversed = log_source[log_type] - if(islist(reversed)) - say_log = reverseRange(reversed.Copy()) - break - if(LAZYLEN(say_log)) - for(var/spoken_memory in say_log) - if(recent_speech.len >= 3)//up to 3 random lines of speech, favoring more recent speech - break - if(prob(50)) - recent_speech[spoken_memory] = say_log[spoken_memory] - if(recent_speech.len) - to_chat(user, span_boldnotice("You catch some drifting memories of their past conversations...")) - for(var/spoken_memory in recent_speech) - to_chat(user, span_notice("[recent_speech[spoken_memory]]")) - if(iscarbon(M)) - var/mob/living/carbon/human/H = M - to_chat(user, span_boldnotice("You find that their intent is to [H.a_intent]...")) - var/datum/dna/the_dna = H.has_dna() - if(the_dna) - to_chat(user, span_boldnotice("You uncover that [H.p_their()] true identity is [the_dna.real_name].")) - else - to_chat(user, span_boldnotice("You can't find a mind to read inside of [M].")) + button_icon_state = "mindread" + cooldown_time = 5 SECONDS + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + antimagic_flags = MAGIC_RESISTANCE_MIND + ranged_mousepointer = 'icons/effects/mouse_pointers/mindswap_target.dmi' + +/datum/action/cooldown/spell/pointed/mindread/is_valid_target(atom/cast_on) + if(!isliving(cast_on)) + return FALSE + var/mob/living/living_cast_on = cast_on + if(!living_cast_on.mind) + to_chat(owner, span_warning("[cast_on] has no mind to read!")) + return FALSE + if(living_cast_on.stat == DEAD) + to_chat(owner, span_warning("[cast_on] is dead!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/pointed/mindread/cast(mob/living/cast_on) + . = ..() + if(cast_on.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) + to_chat(owner, span_warning("As you reach into [cast_on]'s mind, \ + you are stopped by a mental blockage. It seems you've been foiled.")) + return + + if(cast_on == owner) + to_chat(owner, span_warning("You plunge into your mind... Yep, it's your mind.")) + return + + to_chat(owner, span_boldnotice("You plunge into [cast_on]'s mind...")) + if(prob(20)) + // chance to alert the read-ee + to_chat(cast_on, span_danger("You feel something foreign enter your mind.")) + + 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) + var/list/reversed = log_source[log_type] + if(islist(reversed)) + say_log = reverse_range(reversed.Copy()) + break + + for(var/spoken_memory in say_log) + //up to 3 random lines of speech, favoring more recent speech + if(length(recent_speech) >= 3) + break + if(prob(50)) + continue + // log messages with tags like telepathy are displayed like "(Telepathy to Ckey/(target)) "greetings""" + // by splitting the text by using a " delimiter, we can grab JUST the greetings part + recent_speech[spoken_memory] = splittext(say_log[spoken_memory], "\"", 1, 0, TRUE)[3] + + if(length(recent_speech)) + to_chat(owner, span_boldnotice("You catch some drifting memories of their past conversations...")) + for(var/spoken_memory in recent_speech) + to_chat(owner, span_notice("[recent_speech[spoken_memory]]")) + + if(iscarbon(cast_on)) + var/mob/living/carbon/carbon_cast_on = cast_on + to_chat(owner, span_boldnotice("You find that their intent is to [carbon_cast_on.a_intent]...")) + to_chat(owner, span_boldnotice("You uncover that [carbon_cast_on.p_their()] true identity is [carbon_cast_on.mind.name].")) /datum/mutation/human/mindreader/New(class_ = MUT_OTHER, timer, datum/mutation/human/copymut) ..() diff --git a/code/datums/mutations/autotomy.dm b/code/datums/mutations/autotomy.dm new file mode 100644 index 000000000000..fbf25ffa7e78 --- /dev/null +++ b/code/datums/mutations/autotomy.dm @@ -0,0 +1,42 @@ +/datum/mutation/human/self_amputation + name = "Autotomy" + desc = "Allows a creature to voluntary discard a random appendage." + quality = POSITIVE + text_gain_indication = span_notice("Your joints feel loose.") + instability = 30 + power_path = /datum/action/cooldown/spell/self_amputation + + energy_coeff = 1 + synchronizer_coeff = 1 + +/datum/action/cooldown/spell/self_amputation + name = "Drop a limb" + desc = "Concentrate to make a random limb pop right off your body." + button_icon_state = "autotomy" + + cooldown_time = 10 SECONDS + spell_requirements = NONE + +/datum/action/cooldown/spell/self_amputation/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/datum/action/cooldown/spell/self_amputation/cast(mob/living/carbon/cast_on) + . = ..() + if(HAS_TRAIT(cast_on, TRAIT_NODISMEMBER)) + to_chat(cast_on, span_notice("You concentrate really hard, but nothing happens.")) + return + + var/list/parts = list() + for(var/obj/item/bodypart/to_remove as anything in cast_on.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(cast_on, span_notice("You can't shed any more limbs!")) + return + + var/obj/item/bodypart/to_remove = pick(parts) + to_remove.dismember() \ No newline at end of file diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index c29716165200..b53a00a11f98 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -13,14 +13,15 @@ if(prob(1 * GET_MUTATION_SYNCHRONIZER(src)) && owner.stat == CONSCIOUS) owner.visible_message(span_danger("[owner] starts having a seizure!"), span_userdanger("You have a seizure!")) owner.Unconscious(200 * GET_MUTATION_POWER(src)) - owner.Jitter(1000 * GET_MUTATION_POWER(src)) + owner.adjust_jitter(1000 * GET_MUTATION_POWER(src)) SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "epilepsy", /datum/mood_event/epilepsy) addtimer(CALLBACK(src, .proc/jitter_less), 90) /datum/mutation/human/epilepsy/proc/jitter_less() - if(owner) - owner.jitteriness = 10 + if(QDELETED(owner)) + return + owner.set_jitter(20 SECONDS) //Unstable DNA induces random mutations! /datum/mutation/human/bad_dna @@ -77,7 +78,7 @@ if(prob(5) && owner.stat == CONSCIOUS) owner.emote("scream") if(prob(25)) - owner.hallucination += 20 + owner.adjust_hallucinations(20 SECONDS) //Dwarfism shrinks your body and lets you pass tables. /datum/mutation/human/dwarfism @@ -205,6 +206,7 @@ glowth = new(owner) modify() +// Override modify here without a parent call, because we don't actually give an action. /datum/mutation/human/glow/modify() if(!glowth) return @@ -321,7 +323,7 @@ /datum/mutation/human/fire/on_life() if(prob((1+(100-dna.stability)/10)) * GET_MUTATION_SYNCHRONIZER(src)) owner.adjust_fire_stacks(2 * GET_MUTATION_POWER(src)) - owner.IgniteMob() + owner.ignite_mob() /datum/mutation/human/fire/on_acquiring(mob/living/carbon/human/owner) if(..()) diff --git a/code/datums/mutations/cold.dm b/code/datums/mutations/cold.dm index 11bdf0d75898..ae870aed4cfd 100644 --- a/code/datums/mutations/cold.dm +++ b/code/datums/mutations/cold.dm @@ -6,17 +6,16 @@ instability = 10 difficulty = 10 synchronizer_coeff = 1 - power = /obj/effect/proc_holder/spell/targeted/conjure_item/snow + power_path = /datum/action/cooldown/spell/conjure_item/snow -/obj/effect/proc_holder/spell/targeted/conjure_item/snow +/datum/action/cooldown/spell/conjure_item/snow name = "Create Snow" desc = "Concentrates cryokinetic forces to create snow, useful for snow-like construction." - item_type = /obj/item/stack/sheet/mineral/snow - charge_max = 50 + button_icon_state = "snow" + cooldown_time = 5 SECONDS + spell_requirements = NONE delete_old = FALSE - antimagic_allowed = TRUE - action_icon_state = "snow" - + item_type = /obj/item/stack/sheet/mineral/snow /datum/mutation/human/cryokinesis name = "Cryokinesis" @@ -26,21 +25,18 @@ instability = 20 difficulty = 12 synchronizer_coeff = 1 - power = /obj/effect/proc_holder/spell/aimed/cryo + power_path = /datum/action/cooldown/spell/pointed/projectile/cryo conflicts = list(/datum/mutation/human/firebreath) -/obj/effect/proc_holder/spell/aimed/cryo +/datum/action/cooldown/spell/pointed/projectile/cryo name = "Cryobeam" desc = "This power fires a frozen bolt at a target." - charge_max = 150 - cooldown_min = 150 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 3 - projectile_type = /obj/item/projectile/temp/cryo + button_icon_state = "icebeam0" + cooldown_time = 15 SECONDS + spell_requirements = NONE + antimagic_flags = NONE base_icon_state = "icebeam" - action_icon_state = "icebeam" active_msg = "You focus your cryokinesis!" deactive_msg = "You relax." - active = FALSE + projectile_type = /obj/item/projectile/temp/cryo diff --git a/code/datums/mutations/fire_breath.dm b/code/datums/mutations/fire_breath.dm new file mode 100644 index 000000000000..88b1b0681347 --- /dev/null +++ b/code/datums/mutations/fire_breath.dm @@ -0,0 +1,96 @@ +/datum/mutation/human/firebreath + name = "Fire Breath" + desc = "An ancient mutation that gives lizards breath of fire." + quality = POSITIVE + difficulty = 12 + locked = TRUE + text_gain_indication = span_notice("Your throat is burning!") + text_lose_indication = span_notice("Your throat is cooling down.") + power_path = /datum/action/cooldown/spell/cone/staggered/fire_breath + instability = 30 + energy_coeff = 1 + power_coeff = 1 + +/datum/mutation/human/firebreath/modify() + . = ..() + var/datum/action/cooldown/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/cooldown/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 + + 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/cooldown/spell/cone/staggered/fire_breath/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + if(!iscarbon(cast_on)) + return + + var/mob/living/carbon/our_lizard = cast_on + if(!our_lizard.is_mouth_covered()) + return + + our_lizard.adjust_fire_stacks(cone_levels) + our_lizard.ignite_mob() + to_chat(our_lizard, span_warning("Something in front of your mouth catches fire!")) + +/datum/action/cooldown/spell/cone/staggered/fire_breath/after_cast(atom/cast_on) + . = ..() + if(!isliving(cast_on)) + return + + var/mob/living/living_cast_on = cast_on + // 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, + gentle = TRUE, + ) + // Try to set us to our original direction after, so we don't end up backwards. + living_cast_on.setDir(original_dir) + +/datum/action/cooldown/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/cooldown/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/cooldown/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, spread_damage = TRUE) + target_mob.adjust_fire_stacks(max(2, 5 - level)) + target_mob.ignite_mob() + +/datum/action/cooldown/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))) \ No newline at end of file diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm index 2ed13b6395be..105faf2f9445 100644 --- a/code/datums/mutations/hulk.dm +++ b/code/datums/mutations/hulk.dm @@ -102,7 +102,7 @@ /datum/mutation/human/active_hulk/on_attack_hand(atom/target, proximity) if(proximity) //no telekinetic hulk attack if(prob(3)) - owner.Jitter(10) + owner.adjust_jitter(10 SECONDS) owner.adjustStaminaLoss(-0.5) return target.attack_hulk(owner) diff --git a/code/datums/mutations/speech.dm b/code/datums/mutations/speech.dm index 23ed99c1a928..2ac94265a7c5 100644 --- a/code/datums/mutations/speech.dm +++ b/code/datums/mutations/speech.dm @@ -9,7 +9,7 @@ /datum/mutation/human/nervousness/on_life() if(prob(10)) - owner.stuttering = max(10, owner.stuttering) + owner.set_stutter_if_lower(20 SECONDS) /datum/mutation/human/wacky diff --git a/code/datums/mutations/telepathy.dm b/code/datums/mutations/telepathy.dm new file mode 100644 index 000000000000..de01fff9fb30 --- /dev/null +++ b/code/datums/mutations/telepathy.dm @@ -0,0 +1,10 @@ +/datum/mutation/human/telepathy + name = "Telepathy" + desc = "A rare mutation that allows the user to telepathically communicate to others." + quality = POSITIVE + text_gain_indication = span_notice("You can hear your own voice echoing in your mind!") + text_lose_indication = span_notice("You don't hear your mind echo anymore.") + difficulty = 12 + power_path = /datum/action/cooldown/spell/list_target/telepathy + instability = 10 + energy_coeff = 1 \ No newline at end of file diff --git a/code/datums/mutations/touch.dm b/code/datums/mutations/touch.dm index a0d57e7a29eb..b5ae2ff6b304 100644 --- a/code/datums/mutations/touch.dm +++ b/code/datums/mutations/touch.dm @@ -6,28 +6,54 @@ difficulty = 16 text_gain_indication = span_notice("You feel power flow through your hands.") text_lose_indication = span_notice("The energy in your hands subsides.") - power = /obj/effect/proc_holder/spell/targeted/touch/shock + power = /datum/action/cooldown/spell/touch/shock instability = 20 locked = TRUE conflicts = list(/datum/mutation/human/shock/far, /datum/mutation/human/insulated) -/obj/effect/proc_holder/spell/targeted/touch/shock +/datum/action/cooldown/spell/touch/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/shock - charge_max = 100 - clothes_req = FALSE - antimagic_allowed = TRUE - icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "zap" + button_icon_state = "zap" + sound = 'sound/weapons/zapbang.ogg' + cooldown_time = 10 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + icon_icon = 'icons/mob/actions/humble/actions_humble.dmi' + draw_message = span_notice("You channel electricity into your hand.") + drop_message = span_notice("You let the electricity from your hand dissipate.") + + +/datum/action/cooldown/spell/touch/shock/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + if(iscarbon(victim)) + var/mob/living/carbon/carbon_victim = victim + if(carbon_victim.electrocute_act(15, caster, 1, stun = FALSE))//doesnt stun. never let this stun + carbon_victim.dropItemToGround(carbon_victim.get_active_held_item()) + carbon_victim.dropItemToGround(carbon_victim.get_inactive_held_item()) + carbon_victim.adjust_timed_status_effect(15 SECONDS, /datum/status_effect/confusion) + carbon_victim.visible_message( + span_danger("[caster] electrocutes [victim]!"), + span_userdanger("[caster] electrocutes you!"), + ) + return TRUE + + else if(isliving(victim)) + var/mob/living/living_victim = victim + if(living_victim.electrocute_act(15, caster, 1, stun = FALSE)) + living_victim.visible_message( + span_danger("[caster] electrocutes [victim]!"), + span_userdanger("[caster] electrocutes you!"), + ) + return TRUE + + to_chat(caster, span_warning("The electricity doesn't seem to affect [victim]...")) + return TRUE /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." - catchphrase = null - on_use_sound = 'sound/weapons/zapbang.ogg' icon_state = "zapper" item_state = "zapper" var/far = FALSE @@ -42,7 +68,7 @@ user.Beam(C, icon_state="red_lightning", time=15) C.dropItemToGround(C.get_active_held_item()) C.dropItemToGround(C.get_inactive_held_item()) - C.confused += 15 + C.adjust_confusion(15 SECONDS) C.visible_message(span_danger("[user] electrocutes [target]!"),span_userdanger("[user] electrocutes you!")) return ..() else diff --git a/code/datums/mutations/touchfar.dm b/code/datums/mutations/touchfar.dm index 655b006ab4d3..12311ebab610 100644 --- a/code/datums/mutations/touchfar.dm +++ b/code/datums/mutations/touchfar.dm @@ -2,11 +2,11 @@ name = "Extended Shock Touch" desc = "The affected can channel excess electricity through their hands without shocking themselves, allowing them to shock others at range." instability = 30 - text_gain_indication = "You feel unlimited power flow through your hands." - power = /obj/effect/proc_holder/spell/targeted/touch/shock/far + text_gain_indication = span_notice("You feel unlimited power flow through your hands.") + power = /datum/action/cooldown/spell/touch/shock/far conflicts = list(/datum/mutation/human/shock, /datum/mutation/human/insulated) -/obj/effect/proc_holder/spell/targeted/touch/shock/far +/datum/action/cooldown/spell/touch/shock/far name = "Extended Shock Touch" hand_path = /obj/item/melee/touch_attack/shock/far diff --git a/code/datums/mutations/void_magnet.dm b/code/datums/mutations/void_magnet.dm new file mode 100644 index 000000000000..fd6e719914e5 --- /dev/null +++ b/code/datums/mutations/void_magnet.dm @@ -0,0 +1,43 @@ +/datum/mutation/human/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/cooldown/spell/void + energy_coeff = 1 + synchronizer_coeff = 1 + +/datum/mutation/human/void/on_life(delta_time, times_fired) + // Move this onto the spell itself at some point? + var/datum/action/cooldown/spell/void/curse = locate(power_path) in owner + if(!curse) + remove() + return + + if(!curse.is_valid_target(owner)) + 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.cast(owner) + +/datum/action/cooldown/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" + + school = SCHOOL_EVOCATION + cooldown_time = 1 MINUTES + + invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!" + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + antimagic_flags = NONE + +/datum/action/cooldown/spell/void/is_valid_target(atom/cast_on) + return isturf(cast_on.loc) + +/datum/action/cooldown/spell/void/cast(atom/cast_on) + . = ..() + new /obj/effect/immortality_talisman/void(get_turf(cast_on), cast_on) \ No newline at end of file diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs/buffs.dm similarity index 97% rename from code/datums/status_effects/buffs.dm rename to code/datums/status_effects/buffs/buffs.dm index 92f150b379de..f4ac5133e730 100644 --- a/code/datums/status_effects/buffs.dm +++ b/code/datums/status_effects/buffs/buffs.dm @@ -382,36 +382,36 @@ STOP_PROCESSING(SSprocessing, src) //Hippocratic Oath: Applied when the Rod of Asclepius is activated. -/datum/status_effect/hippocraticOath +/datum/status_effect/hippocratic_oath id = "Hippocratic Oath" status_type = STATUS_EFFECT_UNIQUE duration = -1 - tick_interval = 25 - examine_text = span_notice("They seem to have an aura of healing and helpfulness about them.") + tick_interval = 2.5 SECONDS alert_type = null + var/hand var/deathTick = 0 var/efficiency = 1 var/rod_type = /obj/item/rod_of_asclepius -/datum/status_effect/hippocraticOath/on_creation(mob/living/new_owner, _efficiency, _rod_type) +/datum/status_effect/hippocratic_oath/on_creation(mob/living/new_owner, _efficiency, _rod_type) efficiency = _efficiency rod_type = _rod_type . = ..() -/datum/status_effect/hippocraticOath/on_apply() +/datum/status_effect/hippocratic_oath/on_apply() //Makes the user passive, it's in their oath not to harm! ADD_TRAIT(owner, TRAIT_PACIFISM, "hippocraticOath") var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - H.add_hud_to(owner) + H.show_to(owner) return ..() -/datum/status_effect/hippocraticOath/on_remove() +/datum/status_effect/hippocratic_oath/on_remove() REMOVE_TRAIT(owner, TRAIT_PACIFISM, "hippocraticOath") var/datum/atom_hud/H = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - H.remove_hud_from(owner) + H.hide_from(owner) -/datum/status_effect/hippocraticOath/tick() +/datum/status_effect/hippocratic_oath/tick() if(owner.stat == DEAD) if(deathTick < 4) deathTick += 1 @@ -488,9 +488,10 @@ status_type = STATUS_EFFECT_REFRESH /datum/status_effect/good_music/tick() - owner.dizziness = max(0, owner.dizziness - 2) - owner.jitteriness = max(0, owner.jitteriness - 2) - owner.confused = max(0, owner.confused - 1) + if(owner.can_hear()) + owner.adjust_dizzy(-4 SECONDS) + owner.adjust_jitter(-4 SECONDS) + owner.adjust_confusion(-1 SECONDS) SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "goodmusic", /datum/mood_event/goodmusic) /atom/movable/screen/alert/status_effect/regenerative_core diff --git a/code/datums/status_effects/debuffs/confusion.dm b/code/datums/status_effects/debuffs/confusion.dm new file mode 100644 index 000000000000..0f8f0ef3187b --- /dev/null +++ b/code/datums/status_effects/debuffs/confusion.dm @@ -0,0 +1,49 @@ +/// The threshold in which all of our movements are fully randomized, in seconds. +#define CONFUSION_FULL_THRESHOLD 40 +/// A multiplier applied on how much time is left (in seconds) that determines the chance of moving sideways randomly +#define CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND 1.5 +/// A multiplier applied on how much time is left (in seconds) that determines the chance of moving diagonally randomly +#define CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND 3 + +/// A status effect used for adding confusion to a mob. +/datum/status_effect/confusion + id = "confusion" + alert_type = null + remove_on_fullheal = TRUE + +/datum/status_effect/confusion/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/confusion/on_apply() + RegisterSignal(owner, COMSIG_MOB_CLIENT_PRE_MOVE, PROC_REF(on_move)) + return TRUE + +/datum/status_effect/confusion/on_remove() + UnregisterSignal(owner, COMSIG_MOB_CLIENT_PRE_MOVE) + +/// Signal proc for [COMSIG_MOB_CLIENT_PRE_MOVE]. We have a chance to mix up our movement pre-move with confusion. +/datum/status_effect/confusion/proc/on_move(datum/source, list/move_args) + SIGNAL_HANDLER + + // How much time is left in the duration, in seconds. + var/time_left = (duration - world.time) / 10 + var/direction = move_args[MOVE_ARG_DIRECTION] + var/new_dir + + if(time_left > CONFUSION_FULL_THRESHOLD) + new_dir = pick(GLOB.alldirs) + + else if(prob(time_left * CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND)) + new_dir = angle2dir(dir2angle(direction) + pick(90, -90)) + + else if(prob(time_left * CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND)) + new_dir = angle2dir(dir2angle(direction) + pick(45, -45)) + + if(!isnull(new_dir)) + move_args[MOVE_ARG_NEW_LOC] = get_step(owner, new_dir) + move_args[MOVE_ARG_DIRECTION] = new_dir + +#undef CONFUSION_FULL_THRESHOLD +#undef CONFUSION_SIDEWAYS_MOVE_PROB_PER_SECOND +#undef CONFUSION_DIAGONAL_MOVE_PROB_PER_SECOND diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs/debuffs.dm similarity index 83% rename from code/datums/status_effects/debuffs.dm rename to code/datums/status_effects/debuffs/debuffs.dm index c176adc015f9..4b58084059ed 100644 --- a/code/datums/status_effects/debuffs.dm +++ b/code/datums/status_effects/debuffs/debuffs.dm @@ -4,78 +4,231 @@ tick_interval = 0 status_type = STATUS_EFFECT_REPLACE alert_type = null + remove_on_fullheal = TRUE + //heal_flag_necessary = HEAL_CC_STATUS var/needs_update_stat = FALSE -/datum/status_effect/incapacitating/on_creation(mob/living/new_owner, set_duration, updating_canmove) +/datum/status_effect/incapacitating/on_creation(mob/living/new_owner, set_duration) if(isnum(set_duration)) duration = set_duration . = ..() - if(.) - if(updating_canmove) - owner.update_mobility() - if(needs_update_stat || issilicon(owner)) - owner.update_stat() + if(. && (needs_update_stat || issilicon(owner))) + owner.update_stat() + /datum/status_effect/incapacitating/on_remove() - owner.update_mobility() if(needs_update_stat || issilicon(owner)) //silicons need stat updates in addition to normal canmove updates owner.update_stat() + return ..() + //STUN /datum/status_effect/incapacitating/stun id = "stun" +/datum/status_effect/incapacitating/stun/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + ADD_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/stun/on_remove() + REMOVE_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) + REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + REMOVE_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id)) + return ..() + + //KNOCKDOWN /datum/status_effect/incapacitating/knockdown id = "knockdown" +/datum/status_effect/incapacitating/knockdown/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_FLOORED, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/knockdown/on_remove() + REMOVE_TRAIT(owner, TRAIT_FLOORED, TRAIT_STATUS_EFFECT(id)) + return ..() + + //IMMOBILIZED /datum/status_effect/incapacitating/immobilized id = "immobilized" +/datum/status_effect/incapacitating/immobilized/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/immobilized/on_remove() + REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + return ..() + + +//PARALYZED /datum/status_effect/incapacitating/paralyzed id = "paralyzed" +/datum/status_effect/incapacitating/paralyzed/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + ADD_TRAIT(owner, TRAIT_FLOORED, TRAIT_STATUS_EFFECT(id)) + ADD_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/paralyzed/on_remove() + REMOVE_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) + REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + REMOVE_TRAIT(owner, TRAIT_FLOORED, TRAIT_STATUS_EFFECT(id)) + REMOVE_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id)) + return ..() + +//INCAPACITATED +/// This status effect represents anything that leaves a character unable to perform basic tasks (interrupting do-afters, for example), but doesn't incapacitate them further than that (no stuns etc..) +/datum/status_effect/incapacitating/incapacitated + id = "incapacitated" + +// What happens when you get the incapacitated status. You get TRAIT_INCAPACITATED added to you for the duration of the status effect. +/datum/status_effect/incapacitating/incapacitated/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) + +// When the status effect runs out, your TRAIT_INCAPACITATED is removed. +/datum/status_effect/incapacitating/incapacitated/on_remove() + REMOVE_TRAIT(owner, TRAIT_INCAPACITATED, TRAIT_STATUS_EFFECT(id)) + return ..() + + //UNCONSCIOUS /datum/status_effect/incapacitating/unconscious id = "unconscious" needs_update_stat = TRUE +/datum/status_effect/incapacitating/unconscious/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/incapacitating/unconscious/on_remove() + REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + return ..() + /datum/status_effect/incapacitating/unconscious/tick() if(owner.getStaminaLoss()) owner.adjustStaminaLoss(-0.3) //reduce stamina loss by 0.3 per tick, 6 per 2 seconds + //SLEEPING /datum/status_effect/incapacitating/sleeping id = "sleeping" alert_type = /atom/movable/screen/alert/status_effect/asleep needs_update_stat = TRUE - var/mob/living/carbon/carbon_owner - var/mob/living/carbon/human/human_owner + tick_interval = 2 SECONDS -/datum/status_effect/incapacitating/sleeping/on_creation(mob/living/new_owner, updating_canmove) +/datum/status_effect/incapacitating/sleeping/on_apply() . = ..() - if(.) - if(iscarbon(owner)) //to avoid repeated istypes - carbon_owner = owner - if(ishuman(owner)) - human_owner = owner - -/datum/status_effect/incapacitating/sleeping/Destroy() - carbon_owner = null - human_owner = null + if(!.) + return + if(!HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE)) + ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + tick_interval = -1 + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_SLEEPIMMUNE), PROC_REF(on_owner_insomniac)) + RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_SLEEPIMMUNE), PROC_REF(on_owner_sleepy)) + +/datum/status_effect/incapacitating/sleeping/on_remove() + UnregisterSignal(owner, list(SIGNAL_ADDTRAIT(TRAIT_SLEEPIMMUNE), SIGNAL_REMOVETRAIT(TRAIT_SLEEPIMMUNE))) + if(!HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE)) + REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + tick_interval = initial(tick_interval) return ..() +///If the mob is sleeping and gain the TRAIT_SLEEPIMMUNE we remove the TRAIT_KNOCKEDOUT and stop the tick() from happening +/datum/status_effect/incapacitating/sleeping/proc/on_owner_insomniac(mob/living/source) + SIGNAL_HANDLER + REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + tick_interval = -1 + +///If the mob has the TRAIT_SLEEPIMMUNE but somehow looses it we make him sleep and restart the tick() +/datum/status_effect/incapacitating/sleeping/proc/on_owner_sleepy(mob/living/source) + SIGNAL_HANDLER + ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + tick_interval = initial(tick_interval) + +#define HEALING_SLEEP_DEFAULT 0.2 + /datum/status_effect/incapacitating/sleeping/tick() - if(owner.getStaminaLoss()) - owner.adjustStaminaLoss(-0.5) //reduce stamina loss by 0.5 per tick, 10 per 2 seconds - if(human_owner && human_owner.drunkenness) - human_owner.drunkenness *= 0.997 //reduce drunkenness by 0.3% per tick, 6% per 2 seconds - if(prob(20)) - if(carbon_owner) - carbon_owner.handle_dreams() - if(prob(10) && owner.health > owner.crit_threshold) - owner.emote("snore") + if(owner.maxHealth) + var/health_ratio = owner.health / owner.maxHealth + var/healing = HEALING_SLEEP_DEFAULT + + // having high spirits helps us recover + var/datum/component/mood/mob_mood = owner.GetComponent(/datum/component/mood) + if(mob_mood) + switch(mob_mood.sanity_level) + if(SANITY_GREAT) + healing = 0.2 + if(SANITY_NEUTRAL) + healing = 0.1 + if(SANITY_DISTURBED) + healing = 0 + if(SANITY_UNSTABLE) + healing = 0 + if(SANITY_CRAZY) + healing = -0.1 + if(SANITY_INSANE) + healing = -0.2 + + var/turf/rest_turf = get_turf(owner) + var/is_sleeping_in_darkness = rest_turf.get_lumcount() <= LIGHTING_TILE_IS_DARK + + // sleeping with a blindfold or in the dark helps us rest + if(is_blind(owner) || is_sleeping_in_darkness) + healing += 0.1 + + // sleeping in silence is always better + if(HAS_TRAIT(owner, TRAIT_DEAF)) + healing += 0.1 + + // check for beds + if((locate(/obj/structure/bed) in owner.loc)) + healing += 0.2 + else if((locate(/obj/structure/table) in owner.loc)) + healing += 0.1 + + // don't forget the bedsheet + for(var/obj/item/bedsheet/bedsheet in range(owner.loc,0)) + if(bedsheet.loc != owner.loc) //bedsheets in your backpack/neck don't give you comfort + continue + healing += 0.1 + break //Only count the first bedsheet + + if(healing > 0 && health_ratio > 0.8) + owner.adjustBruteLoss(-1 * healing, required_bodytype = BODYPART_ORGANIC) + owner.adjustFireLoss(-1 * healing, required_bodytype = BODYPART_ORGANIC) + owner.adjustToxLoss(-1 * healing * 0.5, TRUE, TRUE, required_biotype = MOB_ORGANIC) + owner.adjustStaminaLoss(min(-1 * healing, -1 * HEALING_SLEEP_DEFAULT)) + // Drunkenness gets reduced by 0.3% per tick (6% per 2 seconds) + owner.set_drunk_effect(owner.get_drunk_amount() * 0.997) + + if(iscarbon(owner)) + var/mob/living/carbon/carbon_owner = owner + carbon_owner.handle_dreams() + + if(prob(2) && owner.health > owner.crit_threshold) + owner.emote("snore") + +#undef HEALING_SLEEP_DEFAULT /atom/movable/screen/alert/status_effect/asleep name = "Asleep" @@ -105,16 +258,25 @@ /datum/status_effect/incapacitating/stasis/on_creation(mob/living/new_owner, set_duration, updating_canmove, new_stasis_mod) . = ..() - stasis_mod = new_stasis_mod - new_owner.life_tickrate += stasis_mod - update_time_of_death() - if(stasis_mod == -1) + if(.) + update_time_of_death() + stasis_mod = new_stasis_mod + new_owner.life_tickrate += stasis_mod owner.reagents?.end_metabolization(owner, FALSE) +/datum/status_effect/incapacitating/stasis/on_apply() + . = ..() + if(!.) + return + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + ADD_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id)) + /datum/status_effect/incapacitating/stasis/tick() update_time_of_death() /datum/status_effect/incapacitating/stasis/on_remove() + REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, TRAIT_STATUS_EFFECT(id)) + REMOVE_TRAIT(owner, TRAIT_HANDS_BLOCKED, TRAIT_STATUS_EFFECT(id)) update_time_of_death() owner.life_tickrate -= stasis_mod return ..() @@ -308,14 +470,10 @@ else if(prob(severity * 2)) warned_outofsight = FALSE if(is_servant) //heals servants of braindamage, hallucination, druggy, dizziness, and confusion - if(owner.hallucination) - owner.hallucination = 0 - if(owner.druggy) - owner.adjust_drugginess(-owner.druggy) - if(owner.dizziness) - owner.dizziness = 0 - if(owner.confused) - owner.confused = 0 + owner.remove_status_effect(/datum/status_effect/hallucination) + owner.remove_status_effect(/datum/status_effect/drugginess) + owner.remove_status_effect(/datum/status_effect/drowsiness) + owner.remove_status_effect(/datum/status_effect/confusion) severity = 0 else if(!owner.anti_magic_check(chargecost = 0) && owner.stat != DEAD && severity) var/static/hum = get_sfx('sound/effects/screech.ogg') //same sound for every proc call @@ -329,13 +487,10 @@ if(prob(severity * 0.15)) to_chat(owner, "\"[text2ratvar(pick(mania_messages))]\"") owner.playsound_local(get_turf(motor), hum, severity, 1) - owner.adjust_drugginess(clamp(max(severity * 0.075, 1), 0, max(0, 50 - owner.druggy))) //7.5% of severity per second, minimum 1 - if(owner.hallucination < 50) - owner.hallucination = min(owner.hallucination + max(severity * 0.075, 1), 50) //7.5% of severity per second, minimum 1 - if(owner.dizziness < 50) - owner.dizziness = min(owner.dizziness + round(severity * 0.05, 1), 50) //5% of severity per second above 10 severity - if(owner.confused < 25) - owner.confused = min(owner.confused + round(severity * 0.025, 1), 25) //2.5% of severity per second above 20 severity + owner.adjust_drugginess(clamp(max(severity * 0.075, 1), 0, max(0, 50 SECONDS))) //7.5% of severity per second, minimum 1 + owner.adjust_hallucinations_up_to(max(severity * 0.075, 1) SECONDS, 50 SECONDS) //7.5% of severity per second, minimum 1 + owner.adjust_dizzy_up_to(round(severity * 0.05, 1) SECONDS, 50 SECONDS) //5% of severity per second above 10 severity + owner.adjust_confusion_up_to(round(severity * 0.025, 1) SECONDS, 25 SECONDS) //2.5% of severity per second above 20 severity owner.adjustToxLoss(severity * 0.02, TRUE, TRUE) //2% of severity per second severity-- @@ -372,13 +527,13 @@ /datum/status_effect/the_shadow/tick() var/turf/T = get_turf(owner) var/light_amount = T.get_lumcount() - if(light_amount > 0.2) + if(light_amount > LIGHTING_TILE_IS_DARK) to_chat(owner, span_notice("As the light reaches the shadows, they dissipate!")) qdel(src) if(owner.stat == DEAD) qdel(src) - owner.hallucination += 2 - owner.confused += 2 + owner.adjust_hallucinations(2 SECONDS) + owner.adjust_confusion(2 SECONDS) owner.adjustEarDamage(0, 5) /datum/status_effect/the_shadow/Destroy() @@ -627,8 +782,8 @@ owner.Paralyze(15) if(iscarbon(owner)) var/mob/living/carbon/C = owner - C.silent = max(2, C.silent) - C.stuttering = max(5, C.stuttering) + C.silent += 2 + C.adjust_stutter(5 SECONDS) if(!old_health) old_health = owner.health var/health_difference = old_health - owner.health @@ -708,8 +863,8 @@ /datum/status_effect/trance/tick() if(stun) - owner.Stun(60, TRUE, TRUE) - owner.dizziness = 20 + owner.Stun(6 SECONDS, TRUE, TRUE) + owner.adjust_dizzy(20 SECONDS) /datum/status_effect/trance/on_apply() if(!iscarbon(owner)) @@ -730,7 +885,7 @@ /datum/status_effect/trance/on_remove() UnregisterSignal(owner, COMSIG_MOVABLE_HEAR) REMOVE_TRAIT(owner, TRAIT_MUTE, "trance") - owner.dizziness = 0 + owner.remove_status_effect(/datum/status_effect/dizziness) if(!owner.has_quirk(/datum/quirk/monochromatic)) owner.remove_client_colour(/datum/client_colour/monochrome) to_chat(owner, span_warning("You snap out of your trance!")) @@ -986,7 +1141,7 @@ var/mob/living/carbon/carbon_owner = owner carbon_owner.adjustFireLoss(5 * repetitions) carbon_owner.adjust_fire_stacks(2) - carbon_owner.IgniteMob() + carbon_owner.ignite_mob() for(var/mob/living/carbon/victim in range(1,carbon_owner)) if(IS_HERETIC(victim) || victim == carbon_owner) continue @@ -1028,7 +1183,7 @@ if(0 to 19) H.vomit() if(20 to 29) - H.Dizzy(10) + H.adjust_dizzy(10) if(30 to 39) H.adjustOrganLoss(ORGAN_SLOT_LIVER,5) if(40 to 49) diff --git a/code/datums/status_effects/debuffs/dizziness.dm b/code/datums/status_effects/debuffs/dizziness.dm new file mode 100644 index 000000000000..ea820c80a5c6 --- /dev/null +++ b/code/datums/status_effects/debuffs/dizziness.dm @@ -0,0 +1,79 @@ +/datum/status_effect/dizziness + id = "dizziness" + tick_interval = 2 SECONDS + alert_type = null + remove_on_fullheal = TRUE + +/datum/status_effect/dizziness/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/dizziness/on_apply() + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(clear_dizziness)) + return TRUE + +/datum/status_effect/dizziness/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + // In case our client's offset is somewhere wacky from the dizziness effect + owner.client?.pixel_x = initial(owner.client?.pixel_x) + owner.client?.pixel_y = initial(owner.client?.pixel_y) + +/// Signal proc that self deletes our dizziness effect +/datum/status_effect/dizziness/proc/clear_dizziness(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/datum/status_effect/dizziness/tick() + // How much time is left, in seconds + var/amount = (duration - world.time) / 10 + if(amount <= 0) + return + + // How strong the dizziness effect is on us. + // If we're resting, the effect is 5x as strong, but also decays 5x fast. + // Meaning effectively, 1 tick is actually dizziness_strength ticks of duration + var/dizziness_strength = owner.resting ? 5 : 1 + var/time_between_ticks = initial(tick_interval) + + // How much time will be left, in seconds, next tick + var/next_amount = max((amount - (dizziness_strength * time_between_ticks * 0.1)), 0) + + // If we have a dizziness strength > 1, we will subtract ticks off of the total duration + if(remove_duration((dizziness_strength - 1) * time_between_ticks)) + return + + // Now we can do the actual dizzy effects. + // Don't bother animating if they're clientless. + if(!owner.client) + return + + // Want to be able to offset things by the time the animation should be "playing" at + var/time = world.time + var/delay = 0 + var/pixel_x_diff = 0 + var/pixel_y_diff = 0 + + // This shit is annoying at high strengthvar/pixel_x_diff = 0 + var/list/view_range_list = getviewsize(owner.client.view) + var/view_range = view_range_list[1] + var/amplitude = amount * (sin(amount * (time)) + 1) + var/x_diff = clamp(amplitude * sin(amount * time), -view_range, view_range) + var/y_diff = clamp(amplitude * cos(amount * time), -view_range, view_range) + pixel_x_diff += x_diff + pixel_y_diff += y_diff + // Brief explanation. We're basically snapping between different pixel_x/ys instantly, with delays between + // Doing this with relative changes. This way we don't override any existing pixel_x/y values + // We use EASE_OUT here for similar reasons, we want to act at the end of the delay, not at its start + // Relative animations are weird, so we do actually need this + animate(owner.client, pixel_x = x_diff, pixel_y = y_diff, 3, easing = JUMP_EASING | EASE_OUT, flags = ANIMATION_RELATIVE) + delay += 0.3 SECONDS // This counts as a 0.3 second wait, so we need to shift the sine wave by that much + + x_diff = amplitude * sin(next_amount * (time + delay)) + y_diff = amplitude * cos(next_amount * (time + delay)) + pixel_x_diff += x_diff + pixel_y_diff += y_diff + animate(pixel_x = x_diff, pixel_y = y_diff, 3, easing = JUMP_EASING | EASE_OUT, flags = ANIMATION_RELATIVE) + + // Now we reset back to our old pixel_x/y, since these animates are relative + animate(pixel_x = -pixel_x_diff, pixel_y = -pixel_y_diff, 3, easing = JUMP_EASING | EASE_OUT, flags = ANIMATION_RELATIVE) diff --git a/code/datums/status_effects/debuffs/drowsiness.dm b/code/datums/status_effects/debuffs/drowsiness.dm new file mode 100644 index 000000000000..d2707049a8c2 --- /dev/null +++ b/code/datums/status_effects/debuffs/drowsiness.dm @@ -0,0 +1,42 @@ +/datum/status_effect/drowsiness + id = "drowsiness" + tick_interval = 2 SECONDS + alert_type = null + remove_on_fullheal = TRUE + +/datum/status_effect/drowsiness/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/drowsiness/on_apply() + if(HAS_TRAIT(owner, TRAIT_SLEEPIMMUNE) || !(owner.status_flags & CANUNCONSCIOUS)) + return FALSE + // Do robots dream of electric sheep? + if(issilicon(owner)) + return FALSE + + RegisterSignal(owner, COMSIG_COMPONENT_CLEAN_FACE_ACT, PROC_REF(on_face_clean)) + return TRUE + +/datum/status_effect/drowsiness/on_remove() + UnregisterSignal(owner, COMSIG_COMPONENT_CLEAN_FACE_ACT) + +/// Signal proc for [COMSIG_COMPONENT_CLEAN_FACE_ACT]. When we wash our face, reduce drowsiness by a bit. +/datum/status_effect/drowsiness/proc/on_face_clean(datum/source) + SIGNAL_HANDLER + + remove_duration(rand(4 SECONDS, 6 SECONDS)) + +/datum/status_effect/drowsiness/tick(delta_time) + // You do not feel drowsy while unconscious or in stasis + if(owner.stat >= UNCONSCIOUS || IS_IN_STASIS(owner)) + return + + // Resting helps against drowsiness + // While resting, we lose 4 seconds of duration (2 additional ticks) per tick + if(owner.resting && remove_duration(2 * initial(tick_interval))) + return + + owner.blur_eyes(4 SECONDS) + if(prob(5)) + owner.AdjustSleeping(10 SECONDS) diff --git a/code/datums/status_effects/debuffs/drugginess.dm b/code/datums/status_effects/debuffs/drugginess.dm new file mode 100644 index 000000000000..318267c4c063 --- /dev/null +++ b/code/datums/status_effects/debuffs/drugginess.dm @@ -0,0 +1,36 @@ +/// Drugginess / "high" effect, makes your screen rainbow +/datum/status_effect/drugginess + id = "drugged" + alert_type = /atom/movable/screen/alert/status_effect/high + remove_on_fullheal = TRUE + +/datum/status_effect/drugginess/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/drugginess/on_apply() + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(remove_drugginess)) + + SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, id, /datum/mood_event/high) + owner.overlay_fullscreen(id, /atom/movable/screen/fullscreen/high) + owner.grant_language(/datum/language/beachbum, TRUE, TRUE, id) + return TRUE + +/datum/status_effect/drugginess/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + + SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, id) + owner.clear_fullscreen(id) + owner.remove_language(/datum/language/beachbum, TRUE, TRUE, id) + +/// Removes all of our drugginess (self delete) on signal +/datum/status_effect/drugginess/proc/remove_drugginess(datum/source, admin_revive) + SIGNAL_HANDLER + + qdel(src) + +/// The status effect for "drugginess" +/atom/movable/screen/alert/status_effect/high + name = "High" + desc = "Whoa man, you're tripping balls! Careful you don't get addicted... if you aren't already." + icon_state = "high" diff --git a/code/datums/status_effects/debuffs/drunk.dm b/code/datums/status_effects/debuffs/drunk.dm new file mode 100644 index 000000000000..06c7d96e89c7 --- /dev/null +++ b/code/datums/status_effects/debuffs/drunk.dm @@ -0,0 +1,210 @@ +// Defines for the ballmer peak. +#define BALLMER_PEAK_LOW_END 12.9 +#define BALLMER_PEAK_HIGH_END 13.8 +#define BALLMER_PEAK_WINDOWS_ME 26 + +/// The threshld which determine if someone is tipsy vs drunk +#define TIPSY_THRESHOLD 6 + +/** + * The drunk status effect. + * Slowly decreases in drunk_value over time, causing effects based on that value. + */ +/datum/status_effect/inebriated + id = "drunk" + tick_interval = 2 SECONDS + status_type = STATUS_EFFECT_REPLACE + remove_on_fullheal = TRUE + /// The level of drunkness we are currently at. + var/drunk_value = 0 + +/datum/status_effect/inebriated/on_creation(mob/living/new_owner, drunk_value = 0) + . = ..() + set_drunk_value(drunk_value) + +/datum/status_effect/inebriated/get_examine_text() + // Dead people don't look drunk + if(owner.stat == DEAD || HAS_TRAIT(owner, TRAIT_FAKEDEATH)) + return null + + // Having your face covered conceals your drunkness + if(iscarbon(owner)) + var/mob/living/carbon/carbon_owner = owner + if(carbon_owner.wear_mask?.flags_inv & HIDEFACE) + return null + if(carbon_owner.head?.flags_inv & HIDEFACE) + return null + + // .01s are used in case the drunk value ends up to be a small decimal. + switch(drunk_value) + if(11 to 21) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] slightly flushed.") + if(21.01 to 41) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] flushed.") + if(41.01 to 51) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] quite flushed and [owner.p_their()] breath smells of alcohol.") + if(51.01 to 61) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] very flushed and [owner.p_their()] movements jerky, with breath reeking of alcohol.") + if(61.01 to 91) + return span_warning("[owner.p_they(TRUE)] look[owner.p_s()] like a drunken mess.") + if(91.01 to INFINITY) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] a shitfaced, slobbering wreck.") + + return null + +/// Sets the drunk value to set_to, deleting if the value drops to 0 or lower +/datum/status_effect/inebriated/proc/set_drunk_value(set_to) + if(!isnum(set_to)) + CRASH("[type] - invalid value passed to set_drunk_value. (Got: [set_to])") + + drunk_value = set_to + if(drunk_value <= 0) + qdel(src) + +/datum/status_effect/inebriated/tick() + // Drunk value does not decrease while dead or in stasis + if(owner.stat == DEAD || IS_IN_STASIS(owner)) + return + + // Every tick, the drunk value decrases by + // 4% the current drunk_value + 0.01 + // (until it reaches 0 and terminates) + set_drunk_value(drunk_value - (0.01 + drunk_value * 0.04)) + if(QDELETED(src)) + return + + on_tick_effects() + +/// Side effects done by this level of drunkness on tick. +/datum/status_effect/inebriated/proc/on_tick_effects() + return + +/** + * Stage 1 of drunk, applied at drunk values between 0 and 6. + * Basically is the "drunk but no side effects" stage. + */ +/datum/status_effect/inebriated/tipsy + alert_type = null + +/datum/status_effect/inebriated/tipsy/set_drunk_value(set_to) + . = ..() + if(QDELETED(src)) + return + + // Become fully drunk at over than 6 drunk value + if(drunk_value >= TIPSY_THRESHOLD) + owner.apply_status_effect(/datum/status_effect/inebriated/drunk, drunk_value) + +/** + * Stage 2 of being drunk, applied at drunk values between 6 and onward. + * Has all the main side effects of being drunk, scaling up as they get more drunk. + */ +/datum/status_effect/inebriated/drunk + alert_type = /atom/movable/screen/alert/status_effect/drunk + +/datum/status_effect/inebriated/drunk/on_apply() + . = ..() + SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, id, /datum/mood_event/drunk) + +/datum/status_effect/inebriated/drunk/on_remove() + clear_effects() + return ..() + +// Going from "drunk" to "tipsy" should remove effects like on_remove +/datum/status_effect/inebriated/drunk/be_replaced() + clear_effects() + return ..() + +/// Clears any side effects we set due to being drunk. +/datum/status_effect/inebriated/drunk/proc/clear_effects() + SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, id) + +/datum/status_effect/inebriated/drunk/set_drunk_value(set_to) + . = ..() + if(QDELETED(src)) + return + + // Return to "tipsyness" when we're below 6. + if(drunk_value < TIPSY_THRESHOLD) + owner.apply_status_effect(/datum/status_effect/inebriated/tipsy, drunk_value) + +/datum/status_effect/inebriated/drunk/on_tick_effects() + // Handle the Ballmer Peak. + // If our owner is a scientist (has the trait "TRAIT_BALLMER_SCIENTIST"), there's a 5% chance + // that they'll say one of the special "ballmer message" lines, depending their drunk-ness level. + REMOVE_TRAIT(owner, TRAIT_SURGERY_PREPARED, "drunk") + var/obj/item/organ/liver/liver_organ = owner.getorganslot(ORGAN_SLOT_LIVER) + if(liver_organ && HAS_TRAIT(liver_organ, TRAIT_BALLMER_SCIENTIST) && prob(5)) + if(drunk_value >= BALLMER_PEAK_LOW_END && drunk_value <= BALLMER_PEAK_HIGH_END) + owner.say(pick_list_replacements(VISTA_FILE, "ballmer_good_msg"), forced = "ballmer") + + if(drunk_value > BALLMER_PEAK_WINDOWS_ME) // by this point you're into windows ME territory + owner.say(pick_list_replacements(VISTA_FILE, "ballmer_windows_me_msg"), forced = "ballmer") + + // There's always a 30% chance to gain some drunken slurring + if(prob(30)) + owner.adjust_slurring(4 SECONDS) + + // And drunk people will always lose jitteriness + owner.adjust_jitter(-6 SECONDS) + + // Over 11, we will constantly gain slurring up to 10 seconds of slurring. + if(drunk_value >= 11) + owner.adjust_slurring_up_to(2.4 SECONDS, 10 SECONDS) + + // Over 41, we have a 30% chance to gain confusion, and we will always have 20 seconds of dizziness. + if(drunk_value >= 41) + if(prob(30)) + owner.adjust_confusion(2 SECONDS) + + owner.set_dizzy_if_lower(20 SECONDS) + ADD_TRAIT(src, TRAIT_SURGERY_PREPARED, "drunk") + + // Over 51, we have a 3% chance to gain a lot of confusion and vomit, and we will always have 50 seconds of dizziness + if(drunk_value >= 51) + owner.set_dizzy_if_lower(50 SECONDS) + if(prob(3)) + owner.adjust_confusion(15 SECONDS) + if(iscarbon(owner)) + var/mob/living/carbon/carbon_owner = owner + carbon_owner.vomit() // Vomiting clears toxloss - consider this a blessing + + // Over 71, we will constantly have blurry eyes + if(drunk_value >= 71) + owner.blur_eyes(drunk_value * 2 SECONDS - 140 SECONDS) + + // Over 81, we will gain constant toxloss + if(drunk_value >= 81) + owner.adjustToxLoss(1) + if(owner.stat == CONSCIOUS && prob(5)) + to_chat(owner, span_warning("Maybe you should lie down for a bit...")) + + // Over 91, we gain even more toxloss, brain damage, and have a chance of dropping into a long sleep + if(drunk_value >= 91) + owner.adjustToxLoss(1) + owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4) + if(owner.stat == CONSCIOUS && prob(20)) + // Don't put us in a deep sleep if the shuttle's here. QoL, mainly. + if(SSshuttle.emergency.mode == SHUTTLE_DOCKED && is_station_level(owner.z)) + to_chat(owner, span_warning("You're so tired... but you can't miss that shuttle...")) + + else + to_chat(owner, span_warning("Just a quick nap...")) + owner.Sleeping(90 SECONDS) + + // And finally, over 100 - let's be honest, you shouldn't be alive by now. + if(drunk_value >= 101) + owner.adjustToxLoss(2) + +/// Status effect for being fully drunk (not tipsy). +/atom/movable/screen/alert/status_effect/drunk + name = "Drunk" + desc = "All that alcohol you've been drinking is impairing your speech, \ + motor skills, and mental cognition. Make sure to act like it." + icon_state = "drunk" + +#undef BALLMER_PEAK_LOW_END +#undef BALLMER_PEAK_HIGH_END +#undef BALLMER_PEAK_WINDOWS_ME + +#undef TIPSY_THRESHOLD diff --git a/code/datums/status_effects/debuffs/hallucination.dm b/code/datums/status_effects/debuffs/hallucination.dm new file mode 100644 index 000000000000..e788d6b21379 --- /dev/null +++ b/code/datums/status_effects/debuffs/hallucination.dm @@ -0,0 +1,117 @@ +/// Hallucination status effect. How most hallucinations end up happening. +/// Hallucinations are drawn from the global weighted list, random_hallucination_weighted_list +/datum/status_effect/hallucination + id = "hallucination" + alert_type = null + tick_interval = 2 SECONDS + remove_on_fullheal = TRUE + /// The lower range of when the next hallucination will trigger after one occurs. + var/lower_tick_interval = 10 SECONDS + /// The upper range of when the next hallucination will trigger after one occurs. + var/upper_tick_interval = 60 SECONDS + /// The cooldown for when the next hallucination can occur + COOLDOWN_DECLARE(hallucination_cooldown) + +/datum/status_effect/hallucination/on_creation(mob/living/new_owner, duration) + if(isnum(duration)) + src.duration = duration + return ..() + +/datum/status_effect/hallucination/on_apply() + if(owner.mob_biotypes == NO_HALLUCINATION_BIOTYPES) + return FALSE + + RegisterSignal(owner, COMSIG_LIVING_HEALTHSCAN, PROC_REF(on_health_scan)) + if(iscarbon(owner)) + RegisterSignal(owner, COMSIG_CARBON_CHECKING_BODYPART, PROC_REF(on_check_bodypart)) + RegisterSignal(owner, COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, PROC_REF(on_bump_airlock)) + + return TRUE + +/datum/status_effect/hallucination/on_remove() + UnregisterSignal(owner, list( + COMSIG_LIVING_HEALTHSCAN, + COMSIG_CARBON_CHECKING_BODYPART, + COMSIG_CARBON_BUMPED_AIRLOCK_OPEN, + )) + +/// Signal proc for [COMSIG_LIVING_HEALTHSCAN]. Show we're hallucinating to (advanced) scanners. +/datum/status_effect/hallucination/proc/on_health_scan(datum/source, list/render_list, advanced, mob/user, mode) + SIGNAL_HANDLER + + if(!advanced) + return + + render_list += "Subject is hallucinating.\n" + +/// Signal proc for [COMSIG_CARBON_CHECKING_BODYPART], +/// checking bodyparts while hallucinating can cause them to appear more damaged than they are +/datum/status_effect/hallucination/proc/on_check_bodypart(mob/living/carbon/source, obj/item/bodypart/examined, list/check_list, list/limb_damage) + SIGNAL_HANDLER + + if(prob(30)) + limb_damage[BRUTE] += rand(30, 40) + if(prob(30)) + limb_damage[BURN] += rand(30, 40) + +/// Signal proc for [COMSIG_CARBON_BUMPED_AIRLOCK_OPEN], bumping an airlock can cause a fake zap. +/// This only happens on airlock bump, future TODO - make this chance roll for attack_hand opening airlocks too +/datum/status_effect/hallucination/proc/on_bump_airlock(mob/living/carbon/source, obj/machinery/door/airlock/bumped) + SIGNAL_HANDLER + + // 1% chance to fake a shock. + if(prob(99) || !source.should_electrocute() || bumped.operating) + return + + source.cause_hallucination(/datum/hallucination/shock, "hallucinated shock from [bumped]",) + return STOP_BUMP + +/datum/status_effect/hallucination/tick(delta_time, times_fired) + if(owner.stat == DEAD) + return + if(!COOLDOWN_FINISHED(src, hallucination_cooldown)) + return + + var/datum/hallucination/picked_hallucination = pickweight(GLOB.random_hallucination_weighted_list) + owner.cause_hallucination(picked_hallucination, "[id] status effect") + COOLDOWN_START(src, hallucination_cooldown, rand(lower_tick_interval, upper_tick_interval)) + +// Sanity related hallucinations +/datum/status_effect/hallucination/sanity + id = "low sanity" + status_type = STATUS_EFFECT_REFRESH + duration = -1 // This lasts "forever", only goes away with sanity gain + +/datum/status_effect/hallucination/sanity/on_apply() + var/datum/component/mood/mob_mood = owner.GetComponent(/datum/component/mood) + if(!mob_mood) + return FALSE + + update_intervals() + return ..() + +/datum/status_effect/hallucination/sanity/refresh(...) + update_intervals() + +/datum/status_effect/hallucination/sanity/tick(delta_time, times_fired) + // Using psicodine / happiness / whatever to become fearless will stop sanity based hallucinations + if(HAS_TRAIT(owner, TRAIT_FEARLESS)) + return + + return ..() + +/// Updates our upper and lower intervals based on our owner's current sanity level. +/datum/status_effect/hallucination/sanity/proc/update_intervals() + var/datum/component/mood/mob_mood = owner.GetComponent(/datum/component/mood) + switch(mob_mood.sanity_level) + if(SANITY_CRAZY) + upper_tick_interval = 8 MINUTES + lower_tick_interval = 4 MINUTES + + if(SANITY_INSANE) + upper_tick_interval = 4 MINUTES + lower_tick_interval = 2 MINUTES + + else + stack_trace("[type] was assigned a mob which was not crazy or insane. (was: [mob_mood.sanity_level])") + qdel(src) diff --git a/code/datums/status_effects/debuffs/jitteriness.dm b/code/datums/status_effects/debuffs/jitteriness.dm new file mode 100644 index 000000000000..82d360aaef76 --- /dev/null +++ b/code/datums/status_effects/debuffs/jitteriness.dm @@ -0,0 +1,62 @@ +/datum/status_effect/jitter + id = "jitter" + tick_interval = 2 SECONDS + alert_type = null + remove_on_fullheal = TRUE + +/datum/status_effect/jitter/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/jitter/on_apply() + // If we're being applied to a dead person, don't make the status effect. + // Just do a bit of jitter animation and be done. + if(owner.stat == DEAD) + owner.do_jitter_animation(duration / 10) + return FALSE + + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(remove_jitter)) + SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, id, /datum/mood_event/jittery) + return TRUE + +/datum/status_effect/jitter/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, id) + // juuust in case, reset our x and y's from our jittering + owner.pixel_x = 0 + owner.pixel_y = 0 + +/datum/status_effect/jitter/get_examine_text() + switch(duration - world.time) + if(5 MINUTES to INFINITY) + return span_boldwarning("[owner.p_they(TRUE)] [owner.p_are()] convulsing violently!") + if(3 MINUTES to 5 MINUTES) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] extremely jittery.") + if(1 MINUTES to 3 MINUTES) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] twitching ever so slightly.") + + return null + +/// Removes all of our jitteriness on a signal +/datum/status_effect/jitter/proc/remove_jitter(datum/source) + SIGNAL_HANDLER + + qdel(src) + +/datum/status_effect/jitter/tick() + // Resting helps against jitter + // While resting, we lose 8 seconds of duration (4 additional ticks) per tick + if(owner.resting && remove_duration(4 * initial(tick_interval))) + return + + var/time_left_in_seconds = (duration - world.time) / 10 + owner.do_jitter_animation(time_left_in_seconds) + +/// Helper proc that causes the mob to do a jittering animation by jitter_amount. +/// jitter_amount will only apply up to 300 (maximum jitter effect). +/mob/living/proc/do_jitter_animation(jitter_amount = 100) + var/amplitude = min(4, (jitter_amount / 100) + 1) + var/pixel_x_diff = rand(-amplitude, amplitude) + var/pixel_y_diff = rand(-amplitude / 3, amplitude / 3) + animate(src, pixel_x = pixel_x_diff, pixel_y = pixel_y_diff , time = 0.2 SECONDS, loop = 6, flags = ANIMATION_RELATIVE|ANIMATION_PARALLEL) + animate(pixel_x = -pixel_x_diff , pixel_y = -pixel_y_diff , time = 0.2 SECONDS, flags = ANIMATION_RELATIVE) diff --git a/code/datums/status_effects/knuckleroot.dm b/code/datums/status_effects/debuffs/knuckleroot.dm similarity index 100% rename from code/datums/status_effects/knuckleroot.dm rename to code/datums/status_effects/debuffs/knuckleroot.dm diff --git a/code/datums/status_effects/debuffs/speech_debuffs.dm b/code/datums/status_effects/debuffs/speech_debuffs.dm new file mode 100644 index 000000000000..7298696bfc1e --- /dev/null +++ b/code/datums/status_effects/debuffs/speech_debuffs.dm @@ -0,0 +1,216 @@ +/datum/status_effect/speech + id = null + alert_type = null + remove_on_fullheal = TRUE + +/datum/status_effect/speech/on_creation(mob/living/new_owner, duration = 10 SECONDS) + src.duration = duration + return ..() + +/datum/status_effect/speech/on_apply() + RegisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE, PROC_REF(handle_message)) + return TRUE + +/datum/status_effect/speech/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE) + +/** + * Signal proc for [COMSIG_LIVING_TREAT_MESSAGE] + * + * Iterates over all of the characters in the passed message + * and calls apply_speech() on each. + */ +/datum/status_effect/speech/proc/handle_message(datum/source, list/message_args) + SIGNAL_HANDLER + + var/phrase = html_decode(message_args[TREAT_MESSAGE_MESSAGE]) + if(!length(phrase)) + return + + var/final_phrase = "" + var/original_char = "" + + for(var/i = 1, i <= length(phrase), i += length(original_char)) + original_char = phrase[i] + + final_phrase += apply_speech(original_char, original_char) + + message_args[TREAT_MESSAGE_MESSAGE] = sanitize(final_phrase) + +/** + * Applies the speech effects on the past character, changing + * the original_char into the modified_char. + * + * Return the modified_char to be reapplied to the message. + */ +/datum/status_effect/speech/proc/apply_speech(original_char, modified_char) + stack_trace("[type] didn't implement apply_speech.") + return original_char + +/datum/status_effect/speech/stutter + id = "stutter" + /// The probability of adding a stutter to any character + var/stutter_prob = 80 + /// Regex of characters we won't apply a stutter to + var/static/regex/no_stutter + +/datum/status_effect/speech/stutter/on_creation(mob/living/new_owner, ...) + . = ..() + if(!.) + return + if(!no_stutter) + no_stutter = regex(@@[aeiouAEIOU ""''()[\]{}.!?,:;_`~-]@) + +/datum/status_effect/speech/stutter/apply_speech(original_char, modified_char) + if(prob(stutter_prob) && !no_stutter.Find(original_char)) + if(prob(10)) + modified_char = "[modified_char]-[modified_char]-[modified_char]-[modified_char]" + else if(prob(20)) + modified_char = "[modified_char]-[modified_char]-[modified_char]" + else if(prob(95)) + modified_char = "[modified_char]-[modified_char]" + else + modified_char = "" + + return modified_char + +/datum/status_effect/speech/stutter/derpspeech + id = "derp_stutter" + /// The probability of making our message entirely uppercase + adding exclamations + var/capitalize_prob = 50 + /// The probability of adding a stutter to the entire message, if we're not already stuttering + var/message_stutter_prob = 15 + +/datum/status_effect/speech/stutter/derpspeech/handle_message(datum/source, list/message_args) + + var/message = html_decode(message_args[TREAT_MESSAGE_MESSAGE]) + + message = replacetext(message, " am ", " ") + message = replacetext(message, " is ", " ") + message = replacetext(message, " are ", " ") + message = replacetext(message, "you", "u") + message = replacetext(message, "help", "halp") + message = replacetext(message, "grief", "grife") + message = replacetext(message, "space", "spess") + message = replacetext(message, "carp", "crap") + message = replacetext(message, "reason", "raisin") + + if(prob(capitalize_prob)) + var/exclamation = pick("!", "!!", "!!!") + message = uppertext(message) + message += "[apply_speech(exclamation, exclamation)]" + + message_args[1] = message + + var/mob/living/living_source = source + if(!isliving(source) || living_source.has_status_effect(/datum/status_effect/speech/stutter)) + return + + // If we're not stuttering, we have a chance of calling parent here, adding stutter effects + if(prob(message_stutter_prob)) + return ..() + + // Otherwise just return and don't call parent, we already modified our speech + return + +/datum/status_effect/speech/slurring + /// The chance that any given character in a message will be replaced with a common character + var/common_prob = 25 + /// The chance that any given character in a message will be replaced with an uncommon character + var/uncommon_prob = 10 + /// The chance that any given character will be entirely replaced with a new string / will have a string appended onto it + var/replacement_prob = 5 + /// The chance that any given character will be doubled, or even tripled + var/doubletext_prob = 0 + + /// The file we pull text modifications from + var/text_modification_file = "" + + /// Common replacements for characters - populated in on_creation + var/list/common_replacements + /// Uncommon replacements for characters - populated in on_creation + var/list/uncommon_replacements + /// Strings that fully replace a character - populated in on_creation + var/list/string_replacements + /// Strings that are appended to a character - populated in on_creation + var/list/string_additions + +/datum/status_effect/speech/slurring/on_creation(mob/living/new_owner, duration = 10 SECONDS) + . = ..() + if(!.) + return + + if(!text_modification_file) + CRASH("[type] was created without a text modification file.") + + var/list/speech_changes = strings(text_modification_file, "replacements") + common_replacements = speech_changes["characters"]["common"] + uncommon_replacements = speech_changes["characters"]["uncommon"] + string_replacements = speech_changes["string_replacements"] + string_additions = speech_changes["string_additions"] + +/datum/status_effect/speech/slurring/apply_speech(original_char, modified_char) + + var/lower_char = lowertext(modified_char) + if(prob(common_prob) && (lower_char in common_replacements)) + var/to_replace = common_replacements[lower_char] + if(islist(to_replace)) + modified_char = pick(to_replace) + else + modified_char = to_replace + + if(prob(uncommon_prob) && (modified_char in uncommon_replacements)) + var/to_replace = uncommon_replacements[modified_char] + if(islist(to_replace)) + modified_char = pick(to_replace) + else + modified_char = to_replace + + if(prob(replacement_prob)) + var/replacements_len = length(string_replacements) + var/additions_len = length(string_additions) + if(replacements_len && additions_len) + // Calculate the probability of grabbing a replacement vs an addition + var/weight = (replacements_len + additions_len) / replacements_len * 100 + if(prob(weight)) + modified_char = pick(string_replacements) + else + modified_char += pick(string_additions) + + else if(replacements_len) + modified_char = pick(string_replacements) + + else if(additions_len) + modified_char += pick(string_additions) + + if(prob(doubletext_prob)) + if(prob(50)) + modified_char += "[modified_char]" + else + modified_char += "[modified_char][modified_char]" + + return modified_char + +/datum/status_effect/speech/slurring/drunk + id = "drunk_slurring" + common_prob = 33 + uncommon_prob = 5 + replacement_prob = 5 + doubletext_prob = 10 + text_modification_file = "slurring_drunk_text.json" + +/datum/status_effect/speech/slurring/cult + id = "cult_slurring" + common_prob = 50 + uncommon_prob = 25 + replacement_prob = 33 + doubletext_prob = 0 + text_modification_file = "slurring_cult_text.json" + +/datum/status_effect/speech/slurring/heretic + id = "heretic_slurring" + common_prob = 50 + uncommon_prob = 20 + replacement_prob = 30 + doubletext_prob = 5 + text_modification_file = "slurring_heretic_text.json" diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index c0d14543bc97..3f13691839e7 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -98,7 +98,7 @@ /datum/status_effect/bounty/on_apply() to_chat(owner, "[span_boldnotice("You hear something behind you talking...")] [span_notice("You have been marked for death by [rewarded]. If you die, they will be rewarded.")]") - playsound(owner, 'sound/weapons/shotgunpump.ogg', 75, 0) + playsound(owner, 'sound/weapons/shotgunpump.ogg', 75, FALSE) return ..() /datum/status_effect/bounty/tick() @@ -109,12 +109,10 @@ /datum/status_effect/bounty/proc/rewards() if(rewarded && rewarded.mind && rewarded.stat != DEAD) to_chat(owner, "[span_boldnotice("You hear something behind you talking...")] [span_notice("Bounty claimed.")]") - playsound(owner, 'sound/weapons/shotgunshot.ogg', 75, 0) + playsound(owner, 'sound/weapons/shotgunshot.ogg', 75, FALSE) to_chat(rewarded, span_greentext("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/cooldown/spell/spell in rewarded.actions) + spell.reset_spell_cooldown() rewarded.adjustBruteLoss(-25) rewarded.adjustFireLoss(-25) rewarded.adjustToxLoss(-25) diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index 5eb5aed711a1..562f8a774be8 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -1,49 +1,86 @@ -//Status effects are used to apply temporary or permanent effects to mobs. Mobs are aware of their status effects at all times. -//This file contains their code, plus code for applying and removing them. -//When making a new status effect, add a define to status_effects.dm in __DEFINES for ease of use! - +/// Status effects are used to apply temporary or permanent effects to mobs. +/// This file contains their code, plus code for applying and removing them. /datum/status_effect - var/id = "effect" //Used for screen alerts. - var/duration = -1 //How long the status effect lasts in DECISECONDS. Enter -1 for an effect that never ends unless removed through some means. - var/tick_interval = 10 //How many deciseconds between ticks, approximately. Leave at 10 for every second. - var/mob/living/owner //The mob affected by the status effect. - var/status_type = STATUS_EFFECT_UNIQUE //How many of the effect can be on one mob, and what happens when you try to add another - var/on_remove_on_mob_delete = FALSE //if we call on_remove() when the mob is deleted - var/examine_text //If defined, this text will appear when the mob is examined - to use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves - var/alert_type = /atom/movable/screen/alert/status_effect //the alert thrown by the status effect, contains name and description - var/atom/movable/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists + /// The ID of the effect. ID is used in adding and removing effects to check for duplicates, among other things. + var/id = "effect" + /// When set initially / in on_creation, this is how long the status effect lasts in deciseconds. + /// While processing, this becomes the world.time when the status effect will expire. + /// -1 = infinite duration. + var/duration = -1 + /// When set initially / in on_creation, this is how long between [proc/tick] calls in deciseconds. + /// While processing, this becomes the world.time when the next tick will occur. + /// -1 = will stop processing, if duration is also unlimited (-1). + var/tick_interval = 1 SECONDS + /// The mob affected by the status effect. + var/mob/living/owner + /// How many of the effect can be on one mob, and/or what happens when you try to add a duplicate. + var/status_type = STATUS_EFFECT_UNIQUE + /// If TRUE, we call [proc/on_remove] when owner is deleted. Otherwise, we call [proc/be_replaced]. + var/on_remove_on_mob_delete = FALSE + /// The typepath to the alert thrown by the status effect when created. + /// Status effect "name"s and "description"s are shown to the owner here. + var/alert_type = /atom/movable/screen/alert/status_effect + /// The alert itself, created in [proc/on_creation] (if alert_type is specified). + var/atom/movable/screen/alert/status_effect/linked_alert + /// Used to define if the status effect should be using SSfastprocess or SSprocessing + var/processing_speed = STATUS_EFFECT_FAST_PROCESS + /// Do we self-terminate when a fullheal is called? + var/remove_on_fullheal = FALSE + // If remove_on_fullheal is TRUE, what flag do we need to be removed? + //var/heal_flag_necessary = HEAL_STATUS + ///If defined, this text will appear when the mob is examined - to use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves + var/examine_text /datum/status_effect/New(list/arguments) on_creation(arglist(arguments)) +/// Called from New() with any supplied status effect arguments. +/// Not guaranteed to exist by the end. +/// Returning FALSE from on_apply will stop on_creation and self-delete the effect. /datum/status_effect/proc/on_creation(mob/living/new_owner, ...) if(new_owner) owner = new_owner - if(owner) - LAZYADD(owner.status_effects, src) - if(!owner || !on_apply()) + if(QDELETED(owner) || !on_apply()) qdel(src) return + if(owner) + LAZYADD(owner.status_effects, src) + RegisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL, PROC_REF(remove_effect_on_heal)) + if(duration != -1) duration = world.time + duration tick_interval = world.time + tick_interval + if(alert_type) - var/atom/movable/screen/alert/status_effect/A = owner.throw_alert(id, alert_type) - A.attached_effect = src //so the alert can reference us, if it needs to - linked_alert = A //so we can reference the alert, if we need to + var/atom/movable/screen/alert/status_effect/new_alert = owner.throw_alert(id, alert_type) + new_alert.attached_effect = src //so the alert can reference us, if it needs to + linked_alert = new_alert //so we can reference the alert, if we need to + if(duration > 0 || initial(tick_interval) > 0) //don't process if we don't care - START_PROCESSING(SSfastprocess, src) + switch(processing_speed) + if(STATUS_EFFECT_FAST_PROCESS) + START_PROCESSING(SSfastprocess, src) + if(STATUS_EFFECT_NORMAL_PROCESS) + START_PROCESSING(SSprocessing, src) + return TRUE /datum/status_effect/Destroy() - STOP_PROCESSING(SSfastprocess, src) + switch(processing_speed) + if(STATUS_EFFECT_FAST_PROCESS) + STOP_PROCESSING(SSfastprocess, src) + if(STATUS_EFFECT_NORMAL_PROCESS) + STOP_PROCESSING(SSprocessing, src) if(owner) + linked_alert = null owner.clear_alert(id) LAZYREMOVE(owner.status_effects, src) on_remove() + UnregisterSignal(owner, COMSIG_LIVING_POST_FULLY_HEAL) owner = null return ..() + /datum/status_effect/process() if(!owner) qdel(src) @@ -56,31 +93,79 @@ /datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself. return TRUE -/datum/status_effect/proc/tick() //Called every tick. -/datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null -/datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted + +/// Gets and formats examine text associated with our status effect. +/// Return 'null' to have no examine text appear (default behavior). +/datum/status_effect/proc/get_examine_text() + return null + +/// Called every tick from process(). +/datum/status_effect/proc/tick(delta_time, times_fired) + return + +/// Called whenever the buff expires or is removed (qdeleted) +/// Note that at the point this is called, it is out of the +/// owner's status_effects list, but owner is not yet null +/datum/status_effect/proc/on_remove() + return + +/// Called instead of on_remove when a status effect +/// of status_type STATUS_EFFECT_REPLACE is replaced by itself, +/// or when a status effect with on_remove_on_mob_delete +/// set to FALSE has its mob deleted +/datum/status_effect/proc/be_replaced() + linked_alert = null owner.clear_alert(id) LAZYREMOVE(owner.status_effects, src) owner = null qdel(src) -/datum/status_effect/proc/refresh() +/// Called before being fully removed (before on_remove) +/// Returning FALSE will cancel removal +/datum/status_effect/proc/before_remove() + return TRUE + +/// Called when a status effect of status_type STATUS_EFFECT_REFRESH +/// has its duration refreshed in apply_status_effect - is passed New() args +/datum/status_effect/proc/refresh(effect, ...) var/original_duration = initial(duration) if(original_duration == -1) return duration = world.time + original_duration -//do_after modifier! -/datum/status_effect/proc/interact_speed_modifier() +/// Adds nextmove modifier multiplicatively to the owner while applied +/datum/status_effect/proc/nextmove_modifier() return 1 -//clickdelay/nextmove modifiers! -/datum/status_effect/proc/nextmove_modifier() +/datum/status_effect/proc/interact_speed_modifier() return 1 +/// Adds nextmove adjustment additiviely to the owner while applied /datum/status_effect/proc/nextmove_adjust() return 0 +/// Signal proc for [COMSIG_LIVING_POST_FULLY_HEAL] to remove us on fullheal +/datum/status_effect/proc/remove_effect_on_heal(datum/source, heal_flags) + SIGNAL_HANDLER + + if(!remove_on_fullheal) + return + +// if(!heal_flag_necessary || (heal_flags & heal_flag_necessary)) + qdel(src) + +/// Remove [seconds] of duration from the status effect, qdeling / ending if we eclipse the current world time. +/datum/status_effect/proc/remove_duration(seconds) + if(duration == -1) // Infinite duration + return FALSE + + duration -= seconds + if(duration <= world.time) + qdel(src) + return TRUE + + return FALSE + //////////////// // ALERT HOOK // //////////////// diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm index f25a64636163..e233744115f6 100644 --- a/code/datums/traits/negative.dm +++ b/code/datums/traits/negative.dm @@ -420,7 +420,7 @@ break if(prob(max(5,(nearby_people*12.5*moodmod)))) //Minimum 1/20 chance of stutter // Add a short stutter, THEN treat our word - quirker.stuttering += max(3, quirker.stuttering) + quirker.adjust_stutter(0.5 SECONDS) new_message += quirker.treat_message(word) else @@ -463,10 +463,10 @@ switch(rand(1,3)) if(1) - quirker.Jitter(5) + quirker.adjust_jitter(5 SECONDS) msg += "causing you to start fidgeting!" if(2) - quirker.stuttering = max(3, quirker.stuttering) + quirker.adjust_stutter(3 SECONDS) msg += "causing you to start stuttering!" if(3) quirker.Stun(2 SECONDS) diff --git a/code/datums/weakrefs.dm b/code/datums/weakrefs.dm index 31e0c3501b78..27bd5d943306 100644 --- a/code/datums/weakrefs.dm +++ b/code/datums/weakrefs.dm @@ -1,15 +1,57 @@ +/// Creates a weakref to the given input. +/// See /datum/weakref's documentation for more information. /proc/WEAKREF(datum/input) if(istype(input) && !QDELETED(input)) - if(istype(input, /datum/weakref)) + if(isweakref(input)) return input if(!input.weak_reference) input.weak_reference = new /datum/weakref(input) return input.weak_reference -/datum/proc/create_weakref() //Forced creation for admin proccalls +/datum/proc/create_weakref() //Forced creation for admin proccalls return WEAKREF(src) +/** + * A weakref holds a non-owning reference to a datum. + * The datum can be referenced again using `resolve()`. + * + * To figure out why this is important, you must understand how deletion in + * BYOND works. + * + * Imagine a datum as a TV in a living room. When one person enters to watch + * TV, they turn it on. Others can come into the room and watch the TV. + * When the last person leaves the room, they turn off the TV because it's + * no longer being used. + * + * A datum being deleted tells everyone who's watching the TV to stop. + * If everyone leaves properly (AKA cleaning up their references), then the + * last person will turn off the TV, and everything is well. + * However, if someone is resistant (holds a hard reference after deletion), + * then someone has to walk in, drag them away, and turn off the TV forecefully. + * This process is very slow, and it's known as hard deletion. + * + * This is where weak references come in. Weak references don't count as someone + * watching the TV. Thus, when what it's referencing is destroyed, it will + * hopefully clean up properly, and limit hard deletions. + * + * A common use case for weak references is holding onto what created itself. + * For example, if a machine wanted to know what its last user was, it might + * create a `var/mob/living/last_user`. However, this is a strong reference to + * the mob, and thus will force a hard deletion when that mob is deleted. + * It is often better in this case to instead create a weakref to the user, + * meaning this type definition becomes `var/datum/weakref/last_user`. + * + * A good rule of thumb is that you should hold strong references to things + * that you *own*. For example, a dog holding a chew toy would be the owner + * of that chew toy, and thus a `var/obj/item/chew_toy` reference is fine + * (as long as it is cleaned up properly). + * However, a chew toy does not own its dog, so a `var/mob/living/dog/owner` + * might be inferior to a weakref. + * This is also a good rule of thumb to avoid circular references, such as the + * chew toy example. A circular reference that doesn't clean itself up properly + * will always hard delete. + */ /datum/weakref var/reference @@ -17,13 +59,50 @@ reference = REF(thing) /datum/weakref/Destroy(force) - if(!force) - return QDEL_HINT_LETMELIVE //Let BYOND autoGC thiswhen nothing is using it anymore. var/datum/target = resolve() + qdel(target) + + if(!force) + return QDEL_HINT_LETMELIVE //Let BYOND autoGC thiswhen nothing is using it anymore. target?.weak_reference = null return ..() +/** + * Retrieves the datum that this weakref is referencing. + * + * This will return `null` if the datum was deleted. This MUST be respected. + */ /datum/weakref/proc/resolve() var/datum/D = locate(reference) return (!QDELETED(D) && D.weak_reference == src) ? D : null +/** + * SERIOUSLY READ THE AUTODOC COMMENT FOR THIS PROC BEFORE EVEN THINKING ABOUT USING IT + * + * Like resolve, but doesn't care if the datum is being qdeleted but hasn't been deleted yet. + * + * The return value of this proc leaves hanging references if the datum is being qdeleted but hasn't been deleted yet. + * + * Do not do anything that would create a lasting reference to the return value, such as giving it a tag, putting it on the map, + * adding it to an atom's contents or vis_contents, giving it a key (if it's a mob), attaching it to an atom (if it's an image), + * or assigning it to a datum or list referenced somewhere other than a temporary value. + * + * Unless you're resolving a weakref to a datum in a COMSIG_PARENT_QDELETING signal handler registered on that very same datum, + * just use resolve instead. + */ +/datum/weakref/proc/hard_resolve() + var/datum/D = locate(reference) + return (D?.weak_reference == src) ? D : null + +/datum/weakref/vv_get_dropdown() + . = ..() + VV_DROPDOWN_OPTION(VV_HK_WEAKREF_RESOLVE, "Go to reference") + +/datum/weakref/vv_do_topic(list/href_list) + . = ..() + if(href_list[VV_HK_WEAKREF_RESOLVE]) + if(!check_rights(NONE)) + return + var/datum/R = resolve() + if(R) + usr.client.debug_variables(R) diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm index 61314df7bd69..82a5cff9b512 100644 --- a/code/datums/wounds/bones.dm +++ b/code/datums/wounds/bones.dm @@ -372,7 +372,7 @@ to_chat(victim, span_userdanger("[user] finishes applying [I] to your [limb.name], and you can feel the bones exploding with pain as they begin melting and reforming!")) else var/painkiller_bonus = 0 - if(victim.drunkenness > 10) + if(victim.get_drunk_amount() > 10) painkiller_bonus += 10 if(victim.reagents?.has_reagent(/datum/reagent/medicine/morphine)) painkiller_bonus += 20 diff --git a/code/game/alternate_appearance.dm b/code/game/alternate_appearance.dm index cfac8a79a819..78a481a97dd8 100644 --- a/code/game/alternate_appearance.dm +++ b/code/game/alternate_appearance.dm @@ -9,7 +9,7 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) for(var/K in alternate_appearances) var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K] if(AA.appearance_key == key) - AA.remove_from_hud(src) + AA.remove_atom_from_hud(src) break /atom/proc/add_alt_appearance(type, key, ...) @@ -17,6 +17,9 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) return if(alternate_appearances && alternate_appearances[key]) return + if(!ispath(type, /datum/atom_hud/alternate_appearance)) + CRASH("Invalid type passed in: [type]") + var/list/arguments = args.Copy(2) return new type(arglist(arguments)) @@ -25,9 +28,16 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) var/transfer_overlays = FALSE /datum/atom_hud/alternate_appearance/New(key) + // We use hud_icons to register our hud, so we need to do this before the parent call + appearance_key = key + hud_icons = list(appearance_key) ..() + GLOB.active_alternate_appearances += src - appearance_key = key + + for(var/mob in GLOB.player_list) + if(mobShouldSee(mob)) + show_to(mob) /datum/atom_hud/alternate_appearance/Destroy() GLOB.active_alternate_appearances -= src @@ -35,18 +45,18 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) /datum/atom_hud/alternate_appearance/proc/onNewMob(mob/M) if(mobShouldSee(M)) - add_hud_to(M) + show_to(M) /datum/atom_hud/alternate_appearance/proc/mobShouldSee(mob/M) return FALSE -/datum/atom_hud/alternate_appearance/add_to_hud(atom/A, image/I) +/datum/atom_hud/alternate_appearance/add_atom_to_hud(atom/A, image/I) . = ..() if(.) LAZYINITLIST(A.alternate_appearances) A.alternate_appearances[appearance_key] = src -/datum/atom_hud/alternate_appearance/remove_from_hud(atom/A) +/datum/atom_hud/alternate_appearance/remove_atom_from_hud(atom/A) . = ..() if(.) LAZYREMOVE(A.alternate_appearances, appearance_key) @@ -57,22 +67,24 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) //an alternate appearance that attaches a single image to a single atom /datum/atom_hud/alternate_appearance/basic var/atom/target - var/image/theImage + var/image/image var/add_ghost_version = FALSE - var/ghost_appearance + var/datum/atom_hud/alternate_appearance/basic/observers/ghost_appearance + uses_global_hud_category = FALSE /datum/atom_hud/alternate_appearance/basic/New(key, image/I, options = AA_TARGET_SEE_APPEARANCE) ..() transfer_overlays = options & AA_MATCH_TARGET_OVERLAYS - theImage = I + image = I target = I.loc if(transfer_overlays) I.copy_overlays(target) - hud_icons = list(appearance_key) - add_to_hud(target, I) + add_atom_to_hud(target) + target.set_hud_image_active(appearance_key, exclusive_hud = src) + if((options & AA_TARGET_SEE_APPEARANCE) && ismob(target)) - add_hud_to(target) + show_to(target) if(add_ghost_version) var/image/ghost_image = image(icon = I.icon , icon_state = I.icon_state, loc = I.loc) ghost_image.override = FALSE @@ -81,43 +93,34 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) /datum/atom_hud/alternate_appearance/basic/Destroy() . = ..() + QDEL_NULL(image) + target = null if(ghost_appearance) QDEL_NULL(ghost_appearance) -/datum/atom_hud/alternate_appearance/basic/add_to_hud(atom/A) +/datum/atom_hud/alternate_appearance/basic/add_atom_to_hud(atom/A) LAZYINITLIST(A.hud_list) - A.hud_list[appearance_key] = theImage + A.hud_list[appearance_key] = image . = ..() -/datum/atom_hud/alternate_appearance/basic/remove_from_hud(atom/A) +/datum/atom_hud/alternate_appearance/basic/remove_atom_from_hud(atom/A) . = ..() - A.hud_list -= appearance_key + LAZYREMOVE(A.hud_list, appearance_key) + A.set_hud_image_inactive(appearance_key) if(. && !QDELETED(src)) qdel(src) /datum/atom_hud/alternate_appearance/basic/copy_overlays(atom/other, cut_old) - theImage.copy_overlays(other, cut_old) + image.copy_overlays(other, cut_old) /datum/atom_hud/alternate_appearance/basic/everyone add_ghost_version = TRUE -/datum/atom_hud/alternate_appearance/basic/everyone/New() - ..() - for(var/mob in GLOB.mob_list) - if(mobShouldSee(mob)) - add_hud_to(mob) - /datum/atom_hud/alternate_appearance/basic/everyone/mobShouldSee(mob/M) - return !isobserver(M) + return !isdead(M) /datum/atom_hud/alternate_appearance/basic/silicons -/datum/atom_hud/alternate_appearance/basic/silicons/New() - ..() - for(var/mob in GLOB.silicon_mobs) - if(mobShouldSee(mob)) - add_hud_to(mob) - /datum/atom_hud/alternate_appearance/basic/silicons/mobShouldSee(mob/M) if(issilicon(M)) return TRUE @@ -126,23 +129,11 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) /datum/atom_hud/alternate_appearance/basic/observers add_ghost_version = FALSE //just in case, to prevent infinite loops -/datum/atom_hud/alternate_appearance/basic/observers/New() - ..() - for(var/mob in GLOB.dead_mob_list) - if(mobShouldSee(mob)) - add_hud_to(mob) - /datum/atom_hud/alternate_appearance/basic/observers/mobShouldSee(mob/M) return isobserver(M) /datum/atom_hud/alternate_appearance/basic/noncult -/datum/atom_hud/alternate_appearance/basic/noncult/New() - ..() - for(var/mob in GLOB.player_list) - if(mobShouldSee(mob)) - add_hud_to(mob) - /datum/atom_hud/alternate_appearance/basic/noncult/mobShouldSee(mob/M) if(!iscultist(M)) return TRUE @@ -150,46 +141,34 @@ GLOBAL_LIST_EMPTY(active_alternate_appearances) /datum/atom_hud/alternate_appearance/basic/cult -/datum/atom_hud/alternate_appearance/basic/cult/New() - ..() - for(var/mob in GLOB.player_list) - if(mobShouldSee(mob)) - add_hud_to(mob) - /datum/atom_hud/alternate_appearance/basic/cult/mobShouldSee(mob/M) if(iscultist(M)) return TRUE return FALSE -/datum/atom_hud/alternate_appearance/basic/blessedAware - -/datum/atom_hud/alternate_appearance/basic/blessedAware/New() - ..() - for(var/mob in GLOB.mob_list) - if(mobShouldSee(mob)) - add_hud_to(mob) +/datum/atom_hud/alternate_appearance/basic/blessed_aware -/datum/atom_hud/alternate_appearance/basic/blessedAware/mobShouldSee(mob/M) - if(M.mind && (M.mind.assigned_role == "Chaplain")) +/datum/atom_hud/alternate_appearance/basic/blessed_aware/mobShouldSee(mob/M) + if(M.mind?.holy_role) return TRUE if (istype(M, /mob/living/simple_animal/hostile/construct/wraith)) return TRUE - if(isrevenant(M) || iseminence(M) || iswizard(M)) + if(isrevenant(M) || iswizard(M)) return TRUE return FALSE -datum/atom_hud/alternate_appearance/basic/onePerson +/datum/atom_hud/alternate_appearance/basic/one_person var/mob/seer -/datum/atom_hud/alternate_appearance/basic/onePerson/mobShouldSee(mob/M) +/datum/atom_hud/alternate_appearance/basic/one_person/mobShouldSee(mob/M) if(M == seer) return TRUE return FALSE -/datum/atom_hud/alternate_appearance/basic/onePerson/New(key, image/I, mob/living/M) +/datum/atom_hud/alternate_appearance/basic/one_person/New(key, image/I, mob/living/M) ..(key, I, FALSE) seer = M - add_hud_to(seer) + show_to(seer) /datum/atom_hud/alternate_appearance/basic/scent_hunter @@ -197,7 +176,7 @@ datum/atom_hud/alternate_appearance/basic/onePerson ..() for(var/mob in GLOB.player_list) if(mobShouldSee(mob)) - add_hud_to(mob) + show_to(mob) /datum/atom_hud/alternate_appearance/basic/scent_hunter/mobShouldSee(mob/M) if(isliving(M)) diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index cca3ec421390..59da75c5c112 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -23,7 +23,7 @@ /// There is a risk of this and contained_turfs leaking, so a subsystem will run it down to 0 incrementally if it gets too large var/list/turf/turfs_to_uncontain = list() - var/area_flags = 0 + var/area_flags = NONE var/map_name // Set in New(); preserves the name set by the map maker, even if renamed by the Blueprints. @@ -673,7 +673,7 @@ GLOBAL_LIST_EMPTY(teleportlocs) if(!L.client.played) SEND_SOUND(L, sound(sound, repeat = 0, wait = 0, volume = 25, channel = CHANNEL_AMBIENCE)) L.client.played = TRUE - addtimer(CALLBACK(L.client, /client/proc/ResetAmbiencePlayed), 600) + addtimer(CALLBACK(L.client, /client/proc/ResetAmbiencePlayed), 1 MINUTES) /** * Called when an atom exits an area diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index bc9e18a1db21..6e3f25170c6b 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -139,6 +139,7 @@ /area/shuttle/supply name = "Supply Shuttle" blob_allowed = FALSE + area_flags = NOTELEPORT /area/shuttle/ai name = "AI Ship Shuttle" @@ -152,10 +153,12 @@ /area/shuttle/escape/luxury name = "Luxurious Emergency Shuttle" + area_flags = NOTELEPORT /area/shuttle/escape/arena name = "The Arena" noteleport = TRUE + area_flags = NOTELEPORT /area/shuttle/escape/meteor name = "\proper a meteor with engines strapped to it" diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 02f7a821db3c..3601c2960248 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -20,8 +20,12 @@ ///Reagents holder var/datum/reagents/reagents = null - ///This atom's HUD (med/sec, etc) images. Associative list. + ///all of this atom's HUD (med/sec, etc) images. Associative list of the form: list(hud category = hud image or images for that category). + ///most of the time hud category is associated with a single image, sometimes its associated with a list of images. + ///not every hud in this list is actually used. for ones available for others to see, look at active_hud_list. var/list/image/hud_list = null + ///all of this atom's HUD images which can actually be seen by players with that hud + var/list/image/active_hud_list = null ///HUD images that this atom can provide. var/list/hud_possible @@ -78,6 +82,10 @@ var/chat_color_darkened // A luminescence-shifted value of the last color calculated for chatmessage overlays + ///Default pixel x shifting for the atom's icon. + var/base_pixel_x = 0 + ///Default pixel y shifting for the atom's icon. + var/base_pixel_y = 0 ///Mobs that are currently do_after'ing this atom, to be cleared from on Destroy() var/list/targeted_by @@ -208,7 +216,7 @@ if(alternate_appearances) for(var/current_alternate_appearance in alternate_appearances) var/datum/atom_hud/alternate_appearance/selected_alternate_appearance = alternate_appearances[current_alternate_appearance] - selected_alternate_appearance.remove_from_hud(src) + selected_alternate_appearance.remove_atom_from_hud(src) if(reagents) qdel(reagents) @@ -508,6 +516,24 @@ SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .) +/** + * Shows any and all examine text related to any status effects the user has. + */ +/mob/living/proc/get_status_effect_examinations() + var/list/examine_list = list() + + for(var/datum/status_effect/effect as anything in status_effects) + var/effect_text = effect.get_examine_text() + if(!effect_text) + continue + + examine_list += effect_text + + if(!length(examine_list)) + return + + return examine_list.Join("\n") + /** * Called when a mob examines (shift click or verb) this atom twice (or more) within EXAMINE_MORE_WINDOW (default 1.5 seconds) * @@ -1309,7 +1335,7 @@ . = ..() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /atom/movable/proc/get_filter(name) if(filter_data && filter_data[name]) @@ -1343,6 +1369,14 @@ for(var/comp_mat in material_comp) .[comp_mat] += material_comp[comp_mat] +///Setter for the `density` variable to append behavior related to its changing. +/atom/proc/set_density(new_value) + SHOULD_CALL_PARENT(TRUE) + if(density == new_value) + return + . = density + density = new_value + /** * Causes effects when the atom gets hit by a rust effect from heretics * diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 74e0f3b3f888..4a1fa4046a53 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -37,6 +37,8 @@ var/movement_type = GROUND //Incase you have multiple types, you automatically use the most useful one. IE: Skating on ice, flippers on water, flying over chasm/space, etc. var/atom/movable/pulling var/grab_state = 0 + // The strongest grab we can acomplish + var/max_grab = GRAB_KILL var/throwforce = 0 var/datum/component/orbiter/orbiting var/can_be_z_moved = TRUE @@ -215,7 +217,7 @@ return FALSE // Are we trying to pull something we are already pulling? Then enter grab cycle and end. if(AM == pulling) - grab_state = state + setGrabState(state) if(istype(AM,/mob/living)) var/mob/living/AMob = AM AMob.grabbedby(src) @@ -226,7 +228,7 @@ AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. pulling = AM AM.pulledby = src - grab_state = state + setGrabState(state) if(ismob(AM)) var/mob/M = AM log_combat(src, M, "grabbed", addition="passive grab") @@ -239,7 +241,7 @@ pulling.pulledby = null var/mob/living/ex_pulled = pulling pulling = null - grab_state = 0 + setGrabState(GRAB_PASSIVE) if(isliving(ex_pulled)) var/mob/living/L = ex_pulled L.update_mobility()// mob gets up if it was lyng down in a chokehold @@ -937,3 +939,30 @@ if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) return FALSE return TRUE + +/** + * Updates the grab state of the movable + * + * This exists to act as a hook for behaviour + */ +/atom/movable/proc/setGrabState(newstate) + if(newstate == grab_state) + return + SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate) + . = grab_state + grab_state = newstate + switch(grab_state) // Current state. + if(GRAB_PASSIVE) + REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) + if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher. + REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + if(GRAB_AGGRESSIVE) + if(. >= GRAB_NECK) // Grab got downgraded. + REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + else // Grab got upgraded from a passive one. + ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) + if(GRAB_NECK, GRAB_KILL) + if(. <= GRAB_AGGRESSIVE) + ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) diff --git a/code/game/data_huds.dm b/code/game/data_huds.dm index 3278a10c77bc..38b25f98b593 100644 --- a/code/game/data_huds.dm +++ b/code/game/data_huds.dm @@ -9,38 +9,37 @@ /atom/proc/add_to_all_human_data_huds() for(var/datum/atom_hud/data/human/hud in GLOB.huds) - hud.add_to_hud(src) + hud.add_atom_to_hud(src) /atom/proc/remove_from_all_data_huds() for(var/datum/atom_hud/data/hud in GLOB.huds) - hud.remove_from_hud(src) + hud.remove_atom_from_hud(src) /datum/atom_hud/data /datum/atom_hud/data/human/medical - hud_icons = list(STATUS_HUD, HEALTH_HUD, NANITE_HUD) + hud_icons = list(STATUS_HUD, HEALTH_HUD) /datum/atom_hud/data/human/medical/basic /datum/atom_hud/data/human/medical/basic/proc/check_sensors(mob/living/carbon/human/H) if(!istype(H)) - return 0 + return FALSE var/obj/item/clothing/under/U = H.w_uniform if(!istype(U)) - return 0 + return FALSE if(U.sensor_mode <= SENSOR_VITALS) - return 0 - return 1 + return FALSE + return TRUE -/datum/atom_hud/data/human/medical/basic/add_to_single_hud(mob/M, mob/living/carbon/H) +/datum/atom_hud/data/human/medical/basic/add_atom_to_single_mob_hud(mob/M, mob/living/carbon/H) if(check_sensors(H)) ..() /datum/atom_hud/data/human/medical/basic/proc/update_suit_sensors(mob/living/carbon/H) - check_sensors(H) ? add_to_hud(H) : remove_from_hud(H) + check_sensors(H) ? add_atom_to_hud(H) : remove_atom_from_hud(H) /datum/atom_hud/data/human/medical/advanced - do_silicon_check = TRUE /datum/atom_hud/data/human/security @@ -49,7 +48,6 @@ /datum/atom_hud/data/human/security/advanced hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD, NANITE_HUD) - do_silicon_check = TRUE /datum/atom_hud/data/human/security/advanced/hos hud_icons = list(ID_HUD, IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD, WANTED_HUD, NANITE_HUD, STATUS_HUD, HEALTH_HUD) @@ -57,13 +55,14 @@ /datum/atom_hud/data/diagnostic /datum/atom_hud/data/diagnostic/basic - hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_CIRCUIT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_NANITE_FULL_HUD, DIAG_LAUNCHPAD_HUD) - do_silicon_check = TRUE + hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD) /datum/atom_hud/data/diagnostic/advanced - hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_CIRCUIT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_NANITE_FULL_HUD, DIAG_LAUNCHPAD_HUD, DIAG_PATH_HUD) + hud_icons = list(DIAG_HUD, DIAG_STAT_HUD, DIAG_BATT_HUD, DIAG_MECH_HUD, DIAG_BOT_HUD, DIAG_TRACK_HUD, DIAG_AIRLOCK_HUD, DIAG_LAUNCHPAD_HUD, DIAG_PATH_HUD) /datum/atom_hud/data/bot_path + // This hud exists so the bot can see itself, that's all + uses_global_hud_category = FALSE hud_icons = list(DIAG_PATH_HUD) /datum/atom_hud/abductor @@ -75,12 +74,12 @@ /datum/atom_hud/ai_detector hud_icons = list(AI_DETECT_HUD) -/datum/atom_hud/ai_detector/add_hud_to(mob/M) +/datum/atom_hud/ai_detector/show_to(mob/new_viewer) ..() - if(M && (hudusers.len == 1)) - for(var/V in GLOB.aiEyes) - var/mob/camera/aiEye/E = V - E.update_ai_detect_hud() + if(!new_viewer || hud_users.len != 1) + return + for(var/mob/camera/aiEye/eye as anything in GLOB.aiEyes) + eye.update_ai_detect_hud() /* MED/SEC/DIAG HUD HOOKS */ @@ -89,7 +88,7 @@ */ /*********************************************** - Medical HUD! Basic mode needs suit sensors on. +Medical HUD! Basic mode needs suit sensors on. ************************************************/ //HELPERS @@ -129,7 +128,10 @@ //called when a living mob changes health /mob/living/proc/med_hud_set_health() - var/image/holder = hud_list[HEALTH_HUD] + var/image/holder = hud_list?[HEALTH_HUD] + if (isnull(holder)) + return + holder.icon_state = "hud[RoundHealth(src)]" var/icon/I = icon(icon, icon_state, dir) holder.pixel_y = I.Height() - world.icon_size @@ -140,7 +142,10 @@ //called when a carbon changes stat, virus or XENO_HOST /mob/living/proc/med_hud_set_status() - var/image/holder = hud_list[STATUS_HUD] + var/image/holder = hud_list?[STATUS_HUD] + if (isnull(holder)) + return + var/icon/I = icon(icon, icon_state, dir) holder.pixel_y = I.Height() - world.icon_size if(stat == DEAD || (HAS_TRAIT(src, TRAIT_FAKEDEATH))) @@ -149,7 +154,10 @@ holder.icon_state = "hudhealthy" /mob/living/carbon/med_hud_set_status() - var/image/holder = hud_list[STATUS_HUD] + var/image/holder = hud_list?[STATUS_HUD] + if (isnull(holder)) + return + var/icon/I = icon(icon, icon_state, dir) var/virus_threat = check_virus() holder.pixel_y = I.Height() - world.icon_size @@ -186,7 +194,7 @@ /*********************************************** - Security HUDs! Basic mode shows only the job. +Security HUDs! Basic mode shows only the job. ************************************************/ //HOOKS @@ -205,54 +213,64 @@ for(var/i in list(IMPTRACK_HUD, IMPLOYAL_HUD, IMPCHEM_HUD)) holder = hud_list[i] holder.icon_state = null + set_hud_image_inactive(i) + for(var/obj/item/implant/I in implants) if(istype(I, /obj/item/implant/tracking)) holder = hud_list[IMPTRACK_HUD] var/icon/IC = icon(icon, icon_state, dir) holder.pixel_y = IC.Height() - world.icon_size holder.icon_state = "hud_imp_tracking" + set_hud_image_active(IMPTRACK_HUD) + else if(istype(I, /obj/item/implant/chem)) holder = hud_list[IMPCHEM_HUD] var/icon/IC = icon(icon, icon_state, dir) holder.pixel_y = IC.Height() - world.icon_size holder.icon_state = "hud_imp_chem" + set_hud_image_active(IMPCHEM_HUD) + if(HAS_TRAIT(src, TRAIT_MINDSHIELD)) holder = hud_list[IMPLOYAL_HUD] var/icon/IC = icon(icon, icon_state, dir) holder.pixel_y = IC.Height() - world.icon_size holder.icon_state = "hud_imp_loyal" + set_hud_image_active(IMPLOYAL_HUD) /mob/living/carbon/human/proc/sec_hud_set_security_status() var/image/holder = hud_list[WANTED_HUD] - var/icon/I = icon(icon, icon_state, dir) - holder.pixel_y = I.Height() - world.icon_size - var/perpname = get_face_name(get_id_name("")) - if(perpname && GLOB.data_core) - var/datum/data/record/R = find_record("name", perpname, GLOB.data_core.security) - if(R) - switch(R.fields["criminal"]) - if("*Arrest*") - holder.icon_state = "hudwanted" - return - if("Search") - holder.icon_state = "hudsearch" - return - if("Incarcerated") - holder.icon_state = "hudincarcerated" - return - if("Suspected") - holder.icon_state = "hudsuspected" - return - if("Paroled") - holder.icon_state = "hudparolled" - return - if("Discharged") - holder.icon_state = "huddischarged" - return - holder.icon_state = null + var/icon/sec_icon = icon(icon, icon_state, dir) + holder.pixel_y = sec_icon.Height() - world.icon_size + var/perp_name = get_face_name(get_id_name("")) + + if(!perp_name || !GLOB.data_core) + holder.icon_state = null + set_hud_image_inactive(WANTED_HUD) + return + + + var/datum/data/record/target = find_record("name", perp_name, GLOB.data_core.security) + if(!target || target.fields["criminal"] == WANTED_NONE) + holder.icon_state = null + set_hud_image_inactive(WANTED_HUD) + return + + switch(target.fields["criminal"]) + if(WANTED_ARREST) + holder.icon_state = "hudwanted" + if(WANTED_PRISONER) + holder.icon_state = "hudincarcerated" + if(WANTED_SUSPECT) + holder.icon_state = "hudsuspected" + if(WANTED_PAROLE) + holder.icon_state = "hudparolled" + if(WANTED_DISCHARGED) + holder.icon_state = "huddischarged" + + set_hud_image_active(WANTED_HUD) /*********************************************** - Diagnostic HUDs! +Diagnostic HUDs! ************************************************/ /mob/living/proc/hud_set_nanite_indicator() @@ -321,10 +339,13 @@ holder.pixel_y = I.Height() - world.icon_size if(!shell) //Not an AI shell holder.icon_state = null + set_hud_image_inactive(DIAG_TRACK_HUD) + return else if(deployed) //AI shell in use by an AI holder.icon_state = "hudtrackingai" - else //Empty AI shell + else //Empty AI shell holder.icon_state = "hudtracking" + set_hud_image_active(DIAG_TRACK_HUD) //AI side tracking of AI shell control /mob/living/silicon/ai/proc/diag_hud_set_deployed() //Shows tracking beacons on the mech @@ -333,8 +354,10 @@ holder.pixel_y = I.Height() - world.icon_size if(!deployed_shell) holder.icon_state = null + set_hud_image_inactive(DIAG_TRACK_HUD) else //AI is currently controlling a shell holder.icon_state = "hudtrackingai" + set_hud_image_active(DIAG_TRACK_HUD) /*~~~~~~~~~~~~~~~~~~~~ BIG STOMPY MECHS @@ -356,16 +379,19 @@ else holder.icon_state = "hudnobatt" - /obj/mecha/proc/diag_hud_set_mechstat() var/image/holder = hud_list[DIAG_STAT_HUD] var/icon/I = icon(icon, icon_state, dir) holder.pixel_y = I.Height() - world.icon_size - holder.icon_state = null if(internal_damage) holder.icon_state = "hudwarn" + set_hud_image_active(DIAG_STAT_HUD) + return + holder.icon_state = null + set_hud_image_inactive(DIAG_STAT_HUD) -/obj/mecha/proc/diag_hud_set_mechtracking() //Shows tracking beacons on the mech +///Shows tracking beacons on the mech +/obj/mecha/proc/diag_hud_set_mechtracking() var/image/holder = hud_list[DIAG_TRACK_HUD] var/icon/I = icon(icon, icon_state, dir) holder.pixel_y = I.Height() - world.icon_size @@ -420,12 +446,24 @@ else holder.icon_state = "" +/mob/living/simple_animal/bot/mulebot/proc/diag_hud_set_mulebotcell() + var/image/holder = hud_list[DIAG_BATT_HUD] + var/icon/I = icon(icon, icon_state, dir) + holder.pixel_y = I.Height() - world.icon_size + if(cell) + var/chargelvl = (cell.charge/cell.maxcharge) + holder.icon_state = "hudbatt[RoundDiagBar(chargelvl)]" + else + holder.icon_state = "hudnobatt" + /*~~~~~~~~~~~~ Airlocks! ~~~~~~~~~~~~~*/ /obj/machinery/door/airlock/proc/diag_hud_set_electrified() + if(secondsElectrified == MACHINE_NOT_ELECTRIFIED) + set_hud_image_inactive(DIAG_AIRLOCK_HUD) + return + var/image/holder = hud_list[DIAG_AIRLOCK_HUD] - if(secondsElectrified != MACHINE_NOT_ELECTRIFIED) - holder.icon_state = "electrified" - else - holder.icon_state = "" + holder.icon_state = "electrified" + set_hud_image_active(DIAG_AIRLOCK_HUD) diff --git a/code/game/gamemodes/bloodsuckers/bloodsucker.dm b/code/game/gamemodes/bloodsuckers/bloodsucker.dm index 60b56bb1354d..7ca2f485636a 100644 --- a/code/game/gamemodes/bloodsuckers/bloodsucker.dm +++ b/code/game/gamemodes/bloodsuckers/bloodsucker.dm @@ -38,14 +38,8 @@ if(!antag_candidates.len) break var/datum/mind/bloodsucker = antag_pick(antag_candidates) - //Yogs start -- fixes plasmaman vampires - var/species_type = bloodsucker?.current?.client.prefs.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type - var/noblood = (NOBLOOD in species.species_traits) - qdel(species) - - if(noblood) + if(!bloodsucker.prepare_bloodsucker(bloodsucker)) antag_candidates -= bloodsucker // kinda need to do this to prevent some edge-case infinite loop or whatever i-- // to undo the imminent increment continue diff --git a/code/game/gamemodes/bloodsuckers/traitorsuckers.dm b/code/game/gamemodes/bloodsuckers/traitorsuckers.dm index dae0657ceb6e..d8ba9b27550a 100644 --- a/code/game/gamemodes/bloodsuckers/traitorsuckers.dm +++ b/code/game/gamemodes/bloodsuckers/traitorsuckers.dm @@ -19,7 +19,7 @@ var/list/possible_bloodsuckers = list() var/list/bloodsuckers = list() - var/const/bloodsucker_amount = 2 + var/const/bloodsucker_amount = 3 /datum/game_mode/traitor/bloodsucker/can_start() . = ..() diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm index 124129bcb5ac..fc7417dd8e6a 100644 --- a/code/game/gamemodes/brother/traitor_bro.dm +++ b/code/game/gamemodes/brother/traitor_bro.dm @@ -61,13 +61,3 @@ /datum/game_mode/traitor/bros/generate_report() return "It's Syndicate recruiting season. Be alert for potential Syndicate infiltrators, but also watch out for disgruntled employees trying to defect. Unlike Nanotrasen, the Syndicate prides itself in teamwork and will only recruit pairs that share a brotherly trust." - -/datum/game_mode/proc/update_brother_icons_added(datum/mind/brother_mind) - var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] - brotherhud.join_hud(brother_mind.current) - set_antag_hud(brother_mind.current, "brother") - -/datum/game_mode/proc/update_brother_icons_removed(datum/mind/brother_mind) - var/datum/atom_hud/antag/brotherhud = GLOB.huds[ANTAG_HUD_BROTHER] - brotherhud.leave_hud(brother_mind.current) - set_antag_hud(brother_mind.current, null) diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm index 086460d6798c..ac79486d1eff 100644 --- a/code/game/gamemodes/clock_cult/clock_cult.dm +++ b/code/game/gamemodes/clock_cult/clock_cult.dm @@ -55,7 +55,8 @@ Credit where due: if(M.mind) if(ishuman(M) && (M.mind.assigned_role in list("Captain", "Chaplain"))) return FALSE - if(M.mind.enslaved_to && !is_servant_of_ratvar(M.mind.enslaved_to)) + var/mob/living/master = M.mind.enslaved_to?.resolve() + if(master && !iscultist(master)) return FALSE if(M.mind.unconvertable) return FALSE @@ -260,18 +261,6 @@ Credit where due: working for this entity and utilizing highly-advanced technology to cross the great distance at will. If they should turn out to be a credible threat, the task falls on you and \ your crew to dispatch it in a timely manner." -/datum/game_mode/proc/update_servant_icons_added(datum/mind/M) - var/datum/atom_hud/antag/A = GLOB.huds[ANTAG_HUD_CLOCKWORK] - A.join_hud(M.current) - set_antag_hud(M.current, "clockwork") - -/datum/game_mode/proc/update_servant_icons_removed(datum/mind/M) - var/datum/atom_hud/antag/A = GLOB.huds[ANTAG_HUD_CLOCKWORK] - A.leave_hud(M.current) - set_antag_hud(M.current, null) - - - //Servant of Ratvar outfit /datum/outfit/servant_of_ratvar name = "Servant of Ratvar" diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm index b3c46194cedc..ec111782e7c5 100644 --- a/code/game/gamemodes/cult/cult.dm +++ b/code/game/gamemodes/cult/cult.dm @@ -26,7 +26,8 @@ return FALSE if(specific_cult && specific_cult.is_sacrifice_target(M.mind)) return FALSE - if(M.mind.enslaved_to && !iscultist(M.mind.enslaved_to)) + var/mob/living/master = M.mind.enslaved_to?.resolve() + if(master && !iscultist(master)) return FALSE if(M.mind.unconvertable) return FALSE @@ -137,16 +138,6 @@ cult_mind.current.Unconscious(100) return TRUE -/datum/game_mode/proc/update_cult_icons_added(datum/mind/cult_mind) - var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT] - culthud.join_hud(cult_mind.current) - set_antag_hud(cult_mind.current, "cult") - -/datum/game_mode/proc/update_cult_icons_removed(datum/mind/cult_mind) - var/datum/atom_hud/antag/culthud = GLOB.huds[ANTAG_HUD_CULT] - culthud.leave_hud(cult_mind.current) - set_antag_hud(cult_mind.current, null) - /datum/game_mode/cult/proc/check_cult_victory() return main_cult.check_cult_victory() @@ -244,7 +235,7 @@ cultist.health *= 0.75 else cultist.Stun(20) - cultist.confused += 15 //30 seconds of confusion + cultist.adjust_confusion(15 SECONDS) to_chat(cultist, span_narsiesmall("Your mind is flooded with pain as the last bloodstone is destroyed!")) /datum/game_mode/proc/cult_loss_anchor() @@ -263,8 +254,8 @@ cultist.maxHealth *= 0.5 cultist.health *= 0.5 else - cultist.Stun(40) - cultist.confused += 30 //one minute of confusion + cultist.Stun(4 SECONDS) + cultist.adjust_confusion(30 SECONDS) to_chat(cultist, span_narsiesmall("You feel a bleakness as the destruction of the anchor cuts off your connection to Nar-Sie!")) /datum/game_mode/proc/disable_bloodstone_cooldown() diff --git a/code/game/gamemodes/devil/game_mode.dm b/code/game/gamemodes/devil/game_mode.dm index 208a24f3e518..eca95a011310 100644 --- a/code/game/gamemodes/devil/game_mode.dm +++ b/code/game/gamemodes/devil/game_mode.dm @@ -14,23 +14,3 @@ validtypes -= type //prevent duplicate objectives, EXCEPT for buy_target. else objective.find_target() - -/datum/game_mode/proc/update_devil_icons_added(datum/mind/devil_mind) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_DEVIL] - hud.join_hud(devil_mind.current) - set_antag_hud(devil_mind.current, "devil") - -/datum/game_mode/proc/update_devil_icons_removed(datum/mind/devil_mind) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_DEVIL] - hud.leave_hud(devil_mind.current) - set_antag_hud(devil_mind.current, null) - -/datum/game_mode/proc/update_soulless_icons_added(datum/mind/soulless_mind) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SOULLESS] - hud.join_hud(soulless_mind.current) - set_antag_hud(soulless_mind.current, "soulless") - -/datum/game_mode/proc/update_soulless_icons_removed(datum/mind/soulless_mind) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SOULLESS] - hud.leave_hud(soulless_mind.current) - set_antag_hud(soulless_mind.current, null) diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm index c20e8e6d89f2..1f116ae2e841 100644 --- a/code/game/gamemodes/dynamic/dynamic.dm +++ b/code/game/gamemodes/dynamic/dynamic.dm @@ -748,7 +748,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1) rule.candidates = list(newPlayer) rule.trim_candidates() - if(!rule.candidates || isemptylist(rule.candidates)) + if(!rule.candidates || !length(rule.candidates)) continue if (rule.ready()) drafted_rules[rule] = rule.get_weight() diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm index db55db17b687..ba24abca5971 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm @@ -268,7 +268,7 @@ assigned -= selected_player message_admins("[ADMIN_LOOKUPFLW(selected_player)] was selected by the [name] ruleset, but couldn't be made into a Bloodsucker.") return FALSE - sucker.bloodsucker_level_unspent = rand(2,3) + sucker.bloodsucker_level_unspent = rand(3,4) message_admins("[ADMIN_LOOKUPFLW(selected_player)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.") log_game("DYNAMIC: [key_name(selected_player)] was selected by the [name] ruleset and has been made into a midround Bloodsucker.") return TRUE diff --git a/code/game/gamemodes/dynamic/readme.md b/code/game/gamemodes/dynamic/readme.md index f3a02856aba2..0b14eceead55 100644 --- a/code/game/gamemodes/dynamic/readme.md +++ b/code/game/gamemodes/dynamic/readme.md @@ -180,4 +180,4 @@ In `/datum/game_mode/dynamic/on_pre_random_event` (in `dynamic_hijacking.dm`), D ![Flow chart to describe the chain of events for Dynamic 2021 to take](https://user-images.githubusercontent.com/35135081/109071468-9cab7e00-76a8-11eb-8f9f-2b920c602ef4.png) -`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`. \ No newline at end of file +`n` is a random value between `random_event_hijack_minimum` and `random_event_hijack_maximum`. Injection chance, should it need to be raised, is increased by `hijacked_random_event_injection_chance`. diff --git a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm index 50ed7b771136..bd857f8740e6 100644 --- a/code/game/gamemodes/eldritch_cult/eldritch_cult.dm +++ b/code/game/gamemodes/eldritch_cult/eldritch_cult.dm @@ -60,17 +60,6 @@ cultie.add_antag_datum(new_antag) return ..() - -/datum/game_mode/proc/update_heretic_icons_added(datum/mind/heretic) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HERETIC] - hud.join_hud(heretic.current) - set_antag_hud(heretic.current, (IS_HERETIC(heretic.current) ? "heretic" : "heretic_beast")) - -/datum/game_mode/proc/update_heretic_icons_removed(datum/mind/heretic) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HERETIC] - hud.leave_hud(heretic.current) - set_antag_hud(heretic.current, null) - /datum/game_mode/heretics/generate_report() return "Cybersun Industries has announced that they have successfully raided a high-security library. The library contained a very dangerous book that was \ shown to possess anomalous properties. We suspect that the book has been copied over, Stay vigilant!" diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index d243a93c4cfe..6d446a17fbda 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -663,7 +663,7 @@ GLOBAL_LIST_EMPTY(possible_items) if(M.current.mind.assigned_role in possible_item.excludefromjob) continue check_items approved_targets += possible_item - return set_target(safepick(approved_targets)) + return set_target(pick(approved_targets)) /datum/objective/steal/proc/set_target(datum/objective_item/item) if(item) @@ -1452,10 +1452,10 @@ GLOBAL_LIST_EMPTY(possible_items_special) /datum/objective/survive/bloodsucker, /datum/objective/bloodsucker/protege, /datum/objective/bloodsucker/heartthief, + /datum/objective/bloodsucker/vassalhim, /datum/objective/bloodsucker/gourmand, // MISC OBJECTIVES // /datum/objective/bloodsucker/monsterhunter, - /datum/objective/bloodsucker/vassalhim, /datum/objective/bloodsucker/frenzy, // Fulp edit END /datum/objective/destroy, diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index e4e70490e728..3209c531e6a6 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -510,7 +510,7 @@ Class Procs: for(var/obj/item/B in W.contents) if(istype(B, P) && istype(A, P)) //won't replace beakers if they have reagents in them to prevent funny explosions - if(istype(B,/obj/item/reagent_containers) && !isemptylist(B.reagents?.reagent_list)) + if(istype(B,/obj/item/reagent_containers) && length(B.reagents?.reagent_list)) continue // If it's a corrupt or rigged cell, attempting to send it through Bluespace could have unforeseen consequences. if(istype(B, /obj/item/stock_parts/cell) && W.works_from_distance) diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index bb938f3c1044..bf08b083d6f2 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -475,7 +475,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list( if(obj_flags & EMAGGED) var/mob/living/M = user M.adjust_fire_stacks(5) - M.IgniteMob() //flew into a star, so you're on fire + M.ignite_mob() //flew into a star, so you're on fire to_chat(user, span_userdanger("You feel an immense wave of heat emanate from the arcade machine. Your skin bursts into flames.")) if(obj_flags & EMAGGED) @@ -548,7 +548,7 @@ GLOBAL_LIST_INIT(arcade_prize_pool, list( if(ORION_TRAIL_RAIDERS) if(prob(50)) to_chat(usr, span_userdanger("You hear battle shouts. The tramping of boots on cold metal. Screams of agony. The rush of venting air. Are you going insane?")) - M.hallucination += 30 + M.adjust_hallucinations(30 SECONDS) else to_chat(usr, span_userdanger("Something strikes you from behind! It hurts like hell and feel like a blunt weapon, but nothing is there...")) M.take_bodypart_damage(30) diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm index e9a1f892f595..7fabf134095c 100644 --- a/code/game/machinery/computer/security.dm +++ b/code/game/machinery/computer/security.dm @@ -749,7 +749,7 @@ if("criminal_status") if(active_security_record) - var/crime = input("Select a status", "Criminal Status Selection") as null|anything in list("None", "Arrest", "Search", "Incarcerated", "Suspected", "Paroled", "Discharged") + var/crime = tgui_input_list("Select a status", "Criminal Status Selection", list("None", "Arrest", "Search", "Incarcerated", "Suspected", "Paroled", "Discharged")) if(!crime) crime = "none" var/old_field = active_security_record.fields["criminal"] diff --git a/code/game/machinery/decontamination.dm b/code/game/machinery/decontamination.dm index d4c0fa783671..99be15974b39 100644 --- a/code/game/machinery/decontamination.dm +++ b/code/game/machinery/decontamination.dm @@ -105,7 +105,7 @@ mob_occupant.adjustFireLoss(rand(15, 26)) mob_occupant.radiation += 500 mob_occupant.adjust_fire_stacks(2) - mob_occupant.IgniteMob() + mob_occupant.ignite_mob() if(iscarbon(mob_occupant) && mob_occupant.stat < UNCONSCIOUS) //Awake, organic and screaming mob_occupant.emote("scream") diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 824030b0741f..5d06e50d6810 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -164,7 +164,7 @@ damage_deflection = AIRLOCK_DAMAGE_DEFLECTION_R prepare_huds() for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) diag_hud_set_electrified() rebuild_parts() @@ -440,7 +440,7 @@ D.removeMe(src) qdel(note) for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.remove_from_hud(src) + diag_hud.remove_atom_from_hud(src) if(brace) //yogs brace.remove() //yogs return ..() @@ -1814,7 +1814,7 @@ for(var/mob/living/carbon/human/H in orange(2,src)) H.Unconscious(160) H.adjust_fire_stacks(20) - H.IgniteMob() //Guaranteed knockout and ignition for nearby people + H.ignite_mob() //Guaranteed knockout and ignition for nearby people H.apply_damage(40, BRUTE, BODY_ZONE_CHEST) diff --git a/code/game/machinery/launch_pad.dm b/code/game/machinery/launch_pad.dm index b6278194a329..434fede510ca 100644 --- a/code/game/machinery/launch_pad.dm +++ b/code/game/machinery/launch_pad.dm @@ -30,20 +30,20 @@ . = ..() prepare_huds() for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) + update_hud() + +/obj/machinery/launchpad/proc/update_hud() var/image/holder = hud_list[DIAG_LAUNCHPAD_HUD] - var/mutable_appearance/MA = new /mutable_appearance() - MA.icon = 'icons/effects/effects.dmi' - MA.icon_state = "launchpad_target" - MA.layer = ABOVE_OPEN_TURF_LAYER - MA.plane = 0 - holder.appearance = MA + var/mutable_appearance/target = mutable_appearance('icons/effects/effects.dmi', "launchpad_target", ABOVE_OPEN_TURF_LAYER, src, GAME_PLANE) + holder.appearance = target update_indicator() /obj/machinery/launchpad/Destroy() - qdel(hud_list[DIAG_LAUNCHPAD_HUD]) + for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) + diag_hud.remove_atom_from_hud(src) return ..() /obj/machinery/launchpad/examine(mob/user) diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm index fe56ec2817f5..af2534ffe04a 100644 --- a/code/game/machinery/newscaster.dm +++ b/code/game/machinery/newscaster.dm @@ -203,6 +203,8 @@ GLOBAL_LIST_EMPTY(allCasters) var/datum/newscaster/feed_channel/viewing_channel = null var/allow_comments = 1 +/obj/machinery/newscaster/pai + /obj/machinery/newscaster/security_unit name = "security newscaster" securityCaster = 1 @@ -279,7 +281,7 @@ GLOBAL_LIST_EMPTY(allCasters) dat+="

The newscaster recognises you as: [scanned_user]" if(1) dat+= "Station Feed Channels
" - if( isemptylist(GLOB.news_network.network_channels) ) + if( !length(GLOB.news_network.network_channels) ) dat+="No active channels found..." else for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) @@ -359,7 +361,7 @@ GLOBAL_LIST_EMPTY(allCasters) dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
" dat+="No further feed story additions are allowed while the D-Notice is in effect.

" else - if( isemptylist(viewing_channel.messages) ) + if( !length(viewing_channel.messages) ) dat+="No feed messages found in channel...
" else var/i = 0 @@ -387,7 +389,7 @@ GLOBAL_LIST_EMPTY(allCasters) dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
" dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.
" dat+="
Select Feed channel to get Stories from:
" - if(isemptylist(GLOB.news_network.network_channels)) + if(!length(GLOB.news_network.network_channels)) dat+="No feed channels found active...
" else for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) @@ -398,7 +400,7 @@ GLOBAL_LIST_EMPTY(allCasters) dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's" dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed" dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
" - if(isemptylist(GLOB.news_network.network_channels)) + if(!length(GLOB.news_network.network_channels)) dat+="No feed channels found active...
" else for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) @@ -407,7 +409,7 @@ GLOBAL_LIST_EMPTY(allCasters) if(12) dat+="[viewing_channel.channel_name]: \[ created by: [viewing_channel.returnAuthor(-1)] \]
" dat+="[(viewing_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
" - if(isemptylist(viewing_channel.messages)) + if(!length(viewing_channel.messages)) dat+="No feed messages found in channel...
" else for(var/datum/newscaster/feed_message/MESSAGE in viewing_channel.messages) @@ -424,7 +426,7 @@ GLOBAL_LIST_EMPTY(allCasters) dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
" dat+="No further feed story additions are allowed while the D-Notice is in effect.

" else - if(isemptylist(viewing_channel.messages)) + if(!length(viewing_channel.messages)) dat+="No feed messages found in channel...
" else for(var/datum/newscaster/feed_message/MESSAGE in viewing_channel.messages) @@ -881,7 +883,7 @@ GLOBAL_LIST_EMPTY(allCasters) if(0) //Cover dat+="
The Griffon
" dat+="
Nanotrasen-standard newspaper, for use on Nanotrasen? Space Facilities

" - if(isemptylist(news_content)) + if(!length(news_content)) if(wantedAuthor) dat+="Contents:
    **Important Security Announcement** \[page [pages+2]\]
" else @@ -908,7 +910,7 @@ GLOBAL_LIST_EMPTY(allCasters) if(notContent(C.DclassCensorTime)) dat+="This channel was deemed dangerous to the general welfare of the station and therefore marked with a D-Notice. Its contents were not transferred to the newspaper at the time of printing." else - if(isemptylist(C.messages)) + if(!length(C.messages)) dat+="No Feed stories stem from this channel..." else var/i = 0 diff --git a/code/game/machinery/telecomms/computers/message.dm b/code/game/machinery/telecomms/computers/message.dm index ba3673650cf4..c41eca104edc 100644 --- a/code/game/machinery/telecomms/computers/message.dm +++ b/code/game/machinery/telecomms/computers/message.dm @@ -395,7 +395,7 @@ //Get out list of viable PDAs var/list/obj/item/pda/sendPDAs = get_viewable_pdas() if(GLOB.PDAs && GLOB.PDAs.len > 0) - customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sortNames(sendPDAs) + customrecepient = input(usr, "Select a PDA from the list.") as null|anything in sortUsernames(sendPDAs) else customrecepient = null diff --git a/code/game/mecha/equipment/tools/medical_tools.dm b/code/game/mecha/equipment/tools/medical_tools.dm index d304396ff9a8..0ae7242751ac 100644 --- a/code/game/mecha/equipment/tools/medical_tools.dm +++ b/code/game/mecha/equipment/tools/medical_tools.dm @@ -323,7 +323,7 @@ var/list/mobs = new for(var/mob/living/carbon/M in mechsyringe.loc) mobs += M - var/mob/living/carbon/M = safepick(mobs) + var/mob/living/carbon/M = pick(mobs) if(M) var/R mechsyringe.visible_message(span_attack(" [M] was hit by the syringe!")) diff --git a/code/game/mecha/equipment/weapons/melee_weapons.dm b/code/game/mecha/equipment/weapons/melee_weapons.dm index b894fa6168c9..c90f62c5c3e2 100644 --- a/code/game/mecha/equipment/weapons/melee_weapons.dm +++ b/code/game/mecha/equipment/weapons/melee_weapons.dm @@ -319,15 +319,15 @@ to_chat(H, span_warning("You muscles seize, making you collapse!")) else H.Paralyze(stunforce) - H.Jitter(20) - H.confused = max(8, H.confused) + H.adjust_jitter(20 SECONDS) + H.adjust_confusion(8 SECONDS) H.apply_effect(EFFECT_STUTTER, stunforce) else if(current_stamina_damage > 70) - H.Jitter(10) - H.confused = max(8, H.confused) + H.adjust_jitter(10 SECONDS) + H.adjust_confusion(8 SECONDS) H.apply_effect(EFFECT_STUTTER, stunforce) else if(current_stamina_damage >= 20) - H.Jitter(5) + H.adjust_jitter(5 SECONDS) H.apply_effect(EFFECT_STUTTER, stunforce) if(isliving(target)) @@ -359,7 +359,7 @@ else L.apply_damage(burn_damage, BURN) L.adjust_fire_stacks(2) - L.IgniteMob() + L.ignite_mob() playsound(L, 'sound/items/welder.ogg', 50, 1) /obj/item/mecha_parts/mecha_equipment/melee_weapon/sword/maul diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm index 5a08cfbb10dd..13d37cdd771a 100644 --- a/code/game/mecha/equipment/weapons/weapons.dm +++ b/code/game/mecha/equipment/weapons/weapons.dm @@ -228,14 +228,14 @@ continue to_chat(M, "HONK") M.SetSleeping(0) - M.stuttering += 20 + M.adjust_stutter(2 SECONDS) M.adjustEarDamage(0, 30) - M.Paralyze(60) + M.Paralyze(6 SECONDS) if(prob(30)) - M.Stun(200) - M.Unconscious(80) + M.Stun(20 SECONDS) + M.Unconscious(8 SECONDS) else - M.Jitter(500) + M.adjust_jitter(50 SECONDS) log_message("Honked from [src.name]. HONK!", LOG_MECHA) var/turf/T = get_turf(src) diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index e8183edbcd3b..acbe6390b333 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -161,7 +161,7 @@ GLOB.mechas_list += src //global mech list prepare_huds() for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) diag_hud_set_mechhealth() diag_hud_set_mechcell() diag_hud_set_mechstat() @@ -511,7 +511,7 @@ if (occupant && !enclosed && !silicon_pilot) if (occupant.fire_stacks < 5) occupant.adjust_fire_stacks(1) - occupant.IgniteMob() + occupant.ignite_mob() /obj/mecha/proc/drop_item()//Derpfix, but may be useful in future for engineering exosuits. return @@ -558,7 +558,7 @@ if(!omnidirectional_attacks && dir_to_target && !(dir_to_target & dir))//wrong direction return if(internal_damage & MECHA_INT_CONTROL_LOST) - target = safepick(view(3,target)) + target = pick(view(3,target)) if(!target) return @@ -594,7 +594,7 @@ selected.start_cooldown() else if(internal_damage & MECHA_INT_CONTROL_LOST) - target = safepick(oview(1,src)) + target = pick(oview(1,src)) if(!melee_can_hit || !istype(target, /atom)) return if(equipment_disabled) @@ -773,19 +773,19 @@ /////////////////////////////////// /obj/mecha/proc/check_for_internal_damage(list/possible_int_damage,ignore_threshold=null) - if(!islist(possible_int_damage) || isemptylist(possible_int_damage)) + if(!islist(possible_int_damage) || !length(possible_int_damage)) return if(prob(20)) if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) for(var/T in possible_int_damage) if(internal_damage & T) possible_int_damage -= T - var/int_dam_flag = safepick(possible_int_damage) + var/int_dam_flag = pick(possible_int_damage) if(int_dam_flag) setInternalDamage(int_dam_flag) if(prob(5)) if(ignore_threshold || obj_integrity*100/max_integrity < internal_damage_threshold) - var/obj/item/mecha_parts/mecha_equipment/ME = safepick(equipment) + var/obj/item/mecha_parts/mecha_equipment/ME = pick(equipment) if(ME) qdel(ME) return diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm index 19e51f64d17b..dfde2b7697ee 100644 --- a/code/game/mecha/mecha_actions.dm +++ b/code/game/mecha/mecha_actions.dm @@ -23,7 +23,7 @@ strafing_action.Remove(user) /datum/action/innate/mecha - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS icon_icon = 'icons/mob/actions/actions_mecha.dmi' var/obj/mecha/chassis @@ -58,7 +58,7 @@ button_icon_state = "mech_internals_[chassis.use_internal_tank ? "on" : "off"]" chassis.occupant_message("Now taking air from [chassis.use_internal_tank?"internal airtank":"environment"].") chassis.log_message("Now taking air from [chassis.use_internal_tank?"internal airtank":"environment"].", LOG_MECHA) - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/mecha/mech_cycle_equip name = "Cycle Equipment. Can also be cycled with throwmode." @@ -82,7 +82,7 @@ chassis.occupant_message("You select [chassis.selected]") send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list()) button_icon_state = "mech_cycle_equip_on" - UpdateButtonIcon() + UpdateButtons() return var/number = 0 for(var/A in available_equipment) @@ -101,7 +101,7 @@ chassis.occupant_message("You switch to [chassis.selected]") button_icon_state = "mech_cycle_equip_on" send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list()) - UpdateButtonIcon() + UpdateButtons() return @@ -120,7 +120,7 @@ chassis.set_light_on(chassis.lights) chassis.occupant_message("Toggled lights [chassis.lights?"on":"off"].") chassis.log_message("Toggled lights [chassis.lights?"on":"off"].", LOG_MECHA) - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/mecha/mech_view_stats name = "View Stats" @@ -151,7 +151,7 @@ occupant_message("Toggled strafing mode [strafe?"on":"off"].") log_message("Toggled strafing mode [strafe?"on":"off"].", LOG_MECHA) - strafing_action.UpdateButtonIcon() + strafing_action.UpdateButtons() //////////////////////////////////////// Specific Ability Actions /////////////////////////////////////////////// //Need to be granted by the mech type, Not default abilities. @@ -189,7 +189,7 @@ chassis.deflect_chance -= chassis.defence_mode_deflect_chance chassis.occupant_message(span_danger("You disable [chassis] defence mode.")) chassis.log_message("Toggled defence mode.", LOG_MECHA) - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/mecha/mech_overload_mode name = "Toggle leg actuators overload" @@ -218,7 +218,7 @@ chassis.step_in = initial(chassis.step_in) chassis.step_energy_drain = chassis.normal_step_energy_drain chassis.occupant_message(span_notice("You disable leg actuators overload.")) - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/mecha/mech_smoke name = "Smoke" @@ -252,7 +252,7 @@ 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() + UpdateButtons() /datum/action/innate/mecha/mech_switch_damtype name = "Reconfigure arm microtool arrays" @@ -275,7 +275,7 @@ chassis.damtype = new_damtype button_icon_state = "mech_damtype_[new_damtype]" playsound(src, 'sound/mecha/mechmove01.ogg', 50, 1) - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/mecha/mech_toggle_phasing name = "Toggle Phasing" @@ -287,4 +287,4 @@ chassis.phasing = !chassis.phasing button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]" chassis.occupant_message("En":"#f00\">Dis"]abled phasing.") - UpdateButtonIcon() + UpdateButtons() diff --git a/code/game/mecha/medical/odysseus.dm b/code/game/mecha/medical/odysseus.dm index 323e77d5736c..4a27ac697a90 100644 --- a/code/game/mecha/medical/odysseus.dm +++ b/code/game/mecha/medical/odysseus.dm @@ -14,13 +14,13 @@ . = ..() if(.) var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.add_hud_to(H) + hud.show_to(H) /obj/mecha/medical/odysseus/go_out() if(isliving(occupant)) var/mob/living/L = occupant var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.remove_hud_from(L) + hud.hide_from(L) ..() /obj/mecha/medical/odysseus/mmi_moved_inside(obj/item/mmi/mmi_as_oc, mob/user) @@ -28,4 +28,4 @@ if(.) var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] var/mob/living/brain/B = mmi_as_oc.brainmob - hud.add_hud_to(B) + hud.show_to(B) diff --git a/code/game/mecha/working/clarke.dm b/code/game/mecha/working/clarke.dm index a360e565b88d..04f6fa9cbb1e 100644 --- a/code/game/mecha/working/clarke.dm +++ b/code/game/mecha/working/clarke.dm @@ -35,13 +35,13 @@ . = ..() if(.) var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED] - hud.add_hud_to(H) + hud.show_to(H) /obj/mecha/working/clarke/go_out() if(isliving(occupant)) var/mob/living/L = occupant var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED] - hud.remove_hud_from(L) + hud.hide_from(L) return ..() /obj/mecha/working/clarke/mmi_moved_inside(obj/item/mmi/M, mob/user) @@ -49,7 +49,7 @@ if(.) var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED] var/mob/living/brain/B = M.brainmob - hud.add_hud_to(B) + hud.show_to(B) //Ore Box Controls diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index f405ade11d6a..d2ba03578acf 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -94,7 +94,7 @@ if(.) if(resistance_flags & ON_FIRE) //Sets the mob on fire if you buckle them to a burning atom/movableect M.adjust_fire_stacks(1) - M.IgniteMob() + M.ignite_mob() /atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force=FALSE) if(istype(buckled_mob) && buckled_mob.buckled == src && (buckled_mob.can_unbuckle() || force)) @@ -142,20 +142,19 @@ span_italics("You hear metal clanking.")) /atom/movable/proc/user_unbuckle_mob(mob/living/buckled_mob, mob/user) - var/mob/living/M = unbuckle_mob(buckled_mob) - if(M) - if(M != user) - M.visible_message(\ - span_notice("[user] unbuckles [M] from [src]."),\ + if(unbuckle_mob(buckled_mob)) + if(buckled_mob != user) + buckled_mob.visible_message(\ + span_notice("[user] unbuckles [buckled_mob] from [src]."),\ span_notice("[user] unbuckles you from [src]."),\ span_italics("You hear metal clanking.")) else - M.visible_message(\ - span_notice("[M] unbuckles [M.p_them()]self from [src]."),\ + buckled_mob.visible_message(\ + span_notice("[buckled_mob] unbuckles [buckled_mob.p_them()]self from [src]."),\ span_notice("You unbuckle yourself from [src]."),\ span_italics("You hear metal clanking.")) add_fingerprint(user) - if(isliving(M.pulledby)) - var/mob/living/L = M.pulledby - L.set_pull_offsets(M, L.grab_state) - return M + if(isliving(buckled_mob.pulledby)) + var/mob/living/L = buckled_mob.pulledby + L.set_pull_offsets(buckled_mob, L.grab_state) + return buckled_mob diff --git a/code/game/objects/effects/anomalies.dm b/code/game/objects/effects/anomalies.dm index 96b0f0e59d2f..815690ec0510 100644 --- a/code/game/objects/effects/anomalies.dm +++ b/code/game/objects/effects/anomalies.dm @@ -203,7 +203,7 @@ do_teleport(AM, locate(AM.x, AM.y, AM.z), 8, channel = TELEPORT_CHANNEL_BLUESPACE) /obj/effect/anomaly/bluespace/detonate() - var/turf/T = safepick(get_area_turfs(impact_area)) + var/turf/T = pick(get_area_turfs(impact_area)) if(T) // Calculate new position (searches through beacons in world) var/obj/item/beacon/chosen diff --git a/code/game/objects/effects/blessing.dm b/code/game/objects/effects/blessing.dm index 8328786d42da..4d3ff290e50b 100644 --- a/code/game/objects/effects/blessing.dm +++ b/code/game/objects/effects/blessing.dm @@ -15,7 +15,7 @@ var/image/I = image(icon = 'icons/effects/effects.dmi', icon_state = "blessed", layer = ABOVE_OPEN_TURF_LAYER, loc = src) I.alpha = 64 I.appearance_flags = RESET_ALPHA - add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessedAware, "blessing", I) + add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/blessed_aware, "blessing", I) RegisterSignal(loc, COMSIG_ATOM_INTERCEPT_TELEPORT, .proc/block_cult_teleport) /obj/effect/blessing/Destroy() diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 677af3020d24..2b7f9eb359a7 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -94,11 +94,28 @@ update_icon() H.update_inv_shoes() + +/** + * Checks if this decal is a valid decal that can be blood crawled in. + */ /obj/effect/decal/cleanable/proc/can_bloodcrawl_in() if((blood_state != BLOOD_STATE_OIL) && (blood_state != BLOOD_STATE_NOT_BLOODY)) return bloodiness - else - return 0 + 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 /obj/effect/decal/cleanable/wash(clean_types) ..() diff --git a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm index 7480b2a40330..af33db7a7140 100644 --- a/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm +++ b/code/game/objects/effects/effect_system/fluid_spread/effects_foam.dm @@ -226,7 +226,7 @@ /obj/effect/particle_effect/fluid/foam/firefighting/Initialize(mapload) . = ..() - //RemoveElement(/datum/element/atmos_sensitive) + //Remove_element(/datum/element/atmos_sensitive) /obj/effect/particle_effect/fluid/foam/firefighting/process() ..() diff --git a/code/game/objects/effects/forcefields.dm b/code/game/objects/effects/forcefields.dm index d3fa9ce2c122..fb1275636884 100644 --- a/code/game/objects/effects/forcefields.dm +++ b/code/game/objects/effects/forcefields.dm @@ -1,38 +1,63 @@ /obj/effect/forcefield - desc = "A space wizard's magic wall." name = "FORCEWALL" + desc = "A space wizard's magic wall." icon_state = "m_shield" anchored = TRUE opacity = 0 density = TRUE CanAtmosPass = ATMOS_PASS_DENSITY - 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 /obj/effect/forcefield/Initialize() . = ..() - if(timeleft) - QDEL_IN(src, timeleft) + if(initial_duration) + 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, charge_cost = 0)) + return TRUE + + return ..() + +/// Cult forcefields /obj/effect/forcefield/cult - desc = "An unholy shield that blocks all attacks." name = "glowing wall" + desc = "An unholy shield that blocks all attacks." icon = 'icons/effects/cult_effects.dmi' icon_state = "cultshield" CanAtmosPass = ATMOS_PASS_NO - timeleft = 200 + initial_duration = 20 SECONDS -///////////Mimewalls/////////// +/// Mime forcefields (invisible walls) /obj/effect/forcefield/mime - icon_state = "nothing" name = "invisible wall" + icon_state = "nothing" desc = "You have a bad feeling about this." alpha = 0 /obj/effect/forcefield/mime/advanced name = "invisible blockade" desc = "You're gonna be here awhile." - timeleft = 600 + initial_duration = 1 MINUTES diff --git a/code/game/objects/effects/phased_mob.dm b/code/game/objects/effects/phased_mob.dm index d12e1dd90296..28f201262e6e 100644 --- a/code/game/objects/effects/phased_mob.dm +++ b/code/game/objects/effects/phased_mob.dm @@ -5,32 +5,60 @@ 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() - // Eject contents if deleted somehow - var/atom/dest = drop_location() - if(!dest) //You're in nullspace you clown - return ..() - var/area/destination_area = get_area(dest) - var/failed_areacheck = FALSE - if(destination_area.noteleport) - failed_areacheck = TRUE - for(var/_phasing_in in contents) - var/atom/movable/phasing_in = _phasing_in - if(!failed_areacheck) - phasing_in.forceMove(drop_location()) - else //this ONLY happens if someone uses a phasing effect to try to land in a NOTELEPORT zone after it is created, AKA trying to exploit. - if(isliving(phasing_in)) - var/mob/living/living_cheaterson = phasing_in - to_chat(living_cheaterson, span_userdanger("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, /mob/living/carbon.proc/vomit), 2 SECONDS) - phasing_in.forceMove(find_safe_turf(z)) + 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.area_flags & NOTELEPORT) + // this ONLY happens if someone uses a phasing effect + // to try to land in a NOTELEPORT zone after it is created, AKA trying to exploit. + if(isliving(jaunter)) + var/mob/living/living_cheaterson = jaunter + to_chat(living_cheaterson, span_userdanger("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 @@ -61,8 +89,3 @@ to_chat(user, span_danger("Some dull, universal force is blocking the way. It's overwhelmingly oppressive force feels dangerous.")) return return newloc - -/// React to signals by deleting the effect. Used for bloodcrawl. -/obj/effect/dummy/phased_mob/proc/deleteself(mob/living/source, obj/effect/decal/cleanable/phase_in_decal) - SHOULD_NOT_SLEEP(TRUE) - qdel(src) diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm index 0f354533774f..69436a3256f9 100644 --- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm +++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm @@ -110,13 +110,25 @@ icon_state = "phaseout" /obj/effect/temp_visual/dir_setting/wraith - name = "blood" - icon = 'icons/mob/mob.dmi' - icon_state = "phase_shift2" - duration = 1.2 SECONDS + name = "shadow" + icon = 'icons/mob/nonhuman-player/cult.dmi' + icon_state = "phase_shift2_cult" + duration = 0.6 SECONDS + +/obj/effect/temp_visual/dir_setting/wraith/angelic + icon_state = "phase_shift2_holy" + +/obj/effect/temp_visual/dir_setting/wraith/mystic + icon_state = "phase_shift2_wizard" /obj/effect/temp_visual/dir_setting/wraith/out - icon_state = "phase_shift" + icon_state = "phase_shift_cult" + +/obj/effect/temp_visual/dir_setting/wraith/out/angelic + icon_state = "phase_shift_holy" + +/obj/effect/temp_visual/dir_setting/wraith/out/mystic + icon_state = "phase_shift_wizard" /obj/effect/temp_visual/dir_setting/tailsweep icon_state = "tailsweep" @@ -420,7 +432,7 @@ /obj/effect/temp_visual/love_heart/invisible/Initialize(mapload, mob/seer) . = ..() var/image/I = image(icon = 'icons/effects/effects.dmi', icon_state = "heart", layer = ABOVE_MOB_LAYER, loc = src) - add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/onePerson, "heart", I, seer) + add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/one_person, "heart", I, seer) I.alpha = 255 I.appearance_flags = RESET_ALPHA animate(I, alpha = 0, time = duration) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index cac69751597c..cda9c936852b 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -4,8 +4,6 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE) // if true, everyone item when created will have its name changed to be // more... RPG-like. -GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons/effects/welding_effect.dmi', "welding_sparks", GASFIRE_LAYER, ABOVE_LIGHTING_PLANE)) - /obj/item name = "item" icon = 'icons/obj/misc.dmi' @@ -161,8 +159,11 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons attack_verb = typelist("attack_verb", attack_verb) . = ..() + + // Handle adding item associated actions for(var/path in actions_types) - new path(src) + add_item_action(path) + actions_types = null if(GLOB.rpg_loot_items) @@ -192,10 +193,56 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons 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) + 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/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 @@ -426,9 +473,11 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons /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) + if(item_flags & DROPDEL) qdel(src) item_flags &= ~IN_INVENTORY @@ -455,10 +504,11 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons /obj/item/proc/equipped(mob/user, slot, initial = FALSE) SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, 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) + item_flags |= IN_INVENTORY if(!initial) if(equip_sound && !initial &&(slot_flags & slotdefine2slotbit(slot))) @@ -466,7 +516,19 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons else if(slot == SLOT_HANDS) playsound(src, pickup_sound, PICKUP_SOUND_VOLUME, ignore_walls = FALSE) -//sometimes we only want to grant the item's action if it's equipped in a specific slot. +/// 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 == SLOT_IN_BACKPACK || slot == SLOT_LEGCUFFED) //these aren't true slots, so avoid granting actions there return FALSE @@ -597,7 +659,7 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons SEND_SIGNAL(src, COMSIG_MOVABLE_IMPACT, hit_atom, throwingdatum) if(is_hot() && isliving(hit_atom)) var/mob/living/L = hit_atom - L.IgniteMob() + L.ignite_mob() var/itempush = 1 if(w_class < 4) itempush = 0 //too light to push anything diff --git a/code/game/objects/items/RCD.dm b/code/game/objects/items/RCD.dm index e576740340c0..b338d205c955 100644 --- a/code/game/objects/items/RCD.dm +++ b/code/game/objects/items/RCD.dm @@ -1028,9 +1028,13 @@ RLD /obj/item/rcd_upgrade/furnishing desc = "It contains the design for chairs, stools, tables, and glass tables." upgrade = RCD_UPGRADE_FURNISHING + /obj/item/rcd_upgrade/conveyor desc = "The disk warns against building an endless conveyor trap, but we know what you're gonna do." upgrade = RCD_UPGRADE_CONVEYORS + +/datum/action/item_action/pick_color + name = "Choose A Color" #undef GLOW_MODE #undef LIGHT_MODE diff --git a/code/game/objects/items/RCL.dm b/code/game/objects/items/RCL.dm index da0e1b9891fb..141e973984f5 100644 --- a/code/game/objects/items/RCL.dm +++ b/code/game/objects/items/RCL.dm @@ -327,3 +327,13 @@ else icon_state = "rclg-1" item_state = "rclg-1" + +/datum/action/item_action/rcl_col + name = "Change Cable Color" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "rcl_rainbow" + +/datum/action/item_action/rcl_gui + name = "Toggle Fast Wiring Gui" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "rcl_gui" \ No newline at end of file diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index c27483213f97..a53bece87f98 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -99,7 +99,7 @@ if (prob(5)) var/mob/living/M = user M.adjust_fire_stacks(1) - M.IgniteMob() + M.ignite_mob() to_chat(user, span_danger("The card shorts out and catches fire in your hands!")) log_combat(user, target, "attempted to emag") if (!istype(target, /obj/machinery/computer/cargo)) @@ -392,6 +392,7 @@ update_label("John Doe", "Clowny") chameleon_action.chameleon_type = /obj/item/card/id chameleon_action.chameleon_name = "ID Card" chameleon_action.initialize_disguises() + add_item_action(chameleon_card_action) /obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) if(!proximity) @@ -586,7 +587,7 @@ update_label("John Doe", "Clowny") if(isliving(loc)) var/mob/living/M = loc M.adjust_fire_stacks(1) - M.IgniteMob() + M.ignite_mob() if(istype(loc,/obj/structure/fireaxecabinet/bridge/spare)) //if somebody is being naughty and putting the temporary spare in the cabinet var/obj/structure/fireaxecabinet/bridge/spare/holder = loc forceMove(holder.loc) diff --git a/code/game/objects/items/chromosome.dm b/code/game/objects/items/chromosome.dm index 509797fffe59..eed29612a627 100644 --- a/code/game/objects/items/chromosome.dm +++ b/code/game/objects/items/chromosome.dm @@ -34,9 +34,13 @@ 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/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm index 04b83d8b0b3c..cfb11cef213d 100644 --- a/code/game/objects/items/cigs_lighters.dm +++ b/code/game/objects/items/cigs_lighters.dm @@ -75,7 +75,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM /obj/item/match/attack(mob/living/carbon/M, mob/living/carbon/user) if(!isliving(M)) return - if(lit && M.IgniteMob()) + if(lit && M.ignite_mob()) message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) @@ -255,7 +255,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM var/turf/location = get_turf(src) var/mob/living/M = loc if(isliving(loc)) - M.IgniteMob() + M.ignite_mob() smoketime -= delta_time if(smoketime <= 0) new type_butt(location) @@ -651,7 +651,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM . = ..() /obj/item/lighter/attack(mob/living/carbon/M, mob/living/carbon/user) - if(lit && M.IgniteMob()) + if(lit && M.ignite_mob()) message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(M)] on fire with [src] at [AREACOORD(user)]") log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") var/obj/item/clothing/mask/cigarette/cig = help_light_cig(M) @@ -889,7 +889,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM if(reagents.get_reagent_amount(/datum/reagent/fuel)) //HOT STUFF C.fire_stacks = 2 - C.IgniteMob() + C.ignite_mob() if(reagents.get_reagent_amount(/datum/reagent/toxin/plasma)) // the plasma explodes when exposed to fire var/datum/effect_system/reagents_explosion/e = new() @@ -915,7 +915,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM var/mob/living/M = loc if(isliving(loc)) - M.IgniteMob() + M.ignite_mob() vapetime += delta_time diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index 396cd04cf103..ec83f4c08b7d 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -668,7 +668,7 @@ C.blur_eyes(3) C.blind_eyes(1) if(C.get_eye_protection() <= 0) // no eye protection? ARGH IT BURNS. - C.confused = max(C.confused, 3) + C.adjust_confusion(3) C.Knockdown(20) if(ishuman(C) && actually_paints) var/mob/living/carbon/human/H = C diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index 612c1e26d3e8..968dcf9c4fad 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -192,7 +192,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/defibrillator/proc/make_paddles() return new /obj/item/twohanded/shockpaddles(src) @@ -530,7 +530,7 @@ H.apply_damage(50, BURN, BODY_ZONE_CHEST) log_combat(user, H, "overloaded the heart of", defib) H.Paralyze(100) - H.Jitter(100) + H.adjust_jitter(100 SECONDS) if(req_defib) defib.deductcharge(revivecost) cooldown = TRUE @@ -624,7 +624,7 @@ H.set_heartattack(FALSE) H.revive() H.emote("gasp") - H.Jitter(100) + H.adjust_jitter(100 SECONDS) SEND_SIGNAL(H, COMSIG_LIVING_MINOR_SHOCK) SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "saved_life", /datum/mood_event/saved_life) log_combat(user, H, "revived", defib) diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 5c24164cf374..86dc0df46738 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -952,7 +952,7 @@ GLOBAL_LIST_EMPTY(PDAs) update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/pda/proc/remove_pen() diff --git a/code/game/objects/items/devices/busterarm/_buster.dm b/code/game/objects/items/devices/busterarm/_buster.dm index c41ad88379bc..9a8f93a70d0f 100644 --- a/code/game/objects/items/devices/busterarm/_buster.dm +++ b/code/game/objects/items/devices/busterarm/_buster.dm @@ -11,7 +11,7 @@ */ /// Base for all buster arm actions /datum/action/cooldown/buster - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS transparent_when_unavailable = TRUE icon_icon = 'icons/mob/actions/actions_arm.dmi' diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index ca7140b04390..e28f7fcf8b09 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -39,7 +39,7 @@ playsound(user, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, 1) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() return 1 /obj/item/flashlight/suicide_act(mob/living/carbon/human/user) diff --git a/code/game/objects/items/devices/multitool.dm b/code/game/objects/items/devices/multitool.dm index e6ef0f3c5af6..6ecde25cf606 100644 --- a/code/game/objects/items/devices/multitool.dm +++ b/code/game/objects/items/devices/multitool.dm @@ -44,25 +44,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/aiEye/remote/ai_detector/eye - var/datum/action/item_action/toggle_multitool/toggle_action /obj/item/multitool/ai_detect/Initialize() . = ..() START_PROCESSING(SSfastprocess, src) eye = new /mob/camera/aiEye/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 ..() @@ -104,8 +102,8 @@ var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] PM.alpha = 64 var/datum/atom_hud/H = GLOB.huds[hud_type] - if(!H.hudusers[user]) - H.add_hud_to(user) + if(!H.hud_users[user]) + H.show_to(user) eye.eye_user = user eye.setLoc(get_turf(src)) @@ -114,7 +112,7 @@ var/atom/movable/screen/plane_master/camera_static/PM = user.hud_used.plane_masters["[CAMERA_STATIC_PLANE]"] PM.alpha = 255 var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) if(eye) eye.setLoc(null) eye.eye_user = null diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 8a20d96c6b0e..3e7136da6c92 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -16,13 +16,13 @@ return OXYLOSS /obj/item/paicard/Initialize() - SSpai.pai_card_list += src + SSpai.paicard_list += src add_overlay("pai-off") return ..() /obj/item/paicard/Destroy() //Will stop people throwing friend pAIs into the singularity so they can respawn - SSpai.pai_card_list -= src + SSpai.paicard_list -= src if (!QDELETED(pai)) QDEL_NULL(pai) return ..() diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm index 057959175173..968105b19a58 100644 --- a/code/game/objects/items/granters.dm +++ b/code/game/objects/items/granters.dm @@ -107,13 +107,13 @@ to_chat(owner, span_notice("You will now fold origami planes.")) button_icon_state = "origami_on" active = TRUE - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/origami/Deactivate() to_chat(owner, span_notice("You will no longer fold origami planes.")) button_icon_state = "origami_off" active = FALSE - UpdateButtonIcon() + UpdateButtons() ///SPELLS/// diff --git a/code/game/objects/items/granters/_granters.dm b/code/game/objects/items/granters/_granters.dm new file mode 100644 index 000000000000..69edf85e6411 --- /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, span_warning("You're already reading this!")) + return FALSE + if(user.is_blind()) + to_chat(user, span_warning("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, span_notice("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, span_notice("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, span_notice("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, span_notice("[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, span_warning("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, span_notice("You start reading about [action_name]...")) + +/obj/item/book/granter/action/on_reading_finished(mob/living/user) + to_chat(user, span_notice("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.UpdateButtons() \ No newline at end of file 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 000000000000..c7bcf0e0fb6e --- /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, span_notice("You learned how to make [initial(crafting_recipe_type.name)].")) \ No newline at end of file diff --git a/code/game/objects/items/granters/crafting/bone_notes.dm b/code/game/objects/items/granters/crafting/bone_notes.dm new file mode 100644 index 000000000000..ee86b5bfda05 --- /dev/null +++ b/code/game/objects/items/granters/crafting/bone_notes.dm @@ -0,0 +1,20 @@ +/obj/item/book/granter/crafting_recipe/boneyard_notes + name = "The Complete Works of Lavaland Bone Architecture" + desc = "Pried from the lead Archaeologist's cold, dead hands, this seems to explain how ancient bone architecture was erected long ago." + crafting_recipe_types = list( + /datum/crafting_recipe/rib, + /datum/crafting_recipe/boneshovel, + /datum/crafting_recipe/halfskull, + /datum/crafting_recipe/skull, + ) + icon = 'icons/obj/library.dmi' + icon_state = "boneworking_learing" + uses = INFINITY + remarks = list( + "Who knew you could bend bones that far back?", + "I guess that was much easier before the planet heated up...", + "So that's how they made those ruins survive the ashstorms. Neat!", + "The page is just filled with insane ramblings about some 'legion' thing.", + "But why would they need vinegar to polish the bones? And rags too?", + "You spend a few moments cleaning dirt and blood off of the page, yeesh.", + ) \ No newline at end of file diff --git a/code/game/objects/items/granters/crafting/cannon.dm b/code/game/objects/items/granters/crafting/cannon.dm new file mode 100644 index 000000000000..5d9fb50234c0 --- /dev/null +++ b/code/game/objects/items/granters/crafting/cannon.dm @@ -0,0 +1,19 @@ +/obj/item/book/granter/crafting_recipe/trash_cannon + name = "diary of a demoted engineer" + desc = "A lost journal. The engineer seems very deranged about their demotion." + crafting_recipe_types = list( + /datum/crafting_recipe/trash_cannon, + /datum/crafting_recipe/trashball, + ) + icon_state = "book1" + remarks = list( + "\"I'll show them! I'll build a CANNON!\"", + "\"Gunpowder is ideal, but i'll have to improvise...\"", + "\"I savor the look on the CE's face when I BLOW down the walls to engineering!\"", + "\"If the supermatter gets loose from my rampage, so be it!\"", + "\"I'VE GONE COMPLETELY MENTAL!\"", + ) + +/obj/item/book/granter/crafting_recipe/trash_cannon/recoil(mob/living/user) + to_chat(user, span_warning("The book turns to dust in your hands.")) + qdel(src) \ No newline at end of file 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 000000000000..c8f6f842f2a8 --- /dev/null +++ b/code/game/objects/items/granters/crafting/desserts.dm @@ -0,0 +1,20 @@ +/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/cocolavatart, + /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?", + ) \ No newline at end of file diff --git a/code/game/objects/items/granters/crafting/pipegun.dm b/code/game/objects/items/granters/crafting/pipegun.dm new file mode 100644 index 000000000000..50a3e8d519be --- /dev/null +++ b/code/game/objects/items/granters/crafting/pipegun.dm @@ -0,0 +1,19 @@ +/obj/item/book/granter/crafting_recipe/pipegun_prime + name = "diary of a dead assistant" + desc = "A battered journal. Looks like he had a pretty rough life." + crafting_recipe_types = list( + /datum/crafting_recipe/pipegun_prime + ) + icon_state = "book1" + remarks = list( + "He apparently mastered some lost guncrafting technique.", + "Why do I have to go through so many hoops to get this shitty gun?", + "That much Grey Bull cannot be healthy...", + "Did he drop this into a moisture trap? Yuck.", + "Toolboxing techniques, huh? I kinda just want to know how to make the gun.", + "What the hell does he mean by 'ancient warrior tradition'?", + ) + +/obj/item/book/granter/crafting_recipe/pipegun_prime/recoil(mob/living/user) + to_chat(user, span_warning("The book turns to dust in your hands.")) + qdel(src) \ No newline at end of file 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 000000000000..9a4b1cacd815 --- /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/cooldown/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, span_notice("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(span_warning("[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, span_warning("You're already far more versed in the spell [action_name] \ + than this flimsy how-to book can provide!")) + else + to_chat(user, span_warning("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, span_notice("You start reading about casting [action_name]...")) + +/obj/item/book/granter/action/spell/on_reading_finished(mob/living/user) + to_chat(user, span_notice("You feel like you've experienced enough to cast [action_name]!")) + var/datum/action/cooldown/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(span_warning("[src] glows dark for a second!")) + +/obj/item/book/granter/action/spell/recoil(mob/living/user) + user.visible_message(span_warning("[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/cooldown/spell) + for(var/datum/action/cooldown/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 = lowertext(initial(granted_action.name)) \ No newline at end of file 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 000000000000..3d1ed9a75ebe --- /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/cooldown/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/animal/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,span_notice("I say thee neigh")) //It still lives here \ No newline at end of file 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 000000000000..23b2e9629fcd --- /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/cooldown/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, span_warning("You go blind!")) + user.blind_eyes(10) \ No newline at end of file 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 000000000000..72ab119aa152 --- /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/cooldown/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,span_warning("[src] suddenly feels very warm!")) + empulse(src, 1, 1) \ No newline at end of file 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 000000000000..13f76d2e3fa3 --- /dev/null +++ b/code/game/objects/items/granters/magic/fireball.dm @@ -0,0 +1,27 @@ +/obj/item/book/granter/action/spell/fireball + granted_action = /datum/action/cooldown/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, + explosion_cause = src, + ) + qdel(src) \ No newline at end of file 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 000000000000..bf1228d72618 --- /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/cooldown/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, span_warning("You suddenly feel very solid!")) + user.Stun(4 SECONDS, ignore_canstun = TRUE) + user.petrify(6 SECONDS) \ No newline at end of file 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 000000000000..115883b4a387 --- /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/cooldown/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, span_warning("You're knocked down!")) + user.Paralyze(4 SECONDS) \ No newline at end of file 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 000000000000..b1b66b485f53 --- /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/cooldown/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/cooldown/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/cooldown/spell/pointed/projectile/finger_guns + action_name = "Finger Guns" + name = "Guide to Advanced Mimery Vol 2" + desc = "There aren't any words written..." \ No newline at end of file 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 000000000000..39f85bc67335 --- /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/cooldown/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(span_notice("[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, span_warning("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, span_notice("You stare at the book some more, but there doesn't seem to be anything else to learn...")) + return + + var/datum/action/cooldown/spell/pointed/mind_transfer/swapper = new(src) + + if(swapper.swap_minds(user, real_stored_swap)) + to_chat(user, span_warning("You're suddenly somewhere else... and someone else?!")) + to_chat(real_stored_swap, span_warning("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(span_warning("[src] fizzles slightly as it stops glowing!")) + + stored_swap_ref = null \ No newline at end of file diff --git a/code/game/objects/items/granters/magic/sacred_flame.dm b/code/game/objects/items/granters/magic/sacred_flame.dm new file mode 100644 index 000000000000..e416c6e6c990 --- /dev/null +++ b/code/game/objects/items/granters/magic/sacred_flame.dm @@ -0,0 +1,14 @@ +/obj/item/book/granter/action/spell/sacredflame + granted_action = /datum/action/cooldown/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...", + ) \ No newline at end of file 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 000000000000..e94d0ec1e2ba --- /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/cooldown/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,span_warning("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/cooldown/spell/smoke/lesser \ No newline at end of file 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 000000000000..ad0104498702 --- /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/cooldown/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,span_warning("[src] suddenly vanishes!")) + qdel(src) \ No newline at end of file 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 000000000000..a73c168794f5 --- /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, span_warning("You already know [martial_name]!")) + return FALSE + return TRUE + +/obj/item/book/granter/martial/on_reading_start(mob/user) + to_chat(user, span_notice("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") \ No newline at end of file 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 000000000000..1492657e25b2 --- /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, span_warning("[src] beeps ominously...")) + +/obj/item/book/granter/martial/cqc/recoil(mob/living/user) + to_chat(user, span_warning("[src] explodes!")) + playsound(src,'sound/effects/explosion1.ogg',40,TRUE) + user.flash_act(1, 1) + user.adjustBruteLoss(6) + user.adjustFireLoss(6) + qdel(src) \ No newline at end of file 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 000000000000..bda885a1bc9c --- /dev/null +++ b/code/game/objects/items/granters/martial_arts/plasma_fist.dm @@ -0,0 +1,35 @@ +/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) + +/obj/item/book/granter/martial/plasma_fist/nobomb + martial = /datum/martial_art/plasma_fist/nobomb \ No newline at end of file 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 000000000000..c17eb9d3997e --- /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) \ No newline at end of file diff --git a/code/game/objects/items/granters/origami.dm b/code/game/objects/items/granters/origami.dm new file mode 100644 index 000000000000..3dbded9b67be --- /dev/null +++ b/code/game/objects/items/granters/origami.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/Activate() + to_chat(owner, span_notice("You will now fold origami planes.")) + button_icon_state = "origami_on" + active = TRUE + UpdateButtons() + +/datum/action/innate/origami/Deactivate() + to_chat(owner, span_notice("You will no longer fold origami planes.")) + button_icon_state = "origami_off" + active = FALSE + UpdateButtons() \ No newline at end of file diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm index 3dbf4a31d196..2b3b97e74c90 100644 --- a/code/game/objects/items/holy_weapons.dm +++ b/code/game/objects/items/holy_weapons.dm @@ -838,13 +838,13 @@ ..() if(hud_type && slot == SLOT_GLASSES) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) + H.show_to(user) /obj/item/nullrod/servoskull/dropped(mob/living/carbon/human/user) ..() if(hud_type && istype(user) && user.glasses == src) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) /obj/item/nullrod/servoskull name = "servitor skull" desc = "Even in death, I still serve" @@ -864,17 +864,17 @@ if(hud_type && slot == SLOT_NECK) to_chat(user, "Sensory augmentation initiated") var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) + H.show_to(user) var/datum/atom_hud/H2 = GLOB.huds[hud_type2] - H2.add_hud_to(user) + H2.show_to(user) /obj/item/nullrod/servoskull/dropped(mob/living/carbon/human/user) ..() if(hud_type && istype(user) && user.wear_neck == src) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) var/datum/atom_hud/H2 = GLOB.huds[hud_type2] - H2.remove_hud_from(user) + H2.hide_from(user) /obj/item/nullrod/cross name = "golden crucifix" diff --git a/code/game/objects/items/implants/implant.dm b/code/game/objects/items/implants/implant.dm index fd79c112c9b1..96bcd5cb8e7c 100644 --- a/code/game/objects/items/implants/implant.dm +++ b/code/game/objects/items/implants/implant.dm @@ -2,8 +2,11 @@ name = "implant" icon = 'icons/obj/implants.dmi' icon_state = "generic" //Shows up as the action button icon + item_flags = DROPDEL + // 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. 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 var/mob/living/imp_in = null var/implant_color = "b" var/allow_multiple = FALSE @@ -23,6 +26,9 @@ /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 @@ -75,10 +81,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() @@ -92,9 +96,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) //bruh who did this (the change that was here befo' this one) 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 07ae3e10b20e..25a8dc00259b 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 var/cooldown = 30 diff --git a/code/game/objects/items/implants/implant_chem.dm b/code/game/objects/items/implants/implant_chem.dm index 23fe57355661..dac6fed8e8a2 100644 --- a/code/game/objects/items/implants/implant_chem.dm +++ b/code/game/objects/items/implants/implant_chem.dm @@ -2,7 +2,6 @@ name = "chem implant" desc = "Injects things." icon_state = "reagents" - activated = FALSE /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 ae1fef109dd9..c91014f6824f 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_exile.dm b/code/game/objects/items/implants/implant_exile.dm index 9b6820633896..29f0fc9a977c 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 db3994cb037c..697813d1cccf 100644 --- a/code/game/objects/items/implants/implant_explosive.dm +++ b/code/game/objects/items/implants/implant_explosive.dm @@ -122,3 +122,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 373658b38646..ec4b185ee133 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 5dea8d41aada..e99b014be5d7 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:
@@ -45,9 +45,8 @@ removed(target, TRUE) return FALSE - var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(target) - if(target.mind.has_antag_datum(/datum/antagonist/vassal || !(vassaldatum.favorite_vassal))) - if(vassaldatum.favorite_vassal) + if(IS_VASSAL(target)) + if(IS_FAVORITE_VASSAL(target)) if(!silent) target.visible_message(span_warning("[target] seems to resist the implant!"), span_warning("You feel something interfering with your mental conditioning, but you resist it!")) removed(target, TRUE) diff --git a/code/game/objects/items/implants/implant_mindshieldtot.dm b/code/game/objects/items/implants/implant_mindshieldtot.dm index 29773a0c4edb..dd3533284f1e 100644 --- a/code/game/objects/items/implants/implant_mindshieldtot.dm +++ b/code/game/objects/items/implants/implant_mindshieldtot.dm @@ -1,7 +1,6 @@ /obj/item/implant/mindshield/tot name = "mindshield implant" desc = "Protects against brainwashing." - activated = 0 /obj/item/implant/mindshield/tot/get_data() var/dat = {"Implant Specifications:
diff --git a/code/game/objects/items/implants/implant_misc.dm b/code/game/objects/items/implants/implant_misc.dm index ca5f0ce19256..fc488757a73d 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:
@@ -76,7 +76,6 @@ /obj/item/implant/health name = "health implant" - activated = 0 var/healthstring = "" /obj/item/implant/health/proc/sensehealth() @@ -92,7 +91,6 @@ /obj/item/implant/radio name = "internal radio implant" - activated = TRUE var/obj/item/radio/radio var/radio_key var/subspace_transmission = FALSE @@ -156,7 +154,6 @@ /obj/item/implant/empshield name = "EMP shield implant" desc = "An implant that completely protects from electro-magnetic pulses. It will shut down briefly if triggered too often." - activated = 0 var/lastemp = 0 var/numrecent = 0 var/warning = TRUE diff --git a/code/game/objects/items/implants/implant_spell.dm b/code/game/objects/items/implants/implant_spell.dm index 916eaa3cedec..ecfbfcf0ef22 100644 --- a/code/game/objects/items/implants/implant_spell.dm +++ b/code/game/objects/items/implants/implant_spell.dm @@ -1,36 +1,56 @@ /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/cooldown/spell/spell_type + /// The actual spell we give to the person on implant + var/datum/action/cooldown/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 (.) - target.RemoveSpell(spell) - if(target.stat != DEAD && !silent) - to_chat(target, span_boldnotice("The knowledge of how to cast [spell] slips out from your mind.")) + 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 (!.) + return FALSE + + if(spell_to_give) + spell_to_give.Remove(source) + if(source.stat != DEAD && !silent) + to_chat(source, span_boldnotice("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 9485cd08638a..cadc7a09f0fd 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 var/lifespan_postmortem = 10 MINUTES //for how long after user death will the implant work? var/allow_teleport = TRUE //will people implanted with this act as teleporter beacons? diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm index cea0e2f1d07d..b8d210db6dc7 100644 --- a/code/game/objects/items/melee/misc.dm +++ b/code/game/objects/items/melee/misc.dm @@ -505,8 +505,8 @@ return span_danger("The baton is still charging!") /obj/item/melee/classic_baton/telescopic/contractor_baton/additional_effects_carbon(mob/living/target, mob/living/user) - target.Jitter(20) - target.stuttering += 20 + target.adjust_jitter(20 SECONDS) + target.adjust_stutter(2 SECONDS) /obj/item/melee/supermatter_sword name = "supermatter sword" diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 122101d3f1d8..80e38c8b0c57 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -36,15 +36,15 @@ to_chat(M, span_warning("You muscles seize, making you collapse!")) else M.Paralyze(stunforce) - M.Jitter(20) - M.confused = max(8, M.confused) + M.adjust_jitter(20 SECONDS) + M.adjust_confusion_up_to(8 SECONDS, 40 SECONDS) M.apply_effect(EFFECT_STUTTER, stunforce) else if(current_stamina_damage > 70) - M.Jitter(10) - M.confused = max(8, M.confused) + M.adjust_jitter(10 SECONDS) + M.adjust_confusion_up_to(8 SECONDS, 40 SECONDS) M.apply_effect(EFFECT_STUTTER, stunforce) else if(current_stamina_damage >= 20) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) M.apply_effect(EFFECT_STUTTER, stunforce) M.visible_message(span_danger("[user] has prodded [M] with [src]!"), \ @@ -329,7 +329,7 @@ span_danger("The siren pierces your hearing!")) for(var/mob/living/carbon/M in get_hearers_in_view(9, user)) if(M.get_ear_protection() == FALSE) - M.confused += 6 + M.adjust_confusion(6 SECONDS) audible_message("HUMAN HARM") playsound(get_turf(src), 'sound/ai/default/harmalarm.ogg', 70, 3) cooldown = world.time + 200 @@ -346,16 +346,16 @@ var/bang_effect = C.soundbang_act(2, 0, 0, 5) switch(bang_effect) if(1) - C.confused += 5 - C.stuttering += 10 - C.Jitter(10) + C.adjust_confusion(5 SECONDS) + C.adjust_stutter(10 SECONDS) + C.adjust_jitter(10 SECONDS) if(2) - C.Paralyze(40) - C.confused += 10 - C.stuttering += 15 - C.Jitter(25) + C.Paralyze(4 SECONDS) + C.adjust_confusion(10 SECONDS) + C.adjust_stutter(15 SECONDS) + C.adjust_jitter(25 SECONDS) playsound(get_turf(src), 'sound/machines/warning-buzzer.ogg', 130, 3) - cooldown = world.time + 600 + cooldown = world.time + 1 MINUTES log_game("[key_name(user)] used an emagged Cyborg Harm Alarm in [AREACOORD(user)]") #define DISPENSE_LOLLIPOP_MODE 1 diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index f0285773ec63..662dcb6e5451 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -459,7 +459,7 @@ icon_state = "selfrepair_[on ? "on" : "off"]" for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() else icon_state = "cyborg_upgrade5" diff --git a/code/game/objects/items/scrolls.dm b/code/game/objects/items/scrolls.dm index d25009a16241..ad2bb3319ea0 100644 --- a/code/game/objects/items/scrolls.dm +++ b/code/game/objects/items/scrolls.dm @@ -8,79 +8,45 @@ item_state = "paper" throw_speed = 3 throw_range = 7 + actions_types = list(/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll) + /// Number of uses the scroll gets. 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/cooldown/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 - - /obj/item/teleportation_scroll/attack_self(mob/user) - user.set_machine(src) - var/dat = "" - - dat += "" - - 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.
" - - dat += "" - - user << browse(dat, "window=scroll") - onclose(user, "scroll") - return - -/obj/item/teleportation_scroll/Topic(href, href_list) - ..() - if (usr.stat || usr.restrained() || 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 - - A = input(user, "Area to jump to", "BOOYEA", A) as null|anything in 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/fluid_spread/smoke/smoke = new - smoke.set_up(2, location = user.loc) - smoke.attach(user) - smoke.start() - var/list/L = list() - var/list/achors = thearea.teleport_anchors - if(achors.len) //check the areas prefered teleportation list if it is empty just use a random enpty tile like before - for(var/turf/T in achors) - if(!is_blocked_turf(T)) - L += T - if(!L.len) - for(var/turf/T in get_area_turfs(thearea.type)) - if(!is_blocked_turf(T)) - 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), forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC, forced = 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/cooldown/spell/teleport/area_teleport/wizard/scroll/teleport = locate() in actions + if(!teleport) + to_chat(user, span_warning("[src] seems to be a faulty teleportation scroll, and has no magic associated.")) + return + if(!teleport.Activate(user)) + return + if(--uses <= 0) + to_chat(user, span_warning("[src] runs out of uses and crumbles to dust!")) + qdel(src) + return TRUE \ No newline at end of file diff --git a/code/game/objects/items/signs.dm b/code/game/objects/items/signs.dm index fa2802870e34..7d450b045b41 100644 --- a/code/game/objects/items/signs.dm +++ b/code/game/objects/items/signs.dm @@ -57,6 +57,15 @@ animate(pixel_y = user.pixel_y + (1 * direction), time = 0.1 SECONDS, easing = SINE_EASING) user.changeNext_move(CLICK_CD_MELEE) +/datum/action/item_action/nano_picket_sign + name = "Retext Nano Picket Sign" + +/datum/action/item_action/nano_picket_sign/Trigger(trigger_flags) + if(!istype(target, /obj/item/picket_sign)) + return + var/obj/item/picket_sign/sign = target + sign.retext(owner) + /datum/crafting_recipe/picket_sign name = "Picket Sign" result = /obj/item/picket_sign diff --git a/code/game/objects/items/stacks/bscrystal.dm b/code/game/objects/items/stacks/bscrystal.dm index 264b82e18de0..477739bb1d1e 100644 --- a/code/game/objects/items/stacks/bscrystal.dm +++ b/code/game/objects/items/stacks/bscrystal.dm @@ -73,7 +73,7 @@ if(iscarbon(user)) var/mob/living/carbon/C = user C.adjust_disgust(30) //Won't immediately make you vomit, just dont use more than one or two at a time - C.confused += 7 + C.adjust_confusion(7 SECONDS) use(1) /obj/item/stack/ore/bluespace_crystal/proc/blink_mob(mob/living/L) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 4b073270bde5..346dec3a8970 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -276,35 +276,30 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ return ..() /obj/item/stack/sheet/mineral/wood/attackby(obj/item/item, mob/user, params) - if(item.get_sharpness()) - user.visible_message( - span_notice("[user] begins whittling [src] into a pointy object."), - span_notice("You begin whittling [src] into a sharp point at one end."), - span_hear("You hear wood carving."), - ) - // 5 Second Timer - if(!do_after(user, 5 SECONDS, src)) - return - // Make Stake - var/obj/item/stake/new_item = new(user.loc) - user.visible_message( - span_notice("[user] finishes carving a stake out of [src]."), - span_notice("You finish carving a stake out of [src]."), - ) - // Prepare to Put in Hands (if holding wood) - var/obj/item/stack/sheet/mineral/wood/wood_stack = src - var/replace = (user.get_inactive_held_item() == wood_stack) - // Use Wood - wood_stack.use(1) - // If stack depleted, put item in that hand (if it had one) - if(!wood_stack && replace) - user.put_in_hands(new_item) - if(istype(item, merge_type)) - var/obj/item/stack/merged_stack = item - if(merge(merged_stack)) - to_chat(user, span_notice("Your [merged_stack.name] stack now contains [merged_stack.get_amount()] [merged_stack.singular_name]\s.")) + if(!item.get_sharpness()) + return ..() + user.visible_message( + span_notice("[user] begins whittling [src] into a pointy object."), + span_notice("You begin whittling [src] into a sharp point at one end."), + span_hear("You hear wood carving."), + ) + // 5 Second Timer + if(!do_after(user, 5 SECONDS, src, NONE, TRUE)) return - return ..() + // Make Stake + var/obj/item/stake/new_item = new(user.loc) + user.visible_message( + span_notice("[user] finishes carving a stake out of [src]."), + span_notice("You finish carving a stake out of [src]."), + ) + // Prepare to Put in Hands (if holding wood) + var/obj/item/stack/sheet/mineral/wood/wood_stack = src + var/replace = (user.get_inactive_held_item() == wood_stack) + // Use Wood + wood_stack.use(1) + // If stack depleted, put item in that hand (if it had one) + if(!wood_stack && replace) + user.put_in_hands(new_item) /obj/item/stack/sheet/mineral/wood/fifty amount = 50 diff --git a/code/game/objects/items/storage/backpack.dm b/code/game/objects/items/storage/backpack.dm index b7623f4a893e..6ff9d6270bea 100644 --- a/code/game/objects/items/storage/backpack.dm +++ b/code/game/objects/items/storage/backpack.dm @@ -367,6 +367,19 @@ var/datum/component/storage/STR = GetComponent(/datum/component/storage) STR.max_combined_w_class = 30 + name = "living duffel bag" + desc = "A cursed clown duffel bag that hungers for food of any kind. A warning label suggests that it eats food inside. \ + If that food happens to be a horribly ruined mess or the chef scrapped out of the microwave, or poisoned in some way, \ + then it might have negative effects on the bag..." + icon_state = "duffel-curse" + inhand_icon_state = "duffel-curse" + slowdown = 1.5 + max_integrity = 100 + +/obj/item/storage/backpack/duffelbag/cursed/Initialize(mapload) + . = ..() + AddComponent(/datum/component/curse_of_hunger, add_dropdel = TRUE) + /obj/item/storage/backpack/duffelbag/captain name = "captain's duffel bag" desc = "A large duffel bag for holding extra captainly goods." diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index a084cac7e8e1..78e6d1ac63a1 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -183,7 +183,7 @@ new /obj/item/clothing/gloves/combat(src) //Maybe 1 TC, so you don't shock yourself new /obj/item/book/granter/spell/lightningbolt(src) //Lightning bolt, LIGHTNING BOLT. A 2 SP cost spell that doesn't require robes and provides ranged potential new /obj/item/book/granter/spell/forcewall(src) //It has the word force in it? But more importantly, it doesn't require robes and it's 1 SP and it's VERY good defense - new /obj/item/book/granter/spell/summonitem(src) //So you can throw your lightsaber and call it back. A 1 SP cost spell that doesn't require robes + new /obj/item/book/granter/action/spell/summonitem(src) //So you can throw your lightsaber and call it back. A 1 SP cost spell that doesn't require robes if("white_whale_holy_grail") //Unique items that don't appear anywhere else, more than 100 carps or your TC back new /obj/item/pneumatic_cannon/speargun(src) @@ -685,8 +685,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_officer(src) diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm index 0474b878692d..9d01d0f0cc83 100644 --- a/code/game/objects/items/stunbaton.dm +++ b/code/game/objects/items/stunbaton.dm @@ -246,15 +246,15 @@ to_chat(L, span_warning("You muscles seize, making you collapse!")) else L.Paralyze(stunforce) - L.Jitter(20) - L.confused = max(8, L.confused) + L.adjust_jitter(20 SECONDS) + L.adjust_confusion(8 SECONDS) L.apply_effect(EFFECT_STUTTER, stunforce) else if(current_stamina_damage > 70) - L.Jitter(10) - L.confused = max(8, L.confused) + L.adjust_jitter(10 SECONDS) + L.adjust_confusion(8 SECONDS) L.apply_effect(EFFECT_STUTTER, stunforce) else if(current_stamina_damage >= 20) - L.Jitter(5) + L.adjust_jitter(5 SECONDS) L.apply_effect(EFFECT_STUTTER, stunforce) if(user) diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm index 7b7e9fdcfec5..d82bcbfc152f 100644 --- a/code/game/objects/items/tanks/jetpack.dm +++ b/code/game/objects/items/tanks/jetpack.dm @@ -49,7 +49,7 @@ to_chat(user, span_notice("You turn the jetpack off.")) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/tank/jetpack/proc/turn_on(mob/user) diff --git a/code/game/objects/items/tanks/watertank.dm b/code/game/objects/items/tanks/watertank.dm index 2328758d6072..128ea9e32b53 100644 --- a/code/game/objects/items/tanks/watertank.dm +++ b/code/game/objects/items/tanks/watertank.dm @@ -329,6 +329,9 @@ #undef RESIN_LAUNCHER #undef RESIN_FOAM +/datum/action/item_action/activate_injector + name = "Activate Injector" + /obj/item/reagent_containers/chemtank name = "backpack chemical injector" desc = "A chemical autoinjector that can be carried on your back." diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm index 0c935a5ca85e..2f1dfe52a8b7 100644 --- a/code/game/objects/items/tools/weldingtool.dm +++ b/code/game/objects/items/tools/weldingtool.dm @@ -151,7 +151,7 @@ if(isliving(O)) var/mob/living/L = O - if(L.IgniteMob()) + if(L.ignite_mob()) message_admins("[ADMIN_LOOKUPFLW(user)] set [key_name_admin(L)] on fire with [src] at [AREACOORD(user)]") log_game("[key_name(user)] set [key_name(L)] on fire with [src] at [AREACOORD(user)]") @@ -165,9 +165,10 @@ update_icon() /obj/item/weldingtool/use_tool(atom/target, mob/living/user, delay, amount, volume, datum/callback/extra_checks, robo_check) - target.add_overlay(GLOB.welding_sparks) + var/mutable_appearance/sparks = mutable_appearance('icons/effects/welding_effect.dmi', "welding_sparks", GASFIRE_LAYER, src, ABOVE_LIGHTING_PLANE) + target.add_overlay(sparks) . = ..() - target.cut_overlay(GLOB.welding_sparks) + target.cut_overlay(sparks) // Returns the amount of fuel in the welder /obj/item/weldingtool/proc/get_fuel() diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm index 935afa77ea25..48a0c0a10735 100644 --- a/code/game/objects/items/twohanded.dm +++ b/code/game/objects/items/twohanded.dm @@ -612,6 +612,10 @@ qdel(src) // CHAINSAW + +/datum/action/item_action/startchainsaw + name = "Pull The Starting Cord" + /obj/item/twohanded/required/chainsaw name = "chainsaw" desc = "A versatile power tool. Useful for limbing trees and delimbing humans." @@ -668,7 +672,7 @@ user.update_inv_hands() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/twohanded/required/chainsaw/doomslayer name = "THE GREAT COMMUNICATOR" @@ -973,6 +977,14 @@ * Vxtvul Hammer */ +/datum/action/item_action/charge_hammer + name = "Charge the Blast Pads" + +/datum/action/item_action/charge_hammer/Trigger() + var/obj/item/twohanded/vxtvulhammer/vxtvulhammer = target + if(istype(vxtvulhammer)) + vxtvulhammer.charge_hammer(owner) + /obj/item/twohanded/vxtvulhammer icon = 'icons/obj/weapons/misc.dmi' icon_state = "vxtvul_hammer0-0" diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 7693ff73df1d..96beec4a7f1f 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -75,7 +75,7 @@ user.visible_message(span_warning("[user] starts climbing onto [src]."), \ span_notice("You start climbing onto [src]...")) var/adjusted_climb_time = climb_time - if(user.restrained()) //climbing takes twice as long when restrained. + if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) //climbing takes twice as long without help from the hands. adjusted_climb_time *= 2 if(isalien(user)) adjusted_climb_time *= 0.25 //aliens are terrifyingly fast diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index df014dbe1ba2..7f236385eb65 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -164,7 +164,7 @@ return ..() /obj/structure/chair/comfy/proc/GetArmrest() - return mutable_appearance('icons/obj/chairs.dmi', "comfychair_armrest") + return mutable_appearance(icon, "[icon_state]_armrest") /obj/structure/chair/comfy/Destroy() QDEL_NULL(armrest) diff --git a/code/game/objects/structures/beds_chairs/pew.dm b/code/game/objects/structures/beds_chairs/pew.dm index 1f88489928ee..b7aa1f65d2bd 100644 --- a/code/game/objects/structures/beds_chairs/pew.dm +++ b/code/game/objects/structures/beds_chairs/pew.dm @@ -69,4 +69,4 @@ /obj/structure/chair/pew/right/post_unbuckle_mob() . = ..() - update_rightpewarmrest() \ No newline at end of file + update_rightpewarmrest() diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index b86fb7fd2bc0..5d7c1ad1b2c8 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -15,6 +15,11 @@ delivery_icon = "deliverycrate" door_anim_time = 0 // no animation var/obj/item/paper/fluff/jobs/cargo/manifest/manifest + breakout_time = 20 SECONDS + ///The resident (owner) of this crate/coffin. + var/mob/living/resident + ///The time it takes to pry this open with a crowbar. + var/pry_lid_timer = 25 SECONDS /obj/structure/closet/crate/Initialize() . = ..() diff --git a/code/game/objects/structures/spirit_board.dm b/code/game/objects/structures/spirit_board.dm index 5d2cf83f7530..c079c99616fe 100644 --- a/code/game/objects/structures/spirit_board.dm +++ b/code/game/objects/structures/spirit_board.dm @@ -59,7 +59,7 @@ light_amount = T.get_lumcount() - if(light_amount > 0.2) + if(light_amount > LIGHTING_TILE_IS_DARK) to_chat(M, span_warning("It's too bright here to use [src.name]!")) return 0 diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index 9416c4d638dc..2b574b751802 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -256,7 +256,7 @@ if(washing_face) SEND_SIGNAL(user, COMSIG_COMPONENT_CLEAN_FACE_ACT, CLEAN_WASH) - user.drowsyness = max(user.drowsyness - rand(2,3), 0) //Washing your face wakes you up if you're falling asleep + user.adjust_drowsiness_up_to(-rand(2,3), 0) //Washing your face wakes you up if you're falling asleep user.wash_cream() else if(ishuman(user)) var/mob/living/carbon/human/human_user = user @@ -291,7 +291,7 @@ flick("baton_active", src) var/stunforce = B.stunforce user.Paralyze(stunforce) - user.stuttering = stunforce/20 + user.adjust_stutter(stunforce/(2 SECONDS)) B.deductcharge(B.hitcost) user.visible_message(span_warning("[user] shocks [user.p_them()]self while attempting to wash the active [B.name]!"), \ span_userdanger("You unwisely attempt to wash [B] while it's still on.")) diff --git a/code/game/turfs/simulated/chasm.dm b/code/game/turfs/simulated/chasm.dm index 812260f5bb35..936050ada576 100644 --- a/code/game/turfs/simulated/chasm.dm +++ b/code/game/turfs/simulated/chasm.dm @@ -123,6 +123,6 @@ /turf/open/chasm/magic/Initialize() . = ..() - var/turf/T = safepick(get_area_turfs(/area/fabric_of_reality)) + var/turf/T = pick(get_area_turfs(/area/fabric_of_reality)) if(T) set_target(T) diff --git a/code/game/turfs/simulated/lava.dm b/code/game/turfs/simulated/lava.dm index 14260bfbd53e..71b14a93724b 100644 --- a/code/game/turfs/simulated/lava.dm +++ b/code/game/turfs/simulated/lava.dm @@ -154,7 +154,7 @@ L.adjustFireLoss(20 * delta_time) if(L) //mobs turning into object corpses could get deleted here. L.adjust_fire_stacks(20 * delta_time) - L.IgniteMob() + L.ignite_mob() /turf/open/lava/smooth name = "lava" diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 7b6461a976ee..94dd844bcad8 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -19,6 +19,9 @@ GLOBAL_LIST_EMPTY(station_turfs) var/blocks_air = FALSE + ///Which directions does this turf block the vision of, taking into account both the turf's opacity and the movable opacity_sources. + var/directional_opacity = NONE + flags_1 = CAN_BE_DIRTY_1 var/list/image/blueprint_data //for the station blueprints, images of objects eg: pipes diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm index 7cd4b191e6cd..eb0b610ee008 100644 --- a/code/modules/VR/vr_sleeper.dm +++ b/code/modules/VR/vr_sleeper.dm @@ -188,8 +188,7 @@ QDEL_NULL(vr_human) /obj/machinery/vr_sleeper/proc/emagNotify() - if(vr_human) - vr_human.Dizzy(10) + vr_human?.adjust_dizzy(10 SECONDS) /obj/effect/landmark/vr_spawn //places you can spawn in VR, auto selected by the vr_sleeper during get_vr_spawnpoint() var/vr_category = "default" //So we can have specific sleepers, eg: "Basketball VR Sleeper", etc. @@ -236,4 +235,4 @@ for (var/mob/living/carbon/human/virtual_reality/H in vr_area) if (H.stat == DEAD && !H.vr_sleeper && !H.real_mind) qdel(H) - addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES) \ No newline at end of file + addtimer(CALLBACK(src, .proc/clean_up), 3 MINUTES) diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index de6fee14eb33..dff35d6c5250 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -254,7 +254,7 @@ dat+="

The newscaster recognises you as:
[src.admin_signature]
" if(1) dat+= "Station Feed Channels
" - if( isemptylist(GLOB.news_network.network_channels) ) + if( !length(GLOB.news_network.network_channels) ) dat+="No active channels found..." else for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) @@ -307,7 +307,7 @@ dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
" dat+="No further feed story additions are allowed while the D-Notice is in effect.

" else - if( isemptylist(src.admincaster_feed_channel.messages) ) + if( !length(src.admincaster_feed_channel.messages) ) dat+="No feed messages found in channel...
" else var/i = 0 @@ -329,7 +329,7 @@ dat+="NOTE: Due to the nature of news Feeds, total deletion of a Feed Story is not possible.
" dat+="Keep in mind that users attempting to view a censored feed will instead see the \[REDACTED\] tag above it.
" dat+="
Select Feed channel to get Stories from:
" - if(isemptylist(GLOB.news_network.network_channels)) + if(!length(GLOB.news_network.network_channels)) dat+="No feed channels found active...
" else for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) @@ -340,7 +340,7 @@ dat+="A D-Notice is to be bestowed upon the channel if the handling Authority deems it as harmful for the station's" dat+="morale, integrity or disciplinary behaviour. A D-Notice will render a channel unable to be updated by anyone, without deleting any feed" dat+="stories it might contain at the time. You can lift a D-Notice if you have the required access at any time.
" - if(isemptylist(GLOB.news_network.network_channels)) + if(!length(GLOB.news_network.network_channels)) dat+="No feed channels found active...
" else for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels) @@ -351,7 +351,7 @@ dat+="[src.admincaster_feed_channel.channel_name]: \[ created by: [src.admincaster_feed_channel.returnAuthor(-1)] \]
" dat+="[(src.admincaster_feed_channel.authorCensor) ? ("Undo Author censorship") : ("Censor channel Author")]
" - if( isemptylist(src.admincaster_feed_channel.messages) ) + if( !length(src.admincaster_feed_channel.messages) ) dat+="No feed messages found in channel...
" else for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) @@ -368,7 +368,7 @@ dat+="ATTENTION: This channel has been deemed as threatening to the welfare of the station, and marked with a Nanotrasen D-Notice.
" dat+="No further feed story additions are allowed while the D-Notice is in effect.

" else - if( isemptylist(src.admincaster_feed_channel.messages) ) + if( !length(src.admincaster_feed_channel.messages) ) dat+="No feed messages found in channel...
" else for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index accce5184121..a0d0b030f433 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -173,6 +173,7 @@ GLOBAL_PROTECT(admin_verbs_debug) /client/proc/export_dynamic_json, /client/proc/run_dynamic_simulations, #endif + /client/proc/debug_spell_requirements, ) GLOBAL_LIST_INIT(admin_verbs_possess, list(/proc/possess, /proc/release)) GLOBAL_PROTECT(admin_verbs_possess) @@ -578,42 +579,80 @@ GLOBAL_PROTECT(admin_verbs_hideable) log_admin("[key_name(usr)] has modified Dynamic Explosion Scale: [ex_scale]") message_admins("[key_name_admin(usr)] has modified Dynamic Explosion Scale: [ex_scale]") -/client/proc/give_spell(mob/T in GLOB.mob_list) +/client/proc/give_spell(mob/spell_recipient in GLOB.mob_list) set category = "Admin.Player Interaction" 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, span_warning("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 spell_list - if(!S) + for(var/datum/action/cooldown/spell/to_add as anything in subtypesof(/datum/action/cooldown/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/cooldown/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, span_warning("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(span_adminnotice("[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(span_danger("Spells given to mindless mobs will not be transferred in mindswap or cloning!")) + var/datum/action/cooldown/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, span_userdanger("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 = "Admin.Player Interaction" set name = "Remove Spell" set desc = "Remove a spell from the selected mob." - if(T && T.mind) - var/obj/effect/proc_holder/spell/S = input("Choose the spell to remove", "NO ABRAKADABRA") as null|anything in 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(span_adminnotice("[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! + var/list/target_spell_list = list() + for(var/datum/action/cooldown/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/cooldown/spell/to_remove = target_spell_list[chosen_spell] + if(!istype(to_remove)) + return + + 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 = "Admin.Player Interaction" @@ -666,7 +705,7 @@ GLOBAL_PROTECT(admin_verbs_hideable) if(!holder) return - if(has_antag_hud()) + if(combo_hud_enabled) toggle_combo_hud() holder.deactivate() @@ -799,3 +838,41 @@ GLOBAL_PROTECT(admin_verbs_hideable) set category = "Misc.Server Debug" src << output("", "statbrowser:create_debug") + +/// 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/cooldown/spell/spell as anything in typesof(/datum/action/cooldown/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_STATION) + 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() \ No newline at end of file diff --git a/code/modules/admin/fun_balloon.dm b/code/modules/admin/fun_balloon.dm index 69ccfba383a6..8f656ae4074f 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/Bumped(atom/movable/AM) @@ -124,7 +124,7 @@ qdel(L.pulling) var/turf/LA = get_turf(pick(warp_points)) L.forceMove(LA) - L.hallucination = 0 + L.remove_status_effect(/datum/status_effect/hallucination) to_chat(L, "The battle is won. Your bloodlust subsides.") for(var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw in L) qdel(chainsaw) @@ -142,7 +142,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/team_panel.dm b/code/modules/admin/team_panel.dm index 6b920022196c..cccb9b6b158d 100644 --- a/code/modules/admin/team_panel.dm +++ b/code/modules/admin/team_panel.dm @@ -35,7 +35,7 @@ var/team_name = stripped_input(user,"Team name ?") if(!team_name) return - var/datum/team/custom/T = new() + var/datum/team/T = new() T.name = team_name message_admins("[key_name_admin(usr)] created new [name] antagonist team.") @@ -146,38 +146,3 @@ /datum/team/proc/get_admin_commands() return list() -//Custom team subtype created by the panel, allow forcing hud for the team for now -/datum/team/custom - var/datum/atom_hud/antag/custom_hud - var/custom_hud_state = "traitor" - -/datum/team/custom/add_member(datum/mind/new_member) - . = ..() - if(custom_hud) - custom_hud.join_hud(new_member.current) - set_antag_hud(new_member.current,custom_hud_state) - -/datum/team/custom/remove_member(datum/mind/member) - . = ..() - if(custom_hud) - custom_hud.leave_hud(member.current) - -/datum/team/custom/get_admin_commands() - . = ..() - .["Force HUD"] = CALLBACK(src,.proc/admin_force_hud) - -//This is here if you want admin created teams to tell each other apart easily. -/datum/team/custom/proc/admin_force_hud(mob/user) - var/list/possible_icons = icon_states('icons/mob/hud.dmi') - var/new_hud_state = input(user,"Choose hud icon state","Custom HUD","traitor") as null|anything in possible_icons - if(!new_hud_state) - return - //suppose could ask for color too - custom_hud_state = new_hud_state - custom_hud = new - custom_hud.self_visible = TRUE - GLOB.huds += custom_hud //Make it show in admin hud - - for(var/datum/mind/M in members) - custom_hud.join_hud(M.current) - set_antag_hud(M.current,custom_hud_state) diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index 1dbe8edd71bf..4e815d67b40a 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -15,7 +15,7 @@ continue turfs.Add(T) - var/turf/T = safepick(turfs) + var/turf/T = pick(turfs) if(!T) to_chat(src, "Nowhere to jump to!", confidential=TRUE) return diff --git a/code/modules/admin/verbs/bluespacearty.dm b/code/modules/admin/verbs/bluespacearty.dm index 46f55dd7d926..6529d2615115 100644 --- a/code/modules/admin/verbs/bluespacearty.dm +++ b/code/modules/admin/verbs/bluespacearty.dm @@ -21,6 +21,6 @@ target.gib(1, 1) else target.adjustBruteLoss(min(99,(target.health - 1))) - target.Paralyze(400) - target.stuttering = 20 + target.Paralyze(40 SECONDS) + target.set_stutter_if_lower(20 SECONDS) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index e0b82e8a04ad..31990ad0b82f 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -20,7 +20,7 @@ /* 21st Sept 2010 Updated by Skie -- Still not perfect but better! Stuff you can't do: -Call proc /mob/proc/Dizzy() for some player +Call proc /mob/proc/adjust_dizzy() for some player Because if you select a player mob as owner it tries to do the proc for /mob/living/carbon/human/ instead. And that gives a run-time error. But you can call procs that are of type /mob/living/carbon/human/proc/ for that player. @@ -286,34 +286,6 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) spawn(0) M.Animalize() - -/client/proc/makepAI(turf/T in GLOB.mob_list) - set category = "Admin.Player Interaction" - set name = "Make pAI" - set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI" - - var/list/available = list() - for(var/mob/C in GLOB.mob_list) - if(C.key) - available.Add(C) - var/mob/choice = input(usr, "Choose a player to play the pAI", "Spawn pAI", sortNames(available)) - if(!choice) - return 0 - if(!isobserver(choice)) - var/confirm = tgui_alert(usr, "[choice.key] isn't ghosting right now. Are you sure you want to yank him out of them out of their body and place them in this pAI?", "Spawn pAI Confirmation", list("Yes", "No")) - if(confirm != "Yes") - return 0 - var/obj/item/paicard/card = new(T) - var/mob/living/silicon/pai/pai = new(card) - pai.name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text - pai.real_name = pai.name - pai.key = choice.key - card.setPersonality(pai) - for(var/datum/paiCandidate/candidate in SSpai.candidates) - if(candidate.key == choice.key) - SSpai.candidates.Remove(candidate) - SSblackbox.record_feedback("tally", "admin_verb", 1, "Make pAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - /client/proc/cmd_admin_alienize(mob/M in GLOB.mob_list) set category = "Admin.Player Interaction" set name = "Make Alien" @@ -611,7 +583,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention) for(var/area/A as anything in GLOB.areas) if(on_station) - var/turf/picked = safepick(get_area_turfs(A.type)) + var/turf/picked = pick(get_area_turfs(A.type)) if(picked && is_station_level(picked.z)) if(!(A.type in areas_all) && !is_type_in_typecache(A, station_areas_blacklist)) areas_all.Add(A.type) diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 7e02ea347b44..cdad56efc96f 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -970,39 +970,52 @@ Traitors and the like can also be revived with the previous role mostly intact. SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Nuke", "[N.timing]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/toggle_combo_hud() - set category = "Admin" + set category = "Admin.Game" set name = "Toggle Combo HUD" set desc = "Toggles the Admin Combo HUD (antag, sci, med, eng)" if(!check_rights(R_ADMIN)) return - var/adding_hud = !has_antag_hud() + if (combo_hud_enabled) + disable_combo_hud() + else + enable_combo_hud() - for(var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) // add data huds - var/datum/atom_hud/H = GLOB.huds[hudtype] - (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) - for(var/datum/atom_hud/antag/H in GLOB.huds) // add antag huds - (adding_hud) ? H.add_hud_to(usr) : H.remove_hud_from(usr) + to_chat(usr, "You toggled your admin combo HUD [combo_hud_enabled ? "ON" : "OFF"].", confidential = TRUE) + message_admins("[key_name_admin(usr)] toggled their admin combo HUD [combo_hud_enabled ? "ON" : "OFF"].") + log_admin("[key_name(usr)] toggled their admin combo HUD [combo_hud_enabled ? "ON" : "OFF"].") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[combo_hud_enabled ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - if(prefs.toggles & COMBOHUD_LIGHTING) - if(adding_hud) - mob.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE - else - mob.lighting_alpha = initial(mob.lighting_alpha) +/client/proc/enable_combo_hud() + if (combo_hud_enabled) + return + + combo_hud_enabled = TRUE + + for (var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) + var/datum/atom_hud/atom_hud = GLOB.huds[hudtype] + atom_hud.show_to(mob) + + for (var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud in GLOB.active_alternate_appearances) + antag_hud.show_to(mob) mob.update_sight() - to_chat(usr, "You toggled your admin combo HUD [adding_hud ? "ON" : "OFF"].", confidential=TRUE) - message_admins("[key_name_admin(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") - log_admin("[key_name(usr)] toggled their admin combo HUD [adding_hud ? "ON" : "OFF"].") - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Combo HUD", "[adding_hud ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/disable_combo_hud() + if (!combo_hud_enabled) + return + combo_hud_enabled = FALSE -/client/proc/has_antag_hud() - var/datum/atom_hud/A = GLOB.huds[ANTAG_HUD_TRAITOR] - return A.hudusers[mob] + for (var/hudtype in list(DATA_HUD_SECURITY_ADVANCED, DATA_HUD_MEDICAL_ADVANCED, DATA_HUD_DIAGNOSTIC_ADVANCED)) + var/datum/atom_hud/atom_hud = GLOB.huds[hudtype] + atom_hud.hide_from(mob) + for (var/datum/atom_hud/alternate_appearance/basic/antagonist_hud/antag_hud in GLOB.active_alternate_appearances) + antag_hud.hide_from(mob) + + mob.update_sight() /client/proc/run_weather() set category = "Admin.Round Interaction" diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index eb22af762432..b358d7d7f57e 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -16,6 +16,10 @@ GLOBAL_LIST_EMPTY(antagonists) var/task_memory = ""//Optional little objectives that are to be removed on a certain milestone var/antag_moodlet //typepath of moodlet that the mob will gain with their status var/can_hijack = HIJACK_NEUTRAL //If these antags are alone on shuttle hijack happens. + ///The antag hud's icon file + var/hud_icon = 'yogstation/icons/mob/antag_hud.dmi' + ///Name of the antag hud we provide to this mob. + var/antag_hud_name //Antag panel properties var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind @@ -28,6 +32,15 @@ GLOBAL_LIST_EMPTY(antagonists) /// The typepath for the outfit to show in the preview for the preferences menu. var/preview_outfit + //ANTAG UI + + ///name of the UI that will try to open, right now using a generic ui + var/ui_name = "AntagInfoGeneric" + ///weakref to button to access antag interface + var/datum/weakref/info_button_ref + /// A weakref to the HUD shown to teammates, created by `add_team_hud` + var/datum/weakref/team_hud_ref + /datum/antagonist/New() GLOB.antagonists += src @@ -37,6 +50,7 @@ GLOBAL_LIST_EMPTY(antagonists) GLOB.antagonists -= src if(owner) LAZYREMOVE(owner.antag_datums, src) + QDEL_NULL(team_hud_ref) owner = null return ..() @@ -61,6 +75,10 @@ GLOBAL_LIST_EMPTY(antagonists) remove_innate_effects(old_body) if(old_body.stat != DEAD && !LAZYLEN(old_body.mind?.antag_datums)) old_body.remove_from_current_living_antags() + var/datum/action/antag_info/info_button = info_button_ref?.resolve() + if(info_button) + info_button.Remove(old_body) + info_button.Grant(new_body) apply_innate_effects(new_body) if(new_body.stat != DEAD) new_body.add_to_current_living_antags() @@ -71,8 +89,21 @@ GLOBAL_LIST_EMPTY(antagonists) //This handles the removal of antag huds/special abilities /datum/antagonist/proc/remove_innate_effects(mob/living/mob_override) + handle_clown_mutation(mob_override || owner.current, removing = FALSE) return +/// Handles adding and removing the clumsy mutation from clown antags. Gets called in apply/remove_innate_effects +/datum/antagonist/proc/handle_clown_mutation(mob/living/mob_override, message, removing = TRUE) + if(!ishuman(mob_override) || !owner.assigned_role == "Clown") + return + var/mob/living/carbon/human/human_override = mob_override + if(removing) // They're a clown becoming an antag, remove clumsy + human_override.dna.remove_mutation(CLOWNMUT) + if(!silent && message) + to_chat(human_override, span_boldnotice("[message]")) + else + human_override.dna.add_mutation(CLOWNMUT) // We're removing their antag status, add back clumsy + //Assign default team and creates one for one of a kind team antagonists /datum/antagonist/proc/create_team(datum/team/team) return @@ -80,12 +111,21 @@ GLOBAL_LIST_EMPTY(antagonists) //Called by the add_antag_datum() mind proc after the instanced datum is added to the mind's antag_datums list. /datum/antagonist/proc/on_gain() SHOULD_CALL_PARENT(TRUE) + var/datum/action/antag_info/info_button if(!owner) CRASH("[src] ran on_gain() without a mind") if(!owner.current) CRASH("[src] ran on_gain() on a mind without a mob") - greet() - apply_innate_effects(owner.current) + if(ui_name)//in the future, this should entirely replace greet. + info_button = new(src) + info_button.Grant(owner.current) + info_button_ref = WEAKREF(info_button) + if(!silent) + greet() + if(ui_name) + to_chat(owner.current, span_boldnotice("For more info, read the panel. you can always come back to it using the button in the top left.")) + info_button.Trigger() + apply_innate_effects() give_antag_moodies() if(is_banned(owner.current) && replace_banned) replace_banned_player() @@ -93,6 +133,8 @@ GLOBAL_LIST_EMPTY(antagonists) owner.current.client.holder.auto_deadmin() if(owner.current.stat != DEAD) owner.current.add_to_current_living_antags() + + SEND_SIGNAL(owner, COMSIG_ANTAGONIST_GAINED, src) /datum/antagonist/proc/is_banned(mob/M) if(!M) @@ -117,17 +159,29 @@ GLOBAL_LIST_EMPTY(antagonists) //Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion. /datum/antagonist/proc/on_removal() SHOULD_CALL_PARENT(TRUE) - remove_innate_effects(owner.current) + if(!owner) + CRASH("Antag datum with no owner.") + + remove_innate_effects() clear_antag_moodies() - if(owner) - LAZYREMOVE(owner.antag_datums, src) - if(!LAZYLEN(owner.antag_datums)) - owner.current.remove_from_current_living_antags() - if(!silent && owner.current) - farewell() + LAZYREMOVE(owner.antag_datums, src) + if(!LAZYLEN(owner.antag_datums)) + owner.current.remove_from_current_living_antags() + if(info_button_ref) + QDEL_NULL(info_button_ref) + if(!silent && owner.current) + farewell() var/datum/team/team = get_team() if(team) team.remove_member(owner) + SEND_SIGNAL(owner, COMSIG_ANTAGONIST_REMOVED, src) + + // Remove HUDs that they should no longer see + var/mob/living/current = owner.current + for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds) + if (!antag_hud.mobShouldSee(current)) + antag_hud.hide_from(current) + qdel(src) /datum/antagonist/proc/greet() @@ -288,6 +342,28 @@ GLOBAL_LIST_EMPTY(antagonists) antag_memory = new_memo task_memory = new_memo +/// Adds a HUD that will show you other members with the same antagonist. +/// If an antag typepath is passed to `antag_to_check`, will check that, otherwise will use the source type. +/datum/antagonist/proc/add_team_hud(mob/target, antag_to_check) + QDEL_NULL(team_hud_ref) + + team_hud_ref = WEAKREF(target.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/has_antagonist, + "antag_team_hud_[REF(src)]", + hud_image_on(target), + antag_to_check || type, + )) + + // Add HUDs that they couldn't see before + for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds) + if (antag_hud.mobShouldSee(owner.current)) + antag_hud.show_to(owner.current) + +/// Takes a location, returns an image drawing "on" it that matches this antag datum's hud icon +/datum/antagonist/proc/hud_image_on(mob/hud_loc) + var/image/hud = image(hud_icon, hud_loc, antag_hud_name) + return hud + //This one is created by admin tools for custom objectives /datum/antagonist/custom antagpanel_category = "Custom" @@ -307,3 +383,67 @@ datum/antagonist/custom/create_team(datum/team/team) else return ..() + +///ANTAGONIST UI STUFF + +/datum/antagonist/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, ui_name, name) + ui.open() + +/datum/antagonist/ui_state(mob/user) + return GLOB.always_state + +///generic helper to send objectives as data through tgui. +/datum/antagonist/proc/get_objectives() + var/objective_count = 1 + var/list/objective_data = list() + //all obj + for(var/datum/objective/objective in objectives) + objective_data += list(list( + "count" = objective_count, + "name" = objective.objective_name, + "explanation" = objective.explanation_text, + "complete" = objective.completed, + )) + objective_count++ + return objective_data + +/datum/antagonist/ui_static_data(mob/user) + var/list/data = list() + data["antag_name"] = name + data["objectives"] = get_objectives() + return data + +//button for antags to review their descriptions/info + +/datum/action/antag_info + name = "Open Antag Information:" + button_icon_state = "info" + +/datum/action/antag_info/New(Target) + . = ..() + if(!button_icon_state) + button_icon_state = initial(button_icon_state) + name += " [target]" + +/datum/action/antag_info/Trigger() + . = ..() + if(!.) + return + + target.ui_interact(owner) + +/datum/action/antag_info/IsAvailable() + if(!target) + stack_trace("[type] was used without a target antag datum!") + return FALSE + . = ..() + if(!.) + return + if(!owner.mind) + return FALSE + if(!(target in owner.mind.antag_datums)) + return FALSE + return TRUE diff --git a/code/modules/antagonists/_common/antag_hud.dm b/code/modules/antagonists/_common/antag_hud.dm index de6d0a4f819a..9fcca9388dbf 100644 --- a/code/modules/antagonists/_common/antag_hud.dm +++ b/code/modules/antagonists/_common/antag_hud.dm @@ -1,53 +1,92 @@ -/datum/atom_hud/antag - hud_icons = list(ANTAG_HUD) - var/self_visible = TRUE - -/datum/atom_hud/antag/hidden - self_visible = FALSE - -/datum/atom_hud/antag/proc/join_hud(mob/M) - //sees_hud should be set to 0 if the mob does not get to see it's own hud type. - if(!istype(M)) - CRASH("join_hud(): [M] ([M.type]) is not a mob!") - if(M.mind.antag_hud) //note: please let this runtime if a mob has no mind, as mindless mobs shouldn't be getting antagged - M.mind.antag_hud.leave_hud(M) - add_to_hud(M) - if(self_visible) - add_hud_to(M) - M.mind.antag_hud = src - -/datum/atom_hud/antag/proc/leave_hud(mob/M) - if(!M) - return - if(!istype(M)) - CRASH("leave_hud(): [M] ([M.type]) is not a mob!") - remove_from_hud(M) - remove_hud_from(M) - if(M.mind) - M.mind.antag_hud = null - - -//GAME_MODE PROCS -//called to set a mob's antag icon state -/proc/set_antag_hud(mob/M, new_icon_state) - if(!istype(M)) - CRASH("set_antag_hud(): [M] ([M.type]) is not a mob!") - var/image/holder = M.hud_list[ANTAG_HUD] - if(holder) - holder.icon_state = new_icon_state - if(M.mind || new_icon_state) //in mindless mobs, only null is acceptable, otherwise we're antagging a mindless mob, meaning we should runtime - M.mind.antag_hud_icon_state = new_icon_state - - -//MIND PROCS -//these are called by mind.transfer_to() -/datum/mind/proc/transfer_antag_huds(datum/atom_hud/antag/newhud) - leave_all_antag_huds() - set_antag_hud(current, antag_hud_icon_state) - if(newhud) - newhud.join_hud(current) - -/datum/mind/proc/leave_all_antag_huds() - for(var/datum/atom_hud/antag/hud in GLOB.huds) - if(hud.hudusers[current]) - hud.leave_hud(current) \ No newline at end of file +/// All active /datum/atom_hud/alternate_appearance/basic/has_antagonist instances +GLOBAL_LIST_EMPTY_TYPED(has_antagonist_huds, /datum/atom_hud/alternate_appearance/basic/has_antagonist) + +/// An alternate appearance that will only show if you have the antag datum +/datum/atom_hud/alternate_appearance/basic/has_antagonist + var/antag_datum_type + +/datum/atom_hud/alternate_appearance/basic/has_antagonist/New(key, image/I, antag_datum_type) + src.antag_datum_type = antag_datum_type + GLOB.has_antagonist_huds += src + return ..(key, I, NONE) + +/datum/atom_hud/alternate_appearance/basic/has_antagonist/Destroy() + GLOB.has_antagonist_huds -= src + return ..() + +/datum/atom_hud/alternate_appearance/basic/has_antagonist/mobShouldSee(mob/M) + return !!M.mind?.has_antag_datum(antag_datum_type) + +/// An alternate appearance that will show all the antagonists this mob has +/datum/atom_hud/alternate_appearance/basic/antagonist_hud + var/list/antag_hud_images = list() + var/index = 1 + + var/datum/mind/mind + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/New(key, datum/mind/mind) + src.mind = mind + + antag_hud_images = get_antag_hud_images(mind) + + var/image/first_antagonist = get_antag_image(1) || image(icon('icons/blanks/32x32.dmi', "nothing"), mind.current) + + RegisterSignals( + mind, + list(COMSIG_ANTAGONIST_GAINED, COMSIG_ANTAGONIST_REMOVED), + PROC_REF(update_antag_hud_images) + ) + + check_processing() + + return ..(key, first_antagonist, NONE) + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/Destroy() + QDEL_LIST(antag_hud_images) + STOP_PROCESSING(SSantag_hud, src) + mind.antag_hud = null + mind = null + + return ..() + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/mobShouldSee(mob/mob) + return Master.current_runlevel >= RUNLEVEL_POSTGAME || (mob.client?.combo_hud_enabled && !isnull(mob.client?.holder)) + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/process(delta_time) + index += 1 + update_icon() + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/check_processing() + if (antag_hud_images.len > 1 && !(DF_ISPROCESSING in datum_flags)) + START_PROCESSING(SSantag_hud, src) + else if (antag_hud_images.len <= 1) + STOP_PROCESSING(SSantag_hud, src) + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/get_antag_image(index) + RETURN_TYPE(/image) + if (antag_hud_images.len) + return antag_hud_images[(index % antag_hud_images.len) + 1] + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/get_antag_hud_images(datum/mind/mind) + var/list/final_antag_hud_images = list() + + for (var/datum/antagonist/antagonist as anything in mind?.antag_datums) + if (isnull(antagonist.antag_hud_name)) + continue + final_antag_hud_images += antagonist.hud_image_on(mind.current) + + return final_antag_hud_images + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/update_icon() + if (antag_hud_images.len == 0) + image.icon = icon('icons/blanks/32x32.dmi', "nothing") + else + image.icon = icon(get_antag_image(index).icon, get_antag_image(index).icon_state) + +/datum/atom_hud/alternate_appearance/basic/antagonist_hud/proc/update_antag_hud_images(datum/mind/source) + SIGNAL_HANDLER + + antag_hud_images = get_antag_hud_images(source) + index = clamp(index, 1, antag_hud_images.len) + update_icon() + check_processing() diff --git a/code/modules/antagonists/_common/antag_spawner.dm b/code/modules/antagonists/_common/antag_spawner.dm index ce68abeb8a92..a1de558df527 100644 --- a/code/modules/antagonists/_common/antag_spawner.dm +++ b/code/modules/antagonists/_common/antag_spawner.dm @@ -273,17 +273,16 @@ /obj/item/antag_spawner/slaughter_demon/spawn_antag(client/C, turf/T, kind = "", datum/mind/user) - var/obj/effect/dummy/crawling/holder = new /obj/effect/dummy/crawling(T) //yogs start - //var/obj/effect/dummy/phased_mob/holder = new /obj/effect/dummy/phased_mob(T) - var/mob/living/simple_animal/slaughter/S = new demon_type(holder) - //S.holder = holder //yogs end + var/mob/living/simple_animal/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 in the same plane of existence as the station. \ - Alt+Click a blood pool to manifest.") //yogs + to_chat(S, span_bold("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.")) //fuck you /obj/item/antag_spawner/slaughter_demon/laughter name = "vial of tickles" diff --git a/code/modules/antagonists/abductor/abductor.dm b/code/modules/antagonists/abductor/abductor.dm index 6a4f8c482bac..18e4eee6c1c6 100644 --- a/code/modules/antagonists/abductor/abductor.dm +++ b/code/modules/antagonists/abductor/abductor.dm @@ -5,6 +5,7 @@ roundend_category = "abductors" antagpanel_category = "Abductor" job_rank = ROLE_ABDUCTOR + antag_hud_name = "abductor" show_in_antagpanel = FALSE //should only show subtypes show_to_ghosts = TRUE var/datum/team/abductor_team/team @@ -97,8 +98,6 @@ H.forceMove(LM.loc) break - update_abductor_icons_added(owner,"abductor") - /datum/antagonist/abductor/scientist/on_gain() ADD_TRAIT(owner.current, TRAIT_ABDUCTOR_SCIENTIST_TRAINING, ABDUCTOR_ANTAGONIST) ADD_TRAIT(owner.current, TRAIT_SURGEON, ABDUCTOR_ANTAGONIST) @@ -184,6 +183,7 @@ name = "Abductee" roundend_category = "abductees" antagpanel_category = "Abductee" + antag_hud_name = "abductee" /datum/antagonist/abductee/on_gain() give_objective() @@ -202,13 +202,6 @@ var/datum/objective/abductee/O = new objtype() objectives += O -/datum/antagonist/abductee/apply_innate_effects(mob/living/mob_override) - update_abductor_icons_added(mob_override ? mob_override.mind : owner,"abductee") - -/datum/antagonist/abductee/remove_innate_effects(mob/living/mob_override) - update_abductor_icons_removed(mob_override ? mob_override.mind : owner) - - // LANDMARKS /obj/effect/landmark/abductor var/team_number = 1 @@ -235,13 +228,3 @@ if(E.team_number == T.team_number) return E.points >= target_amount return FALSE - -/datum/antagonist/proc/update_abductor_icons_added(datum/mind/alien_mind,hud_type) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR] - hud.join_hud(alien_mind.current) - set_antag_hud(alien_mind.current, hud_type) - -/datum/antagonist/proc/update_abductor_icons_removed(datum/mind/alien_mind) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_ABDUCTOR] - hud.leave_hud(alien_mind.current) - set_antag_hud(alien_mind.current, null) diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm index 1da4bf1510a2..aa62d6a893ad 100644 --- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm +++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm @@ -53,7 +53,7 @@ H.update_inv_wear_suit() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/suit/armor/abductor/vest/item_action_slot_check(slot, mob/user) if(slot == SLOT_WEAR_SUIT) //we only give the mob the ability to activate the vest if he's actually wearing it. @@ -518,7 +518,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} to_chat(user, span_warning("The specimen's tinfoil protection is interfering with the sleep inducement!")) L.visible_message(span_danger("[user] tried to induced sleep in [L] with [src], but [L.p_their()] tinfoil protection [L.p_them()]!"), \ span_userdanger("You feel a strange wave of heavy drowsiness wash over you, but your tinfoil protection deflects most of it!")) - L.drowsyness += 2 + L.adjust_drowsiness(2 SECONDS) return L.visible_message(span_danger("[user] has induced sleep in [L] with [src]!"), \ span_userdanger("You suddenly feel very drowsy!")) @@ -531,7 +531,7 @@ Congratulations! You are now trained for invasive xenobiology research!"} L.visible_message(span_danger("[user] tried to induce sleep in [L] with [src], but [L.p_their()] tinfoil protection completely protected [L.p_them()]!"), \ span_userdanger("Any sense of drowsiness is quickly diminished as your tinfoil protection deflects the effects!")) return - L.drowsyness += 1 + L.adjust_drowsiness(1 SECONDS) to_chat(user, span_warning("Sleep inducement works fully only on stunned specimens! ")) L.visible_message(span_danger("[user] tried to induce sleep in [L] with [src]!"), \ span_userdanger("You suddenly feel drowsy!")) diff --git a/code/modules/antagonists/abductor/equipment/gland.dm b/code/modules/antagonists/abductor/equipment/gland.dm index 4bb9d1a76549..011618758ffd 100644 --- a/code/modules/antagonists/abductor/equipment/gland.dm +++ b/code/modules/antagonists/abductor/equipment/gland.dm @@ -80,7 +80,7 @@ if(initial(uses) == 1) uses = initial(uses) var/datum/atom_hud/abductor/hud = GLOB.huds[DATA_HUD_ABDUCTOR] - hud.remove_from_hud(owner) + hud.remove_atom_from_hud(owner) clear_mind_control() ..() @@ -89,7 +89,7 @@ if(special != 2 && uses) // Special 2 means abductor surgery Start() var/datum/atom_hud/abductor/hud = GLOB.huds[DATA_HUD_ABDUCTOR] - hud.add_to_hud(owner) + hud.add_atom_to_hud(owner) update_gland_hud() /obj/item/organ/heart/gland/on_life() @@ -170,13 +170,13 @@ switch(pick(1,3)) if(1) to_chat(H, span_userdanger("You hear a loud buzz in your head, silencing your thoughts!")) - H.Stun(50) + H.Stun(5 SECONDS) if(2) to_chat(H, span_warning("You hear an annoying buzz in your head.")) - H.confused += 15 + H.adjust_confusion(15 SECONDS) H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10, 160) if(3) - H.hallucination += 60 + H.adjust_hallucinations(1 MINUTES) /obj/item/organ/heart/gland/mindshock/mind_control(command, mob/living/user) if(!ownerCheck() || !mind_control_uses || active_mind_control) diff --git a/code/modules/antagonists/blob/blob.dm b/code/modules/antagonists/blob/blob.dm index 80fcaaf6cbb6..420e91ea6836 100644 --- a/code/modules/antagonists/blob/blob.dm +++ b/code/modules/antagonists/blob/blob.dm @@ -4,8 +4,10 @@ antagpanel_category = "Blob" show_to_ghosts = TRUE job_rank = ROLE_BLOB - + ui_name = "AntagInfoBlob" + /// Action to release a blob infection var/datum/action/innate/blobpop/pop_action + /// Initial points for a human blob var/starting_points_human_blob = 60 var/point_rate_human_blob = 2 diff --git a/code/modules/antagonists/blob/blobstrains/blazing_oil.dm b/code/modules/antagonists/blob/blobstrains/blazing_oil.dm index 7c94f55dc818..a575d81bdeff 100644 --- a/code/modules/antagonists/blob/blobstrains/blazing_oil.dm +++ b/code/modules/antagonists/blob/blobstrains/blazing_oil.dm @@ -37,7 +37,7 @@ /datum/reagent/blob/blazing_oil/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O) reac_volume = ..() M.adjust_fire_stacks(round(reac_volume/10)) - M.IgniteMob() + M.ignite_mob() if(M) M.apply_damage(0.8*reac_volume, BURN, wound_bonus=CANT_WOUND) if(iscarbon(M)) diff --git a/code/modules/antagonists/blob/structures/_blob.dm b/code/modules/antagonists/blob/structures/_blob.dm index a65b79e33b41..faab39fc9ded 100644 --- a/code/modules/antagonists/blob/structures/_blob.dm +++ b/code/modules/antagonists/blob/structures/_blob.dm @@ -309,7 +309,7 @@ /obj/structure/blob/examine(mob/user) . = ..() var/datum/atom_hud/hud_to_check = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - if(user.research_scanner || hud_to_check.hudusers[user]) + if(user.research_scanner || hud_to_check.hud_users[user]) . += "Your HUD displays an extensive report...
" if(overmind) . += overmind.blobstrain.examine(user) diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_assets.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_assets.dm new file mode 100644 index 000000000000..e8eccc580a7d --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_assets.dm @@ -0,0 +1,15 @@ +/datum/asset/simple/bloodsucker_icons + +/datum/asset/simple/bloodsucker_icons/register() + for(var/datum/bloodsucker_clan/clans as anything in typesof(/datum/bloodsucker_clan)) + if(!initial(clans.joinable_clan)) + continue + add_bloodsucker_icon(initial(clans.join_icon), initial(clans.join_icon_state)) + + for(var/datum/action/bloodsucker/power as anything in subtypesof(/datum/action/bloodsucker)) + add_bloodsucker_icon(initial(power.button_icon), initial(power.button_icon_state)) + + return ..() + +/datum/asset/simple/bloodsucker_icons/proc/add_bloodsucker_icon(bloodsucker_icon, bloodsucker_icon_state) + assets[sanitize_filename("bloodsucker.[bloodsucker_icon_state].png")] = icon(bloodsucker_icon, bloodsucker_icon_state) diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_daylight.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_daylight.dm deleted file mode 100644 index ca02ccc0c14f..000000000000 --- a/code/modules/antagonists/bloodsuckers/bloodsucker_daylight.dm +++ /dev/null @@ -1,192 +0,0 @@ -/// 45 seconds -#define TIME_BLOODSUCKER_DAY 45 -/// 15 minutes -#define TIME_BLOODSUCKER_NIGHT 900 -/// 1.5 minutes -#define TIME_BLOODSUCKER_DAY_WARN 90 -/// 30 seconds -#define TIME_BLOODSUCKER_DAY_FINAL_WARN 30 -/// 5 seconds -#define TIME_BLOODSUCKER_BURN_INTERVAL 5 - -/// Over Time, tick down toward a "Solar Flare" of UV buffeting the station. This period is harmful to vamps. -/obj/effect/sunlight - ///If the Sun is currently out our not - var/amDay = FALSE - ///The time between the next cycle - var/time_til_cycle = TIME_BLOODSUCKER_NIGHT - ///If Bloodsuckers have been given their level yet - var/issued_XP = FALSE - -/obj/effect/sunlight/Initialize() - . = ..() - START_PROCESSING(SSprocessing, src) - -/obj/effect/sunlight/Destroy() - STOP_PROCESSING(SSprocessing, src) - return ..() - -/obj/effect/sunlight/process() - /// Update all Bloodsucker sunlight huds - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - if(istype(bloodsuckerdatum)) - bloodsuckerdatum.update_sunlight(max(0, time_til_cycle), amDay) // This pings all HUDs - time_til_cycle-- - if(amDay) - if(time_til_cycle > 0) - punish_vamps() - if(!issued_XP && time_til_cycle <= 15) - issued_XP = TRUE - /// Cycle through all vamp antags and check if they're inside a closet. - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum) - // Rank up! Must still be in a coffin to level! - bloodsuckerdatum.RankUp() - if(time_til_cycle <= 1) - warn_daylight(5, span_announce("The solar flare has ended, and the daylight danger has passed...for now."), \ - span_announce("The solar flare has ended, and the daylight danger has passed...for now."), \ - "") - amDay = FALSE - issued_XP = FALSE - time_til_cycle = TIME_BLOODSUCKER_NIGHT - message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [TIME_BLOODSUCKER_NIGHT / 60] minutes.)") - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - if(!istype(bloodsuckerdatum)) - continue - take_home_power() - else - switch(time_til_cycle) - if(TIME_BLOODSUCKER_DAY_WARN) - warn_daylight(1, span_danger("Solar Flares will bombard the station with dangerous UV in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. Prepare to seek cover in a coffin or closet."), \ - "", \ - "") - give_home_power() - if(TIME_BLOODSUCKER_DAY_FINAL_WARN) - message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.") - warn_daylight(2, span_userdanger("Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!"), \ - span_danger("In [TIME_BLOODSUCKER_DAY_FINAL_WARN / 10], your master will be at risk of a Solar Flare. Make sure they find cover!"), \ - "") - if(TIME_BLOODSUCKER_BURN_INTERVAL) - warn_daylight(3, span_userdanger("Seek cover, for Sol rises!"), \ - "", \ - "") - if(0) - amDay = TRUE - time_til_cycle = TIME_BLOODSUCKER_DAY - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - if(!istype(bloodsuckerdatum)) - continue - if(bloodsuckerdatum.my_clan == CLAN_GANGREL) - give_transform_power() - if(bloodsuckerdatum.altar_uses > 0) - to_chat(bloodsuckerdatum, span_notice("Your Altar uses have been reset!")) - bloodsuckerdatum.altar_uses = 0 - warn_daylight(4, span_userdanger("Solar flares bombard the station with deadly UV light!
Stay in cover for the next [TIME_BLOODSUCKER_DAY] seconds or risk Final Death!"), \ - span_userdanger("Solar flares bombard the station with UV light!"), \ - span_userdanger("The sunlight is visible throughout the station, the Bloodsuckers must be asleep by now!")) - message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [TIME_BLOODSUCKER_DAY] seconds.)") - -/obj/effect/sunlight/proc/warn_daylight(danger_level = 0, vampwarn = "", vassalwarn = "", hunteralert = "") - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds)) - continue - to_chat(bloodsucker_minds, vampwarn) - if(bloodsucker_minds.current) - switch(danger_level) - if(1) - bloodsucker_minds.current.playsound_local(null, 'sound/effects/griffin_3.ogg', 50 + danger_level, TRUE) - if(2) - bloodsucker_minds.current.playsound_local(null, 'sound/effects/griffin_5.ogg', 50 + danger_level, TRUE) - if(3) - bloodsucker_minds.current.playsound_local(null, 'sound/effects/alert.ogg', 75, TRUE) - if(4) - bloodsucker_minds.current.playsound_local(null, 'sound/ambience/ambimystery.ogg', 100, TRUE) - if(5) - bloodsucker_minds.current.playsound_local(null, 'sound/spookoween/ghosty_wind.ogg', 90, TRUE) - if(vassalwarn != "") - for(var/datum/mind/vassal_minds as anything in get_antag_minds(/datum/antagonist/vassal)) - if(!istype(vassal_minds)) - continue - if(vassal_minds.has_antag_datum(/datum/antagonist/bloodsucker)) - continue - to_chat(vassal_minds, vassalwarn) - if(hunteralert != "") - for(var/datum/mind/monsterhunter_minds as anything in get_antag_minds(/datum/antagonist/monsterhunter)) - if(!istype(monsterhunter_minds)) - continue - to_chat(monsterhunter_minds, hunteralert) - -/// Cycle through all vamp antags and check if they're inside a closet. -/obj/effect/sunlight/proc/punish_vamps() - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - if(!istype(bloodsuckerdatum)) - continue - if(isstructure(bloodsucker_minds.current.loc)) - if(istype(bloodsucker_minds.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection - SEND_SIGNAL(bloodsucker_minds.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/coffinsleep) - continue - if(COOLDOWN_FINISHED(bloodsuckerdatum, bloodsucker_spam_sol_burn)) // Closets offer SOME protection - to_chat(bloodsucker_minds, span_warning("Your skin sizzles. [bloodsucker_minds.current.loc] doesn't protect well against UV bombardment.")) - COOLDOWN_START(bloodsuckerdatum, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol - bloodsucker_minds.current.adjustFireLoss(0.5 + bloodsuckerdatum.bloodsucker_level / 2) - bloodsucker_minds.current.updatehealth() - SEND_SIGNAL(bloodsucker_minds.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_1) - else // Out in the Open? - if(COOLDOWN_FINISHED(bloodsuckerdatum, bloodsucker_spam_sol_burn)) - if(bloodsuckerdatum.bloodsucker_level > 0) - to_chat(bloodsucker_minds, span_userdanger("The solar flare sets your skin ablaze!")) - else - to_chat(bloodsucker_minds, span_userdanger("The solar flare scalds your neophyte skin!")) - COOLDOWN_START(bloodsuckerdatum, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol - if(bloodsucker_minds.current.fire_stacks <= 0) - bloodsucker_minds.current.fire_stacks = 0 - if(bloodsuckerdatum.bloodsucker_level > 0) - bloodsucker_minds.current.adjust_fire_stacks(0.2 + bloodsuckerdatum.bloodsucker_level / 10) - bloodsucker_minds.current.IgniteMob() - bloodsucker_minds.current.adjustFireLoss(2 + bloodsuckerdatum.bloodsucker_level) - bloodsucker_minds.current.updatehealth() - SEND_SIGNAL(bloodsucker_minds.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_2) - -/// It's late, give the "Vanishing Act" (gohome) power to Bloodsuckers. -/obj/effect/sunlight/proc/give_home_power() - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current) || !iscarbon(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - if(istype(bloodsuckerdatum) && bloodsuckerdatum.lair && !(locate(/datum/action/bloodsucker/gohome) in bloodsuckerdatum.powers)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/gohome) - -/// It's over now, remove the "Vanishing Act" (gohome) power from Bloodsuckers. -/obj/effect/sunlight/proc/take_home_power() - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - for(var/datum/action/bloodsucker/power in bloodsuckerdatum.powers) - if(istype(power, /datum/action/bloodsucker/gohome)) - bloodsuckerdatum.RemovePower(power) - -/obj/effect/sunlight/proc/give_transform_power() - for(var/datum/mind/bloodsucker_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) - if(!istype(bloodsucker_minds) || !istype(bloodsucker_minds.current)) - continue - var/datum/antagonist/bloodsucker/bloodsuckerdatum = bloodsucker_minds.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum.my_clan != CLAN_GANGREL) - continue - if(!(locate(/datum/action/bloodsucker/gangrel/transform) in bloodsuckerdatum.powers)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/gangrel/transform) diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm index f3f21fcc52e3..b63a3be44718 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_flaws.dm @@ -1,100 +1,26 @@ -///////////////////////////////////////////////////////////////////////////////////////// -// Any changes to clans have to be reflected in '/obj/item/book/kindred' /search proc. // -///////////////////////////////////////////////////////////////////////////////////////// -/datum/antagonist/bloodsucker/proc/AssignClanAndBane(tzimisce = FALSE) - var/mob/living/carbon/human/bloodsucker = owner.current +/datum/antagonist/bloodsucker/proc/assign_clan_and_bane(tzimisce = FALSE) if(tzimisce) - my_clan = CLAN_TZIMISCE - to_chat(owner, span_announce("As you arrive near the station, you recall all you know and why you are here.\n\ - * As a part of the Tzimisce Clan, you are able to Dice corpses into muscle pieces.\n\ - * With muscle pieces you are able to mutilate dead husked vassals into greater beings,\n\ - * The more you climb up the Ranks the more research you gather about fleshcrafting and beings you can make.\n\ - * Remember to fuel a vassalrack with the muscles to be able to toy with those dead vassals, you are also able to ressurect people this way.\n\ - * Finally, your Favorite Vassal will turn into a battle monster to help you in combat.")) - AddHumanityLost(5.6) - BuyPower(new /datum/action/bloodsucker/targeted/dice) - bloodsucker.faction |= "bloodhungry" //flesh monster's clan - var/list/powerstoremove = list(/datum/action/bloodsucker/veil, /datum/action/bloodsucker/masquerade) - for(var/datum/action/bloodsucker/P in powers) - if(is_type_in_list(P, powerstoremove)) - RemovePower(P) + my_clan = new /datum/bloodsucker_clan/tzimisce(owner.current) + return + if(my_clan) return - var/static/list/clans = list( - CLAN_GANGREL, - CLAN_LASOMBRA, - CLAN_TOREADOR, - ) var/list/options = list() - options = clans - // Brief descriptions in case they don't read the Wiki. - to_chat(owner, span_announce("List of all Clans:\n\ - Gangrel - Prone to Frenzy, strange outcomes from being on frenzy, special power.\n\ - Lasombra - Life in the shadows, very weak to fire but no brute damage, upgradable abilities through tasks.\n\ - Toreador - More human then other bloodsucker, easily disguise among crew, but bound with morals.")) + var/list/radial_display = list() + for(var/datum/bloodsucker_clan/all_clans as anything in typesof(/datum/bloodsucker_clan)) + if(!initial(all_clans.joinable_clan)) //flavortext only + continue + options[initial(all_clans.name)] = all_clans + var/datum/radial_menu_choice/option = new + option.image = image(icon = initial(all_clans.join_icon), icon_state = initial(all_clans.join_icon_state)) + option.info = "[initial(all_clans.name)] - [span_boldnotice(initial(all_clans.join_description))]" + radial_display[initial(all_clans.name)] = option - var/answer = input("You have Ranked up far enough to remember your clan. Which clan are you part of?", "Your mind feels luxurious...") in options - if(!answer) - to_chat(owner, span_warning("You have willfully decided to stay ignorant.")) + var/chosen_clan = show_radial_menu(owner.current, owner.current, radial_display) + chosen_clan = options[chosen_clan] + if(QDELETED(src) || QDELETED(owner.current)) + return FALSE + if(!chosen_clan) + to_chat(owner, span_announce("You choose to remain ignorant, for now.")) return - switch(answer) - if(CLAN_GANGREL) - my_clan = CLAN_GANGREL - to_chat(owner, span_announce("You have Ranked up enough to learn: You are part of the Gangrel Clan!\n\ - * As part of the Gangrel Clan, your inner beast has a stronger impact in your undead life.\n\ - * You are prone to falling into a frenzy, and will unleash a wild beast form when doing so,\n\ - * Though once per night you are able to unleash your inner beast to help you in combat.\n\ - * Due to growing more feral you've also strayed away from other bloodsuckers and will only be able to maintain one vassal.\n\ - * Finally, your Favorite Vassal will gain the Minor Beast Form ability to help you in combat.")) - AddHumanityLost(16.8) - BuyPower(new /datum/action/bloodsucker/gangrel/transform) - bloodsucker.faction |= "bloodhungry" //i love animals i love animals - RemovePower(/datum/action/bloodsucker/masquerade) - var/datum/objective/bloodsucker/frenzy/gangrel_objective = new - gangrel_objective.owner = owner - objectives += gangrel_objective - if(CLAN_LASOMBRA) - my_clan = CLAN_LASOMBRA - to_chat(owner, span_announce("You have Ranked up enough to learn: You are part of the Lasombra Clan!\n\ - * As part of the Lasombra Clan, your past teachings have shown you how to become in touch with the Abyss and practice its prophecies.\n\ - * It'll take long before the Abyss can break through this plane's veil, but you'll try to salvage any of the energy that comes through,\n\ - * To harness it's energy you must first find an influence and make a Resting Place altar to feed the harvested essence to.\n\ - * On the Resting Place you can give ranks or blood in exchange for shadowpoints, that can be spent on the table to ascend your abilities.\n\ - * The Abyss has blackened your veins and made you immune to brute damage but highly receptive to burn, so you might need to be extra careful when in Torpor.\n\ - * Finally, your Favorite Vassal will gain the Lesser Glare and Shadow Walk abilities to help you in combat.")) - bloodsucker.physiology.burn_mod *= 2 - bloodsucker.physiology.brute_mod *= 0 - bloodsucker.eye_color = "f00" - ADD_TRAIT(bloodsucker, CULT_EYES, BLOODSUCKER_TRAIT) - bloodsucker.faction |= "bloodhungry" - bloodsucker.update_body() - var/obj/item/organ/heart/nightmare/nightmarish_heart = new - nightmarish_heart.Insert(bloodsucker) - nightmarish_heart.Stop() - for(var/obj/item/light_eater/blade in bloodsucker.held_items) - QDEL_NULL(blade) - GLOB.reality_smash_track.AddMind(owner) - var/datum/objective/bloodsucker/hierarchy/lasombra_objective = new - lasombra_objective.owner = owner - objectives += lasombra_objective - to_chat(owner, span_notice("You have also learned how to channel the abyss's power into an iron knight's armor that can be build in the structure ta and activated as a trap for your lair.")) - owner.teach_crafting_recipe(/datum/crafting_recipe/possessedarmor) - owner.teach_crafting_recipe(/datum/crafting_recipe/restingplace) - if(CLAN_TOREADOR) - my_clan = CLAN_TOREADOR - to_chat(owner, span_announce("You have Ranked up enough to learn: You are part of the Toreador Clan!\n\ - * As part of the Toreador, you can't ignore your own emotions and you disrespect inhuman actions.\n\ - * Being in Masquerade doesn't spend your blood, or have any negative effects on your immortal powers.\n\ - * You passively raise morale of your living vassals around you.\n\ - * Also you get really sad when comitting inhumane actions, but your humanity loss can't go above a certain threshold.\n\ - * Remember that those bloodsuckers who dare to act inhuman or break the Masquerade shouldn't be forgiven, and deserve only death.\n\ - * Finally, your Favorite Vassal will gain the Mesmerise ability to help you in non-lethally dealing with enemies or vassalising people.")) - if(owner.current && ishuman(owner.current) && !owner.current.GetComponent(/datum/component/mood)) - owner.current.AddComponent(/datum/component/mood) //You are not a emotionless beast! - - for(var/datum/action/bloodsucker/masquerade/masquarade_spell in powers) - if(!istype(masquarade_spell)) - continue - masquarade_spell.bloodcost = 0 - masquarade_spell.constant_bloodcost = 0 //Wow very cool code, good job - + my_clan = new chosen_clan(owner.current) owner.announce_objectives() diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm index 18d54afff461..0d3c71cc937f 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_frenzy.dm @@ -11,10 +11,9 @@ /datum/martial_art/frenzygrab/grab_act(mob/living/user, mob/living/target) if(user != target) target.grabbedby(user) - user.grab_state = GRAB_AGGRESSIVE - restraining = TRUE + target.grippedby(user, instant = TRUE) return TRUE - ..() + return ..() /** * # Status effect @@ -28,7 +27,7 @@ status_type = STATUS_EFFECT_UNIQUE duration = -1 tick_interval = 10 - examine_text = span_notice("They look feral and inhuman!") + examine_text = span_notice("They seem... inhumane, and feral!") alert_type = /atom/movable/screen/alert/status_effect/frenzy /// Store whether they were an advancedtooluser, to give the trait back upon exiting. var/was_tooluser = FALSE @@ -40,6 +39,7 @@ desc = "You are in a Frenzy! You are entirely feral, and depending on your Clan - fighting for your life! Feeding while in this state is instant!" icon = 'icons/mob/actions/actions_bloodsucker.dmi' icon_state = "power_recover" + alerttooltipstyle = "cult" /atom/movable/screen/alert/status_effect/masquerade/MouseEntered(location,control,params) desc = initial(desc) @@ -50,9 +50,11 @@ bloodsuckerdatum = IS_BLOODSUCKER(user) // Disable ALL Powers and notify their entry - bloodsuckerdatum.DisableAllPowers() + bloodsuckerdatum.DisableAllPowers(forced = TRUE) to_chat(owner, span_userdanger("Blood! You need Blood, now! You enter a total Frenzy! Your skin starts sizzling....")) to_chat(owner, span_announce("* Bloodsucker Tip: While in Frenzy, you instantly Aggresively grab, have stun resistance, and cannot speak, hear, or use any powers outside of Feed and Trespass (If you have it).")) + owner.balloon_alert(owner, "you enter a frenzy!") + // Stamina resistances user.physiology.stamina_mod *= 0.4 @@ -70,8 +72,8 @@ var/obj/item/cuffs = user.get_item_by_slot(SLOT_HANDCUFFED) var/obj/item/legcuffs = user.get_item_by_slot(SLOT_LEGCUFFED) if(user.handcuffed || user.legcuffed) - user.clear_cuffs(cuffs, TRUE) - user.clear_cuffs(legcuffs, TRUE) + user.clear_cuffs(cuffs, TRUE, TRUE) + user.clear_cuffs(legcuffs, TRUE, TRUE) // Keep track of how many times we've entered a Frenzy. bloodsuckerdatum.frenzies++ bloodsuckerdatum.frenzied = TRUE @@ -79,7 +81,7 @@ /datum/status_effect/frenzy/on_remove() var/mob/living/carbon/human/user = owner - to_chat(owner, span_warning("You come back to your senses.")) + owner.balloon_alert(owner, "you come back to your senses.") REMOVE_TRAIT(owner, TRAIT_MUTE, FRENZY_TRAIT) REMOVE_TRAIT(owner, TRAIT_DEAF, FRENZY_TRAIT) REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, FRENZY_TRAIT) @@ -90,7 +92,7 @@ owner.remove_movespeed_modifier(type) bloodsuckerdatum.frenzygrab.remove(user) owner.remove_client_colour(/datum/client_colour/cursed_heart_blood) - owner.Dizzy(3 SECONDS) + owner.adjust_dizzy(3 SECONDS) owner.Paralyze(2 SECONDS) user.physiology.stamina_mod /= 0.4 @@ -102,8 +104,9 @@ var/obj/item/cuffs = user.get_item_by_slot(SLOT_HANDCUFFED) var/obj/item/legcuffs = user.get_item_by_slot(SLOT_LEGCUFFED) if(user.handcuffed || user.legcuffed) - user.clear_cuffs(cuffs, TRUE) - user.clear_cuffs(legcuffs, TRUE) + user.clear_cuffs(cuffs, TRUE, TRUE) + user.clear_cuffs(legcuffs, TRUE, TRUE) + user.balloon_alert_to_viewers("snaps [user.p_their()] restraints!", "you break free of your restraints!") if(!bloodsuckerdatum.frenzied) return - user.adjustFireLoss(min(0.5 + (bloodsuckerdatum.humanity_lost / 15), bloodsuckerdatum.my_clan == CLAN_GANGREL ? 2 : 100)) + user.adjustFireLoss(min(0.5 + (bloodsuckerdatum.humanity_lost / 15), bloodsuckerdatum.my_clan?.get_clan() == CLAN_GANGREL ? 2 : 100)) //:D diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_hud.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_hud.dm new file mode 100644 index 000000000000..2e3e896f5831 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_hud.dm @@ -0,0 +1,98 @@ +/// 1 tile down +#define UI_BLOOD_DISPLAY "WEST:6,CENTER-1:0" +/// 2 tiles down +#define UI_VAMPRANK_DISPLAY "WEST:6,CENTER-2:-5" +/// 6 pixels to the right, zero tiles & 5 pixels DOWN. +#define UI_SUNLIGHT_DISPLAY "WEST:6,CENTER-0:0" + +///Maptext define for Bloodsucker HUDs +#define FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, value) MAPTEXT("
[round(value,1)]
") +///Maptext define for Bloodsucker Sunlight HUDs +#define FORMAT_BLOODSUCKER_SUNLIGHT_TEXT(valuecolor, value) MAPTEXT("
[value]
") + + +/atom/movable/screen/bloodsucker + icon = 'icons/mob/actions/actions_bloodsucker.dmi' + +/atom/movable/screen/bloodsucker/blood_counter + name = "Blood Consumed" + icon_state = "blood_display" + screen_loc = UI_BLOOD_DISPLAY + +/atom/movable/screen/bloodsucker/rank_counter + name = "Bloodsucker Rank" + icon_state = "rank" + screen_loc = UI_VAMPRANK_DISPLAY + +/atom/movable/screen/bloodsucker/sunlight_counter + name = "Solar Flare Timer" + icon_state = "sunlight" + screen_loc = UI_SUNLIGHT_DISPLAY + +///Updates the counter on said HUD +/atom/movable/screen/bloodsucker/proc/update_counter() + +/atom/movable/screen/bloodsucker/blood_counter/update_counter(value, valuecolor) + . = ..() + maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, value) + +/atom/movable/screen/bloodsucker/rank_counter/update_counter(value, valuecolor) + . = ..() + maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, value) + +/atom/movable/screen/bloodsucker/sunlight_counter/update_counter(value, valuecolor) + . = ..() + maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT(valuecolor, value) + +/// Update Blood Counter + Rank Counter +/datum/antagonist/bloodsucker/proc/update_hud() + var/valuecolor + if(bloodsucker_blood_volume > BLOOD_VOLUME_SAFE(owner.current)) + valuecolor = "#FFDDDD" + else if(bloodsucker_blood_volume > BLOOD_VOLUME_BAD(owner.current)) + valuecolor = "#FFAAAA" + + if(blood_display) + blood_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_blood_volume) + + if(vamprank_display) + if(bloodsucker_level_unspent) + vamprank_display.icon_state = "[initial(vamprank_display.icon_state)]_up" + else + vamprank_display.icon_state = initial(vamprank_display.icon_state) + vamprank_display.maptext = FORMAT_BLOODSUCKER_HUD_TEXT(valuecolor, bloodsucker_level) + + if(sunlight_display) + if(SSsunlight.sunlight_active) + valuecolor = "#FF5555" + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_day" + else + switch(round(SSsunlight.time_til_cycle, 1)) + if(0 to 30) + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_30" + valuecolor = "#FFCCCC" + if(31 to 60) + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_60" + valuecolor = "#FFE6CC" + if(61 to 90) + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_90" + valuecolor = "#FFFFCC" + else + sunlight_display.icon_state = "[initial(sunlight_display.icon_state)]_night" + valuecolor = "#FFFFFF" + sunlight_display.maptext = FORMAT_BLOODSUCKER_SUNLIGHT_TEXT( \ + valuecolor, \ + (SSsunlight.time_til_cycle >= 60) ? "[round(SSsunlight.time_til_cycle / 60, 1)] m" : "[round(SSsunlight.time_til_cycle, 1)] s" \ + ) + +/// 1 tile down +#undef UI_BLOOD_DISPLAY +/// 2 tiles down +#undef UI_VAMPRANK_DISPLAY +/// 6 pixels to the right, zero tiles & 5 pixels DOWN. +#undef UI_SUNLIGHT_DISPLAY + +///Maptext define for Bloodsucker HUDs +#undef FORMAT_BLOODSUCKER_HUD_TEXT +///Maptext define for Bloodsucker Sunlight HUDs +#undef FORMAT_BLOODSUCKER_SUNLIGHT_TEXT diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm index 09f8932ba553..68dbf800d737 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_integration.dm @@ -1,14 +1,7 @@ -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// TG OVERWRITES - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/// Gives Curators their abilities -/datum/outfit/job/curator/post_equip(mob/living/carbon/human/user, visualsOnly = FALSE) - . = ..() - - ADD_TRAIT(user, TRAIT_BLOODSUCKER_HUNTER, JOB_TRAIT) +/* + * OVERWRITES +*/ /datum/species/jelly/slime/spec_life(mob/living/carbon/human/user) // Prevents Slimeperson 'gaming @@ -31,7 +24,7 @@ . = ..() // Used to keep track of how much Blood we've drank so far -/mob/living/carbon/human/get_status_tab_items() +/mob/living/get_status_tab_items() . = ..() if(mind) var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker) @@ -61,7 +54,7 @@ return TRUE // EXAMINING -/mob/living/carbon/human/proc/ReturnVampExamine(mob/living/viewer) +/mob/living/carbon/proc/return_vamp_examine(mob/living/viewer) if(!mind || !viewer.mind) return "" // Target must be a Vamp @@ -79,7 +72,7 @@ if(!(HAS_TRAIT(viewer, TRAIT_BLOODSUCKER_HUNTER) && bloodsuckerdatum.broke_masquerade)) return "" // Default String - var/returnString = "\[[bloodsuckerdatum.ReturnFullName(1)]\]" + var/returnString = "\[[bloodsuckerdatum.return_full_name(1)]\]" var/returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "bloodsucker")]" // In Disguise (Veil)? @@ -107,7 +100,7 @@ returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal")]" // Am I someone ELSE'S Vassal? else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer)) - returnString += "This [dna.species.name] bears the mark of [vassaldatum.master.ReturnFullName(vassaldatum.master.owner.current,TRUE)][vassaldatum.master.broke_masquerade ? " who has broken the Masquerade" : ""]" + returnString += "This [dna.species.name] bears the mark of [vassaldatum.master.return_full_name(vassaldatum.master.owner.current,TRUE)][vassaldatum.master.broke_masquerade ? " who has broken the Masquerade" : ""]" returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal_grey")]" // Are you serving the same master as I am? else if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in vassaldatum?.master.vassals) diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm index 9bf707dac7e3..25744e988319 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_mobs.dm @@ -196,6 +196,7 @@ user.blood_volume += 10 adjustFireLoss(2.5) updatehealth() //3 minutes to die + if(satiation >= 3) to_chat(src, span_notice("It has been fed. You turn back to normal.")) qdel(src) diff --git a/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm b/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm index 646a07bd6097..6f8b445e1b13 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsucker_objectives.dm @@ -47,7 +47,7 @@ // WIN CONDITIONS? /datum/objective/bloodsucker/lair/check_completion() var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum && bloodsuckerdatum.coffin && bloodsuckerdatum.lair) + if(bloodsuckerdatum && bloodsuckerdatum.coffin && bloodsuckerdatum.bloodsucker_lair_area) return TRUE return FALSE @@ -100,7 +100,7 @@ target_department = VASSALIZE_COMMAND // Vassalize a certain department else - target_amount = rand(2,3) + target_amount = rand(2, 3) target_department = pick(departments) ..() @@ -350,12 +350,12 @@ name = "frenzy" /datum/objective/bloodsucker/frenzy/New() - target_amount = rand(3,4) + target_amount = rand(1, 2) ..() /datum/objective/bloodsucker/frenzy/update_explanation_text() . = ..() - explanation_text = "Enter Frenzy [target_amount] of times without succumbing to Final Death." + explanation_text = "Enter Frenzy [target_amount == 1 ? "atleast once" : "2 times"] without succumbing to Final Death." /datum/objective/bloodsucker/frenzy/check_completion() var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker) @@ -369,12 +369,12 @@ name = "hierarchy" /datum/objective/bloodsucker/hierarchy/New() - target_amount = rand(4,5) + target_amount = rand(1, 2) ..() /datum/objective/bloodsucker/hierarchy/update_explanation_text() . = ..() - explanation_text = "Ascend [target_amount] abilities using a Resting Place altar." + explanation_text = "Ascend [target_amount == 1 ? "atleast 1 ability" : "2 abilities"] using a Resting Place altar." /datum/objective/bloodsucker/hierarchy/check_completion() var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker) @@ -413,17 +413,17 @@ name = "leader" /datum/objective/bloodsucker/leader/New() - target_amount = rand(2,3) + target_amount = rand(2, 3) ..() // EXPLANATION /datum/objective/bloodsucker/leader/update_explanation_text() . = ..() - explanation_text = "Convert [target_amount] of Vassals into your vassals." + explanation_text = "Convert [target_amount] mortal beings into your vassals." // WIN CONDITIONS? /datum/objective/bloodsucker/leader/check_completion() var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.current.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum && bloodsuckerdatum.vassals >= target_amount) + if(LAZYLEN(bloodsuckerdatum?.vassals) >= target_amount) return TRUE return FALSE diff --git a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm index 29c8a50c466d..346ca8f7c63e 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsuckers.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsuckers.dm @@ -4,8 +4,12 @@ roundend_category = "bloodsuckers" antagpanel_category = "Bloodsucker" job_rank = ROLE_BLOODSUCKER + antag_hud_name = "bloodsucker" + show_to_ghosts = TRUE show_name_in_check_antagonists = TRUE can_coexist_with_others = FALSE + ui_name = "AntagInfoBloodsucker" + preview_outfit = /datum/outfit/bloodsucker_outfit // TIMERS // ///Timer between alerts for Burn messages @@ -24,6 +28,8 @@ var/humanity_lost = 0 ///Have we been broken the Masquerade? var/broke_masquerade = FALSE + ///How many Masquerade Infractions do we have? + var/masquerade_infractions = 0 ///If we are currently in a Frenzy var/frenzied = FALSE ///If we have a task assigned @@ -32,13 +38,9 @@ var/altar_uses = 0 ///ALL Powers currently owned - var/list/datum/action/powers = list() - ///Bloodsucker Clan - Used for dealing with Sol - var/datum/team/vampireclan/clan + var/list/datum/action/bloodsucker/powers = list() ///Frenzy Grab Martial art given to Bloodsuckers in a Frenzy var/datum/martial_art/frenzygrab/frenzygrab = new - ///You get assigned a Clan once you Rank up enough - var/my_clan = NONE ///How many clan points you have -> Used in clans in order to assert certain limits // Upgrades and stuff var/clanpoints = 0 ///How much progress have you done on your clan @@ -46,18 +48,23 @@ ///Vassals under my control. Periodically remove the dead ones. var/list/datum/antagonist/vassal/vassals = list() - ///Have we selected our Favorite Vassal yet? - var/has_favorite_vassal = FALSE + ///Special vassals I own, to not have double of the same type. + var/list/datum/antagonist/vassal/special_vassals = list() var/bloodsucker_level var/bloodsucker_level_unspent = 1 var/passive_blood_drain = -0.1 var/additional_regen var/bloodsucker_regen_rate = 0.3 + /// How much blood we have, starting off at default blood levels. + var/bloodsucker_blood_volume = BLOOD_VOLUME_GENERIC + /// How much blood we can have at once, increases per level. var/max_blood_volume = 600 + var/datum/bloodsucker_clan/my_clan + // Used for Bloodsucker Objectives - var/area/lair + var/area/bloodsucker_lair_area var/obj/structure/closet/crate/coffin var/total_blood_drank = 0 var/frenzy_blood_drank = 0 @@ -65,14 +72,19 @@ var/task_blood_required = 0 var/task_blood_drank = 0 var/frenzies = 0 + ///Blood display HUD + var/atom/movable/screen/bloodsucker/blood_counter/blood_display + ///Vampire level display HUD + var/atom/movable/screen/bloodsucker/rank_counter/vamprank_display + ///Sunlight timer HUD + var/atom/movable/screen/bloodsucker/sunlight_counter/sunlight_display /// Static typecache of all bloodsucker powers. var/static/list/all_bloodsucker_powers = typecacheof(/datum/action/bloodsucker, TRUE) /// Antagonists that cannot be Vassalized no matter what - var/list/vassal_banned_antags = list( + var/static/list/vassal_banned_antags = list( /datum/antagonist/bloodsucker, /datum/antagonist/monsterhunter, - /datum/antagonist/xeno, ) ///Default Bloodsucker traits var/static/list/bloodsucker_traits = list( @@ -94,33 +106,72 @@ TRAIT_RESISTDAMAGESLOWDOWN, ) -/mob/living/proc/explain_powers() - set name = "Bloodsucker Help" - set category = "Mentor" - - var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind.has_antag_datum(/datum/antagonist/bloodsucker) - var/choice = input(usr, "What Power are you looking into?", "Mentorhelp v2") in bloodsuckerdatum.powers - if(!choice) - return - var/datum/action/bloodsucker/power = choice - to_chat(usr, span_warning("[power.power_explanation]")) +/datum/antagonist/bloodsucker/can_be_owned(datum/mind/new_owner) + . = ..() + if(!.) + return FALSE + if(!new_owner.can_make_bloodsucker()) + return FALSE + return TRUE -/// These handles the application of antag huds/special abilities +/** + * Apply innate effects is everything given to the mob + * When a body is tranferred, this is called on the new mob + * while on_gain is called ONCE per ANTAG, this is called ONCE per BODY. + */ /datum/antagonist/bloodsucker/apply_innate_effects(mob/living/mob_override) - RegisterSignal(owner.current, COMSIG_LIVING_BIOLOGICAL_LIFE, .proc/LifeTick) - if((owner.assigned_role == "Clown")) - var/mob/living/carbon/H = owner.current - if(H && istype(H)) - if(!silent) - H.dna.remove_mutation(CLOWNMUT) - to_chat(owner, "As a vampiric clown, you are no longer a danger to yourself. Your clownish nature has been subdued by your thirst for blood.") + . = ..() + var/mob/living/current_mob = mob_override || owner.current + RegisterSignal(current_mob, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(current_mob, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(LifeTick)) + handle_clown_mutation(current_mob, mob_override ? null : "As a vampiric clown, you are no longer a danger to yourself. Your clownish nature has been subdued by your thirst for blood.") + add_team_hud(current_mob) + + if(current_mob.hud_used) + on_hud_created() + else + RegisterSignal(current_mob, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created)) + +/** + * Remove innate effects is everything given to the mob + * When a body is tranferred, this is called on the new mob + * while on_removal is called ONCE per ANTAG, this is called ONCE per BODY. + */ /datum/antagonist/bloodsucker/remove_innate_effects(mob/living/mob_override) - UnregisterSignal(owner.current, COMSIG_LIVING_BIOLOGICAL_LIFE) - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/H = owner.current - if(H && istype(H)) - H.dna.add_mutation(CLOWNMUT) + . = ..() + var/mob/living/current_mob = mob_override || owner.current + UnregisterSignal(current_mob, list(COMSIG_LIVING_BIOLOGICAL_LIFE, COMSIG_PARENT_EXAMINE)) + + if(current_mob.hud_used) + var/datum/hud/hud_used = current_mob.hud_used + + hud_used.infodisplay -= blood_display + hud_used.infodisplay -= vamprank_display + hud_used.infodisplay -= sunlight_display + + QDEL_NULL(blood_display) + QDEL_NULL(vamprank_display) + QDEL_NULL(sunlight_display) + +/datum/antagonist/bloodsucker/proc/on_hud_created(datum/source) + SIGNAL_HANDLER + + var/datum/hud/bloodsucker_hud = owner.current.hud_used + + blood_display = new /atom/movable/screen/bloodsucker/blood_counter() + blood_display.hud = bloodsucker_hud + bloodsucker_hud.infodisplay += blood_display + + vamprank_display = new /atom/movable/screen/bloodsucker/rank_counter() + vamprank_display.hud = bloodsucker_hud + bloodsucker_hud.infodisplay += vamprank_display + + sunlight_display = new /atom/movable/screen/bloodsucker/sunlight_counter() + sunlight_display.hud = bloodsucker_hud + bloodsucker_hud.infodisplay += sunlight_display + + bloodsucker_hud.show_hud(bloodsucker_hud.hud_version) /datum/antagonist/bloodsucker/get_admin_commands() . = ..() @@ -133,13 +184,20 @@ else .["Break Masquerade"] = CALLBACK(src, .proc/break_masquerade) -/// Called by the add_antag_datum() mind proc after the instanced datum is added to the mind's antag_datums list. +///Called when you get the antag datum, called only ONCE per antagonist. /datum/antagonist/bloodsucker/on_gain() - if(IS_VASSAL(owner.current)) // Vassals shouldnt be getting the same benefits as Bloodsuckers. + RegisterSignal(SSsunlight, COMSIG_SOL_RANKUP_BLOODSUCKERS, PROC_REF(sol_rank_up)) + RegisterSignal(SSsunlight, COMSIG_SOL_NEAR_START, PROC_REF(sol_near_start)) + RegisterSignal(SSsunlight, COMSIG_SOL_END, PROC_REF(on_sol_end)) + RegisterSignal(SSsunlight, COMSIG_SOL_RISE_TICK, PROC_REF(handle_sol)) + RegisterSignal(SSsunlight, COMSIG_SOL_WARNING_GIVEN, PROC_REF(give_warning)) + + if(IS_FAVORITE_VASSAL(owner.current)) // Vassals shouldnt be getting the same benefits as Bloodsuckers. bloodsucker_level_unspent = 0 + show_in_roundend = FALSE else // Start Sunlight if first Bloodsucker - clan.check_start_sunlight() + check_start_sunlight() // Name and Titles SelectFirstName() SelectTitle(am_fledgling = TRUE) @@ -148,16 +206,16 @@ forge_bloodsucker_objectives() . = ..() - update_bloodsucker_icons_added(owner.current) // Assign Powers AssignStarterPowersAndStats() /// Called by the remove_antag_datum() and remove_all_antag_datums() mind procs for the antag datum to handle its own removal and deletion. /datum/antagonist/bloodsucker/on_removal() /// End Sunlight? (if last Vamp) - clan.check_cancel_sunlight() - update_bloodsucker_icons_removed(owner.current) + UnregisterSignal(SSsunlight, list(COMSIG_SOL_RANKUP_BLOODSUCKERS, COMSIG_SOL_NEAR_START, COMSIG_SOL_END, COMSIG_SOL_RISE_TICK, COMSIG_SOL_WARNING_GIVEN)) ClearAllPowersAndStats() + check_cancel_sunlight() //check if sunlight should end + QDEL_NULL(my_clan) return ..() /datum/antagonist/bloodsucker/on_body_transfer(mob/living/old_body, mob/living/new_body) @@ -206,7 +264,7 @@ /datum/antagonist/bloodsucker/greet() . = ..() - var/fullname = ReturnFullName(TRUE) + var/fullname = return_full_name(TRUE) to_chat(owner, span_userdanger("You are [fullname], a strain of vampire known as a Bloodsucker!")) owner.announce_objectives() if(bloodsucker_level_unspent >= 2) @@ -219,12 +277,6 @@ // Refill with Blood so they don't instantly die. owner.current.blood_volume = max(owner.current.blood_volume, BLOOD_VOLUME_NORMAL(owner.current)) -/datum/antagonist/bloodsucker/proc/add_objective(datum/objective/added_objective) - objectives += added_objective - -/datum/antagonist/bloodsucker/proc/remove_objectives(datum/objective/removed_objective) - objectives -= removed_objective - // Called when using admin tools to give antag status, admin spawned bloodsuckers don't get turned human if plasmaman. /datum/antagonist/bloodsucker/admin_add(datum/mind/new_owner, mob/admin) var/levels = input("How many unspent Ranks would you like [new_owner] to have?","Bloodsucker Rank", bloodsucker_level_unspent) as null | num @@ -236,60 +288,59 @@ log_admin("[key_name(usr)][msg]") new_owner.add_antag_datum(src) -/** - * # Vampire Clan - * - * This is used for dealing with the Vampire Clan. - * This handles Sol for Bloodsuckers, making sure to not have several. - * None of this should appear in game, we are using it JUST for Sol. All Bloodsuckers should have their individual report. - */ +/datum/antagonist/bloodsucker/ui_data(mob/user) + var/list/data = list() -/datum/team/vampireclan - name = "Clan" + data["in_clan"] = !!my_clan + var/list/clan_data = list() + if(my_clan) + clan_data["clan_name"] = my_clan.name + clan_data["clan_description"] = my_clan.description + clan_data["clan_icon"] = my_clan.join_icon_state - /// Sunlight Timer. Created on first Bloodsucker assign. Destroyed on last removed Bloodsucker. - var/obj/effect/sunlight/bloodsucker_sunlight + data["clan"] += list(clan_data) -/datum/antagonist/bloodsucker/create_team(datum/team/vampireclan/team) - if(!team) - for(var/datum/antagonist/bloodsucker/bloodsuckerdatums in GLOB.antagonists) - if(!bloodsuckerdatums.owner) - continue - if(bloodsuckerdatums.clan) - clan = bloodsuckerdatums.clan - return - clan = new /datum/team/vampireclan - return - if(!istype(team)) - stack_trace("Wrong team type passed to [type] initialization.") - clan = team + return data + +/datum/antagonist/bloodsucker/ui_static_data(mob/user) + var/list/data = list() + //we don't need to update this that much. + for(var/datum/action/bloodsucker/power as anything in powers) + var/list/power_data = list() + + power_data["power_name"] = power.name + power_data["power_explanation"] = power.power_explanation + power_data["power_icon"] = power.button_icon_state -/datum/antagonist/bloodsucker/get_team() - return clan + data["power"] += list(power_data) + + return data + ..() + +/datum/antagonist/bloodsucker/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/bloodsucker_icons), + ) -/datum/team/vampireclan/roundend_report() - if(members.len <= 0) +/datum/antagonist/bloodsucker/ui_act(action, params, datum/tgui/ui) + . = ..() + if(.) return - var/list/report = list() - report += "Lurking in the darkness, the Bloodsuckers were:
" - for(var/datum/mind/mind_members in members) - for(var/datum/antagonist/bloodsucker/individual_bloodsuckers in mind_members.antag_datums) - if(mind_members.has_antag_datum(/datum/antagonist/vassal)) // Skip over Ventrue's Favorite Vassal - continue - report += individual_bloodsuckers.roundend_report() - return "
[report.Join("
")]
" + switch(action) + if("join_clan") + assign_clan_and_bane() + ui.send_full_update(force = TRUE) + return + -/// Individual roundend report /datum/antagonist/bloodsucker/roundend_report() - // Get the default Objectives var/list/report = list() + // Vamp name - report += "
\[[ReturnFullName(TRUE)]\]" + report += "
[span_header("\[[return_full_name()]\]")]
" report += printplayer(owner) - // Clan (Actual Clan, not Team) name - if(my_clan != NONE) - report += "They were part of the [my_clan]!" + if(my_clan) + report += "They were part of the [my_clan.name]!" // Default Report var/objectives_complete = TRUE @@ -306,21 +357,31 @@ break // Now list their vassals - if(vassals.len > 0) - report += "Their Vassals were..." - for(var/datum/antagonist/vassal/all_vassals in vassals) - if(all_vassals.owner) - var/jobname = all_vassals.owner.assigned_role ? "the [all_vassals.owner.assigned_role]" : "" - report += "[all_vassals.owner.name] [jobname][all_vassals.favorite_vassal == TRUE ? " and was the Favorite Vassal" : ""]" - - if(objectives.len == 0 || objectives_complete && optional_objectives_complete) + if(vassals.len) + report += span_header("Their Vassals were...") + for(var/datum/antagonist/vassal/all_vassals as anything in vassals) + if(!all_vassals.owner) + continue + var/list/vassal_report = list() + vassal_report += "[all_vassals.owner.name]" + + if(all_vassals.owner.assigned_role) + vassal_report += " the [all_vassals.owner.assigned_role]" + if(IS_FAVORITE_VASSAL(all_vassals.owner.current)) + vassal_report += " and was the Favorite Vassal" + else if(IS_REVENGE_VASSAL(all_vassals.owner.current)) + vassal_report += " and was the Revenge Vassal" + report += vassal_report.Join() + + if(!LAZYLEN(objectives) || objectives_complete && optional_objectives_complete) report += span_greentext(span_big("The [name] was successful!")) else if(objectives_complete && !optional_objectives_complete) report += span_marooned("The [name] survived, but has not made a name for [owner.current.p_them()]self...") else report += span_redtext(span_big("The [name] has failed!")) report += get_flavor(objectives_complete, optional_objectives_complete) - return report + + return report.Join("
") /// Evaluates the conditions of the bloodsucker at the end of each round to pick a flavor message to add /datum/antagonist/bloodsucker/proc/get_flavor(objectives_complete, optional_objectives_complete) @@ -381,30 +442,43 @@ else //perish or just fuck up and fail your primary objectives flavor_message += pick(list( - "Thus ends the story of [ReturnFullName(TRUE)]. No doubt future generations will look back on your legacy and reflect on the lessons of the past. If they remember you at all." + "Thus ends the story of [return_full_name(TRUE)]. No doubt future generations will look back on your legacy and reflect on the lessons of the past. If they remember you at all." )) flavor += "[flavor_message]" return "
[flavor.Join("
")]
" /** - * # Assigning Sol + * # Vampire Clan * - * Sol is the sunlight, during this period, all Bloodsuckers must be in their coffin, else they burn. - * This was originally dealt with by the gamemode, but as gamemodes no longer exist, it is dealt with by the team. + * This is used for dealing with the Vampire Clan. + * This handles Sol for Bloodsuckers, making sure to not have several. + * None of this should appear in game, we are using it JUST for Sol. All Bloodsuckers should have their individual report. */ -/// Start Sol, called when someone is assigned Bloodsucker -/datum/team/vampireclan/proc/check_start_sunlight() - if(members.len <= 1) - message_admins("New Sol has been created due to Bloodsucker assignment.") - bloodsucker_sunlight = new() - -/// End Sol, if you're the last Bloodsucker -/datum/team/vampireclan/proc/check_cancel_sunlight() - // No minds in the clan? Delete Sol. - if(members.len <= 1) - message_admins("Sol has been deleted due to the lack of Bloodsuckers") - QDEL_NULL(bloodsucker_sunlight) +///Ranks the Bloodsucker up, called by Sol. +/datum/antagonist/bloodsucker/proc/sol_rank_up(atom/source) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, PROC_REF(RankUp)) + +///Called when Sol is near starting. +/datum/antagonist/bloodsucker/proc/sol_near_start(atom/source) + SIGNAL_HANDLER + if(bloodsucker_lair_area && !(locate(/datum/action/bloodsucker/gohome) in powers)) + BuyPower(new /datum/action/bloodsucker/gohome) + if(my_clan?.get_clan() == CLAN_GANGREL && !(locate(/datum/action/bloodsucker/gangrel/transform) in powers)) + BuyPower(new /datum/action/bloodsucker/gangrel/transform) + +///Called when Sol first ends. +/datum/antagonist/bloodsucker/proc/on_sol_end(atom/source) + SIGNAL_HANDLER + check_end_torpor() + for(var/datum/action/bloodsucker/power in powers) + if(istype(power, /datum/action/bloodsucker/gohome)) + RemovePower(power) + if(altar_uses) + to_chat(owner, span_boldnotice("Your Altar uses have been reset!")) + altar_uses = 0 /// Buying powers /datum/antagonist/bloodsucker/proc/BuyPower(datum/action/bloodsucker/power) @@ -412,10 +486,6 @@ power.Grant(owner.current) /datum/antagonist/bloodsucker/proc/RemovePower(datum/action/bloodsucker/power) - for(var/datum/action/bloodsucker/all_powers as anything in powers) - if(initial(power.name) == all_powers.name) - power = all_powers - break if(power.active) power.DeactivatePower() powers -= power @@ -423,11 +493,10 @@ /datum/antagonist/bloodsucker/proc/AssignStarterPowersAndStats() // Purchase Roundstart Powers - BuyPower(new /datum/action/bloodsucker/feed) - BuyPower(new /datum/action/bloodsucker/masquerade) - if(!IS_VASSAL(owner.current)) // Favorite Vassal gets their own. - BuyPower(new /datum/action/bloodsucker/veil) - add_verb(owner.current, /mob/living/proc/explain_powers) + for(var/datum/action/bloodsucker/all_powers as anything in all_bloodsucker_powers) + if(!(initial(all_powers.purchase_flags) & BLOODSUCKER_DEFAULT_POWER)) + continue + BuyPower(new all_powers) // Traits: Species var/mob/living/carbon/human/user = owner.current if(ishuman(owner.current)) @@ -447,18 +516,12 @@ owner.current.grant_all_languages(FALSE, FALSE, TRUE) owner.current.grant_language(/datum/language/vampiric) /// Clear Disabilities & Organs - HealVampireOrgans() + heal_vampire_organs() /datum/antagonist/bloodsucker/proc/ClearAllPowersAndStats() - /// Remove huds - remove_hud() // Powers - remove_verb(owner.current, /mob/living/proc/explain_powers) - while(powers.len) - var/datum/action/bloodsucker/power = pick(powers) - powers -= power - power.Remove(owner.current) - // owner.RemoveSpell(power) + for(var/datum/action/bloodsucker/all_powers as anything in powers) + RemovePower(all_powers) /// Stats if(ishuman(owner.current)) var/mob/living/carbon/human/user = owner.current @@ -486,12 +549,23 @@ user_eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE user.update_sight() +/datum/antagonist/bloodsucker/proc/give_masquerade_infraction() + if(broke_masquerade) + return + masquerade_infractions++ + if(masquerade_infractions >= 3) + break_masquerade() + else + to_chat(owner.current, span_cultbold("You violated the Masquerade! Break the Masquerade [3 - masquerade_infractions] more times and you will become a criminal to the Bloodsucker's Cause!")) + + /datum/antagonist/bloodsucker/proc/RankUp() - set waitfor = FALSE - var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(owner.current) - if(!owner || !owner.current || vassaldatum) + if(!owner || !owner.current || IS_FAVORITE_VASSAL(owner.current)) return bloodsucker_level_unspent++ + if(!my_clan) + to_chat(owner.current, span_notice("You have gained a rank. Join a Clan to spend it.")) + return passive_blood_drain -= 0.03 * bloodsucker_level //do something. It's here because if you are gaining points through other means you are doing good // Spend Rank Immediately? if(!istype(owner.current.loc, /obj/structure/closet/crate/coffin)) @@ -516,84 +590,16 @@ power.UpdateDesc() ///Disables all powers, accounting for torpor -/datum/antagonist/bloodsucker/proc/DisableAllPowers() +/datum/antagonist/bloodsucker/proc/DisableAllPowers(forced = FALSE) for(var/datum/action/bloodsucker/power as anything in powers) - if((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(owner.current, TRAIT_NODEATH)) + if(forced || ((power.check_flags & BP_CANT_USE_IN_TORPOR) && HAS_TRAIT(owner.current, TRAIT_NODEATH))) if(power.active) power.DeactivatePower() -/datum/antagonist/bloodsucker/proc/SpendRank(spend_rank = TRUE, ask = TRUE) - set waitfor = FALSE - - if(!owner || !owner.current || !owner.current.client || (spend_rank && bloodsucker_level_unspent <= 0.5)) +/datum/antagonist/bloodsucker/proc/SpendRank(mob/living/carbon/human/target, cost_rank = TRUE, blood_cost, ask = TRUE) + if(!owner || !owner.current || !owner.current.client || (cost_rank && bloodsucker_level_unspent <= 0.5)) return - // Purchase Power Prompt - var/list/options = list() - for(var/datum/action/bloodsucker/power as anything in all_bloodsucker_powers) - if(initial(power.purchase_flags) & BLOODSUCKER_CAN_BUY && !(locate(power) in powers)) - options[initial(power.name)] = power - - if(options.len < 1) - to_chat(owner.current, span_notice("You grow more ancient by the night!")) - else - // Give them the UI to purchase a power. - var/choice = tgui_input_list(owner.current, "You have the opportunity to grow more ancient, increasing the level of all your powers by 1. Select a power to advance your Rank.", "Your Blood Thickens...", options) - // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels. - if(spend_rank && bloodsucker_level_unspent <= 0) - return - // Did you choose a power? - if(!choice || !options[choice]) - to_chat(owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later.")) - return - if((locate(options[choice]) in powers)) - to_chat(owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later.")) - return - // Prevent Bloodsuckers from purchasing a power while outside of their Coffin. - if(!istype(owner.current.loc, /obj/structure/closet/crate/coffin)) - to_chat(owner.current, span_warning("You must be in your Coffin to purchase Powers.")) - return - - // Good to go - Buy Power! - var/datum/action/bloodsucker/purchased_power = options[choice] - BuyPower(new purchased_power) - to_chat(owner.current, span_notice("You have learned how to use [choice]!")) - - // Advance Powers - Includes the one you just purchased. - LevelUpPowers() - // Bloodsucker-only Stat upgrades - bloodsucker_regen_rate += 0.05 - max_blood_volume += 100 - // Misc. Stats Upgrades - if(ishuman(owner.current)) - var/mob/living/carbon/human/user = owner.current - var/datum/species/user_species = user.dna.species - user_species.punchdamagelow += 0.5 - // This affects the hitting power of Brawn. - user_species.punchdamagehigh += 0.5 - user_species.punchstunthreshold += 0.5 - - // We're almost done - Spend your Rank now. - bloodsucker_level++ - if(spend_rank) - bloodsucker_level_unspent-- - // Ranked up enough? Let them join a Clan. - if(bloodsucker_level == 3 && my_clan == NONE) //updated for tzimisce - AssignClanAndBane() - - // Ranked up enough to get your true Reputation? - if(bloodsucker_level == 4) - SelectReputation(am_fledgling = FALSE, forced = TRUE) - - // Done! Let them know & Update their HUD. - to_chat(owner.current, span_notice("You are now a rank [bloodsucker_level] Bloodsucker. Your strength, health, feed rate, regen rate, and maximum blood capacity have all increased!\n\ - * Your existing powers have all ranked up as well!")) - update_hud(owner.current) - owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, TRUE, pressure_affected = FALSE) - if(bloodsucker_level_unspent && spend_rank) - if(ask) - if(tgui_alert(owner.current, "You have leftover ranks, do you want to spend them all?", "Time Management Team", list("Yes", "No")) == "No") - return - SpendRank(ask = FALSE) + SEND_SIGNAL(my_clan, BLOODSUCKER_RANK_UP, src, cost_rank, blood_cost, ask) //////////////////////////////////////////////////////////////////////////////////////////////// @@ -611,13 +617,15 @@ // Objective 1: Vassalize a Head/Command, or a specific target var/list/rolled_objectives = list() - switch(rand(1, 3)) + switch(rand(1, 4)) if(1) //Drink Objective rolled_objectives = list(new /datum/objective/bloodsucker/gourmand) if(2) //Protege Objective rolled_objectives = list(new /datum/objective/bloodsucker/protege) - if(3) //Heart Thief + if(3) //Heart Thief Objective rolled_objectives = list(new /datum/objective/bloodsucker/heartthief) + if(4) //Vassal Specific Objective + rolled_objectives = list(new /datum/objective/bloodsucker/vassalhim) for(var/datum/objective/bloodsucker/objective in rolled_objectives) objective.owner = owner @@ -626,7 +634,7 @@ /// Name shown on antag list /datum/antagonist/bloodsucker/antag_listing_name() - return ..() + "([ReturnFullName(TRUE)])" + return ..() + "([return_full_name(TRUE)])" /// Whatever interesting things happened to the antag admins should know about /// Include additional information about antag in this part @@ -678,7 +686,7 @@ bloodsucker_title = pick ("Count","Baron","Viscount","Prince","Duke","Tzar","Dreadlord","Lord","Master") else bloodsucker_title = pick ("Countess","Baroness","Viscountess","Princess","Duchess","Tzarina","Dreadlady","Lady","Mistress") - to_chat(owner, span_announce("You have earned a title! You are now known as [ReturnFullName(TRUE)]!")) + to_chat(owner, span_announce("You have earned a title! You are now known as [return_full_name(TRUE)]!")) // Titles [Fledgling] else bloodsucker_title = null @@ -712,13 +720,13 @@ "Corrupt","Hellspawn","Tyrant","Sanguineous", ) - to_chat(owner, span_announce("You have earned a reputation! You are now known as [ReturnFullName(TRUE)]!")) + to_chat(owner, span_announce("You have earned a reputation! You are now known as [return_full_name(TRUE)]!")) /datum/antagonist/bloodsucker/proc/AmFledgling() return !bloodsucker_title -/datum/antagonist/bloodsucker/proc/ReturnFullName(include_rep = FALSE) +/datum/antagonist/bloodsucker/proc/return_full_name(include_rep = FALSE) var/fullname // Name First @@ -740,15 +748,16 @@ to_chat(owner.current, span_cultboldtalic("You have broken the Masquerade!")) to_chat(owner.current, span_warning("Bloodsucker Tip: When you break the Masquerade, you become open for termination by fellow Bloodsuckers, and your Vassals are no longer completely loyal to you, as other Bloodsuckers can steal them for themselves!")) broke_masquerade = TRUE - set_antag_hud(owner.current, "masquerade_broken") + antag_hud_name = "masquerade_broken" + add_team_hud(owner.current) for(var/datum/mind/clan_minds as anything in get_antag_minds(/datum/antagonist/bloodsucker)) if(owner == clan_minds) continue if(!isliving(clan_minds.current)) continue var/datum/antagonist/bloodsucker/bloodsuckerdatum = clan_minds.has_antag_datum(/datum/antagonist/bloodsucker) - to_chat(clan_minds, span_userdanger("[owner.current] has broken the Masquerade![bloodsuckerdatum.my_clan == CLAN_TOREADOR ? "Ensure they are eliminated at all costs!" : ""]")) - if(bloodsuckerdatum.my_clan == CLAN_TOREADOR) + to_chat(clan_minds, span_userdanger("[owner.current] has broken the Masquerade![bloodsuckerdatum.my_clan?.get_clan() == CLAN_TOREADOR ? "Ensure they are eliminated at all costs!" : ""]")) + if(bloodsuckerdatum.my_clan?.get_clan() == CLAN_TOREADOR) var/datum/objective/assassinate/masquerade_objective = new /datum/objective/assassinate masquerade_objective.target = owner.current masquerade_objective.explanation_text = "Ensure [owner.current], who has broken the Masquerade, suffers Final Death." @@ -761,191 +770,21 @@ return to_chat(owner.current, span_cultboldtalic("You have re-entered the Masquerade.")) broke_masquerade = FALSE - set_antag_hud(owner.current, "bloodsucker") - - -///////////////////////////////////// -// BLOOD COUNTER & RANK MARKER ! // -///////////////////////////////////// - -/datum/antagonist/bloodsucker/proc/remove_hud() - owner.current.hud_used.blood_display.invisibility = INVISIBILITY_ABSTRACT - owner.current.hud_used.vamprank_display.invisibility = INVISIBILITY_ABSTRACT - owner.current.hud_used.sunlight_display.invisibility = INVISIBILITY_ABSTRACT - -/// Update Blood Counter + Rank Counter -/datum/antagonist/bloodsucker/proc/update_hud(updateRank = FALSE) - if(!owner.current.hud_used) - return - var/valuecolor - if(owner.current.hud_used && owner.current.hud_used.blood_display) - if(owner.current.blood_volume > BLOOD_VOLUME_SAFE(owner.current)) - valuecolor = "#FFDDDD" - else if(owner.current.blood_volume > BLOOD_VOLUME_BAD(owner.current)) - valuecolor = "#FFAAAA" - owner.current.hud_used.blood_display.update_counter(owner.current.blood_volume, valuecolor) - if(owner.current.hud_used && owner.current.hud_used.vamprank_display) - owner.current.hud_used.vamprank_display.update_counter(bloodsucker_level, valuecolor) - /// Only change icon on special request. - if(updateRank) - owner.current.hud_used.vamprank_display.icon_state = (bloodsucker_level_unspent > 0) ? "rank_up" : "rank" - -/// Update Sun Time -/datum/antagonist/bloodsucker/proc/update_sunlight(value, amDay = FALSE) - if(!owner.current.hud_used) - return - var/valuecolor - if(owner.current.hud_used && owner.current.hud_used.sunlight_display) - var/sunlight_display_icon = "sunlight_" - if(amDay) - sunlight_display_icon += "day" - valuecolor = "#FF5555" - else - switch(round(value, 1)) - if(0 to 30) - sunlight_display_icon += "30" - valuecolor = "#FFCCCC" - if(31 to 60) - sunlight_display_icon += "60" - valuecolor = "#FFE6CC" - if(61 to 90) - sunlight_display_icon += "90" - valuecolor = "#FFFFCC" - else - sunlight_display_icon += "night" - valuecolor = "#FFFFFF" - - var/value_string = (value >= 60) ? "[round(value / 60, 1)] m" : "[round(value, 1)] s" - owner.current.hud_used.sunlight_display.update_counter(value_string, valuecolor) - owner.current.hud_used.sunlight_display.icon_state = sunlight_display_icon - -/atom/movable/screen/bloodsucker/blood_counter/update_counter(value, valuecolor) - ..() - maptext = "
[round(value,1)]
" - -/atom/movable/screen/bloodsucker/rank_counter/update_counter(value, valuecolor) - ..() - maptext = "
[round(value,1)]
" - -/atom/movable/screen/bloodsucker/sunlight_counter/update_counter(value, valuecolor) - ..() - maptext = "
[value]
" - -/** - * # Assigning Bloodsucker status - * - * Here we assign the Bloodsuckers themselves, ensuring they arent Plasmamen - * Also deals with Vassalization status. - */ - -/datum/mind/proc/prepare_bloodsucker(datum/mind/convertee, datum/mind/converter) - // Species Must have a HEART (Sorry Plasmamen) - var/mob/living/carbon/human/user = convertee.current - if(!(user.dna?.species) || !(user.mob_biotypes & MOB_ORGANIC)) - user.set_species(/datum/species/human) - user.apply_pref_name(/datum/preference/name/real_name, user.client) - // Check for Fledgeling - if(converter) - message_admins("[convertee] has become a Bloodsucker, and was created by [converter].") - log_admin("[convertee] has become a Bloodsucker, and was created by [converter].") - return TRUE - -/datum/mind/proc/make_bloodsucker(datum/mind/bloodsucker) - if(bloodsucker) - var/mob/living/carbon/human/user = bloodsucker.current - if(!(user.dna?.species) || !(user.mob_biotypes & MOB_ORGANIC)) - prepare_bloodsucker(bloodsucker) - add_antag_datum(/datum/antagonist/bloodsucker) - return TRUE - else - return - -/datum/mind/proc/remove_bloodsucker() - var/datum/antagonist/bloodsucker/removed_bloodsucker = has_antag_datum(/datum/antagonist/bloodsucker) - if(removed_bloodsucker) - remove_antag_datum(/datum/antagonist/bloodsucker) - special_role = null - -/datum/antagonist/bloodsucker/proc/can_make_vassal(mob/living/converted, datum/mind/converter, can_vassal_sleeping = FALSE)//, check_antag_or_loyal=FALSE) - // Not Correct Type: Abort - if(!iscarbon(converted) || !converter) - return FALSE - if(converted.stat > UNCONSCIOUS && !can_vassal_sleeping) - return FALSE - // No Mind! - if(!converted.mind) - to_chat(converter, span_danger("[converted] isn't self-aware enough to be made into a Vassal.")) - return FALSE - // Already MY Vassal - var/datum/antagonist/vassal/vassaldatum = converted.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(istype(vassaldatum) && vassaldatum.master) - if(vassaldatum.master.owner == converter) - to_chat(converter, span_danger("[converted] is already your loyal Vassal!")) - else - to_chat(converter, span_danger("[converted] is the loyal Vassal of another Bloodsucker!")) - return FALSE - // Already Antag or Loyal (Vamp Hunters count as antags) - if(!isnull(converted.mind.enslaved_to) || AmInvalidAntag(converted)) - to_chat(converter, span_danger("[converted] resists the power of your blood to dominate their mind!")) - return FALSE - return TRUE - -/datum/antagonist/bloodsucker/proc/AmValidAntag(mob/target) - /// Check if they are an antag, if so, check if they're Invalid. - if(target.mind?.special_role || !isnull(target.mind?.antag_datums)) - return !AmInvalidAntag(target) - /// Otherwise, just cancel out. - return FALSE - -/datum/antagonist/bloodsucker/proc/AmInvalidAntag(mob/target) - /// Not an antag? - if(!is_special_character(target)) - return FALSE - /// Checks if the person is an antag banned from being vassalized, stored in bloodsucker's datum. - for(var/datum/antagonist/antag_datum in target.mind.antag_datums) - if(antag_datum.type in vassal_banned_antags) - //message_admins("DEBUG VASSAL: Found Invalid: [antag_datum] // [antag_datum.type]") - return TRUE -// message_admins("DEBUG VASSAL: Valid Antags! (total of [target.antag_datums.len])") - // WHEN YOU DELETE THE ABOVE: Remove the 3 second timer on converting the vassal too. - return FALSE - -/datum/antagonist/bloodsucker/proc/attempt_turn_vassal(mob/living/carbon/convertee, can_vassal_sleeping = FALSE) - convertee.silent = 0 - return make_vassal(convertee, owner, can_vassal_sleeping) - -/datum/antagonist/bloodsucker/proc/make_vassal(mob/living/convertee, datum/mind/converter, sleeping = FALSE) - if(!can_make_vassal(convertee, converter, can_vassal_sleeping = sleeping)) - return FALSE - // Make Vassal - var/datum/antagonist/vassal/vassaldatum = new(convertee.mind) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = converter.has_antag_datum(/datum/antagonist/bloodsucker) - vassaldatum.master = bloodsuckerdatum - convertee.mind.add_antag_datum(vassaldatum, vassaldatum.master.get_team()) - // Update Bloodsucker Title - bloodsuckerdatum.SelectTitle(am_fledgling = FALSE) // Only works if you have no title yet. - // Log it - message_admins("[convertee] has become a Vassal, and is enslaved to [converter].") - log_admin("[convertee] has become a Vassal, and is enslaved to [converter].") - return TRUE - -/** - * # HUD - */ -/datum/antagonist/bloodsucker/proc/update_bloodsucker_icons_added(datum/mind/m) - var/datum/atom_hud/antag/vamphud = GLOB.huds[ANTAG_HUD_BLOODSUCKER] - vamphud.join_hud(owner.current) - set_antag_hud(owner.current, "bloodsucker") +/datum/antagonist/bloodsucker/get_preview_icon() + var/icon/final_icon = render_preview_outfit(/datum/outfit/bloodsucker_outfit) + final_icon.Blend(icon('icons/effects/blood.dmi', "uniformblood"), ICON_OVERLAY) -/datum/antagonist/bloodsucker/proc/update_bloodsucker_icons_removed(datum/mind/m) - var/datum/atom_hud/antag/vamphud = GLOB.huds[ANTAG_HUD_BLOODSUCKER] - vamphud.leave_hud(owner.current) - set_antag_hud(owner.current, null) + return finish_preview_icon(final_icon) -/datum/antagonist/bloodsucker/get_preview_icon() - var/icon/bloodsucker_icon = icon('icons/mob/bloodsucker_mobs.dmi', "batform") +/datum/outfit/bloodsucker_outfit + name = "Bloodsucker outfit (Preview only)" + suit = /obj/item/clothing/suit/dracula - bloodsucker_icon.Scale(ANTAGONIST_PREVIEW_ICON_SIZE, ANTAGONIST_PREVIEW_ICON_SIZE) +/datum/outfit/bloodsucker_outfit/post_equip(mob/living/carbon/human/enrico, visualsOnly=FALSE) + enrico.hair_style = "Undercut" + enrico.hair_color = "FFF" + enrico.skin_tone = "african2" - return bloodsucker_icon + enrico.update_body() + enrico.update_hair() diff --git a/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm b/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm index 859c958d6e92..d23889204117 100644 --- a/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm +++ b/code/modules/antagonists/bloodsuckers/bloodsuckers_objects.dm @@ -17,22 +17,23 @@ /obj/item/restraints/legcuffs/beartrap/bloodsucker/attack_self(mob/user) var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - lair_area = bloodsuckerdatum.lair - lair_owner = user + lair_area = bloodsuckerdatum?.bloodsucker_lair_area + var/mob/lair_owner + = user START_PROCESSING(SSobj, src) if(!bloodsuckerdatum) - to_chat(user, span_notice("Although it seems simple you have no idea how to reactivate the stake trap.")) + to_chat(user, span_notice("Although it seems simple you have no idea how to reactivate [src].")) return if(armed) STOP_PROCESSING(SSobj,src) return ..() //disarm it, otherwise continue to try and place - if(!bloodsuckerdatum.lair) + if(!bloodsuckerdatum.bloodsucker_lair_area) to_chat(user, span_danger("You don't have a lair. Claim a coffin to make that location your lair.")) return if(lair_area != get_area(src)) to_chat(user, span_danger("You may only activate this trap in your lair: [lair_area].")) return - lair_area = bloodsuckerdatum.lair + lair_area = bloodsuckerdatum.bloodsucker_lair_area lair_owner = user START_PROCESSING(SSobj, src) ..() @@ -73,10 +74,7 @@ ////////////////////// /// Do I have a stake in my heart? -/mob/proc/AmStaked() - return FALSE - -/mob/living/AmStaked() +/mob/living/proc/am_staked() var/obj/item/bodypart/chosen_bodypart = get_bodypart(BODY_ZONE_CHEST) if(!chosen_bodypart) return FALSE @@ -198,6 +196,97 @@ . = ..() AddComponent(/datum/component/squeak, list('sound/items/bikehorn.ogg'=1), 50) +////////////////////// +// TOOLS // +////////////////////// + +/obj/item/bloodsucker + icon = 'icons/obj/vamp_obj.dmi' + lefthand_file = 'icons/mob/inhands/antag/bs_leftinhand.dmi' + righthand_file = 'icons/mob/inhands/antag/bs_rightinhand.dmi' + +/obj/item/bloodsucker/chisel + name = "chisel" + desc = "Despite not being the most precise or faster tool, it feels the best to work with nonetheless." + icon_state = "chisel" + slot_flags = ITEM_SLOT_BELT | ITEM_SLOT_POCKET + w_class = WEIGHT_CLASS_SMALL + attack_verb = list("chiseled", "stabbed", "poked") + sharpness = SHARP_POINTY + force = 6 + throwforce = 4 + +/* #define PAINTING_TYPE_MESMERIZE "Mesmerizing Painting" +#define PAINTING_TYPE_CHARM "Charming Painting" +#define PAINTING_TYPE_CREEPY "Creepy Painting" + +/obj/item/bloodsucker/bloodybrush + name = "paintbrush" + desc = "Draws from a source that should never run dry, an artist's dream." + icon_state = "brush" + w_class = WEIGHT_CLASS_SMALL + +/obj/item/bloodsucker/bloodybrush/proc/paint(mob/living/painter, obj/item/canvas/raw_piece) + var/list/possible_effects = list() + var/painting_type = tgui_input_list(painter, "You paint a...", "Conscience Flux", possible_effects) + var/base_x = painter.pixel_x + var/base_y = painter.pixel_y + animate(painter, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS, loop = -1) + balloon_alert(painter, "a little bit here...") + var/message = TRUE + for(var/i in 1 to 25) + var/x_offset = base_x + rand(-3, 3) + var/y_offset = base_y + rand(-3, 3) + animate(pixel_x = x_offset, pixel_y = y_offset, time = 0.1 SECONDS) + if(message) + balloon_alert(painter, "..fill that up there...") + message = FALSE + if(!do_after(painter, 10 SECONDS, raw_piece)) + animate(painter, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS) + balloon_alert(painter, "..ah... dammit.") + return FALSE + animate(painter, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS) + balloon_alert(painter, "..and it's done.") + qdel(raw_piece) + var/obj/structure/sign/painting/sign_to_crop = new /obj/structure/sign/painting(get_turf(painter)) + var/obj/item/wirecutters/cutters = new /obj/item/wirecutters(get_turf(painter)) + sign_to_crop.load_persistent() + sign_to_crop.C.special_effect = painting_type + sign_to_crop.wirecutter_act(painter, cutters) + qdel(sign_to_crop) + qdel(cutters) + +/obj/item/canvas/attackby(obj/item/I, mob/living/user, params) + if(!istype(I, /obj/item/bloodsucker/bloodybrush)) + return ..() + if(!IS_BLOODSUCKER(user)) + return + var/obj/item/bloodsucker/bloodybrush/brush = I + var/turf/current_turf = get_turf(src) + if(!LAZYFIND(current_turf.contents, /obj/structure/easel)) + to_chat(user, span_warning("You need a easel to support your canvas while you paint!")) + return + brush.paint(user, src) + +#undef PAINTING_TYPE_MESMERIZE +#undef PAINTING_TYPE_CHARM +#undef PAINTING_TYPE_CREEPY */ + +////////////////////// +// MISC // +////////////////////// + +/obj/item/bloodsucker/abyssal_essence + name = "abyssal essence" + desc = "As you glare at the abyssal essence, you feel it glaring back." + icon_state = "abyssal_essence" + item_state = "abyssal_essence" + throwforce = 0 + w_class = WEIGHT_CLASS_TINY + throw_speed = 3 + throw_range = 7 + pressure_resistance = 10 + ////////////////////// // ARCHIVES // ////////////////////// @@ -253,141 +342,54 @@ // target is the person being hit here /obj/item/book/kindred/afterattack(mob/living/target, mob/living/user, flag, params) . = ..() - if(!user.can_read(src)) + if(!user.can_read(src) || in_use || (target == user) || !ismob(target)) return // Curator/Tremere using it - if(HAS_TRAIT(user, TRAIT_BLOODSUCKER_HUNTER)) - if(in_use || (target == user) || !ismob(target)) + if(!HAS_TRAIT(user.mind, TRAIT_BLOODSUCKER_HUNTER)) + if(IS_BLOODSUCKER(user)) + to_chat(user, span_notice("[src] seems to be too complicated for you. It would be best to leave this for someone else to take.")) return - user.visible_message(span_notice("[user] begins to quickly look through [src], repeatedly looking back up at [target].")) - in_use = TRUE - if(!do_mob(user, target, 3 SECONDS, NONE, TRUE)) - to_chat(user, span_notice("You quickly close [src].")) - in_use = FALSE - return - in_use = FALSE - var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(target) - // Are we a Bloodsucker | Are we on Masquerade. If one is true, they will fail. - if(IS_BLOODSUCKER(target) && !HAS_TRAIT(target, TRAIT_MASQUERADE)) - if(bloodsuckerdatum.broke_masquerade) - to_chat(user, span_warning("[target], also known as '[bloodsuckerdatum.ReturnFullName(TRUE)]', is indeed a Bloodsucker, but you already knew this.")) - return - else - to_chat(user, span_warning("You found the one! [target], also known as '[bloodsuckerdatum.ReturnFullName(TRUE)]', is not knowingly part of a Clan. You quickly note this information down, memorizing it.")) - bloodsuckerdatum.break_masquerade() - else - to_chat(user, span_notice("You fail to draw any conclusions to [target] being a Bloodsucker.")) - // Bloodsucker using it - else if(IS_BLOODSUCKER(user)) - to_chat(user, span_notice("[src] seems to be too complicated for you. It would be best to leave this for someone else to take.")) - else to_chat(user, span_warning("[src] burns your hands as you try to use it!")) - user.apply_damage(12, BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) - -/* - * # Reading the Book - */ -/obj/item/book/kindred/attack_self(mob/living/carbon/user) -// Don't call parent since it handles reading the book. -// . = ..() - if(!user.can_read(src)) + user.apply_damage(3, BURN, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM)) return - // Curator/Tremere using it - if(HAS_TRAIT(user, TRAIT_BLOODSUCKER_HUNTER)) + + in_use = TRUE + user.balloon_alert_to_viewers(user, "reading book...", "looks at [target] and [src]") + if(!do_after(user, 3 SECONDS, target)) + to_chat(user, span_notice("You quickly close [src].")) + in_use = FALSE + return + if(HAS_TRAIT(user.mind, TRAIT_BLOODSUCKER_HUNTER)) user.visible_message(span_notice("[user] opens [src] and begins reading intently.")) ui_interact(user) return - // Bloodsucker using it - if(IS_BLOODSUCKER(user)) - to_chat(user, span_notice("[src] seems to be too complicated for you. It would be best to leave this for someone else to take.")) - return - to_chat(user, span_warning("You feel your eyes burn as you begin to read through [src]!")) - var/obj/item/organ/eyes/eyes = user.getorganslot(ORGAN_SLOT_EYES) - user.blur_eyes(5) - eyes.applyOrganDamage(5) + in_use = FALSE + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(target) + // Are we a Bloodsucker | Are we on Masquerade. If one is true, they will fail. + if(IS_BLOODSUCKER(target) && !HAS_TRAIT(target, TRAIT_MASQUERADE)) + if(bloodsuckerdatum.broke_masquerade) + to_chat(user, span_warning("[target], also known as '[bloodsuckerdatum.return_full_name()]', is indeed a Bloodsucker, but you already knew this.")) + return + to_chat(user, span_warning("[target], also known as '[bloodsuckerdatum.return_full_name()]', [bloodsuckerdatum.my_clan ? "is part of the [bloodsuckerdatum.my_clan]!" : "is not part of a clan."] You quickly note this information down, memorizing it.")) + bloodsuckerdatum.break_masquerade() + else + to_chat(user, span_notice("You fail to draw any conclusions to [target] being a Bloodsucker.")) + +/obj/item/book/kindred/attack_self(mob/living/user) + ui_interact(user) /obj/item/book/kindred/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) - ui = new(user, src, "KindredArchives", name) + ui = new(user, src, "KindredBook", name) ui.open() -/obj/item/book/kindred/ui_act(action, params) - . = ..() - if(.) - return - if(!action) - return FALSE - SStgui.close_uis(src) - INVOKE_ASYNC(src, .proc/search, usr, action) - -// Flavortext stuff -/obj/item/book/kindred/proc/search(mob/reader, clan) - dat = "This is all knowledge about the Clan:
" - switch(clan) - if(CLAN_BRUJAH) - dat += "This Clan has proven to be the strongest in melee combat, boasting a powerful punch.
\ - They also appear to be more calm than the others, entering their 'frenzies' whenever they want, but dont seem affected.
\ - Be wary, as they are fearsome warriors, rebels and anarchists, with an inclination towards Frenzy.
\ - Favorite Vassal: Their favorite Vassal gains the Brawn ability. \ - Strength: Frenzy will not kill them, punches deal a lot of damage.
\ - Weakness: They have to spend Blood on powers while in Frenzy too." - if(CLAN_TOREADOR) - dat += "The most charming Clan of them all, being borderline party animals, allowing them to very easily disguise among the crew.
\ - They are more in touch with their morals, so they suffer and benefit more strongly from the humanity cost or gain of their actions.
\ - They can be best defined as 'The most humane kind of vampire', due to their kindred with an obsession with perfectionism and beauty
\ - Favorite Vassal: Their favorite Vassal gains the Mesmerize ability \ - Strength: Highly charismatic and influential.
\ - Weakness: Morally weak." - if(CLAN_NOSFERATU) - dat += "This Clan has been the most obvious to find information about.
\ - They are disfigured, ghoul-like vampires upon embrace by their Sire, scouts that travel through desolate paths to avoid violating the Masquerade.
\ - They make no attempts at hiding themselves within the crew, and have a terrible taste for heavy items.
\ - They also seem to manage to fit themsleves into small spaces such as vents.
\ - Favorite Vassal: Their Favorite Vassal gains the ability to ventcrawl while naked and becomes disfigured. \ - Strength: Ventcrawl.
\ - Weakness: Can't disguise themselves, permanently pale, can easily be discovered by their DNA or Blood Level." - if(CLAN_TREMERE) - dat += "This Clan seems to hate entering the Chapel.
\ - They are a secluded Clan, they are Vampires who've mastered the power of blood, and seek knowledge.
\ - They appear to be focused more on their Blood Magic than their other Powers, getting stronger faster the more Vassals they have.
\ - They have 3 different paths they can take, from reviving people as Vassals, to stealing blood with beams made of the same essence.
\ - Favorite Vassal: Their Favorite Vassal gains the ability to shift into a Bat at will. \ - Strength: 3 different Powers that get stupidly strong overtime.
\ - Weakness: Cannot get regular Powers, with no way to get stun resistance outside of Frenzy." - if(CLAN_GANGREL) - dat += "This Clan seems to be closer to Animals than to other Vampires.
\ - They also go by the name of Werewolves, as that is what appears when they enter a Frenzy.
\ - Despite this, they appear to be scared of 'True Faith', someone's ultimate and undying Faith, which itself doesn't require being something Religious.
\ - They hate seeing many people, and tend to avoid Stations that have more crewmembers than Nanotrasen's average. Due to this, they are harder to find than others.
\ - Favorite Vassal: Their Favorite Vassal turns into a Werewolf whenever their Master does.. \ - Strength: Feral, Werewolf during Frenzy.
\ - Weakness: Weak to True Faith." - if(CLAN_VENTRUE) - dat += "This Clan seems to despise drinking from non sentient organics.
\ - They are Masters of manipulation, Greedy and entitled. Authority figures between the kindred society.
\ - They seem to take their Vassal's lives very seriously, going as far as to give Vassals some of their own Blood.
\ - Compared to other types, this one relies on their Vassals, rather than fighting for themselves.
\ - Favorite Vassal: Their Favorite Vassal will slowly be turned into a Bloodsucker overtime. \ - Strength: Slowly turns a Vassal into a Bloodsucker.
\ - Weakness: Does not gain more abilities overtime, it is best to target the Bloodsucker over the Vassal." - if(CLAN_MALKAVIAN) - dat += "There is barely any information known about this Clan.
\ - Members of this Clan seems to mumble things to themselves, unaware of their surroundings.
\ - They also seem to enter and dissapear into areas randomly, as if not even they know where they are.
\ - Favorite Vassal: Unknown. \ - Strength: Unknown.
\ - Weakness: Unknown." - if(CLAN_LASOMBRA) - dat += "This Clan seems to adore living in the Shadows and worshipping it's secrets.
\ - They take their research and vanity seriously, they are always very proud of themselves after even minor achievements.
\ - They appear to be in search of a station with a veil weakness to be able to channel their shadow's abyssal powers.
\ - Their research into this sector has lead them to adopt red eyes as to view better in the shadows, which leads unmasked Lasombras to be easily identifiable.
\ - Homewer they have appeared to have also evolved a hard chintin in their veins, which makes them invulnerable to brute damage.
\ - Favorite Vassal: Their Favorite Vassal appears to have been imbued with abyssal essence and is able to blend in with the shadows. \ - Strength: They are able to slowly advance their abilities.
\ - Weakness: Immensely weak to burn damage." - if(CLAN_TZIMISCE) - dat += "The page is covered in blood..." - - reader << browse("Penned by [author].
" + "[dat]", "window=book[window_size != null ? ";size=[window_size]" : ""]") +/obj/item/book/kindred/ui_static_data(mob/user) + var/data = list() + for(var/datum/bloodsucker_clan/clans as anything in subtypesof(/datum/bloodsucker_clan)) + var/clan_data = list() + clan_data["clan_name"] = initial(clans.name) + clan_data["clan_desc"] = initial(clans.description) + data["clans"] += list(clan_data) + + return data diff --git a/code/modules/antagonists/bloodsuckers/clans/_clan.dm b/code/modules/antagonists/bloodsuckers/clans/_clan.dm new file mode 100644 index 000000000000..0f0a9b3b0179 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/clans/_clan.dm @@ -0,0 +1,260 @@ +///List of all Bloodsuckers in a clan, separated by their clans. +GLOBAL_LIST_EMPTY(bloodsucker_clan_members) + +/** + * Bloodsucker clans + * + * Handles everything related to clans. + * the entire idea of datumizing this came to me in a dream. + */ +/datum/bloodsucker_clan + ///The name of the clan we're in. + var/name = CLAN_NONE + ///Description of what the clan is, given when joining and through your antag UI. + var/description = "The Caitiff is as basic as you can get with Bloodsuckers. \n\ + Entirely Clan-less, they are blissfully unaware of who they really are. \n\ + No additional abilities is gained, nothing is lost, if you want a plain Bloodsucker, this is it. \n\ + The Favorite Vassal will gain the Brawn ability, to help in combat." + ///The clan objective that is required to greentext. + var/clan_objective + ///The icon of the radial icon to join this clan. + var/join_icon = 'icons/mob/bloodsucker_clan_icons.dmi' + ///Same as join_icon, but the state + var/join_icon_state = "caitiff" + ///Description shown when trying to join the clan. + var/join_description = "The default, Classic Bloodsucker." + ///Whether the clan can be joined by players. FALSE for flavortext-only clans. + var/joinable_clan = TRUE + + ///How the Bloodsucker ranks up, if they do. + var/rank_up_type = BLOODSUCKER_RANK_UP_NORMAL + ///Whether they become entirely stun immune when entering Frenzy. + var/frenzy_stun_immune = FALSE + ///The clan's manipulation specialty. + var/control_type = BLOODSUCKER_CONTROL_BLOOD + ///How we will drink blood using Feed. + var/blood_drink_type = BLOODSUCKER_DRINK_NORMAL + +/datum/bloodsucker_clan/New(mob/living/carbon/user) + . = ..() + if(!GLOB.bloodsucker_clan_members["[name]"]) + GLOB.bloodsucker_clan_members["[name]"] = list() + GLOB.bloodsucker_clan_members["[name]"] |= user + + RegisterSignal(src, BLOODSUCKER_HANDLE_LIFE, .proc/handle_clan_life) + RegisterSignal(src, BLOODSUCKER_RANK_UP, .proc/on_spend_rank) + + RegisterSignal(src, BLOODSUCKER_PRE_MAKE_FAVORITE, .proc/on_offer_favorite) + RegisterSignal(src, BLOODSUCKER_MAKE_FAVORITE, .proc/on_favorite_vassal) + + RegisterSignal(src, BLOODSUCKER_MADE_VASSAL, .proc/on_vassal_made) + RegisterSignal(src, BLOODSUCKER_EXIT_TORPOR, .proc/on_exit_torpor) + RegisterSignal(src, BLOODSUCKER_FINAL_DEATH, .proc/on_final_death) + + give_clan_objective(user) + +/datum/bloodsucker_clan/Destroy(force, mob/living/carbon/user) + UnregisterSignal(src, list( + BLOODSUCKER_HANDLE_LIFE, + BLOODSUCKER_RANK_UP, + BLOODSUCKER_PRE_MAKE_FAVORITE, + BLOODSUCKER_MAKE_FAVORITE, + BLOODSUCKER_MADE_VASSAL, + BLOODSUCKER_EXIT_TORPOR, + BLOODSUCKER_FINAL_DEATH, + )) + GLOB.bloodsucker_clan_members[name] -= user + return ..() + +///legacy code support +/datum/bloodsucker_clan/proc/get_clan() + return name + +/datum/bloodsucker_clan/proc/give_clan_objective(mob/living/user) + if(isnull(clan_objective)) + return + var/datum/objective/bloodsucker/given_objective = new clan_objective + given_objective.owner = user.mind + given_objective.objective_name = "Clan Objective" + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user) + bloodsuckerdatum.objectives += given_objective + user.mind.announce_objectives() + +/** + * Called when a Bloodsucker exits Torpor + * args: + * user - the Bloodsucker exiting Torpor + */ +/datum/bloodsucker_clan/proc/on_exit_torpor(atom/source, mob/living/carbon/user) + SIGNAL_HANDLER + + +/** + * Called when a Bloodsucker enters Final Death + * args: + * user - the Bloodsucker exiting Torpor + */ +/datum/bloodsucker_clan/proc/on_final_death(atom/source, mob/living/carbon/user) + SIGNAL_HANDLER + return FALSE + +/** + * Called during Bloodsucker's LifeTick + * args: + * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this. + */ +/datum/bloodsucker_clan/proc/handle_clan_life(atom/source, datum/antagonist/bloodsucker/bloodsuckerdatum) + SIGNAL_HANDLER + +/** + * Called when a Bloodsucker successfully Vassalizes someone. + * args: + * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this. + */ +/datum/bloodsucker_clan/proc/on_vassal_made(atom/source, mob/living/user, mob/living/target) + SIGNAL_HANDLER + user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE) + target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE) + target.do_jitter_animation(15) + INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, emote), "laugh") + +/** + * Called when a Bloodsucker successfully starts spending their Rank + * args: + * bloodsuckerdatum - the antagonist datum of the Bloodsucker running this. + * target - The Vassal (if any) we are upgrading. + * cost_rank - TRUE/FALSE on whether this will cost us a rank when we go through with it. + * blood_cost - A number saying how much it costs to rank up + * ask - If they want to automatically spend the rest of their ranks + */ +/datum/bloodsucker_clan/proc/on_spend_rank(datum/source, datum/antagonist/bloodsucker/bloodsuckerdatum, cost_rank = TRUE, blood_cost, ask = TRUE) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, .proc/spend_rank, bloodsuckerdatum, cost_rank, blood_cost, ask) + +/datum/bloodsucker_clan/proc/spend_rank(datum/antagonist/bloodsucker/bloodsuckerdatum, cost_rank = TRUE, blood_cost, ask = TRUE) + // Purchase Power Prompt + var/list/options = list() + for(var/datum/action/bloodsucker/power as anything in bloodsuckerdatum.all_bloodsucker_powers) + if(initial(power.purchase_flags) & BLOODSUCKER_CAN_BUY && !(locate(power) in bloodsuckerdatum.powers)) + options[initial(power.name)] = power + + if(!LAZYLEN(options)) + to_chat(bloodsuckerdatum.owner.current, span_notice("You grow more ancient by the night!")) + else + // Give them the UI to purchase a power. + var/choice = tgui_input_list(bloodsuckerdatum.owner.current, "You have the opportunity to grow more ancient. Select a power to advance your Rank.", "Your Blood Thickens...", options) + // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels. + if(cost_rank && bloodsuckerdatum.bloodsucker_level_unspent <= 0) + return + // Did you choose a power? + if(!choice || !options[choice]) + to_chat(bloodsuckerdatum.owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later.")) + return + // Prevent Bloodsuckers from closing/reopning their coffin to spam Levels. + if(locate(options[choice]) in bloodsuckerdatum.powers) + to_chat(bloodsuckerdatum.owner.current, span_notice("You prevent your blood from thickening just yet, but you may try again later.")) + return + // Prevent Bloodsuckers from purchasing a power while outside of their Coffin. + if(!istype(bloodsuckerdatum.owner.current.loc, /obj/structure/closet/crate/coffin)) + to_chat(bloodsuckerdatum.owner.current, span_warning("You must be in your Coffin to purchase Powers.")) + return + + // Good to go - Buy Power! + var/datum/action/bloodsucker/purchased_power = options[choice] + bloodsuckerdatum.BuyPower(new purchased_power) + bloodsuckerdatum.owner.current.balloon_alert(bloodsuckerdatum.owner.current, "learned [choice]!") + to_chat(bloodsuckerdatum.owner.current, span_notice("You have learned how to use [choice]!")) + + finalize_spend_rank(bloodsuckerdatum, cost_rank, blood_cost, ask) + +/datum/bloodsucker_clan/proc/finalize_spend_rank(datum/antagonist/bloodsucker/bloodsuckerdatum, cost_rank = TRUE, blood_cost, ask = TRUE) + bloodsuckerdatum.LevelUpPowers() + bloodsuckerdatum.bloodsucker_regen_rate += 0.05 + bloodsuckerdatum.max_blood_volume += 100 + + // Misc. Stats Upgrades + if(ishuman(bloodsuckerdatum.owner.current)) + var/mob/living/carbon/human/user = bloodsuckerdatum.owner.current + var/datum/species/user_species = user.dna.species + user_species.punchdamagelow += 0.5 + // This affects the hitting power of Brawn. + user_species.punchdamagehigh += 0.5 + user_species.punchstunthreshold += 0.5 + + // We're almost done - Spend your Rank now. + bloodsuckerdatum.bloodsucker_level++ + if(cost_rank) + bloodsuckerdatum.bloodsucker_level_unspent-- + if(blood_cost) + bloodsuckerdatum.AddBloodVolume(-blood_cost) + + // Ranked up enough to get your true Reputation? + if(bloodsuckerdatum.bloodsucker_level == 4) + bloodsuckerdatum.SelectReputation(am_fledgling = FALSE, forced = TRUE) + + to_chat(bloodsuckerdatum.owner.current, span_notice("You are now a rank [bloodsuckerdatum.bloodsucker_level] Bloodsucker. \ + Your strength, health, feed rate, regen rate, and maximum blood capacity have all increased! \n\ + * Your existing powers have all ranked up as well!")) + if(ask) //please no + bloodsuckerdatum.owner.current.playsound_local(null, 'sound/effects/pope_entry.ogg', 25, TRUE, pressure_affected = FALSE) + bloodsuckerdatum.update_hud() + if(bloodsuckerdatum.bloodsucker_level_unspent && cost_rank) + if(ask) + if(tgui_alert(bloodsuckerdatum.owner.current, "You have leftover ranks, do you want to spend them all?", "Time Management Team", list("Yes", "No")) == "No") + return + spend_rank(bloodsuckerdatum, cost_rank, blood_cost, FALSE) + +/** + * Called when we are trying to turn someone into a Favorite Vassal + * args: + * bloodsuckerdatum - the antagonist datum of the Bloodsucker performing this. + * vassaldatum - the antagonist datum of the Vassal being offered up. + */ +/datum/bloodsucker_clan/proc/on_offer_favorite(datum/source, datum/antagonist/bloodsucker/bloodsuckerdatum, datum/antagonist/vassal/vassaldatum) + SIGNAL_HANDLER + + INVOKE_ASYNC(src, .proc/offer_favorite, bloodsuckerdatum, vassaldatum) + +/datum/bloodsucker_clan/proc/offer_favorite(datum/antagonist/bloodsucker/bloodsuckerdatum, datum/antagonist/vassal/vassaldatum) + if(vassaldatum.special_type) + to_chat(bloodsuckerdatum.owner.current, span_notice("This Vassal was already assigned a special position.")) + return FALSE + if(!vassaldatum.owner.can_make_bloodsucker(creator = bloodsuckerdatum.owner)) + to_chat(bloodsuckerdatum.owner.current, span_notice("This Vassal is unable to gain a Special rank due to innate features.")) + return FALSE + + var/list/options = list() + var/list/radial_display = list() + for(var/datum/antagonist/vassal/vassaldatums as anything in subtypesof(/datum/antagonist/vassal)) + if(bloodsuckerdatum.special_vassals[initial(vassaldatums.special_type)]) + continue + options[initial(vassaldatums.name)] = vassaldatums + + var/datum/radial_menu_choice/option = new + option.image = image(icon = initial(vassaldatums.hud_icon), icon_state = initial(vassaldatums.antag_hud_name)) + option.info = "[initial(vassaldatums.name)] - [span_boldnotice(initial(vassaldatums.vassal_description))]" + radial_display[initial(vassaldatums.name)] = option + + if(!options.len) + return + + to_chat(bloodsuckerdatum.owner.current, span_notice("You can change who this Vassal is, who are they to you?")) + var/vassal_response = show_radial_menu(bloodsuckerdatum.owner.current, vassaldatum.owner.current, radial_display) + if(!vassal_response) + return + vassal_response = options[vassal_response] + if(QDELETED(src) || QDELETED(bloodsuckerdatum.owner.current) || QDELETED(vassaldatum.owner.current)) + return FALSE + vassaldatum.make_special(vassal_response) + bloodsuckerdatum.bloodsucker_blood_volume -= 150 + +/** + * Called when we are successfully turn a Vassal into a Favorite Vassal + * args: + * vassaldatum - the antagonist datum of the Vassal being offered up. + * bloodsucker - mob of the Bloodsucker who turned them into a Vassal. + */ +/datum/bloodsucker_clan/proc/on_favorite_vassal(datum/source, datum/antagonist/vassal/vassaldatum, mob/living/bloodsucker) + SIGNAL_HANDLER + vassaldatum.BuyPower(new /datum/action/bloodsucker/targeted/brawn) diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_flavortext.dm b/code/modules/antagonists/bloodsuckers/clans/clan_flavortext.dm new file mode 100644 index 000000000000..4b53ba1bb6c9 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/clans/clan_flavortext.dm @@ -0,0 +1,48 @@ +/datum/bloodsucker_clan/ventrue + name = CLAN_VENTRUE + description = "The Ventrue Clan is extremely snobby with their meals, and refuse to drink blood from people without a mind. \n\ + There is additionally no way to rank themselves up, instead will have to rank their Favorite vassal through a Persuasion Rack. \n\ + The Favorite Vassal will slowly turn into a Bloodsucker this way, until they finally lose their last bits of Humanity." + joinable_clan = FALSE +// clan_objective = /datum/objective/bloodsucker/embrace + join_icon_state = "ventrue" + join_description = "Lose the ability to drink from mindless mobs, can't level up or gain new powers, \ + instead you raise a vassal into a Bloodsucker." +// rank_up_type = BLOODSUCKER_RANK_UP_VASSAL + blood_drink_type = BLOODSUCKER_DRINK_SNOBBY + +/datum/bloodsucker_clan/tremere + name = CLAN_TREMERE + description = "The Tremere Clan is extremely weak to True Faith, and will burn when entering areas considered such, like the Chapel. \n\ + Additionally, a whole new moveset is learned, built on Blood magic rather than Blood abilities, which are upgraded overtime. \n\ + More ranks can be gained by Vassalizing crewmembers. \n\ + The Favorite Vassal gains the Batform spell, being able to morph themselves at will." + joinable_clan = FALSE +// clan_objective = /datum/objective/bloodsucker/tremere_power + join_icon_state = "tremere" + join_description = "You will burn if you enter the Chapel, lose all default powers, \ + but gain Blood Magic instead, powers you level up overtime." + +/datum/bloodsucker_clan/nosferatu + name = CLAN_NOSFERATU + description = "The Nosferatu Clan is unable to blend in with the crew, with no abilities such as Masquerade and Veil. \n\ + Additionally, has a permanent bad back and looks like a Bloodsucker upon a simple examine, and is entirely unidentifiable, \n\ + they can fit in the vents regardless of their form and equipment. \n\ + The Favorite Vassal is permanetly disfigured, and can also ventcrawl, but only while entirely nude." + joinable_clan = FALSE +// clan_objective = /datum/objective/bloodsucker/kindred + join_icon_state = "nosferatu" + join_description = "You are permanetly disfigured, look like a Bloodsucker to all who examine you, \ + lose your Masquerade ability, but gain the ability to Ventcrawl even while clothed." + blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY + +/datum/bloodsucker_clan/malkavian + name = CLAN_MALKAVIAN + description = "Little is documented about Malkavians. Complete insanity is the most common theme. \n\ + The Favorite Vassal will suffer the same fate as the Master." + join_icon_state = "malkavian" + join_description = "Completely insane. You gain constant hallucinations, become a prophet with unintelligable rambling, \ + and become the enforcer of the Masquerade code." + joinable_clan = FALSE + frenzy_stun_immune = TRUE + blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_gangrel.dm b/code/modules/antagonists/bloodsuckers/clans/clan_gangrel.dm new file mode 100644 index 000000000000..aa26b7c04191 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/clans/clan_gangrel.dm @@ -0,0 +1,23 @@ +/datum/bloodsucker_clan/gangrel + name = CLAN_GANGREL + description = "Closer to Animals than Bloodsuckers, known as Werewolves waiting to happen, \n\ + these are the most fearful of True Faith, being the most lethal thing they would ever see the night of. \n\ + Full Moons do not seem to have an effect, despite common-told stories. \n\ + The Favorite Vassal turns into a Werewolf whenever their Master does." + join_icon_state = "gangrel" + join_description = "Purely animalistic, full of transformation abilities, and special frenzy, an active threat at all times." + frenzy_stun_immune = TRUE + blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY + +/datum/bloodsucker_clan/gangrel/New(mob/living/carbon/user) + . = ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user) + bloodsuckerdatum.AddHumanityLost(16.8) + bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/gangrel/transform) + user.faction |= "bloodhungry" //i love animals i love animals + for(var/datum/action/bloodsucker/masquerade/masquerade_power in bloodsuckerdatum.powers) + bloodsuckerdatum.RemovePower(masquerade_power) + +/datum/bloodsucker_clan/gangrel/on_favorite_vassal(datum/source, datum/antagonist/vassal/vassaldatum, mob/living/bloodsucker) + var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/batform = new(vassaldatum.owner || vassaldatum.owner.current) + vassaldatum.owner.AddSpell(batform) diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm new file mode 100644 index 000000000000..faf96e6ca79e --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/clans/clan_lasombra.dm @@ -0,0 +1,43 @@ +/datum/bloodsucker_clan/lasombra + name = CLAN_LASOMBRA + description = "This Clan seems to adore living in the Shadows, worshipping it's secrets. \n\ + They take their research and vanity seriously, they are always very proud of themselves after even minor achievements. \n\ + They appear to be in search of a station with a veil weakness to be able to channel their shadow's abyssal powers. \n\ + Thanks to this, they have also evolved a dark liquid in their veins, which makes them able to manipulate shadows. \n\ + Their Favorite Vassal appears to have been imbued with abyssal essence and is able to blend in with the shadows." + join_icon_state = "lasombra" + join_description = "Heal more on the dark, transform abilties into upgraded ones, become one with the darkness." + +/datum/bloodsucker_clan/lasombra/New(mob/living/carbon/user) + . = ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user) + bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/lasombra) + if(ishuman(user)) + var/mob/living/carbon/human/human_user = user + human_user.eye_color = BLOODCULT_EYE + human_user.updateappearance() + ADD_TRAIT(user, CULT_EYES, BLOODSUCKER_TRAIT) + user.faction |= "bloodhungry" + user.update_body() + var/obj/item/organ/heart/nightmare/nightmarish_heart = new + nightmarish_heart.Insert(user) + nightmarish_heart.Stop() + for(var/obj/item/light_eater/blade in user.held_items) + QDEL_NULL(blade) + var/datum/objective/bloodsucker/hierarchy/lasombra_objective = new + lasombra_objective.owner = user.mind + bloodsuckerdatum.objectives += lasombra_objective + GLOB.reality_smash_track.AddMind(user.mind) + to_chat(user, span_notice("You have also learned how to channel the abyss's power into an iron knight's armor that can be build in the structure tab and activated as a trap for your lair.")) + user.mind.teach_crafting_recipe(/datum/crafting_recipe/possessedarmor) + user.mind.teach_crafting_recipe(/datum/crafting_recipe/restingplace) + +/datum/bloodsucker_clan/lasombra/on_favorite_vassal(datum/source, datum/antagonist/vassal/vassaldatum, mob/living/bloodsucker) + if(ishuman(vassaldatum.owner.current)) + var/mob/living/carbon/human/vassal = vassaldatum.owner.current + vassal.see_in_dark = 8 + vassal.eye_color = BLOODCULT_EYE + vassal.updateappearance() + var/list/powers = list(new /obj/effect/proc_holder/spell/targeted/lesser_glare, new /obj/effect/proc_holder/spell/targeted/shadowwalk) + for(var/obj/effect/proc_holder/spell/targeted/power in powers) + vassaldatum.owner.AddSpell(power) diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_toreador.dm b/code/modules/antagonists/bloodsuckers/clans/clan_toreador.dm new file mode 100644 index 000000000000..155c8eb5ee7a --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/clans/clan_toreador.dm @@ -0,0 +1,31 @@ +/datum/bloodsucker_clan/toreador + name = CLAN_TOREADOR + description = "The most charming Clan of them all, allowing them to very easily disguise among the crew. \n\ + More in touch with their morals, they suffer and benefit more strongly from humanity cost or gain of their actions. \n\ + Known as 'The most humane kind of vampire', they have an obsession with perfectionism and beauty \n\ + The Favorite Vassal gains the Mesmerize ability." + join_icon_state = "toreador" + join_description = "Powerful Mesmerize, build statues to boost your status and mood, gather a large audience for your performance." + blood_drink_type = BLOODSUCKER_DRINK_SNOBBY + control_type = BLOODSUCKER_CONTROL_METAL + +/datum/bloodsucker_clan/toreador/New(mob/living/carbon/user) + . = ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user) + if(user && ishuman(user) && !user.GetComponent(/datum/component/mood)) + user.AddComponent(/datum/component/mood) //You are not a emotionless beast! //trolled! +// user.mind.teach_crafting_recipe(/datum/crafting_recipe/bloodybrush) + user.mind.teach_crafting_recipe(/datum/crafting_recipe/moldingstone) + user.mind.teach_crafting_recipe(/datum/crafting_recipe/chisel) + + for(var/datum/action/bloodsucker/masquerade/masquarade_spell in bloodsuckerdatum.powers) + if(!istype(masquarade_spell)) + continue + masquarade_spell.bloodcost = 0 + masquarade_spell.constant_bloodcost = 0 + var/datum/objective/bloodsucker/leader/toreador_objective = new + toreador_objective.owner = user.mind + bloodsuckerdatum.objectives += toreador_objective + +/datum/bloodsucker_clan/toreador/on_favorite_vassal(datum/source, datum/antagonist/vassal/vassaldatum, mob/living/bloodsucker) + vassaldatum.BuyPower(new /datum/action/bloodsucker/targeted/mesmerize) diff --git a/code/modules/antagonists/bloodsuckers/clans/clan_tzimisce.dm b/code/modules/antagonists/bloodsuckers/clans/clan_tzimisce.dm new file mode 100644 index 000000000000..1ab6262f3659 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/clans/clan_tzimisce.dm @@ -0,0 +1,30 @@ +/datum/bloodsucker_clan/tzimisce + name = CLAN_TZIMISCE + description = "The page is covered in blood..." + join_icon_state = "tzimisce" + joinable_clan = FALSE //important + blood_drink_type = BLOODSUCKER_DRINK_INHUMANELY + control_type = BLOODSUCKER_CONTROL_FLESH + +/datum/bloodsucker_clan/tzimisce/New(mob/living/carbon/user) + . = ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user) + bloodsuckerdatum.AddHumanityLost(5.6) + bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/dice) + user.faction |= "bloodhungry" //flesh monster's clan + var/list/powerstoremove = list(/datum/action/bloodsucker/veil, /datum/action/bloodsucker/masquerade) + for(var/datum/action/bloodsucker/P in bloodsuckerdatum.powers) + if(is_type_in_list(P, powerstoremove)) + bloodsuckerdatum.RemovePower(P) + +/datum/bloodsucker_clan/tzimisce/on_favorite_vassal(datum/source, datum/antagonist/vassal/vassaldatum, mob/living/bloodsucker) + if(!ishuman(vassaldatum.owner.current)) + return + var/mob/living/carbon/human/vassal = vassaldatum.owner.current + if(!do_mob(bloodsucker, vassal, 1 SECONDS, TRUE)) + return + playsound(vassal.loc, 'sound/weapons/slash.ogg', 50, TRUE, -1) + if(!do_mob(bloodsucker, vassal, 1 SECONDS, TRUE)) + return + playsound(vassal.loc, 'sound/effects/splat.ogg', 50, TRUE) + vassal.set_species(/datum/species/szlachta) diff --git a/code/modules/antagonists/bloodsuckers/powers/_powers.dm b/code/modules/antagonists/bloodsuckers/powers/_powers.dm index b97eeeed6909..2bc530a93d23 100644 --- a/code/modules/antagonists/bloodsuckers/powers/_powers.dm +++ b/code/modules/antagonists/bloodsuckers/powers/_powers.dm @@ -106,17 +106,28 @@ return FALSE // Cooldown? if(!COOLDOWN_FINISHED(src, bloodsucker_power_cooldown)) - to_chat(owner, span_warning("[src] on cooldown!")) - return FALSE + owner.balloon_alert(owner, "power unavailable!") + to_chat(owner, "[src] on cooldown!") + return FALSE + if(!bloodsuckerdatum_power) + var/mob/living/living_owner = owner + if(living_owner.blood_volume < bloodcost) + to_chat(owner, span_warning("You need at least [bloodcost] blood to activate [name]")) + return FALSE + return TRUE + // Have enough blood? Bloodsuckers in a Frenzy don't need to pay them - var/mob/living/user = owner - if(bloodsuckerdatum_power?.frenzied) + if(bloodsuckerdatum_power.frenzied) return TRUE - if(user.blood_volume < bloodcost) + if(bloodsuckerdatum_power.bloodsucker_blood_volume < bloodcost) to_chat(owner, span_warning("You need at least [bloodcost] blood to activate [name]")) return FALSE return TRUE +///Called when the Power is upgraded. +/datum/action/bloodsucker/proc/upgrade_power() + level_current++ + ///Checks if the Power is available to use. /datum/action/bloodsucker/proc/CheckCanUse(mob/living/carbon/user) if(!owner) @@ -132,7 +143,7 @@ to_chat(user, span_warning("You cannot use powers while in a Frenzy!")) return FALSE // Stake? - if((check_flags & BP_CANT_USE_WHILE_STAKED) && user.AmStaked()) + if((check_flags & BP_CANT_USE_WHILE_STAKED) && user.am_staked()) to_chat(user, span_warning("You have a stake in your chest! Your powers are useless.")) return FALSE // Conscious? -- We use our own (AB_CHECK_CONSCIOUS) here so we can control it more, like the error message. @@ -144,7 +155,7 @@ to_chat(user, span_warning("Not while you're incapacitated!")) return FALSE // Constant Cost (out of blood) - if(constant_bloodcost > 0 && user.blood_volume <= 0) + if(constant_bloodcost && bloodsuckerdatum_power?.bloodsucker_blood_volume <= 0) to_chat(user, span_warning("You don't have the blood to upkeep [src].")) return FALSE return TRUE @@ -175,16 +186,20 @@ /datum/action/bloodsucker/proc/CheckCanDeactivate() return TRUE -/datum/action/bloodsucker/UpdateButtonIcon(force = FALSE) +/datum/action/bloodsucker/UpdateButtons(force = FALSE) background_icon_state = active ? background_icon_state_on : background_icon_state_off . = ..() /datum/action/bloodsucker/proc/PayCost() + // Non-bloodsuckers will pay in other ways. + if(!bloodsuckerdatum_power) + var/mob/living/living_owner = owner + living_owner.blood_volume -= bloodcost + return // Bloodsuckers in a Frenzy don't have enough Blood to pay it, so just don't. - if(bloodsuckerdatum_power?.frenzied) + if(bloodsuckerdatum_power.frenzied) return - var/mob/living/carbon/human/user = owner - user.blood_volume -= bloodcost + bloodsuckerdatum_power.bloodsucker_blood_volume -= bloodcost bloodsuckerdatum_power?.update_hud() /datum/action/bloodsucker/proc/ActivatePower() @@ -192,7 +207,7 @@ if(power_flags & BP_AM_TOGGLE) RegisterSignal(owner, COMSIG_LIVING_BIOLOGICAL_LIFE, .proc/UsePower) owner.log_message("used [src][bloodcost != 0 ? " at the cost of [bloodcost]" : ""].", LOG_ATTACK, color="red") - UpdateButtonIcon() + UpdateButtons() /datum/action/bloodsucker/proc/DeactivatePower() if(power_flags & BP_AM_TOGGLE) @@ -201,7 +216,7 @@ RemoveAfterUse() return active = FALSE - UpdateButtonIcon() + UpdateButtons() StartCooldown() ///Used by powers that are continuously active (That have BP_AM_TOGGLE flag) @@ -213,14 +228,18 @@ return FALSE // We can keep this up (For now), so Pay Cost! if(!(power_flags & BP_AM_COSTLESS_UNCONSCIOUS) && user.stat != CONSCIOUS) - bloodsuckerdatum_power?.AddBloodVolume(-constant_bloodcost) + if(bloodsuckerdatum_power) + bloodsuckerdatum_power.AddBloodVolume(-constant_bloodcost) + else + var/mob/living/living_owner = owner + living_owner.blood_volume -= constant_bloodcost return TRUE /// Checks to make sure this power can stay active /datum/action/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target) if(!user) return FALSE - if(!constant_bloodcost > 0 || user.blood_volume > 0) + if(!constant_bloodcost > 0 || bloodsuckerdatum_power.bloodsucker_blood_volume) return TRUE /// Used to unlearn Single-Use Powers diff --git a/code/modules/antagonists/bloodsuckers/powers/cloak.dm b/code/modules/antagonists/bloodsuckers/powers/cloak.dm index 512c7b04b241..77ff1bc4349f 100644 --- a/code/modules/antagonists/bloodsuckers/powers/cloak.dm +++ b/code/modules/antagonists/bloodsuckers/powers/cloak.dm @@ -2,7 +2,7 @@ name = "Cloak of Darkness" desc = "Blend into the shadows and become invisible to the untrained and Artificial eye." button_icon_state = "power_cloak" - power_explanation = "Cloak of Darkness:\n\ + power_explanation = "Cloak of Darkness<:\n\ Activate this Power in the shadows and you will slowly turn nearly invisible.\n\ While using Cloak of Darkness, attempting to run will crush you.\n\ Additionally, while Cloak is active, you are completely invisible to the AI.\n\ @@ -22,7 +22,7 @@ if(!.) return FALSE for(var/mob/living/watchers in viewers(9, owner) - owner) - to_chat(owner, span_warning("You can only vanish unseen.")) + owner.balloon_alert(owner, "you can only vanish unseen.") return FALSE return TRUE @@ -35,7 +35,7 @@ user.toggle_move_intent() user.digitalinvis = 1 user.digitalcamo = 1 - to_chat(user, span_notice("You put your Cloak of Darkness on.")) + user.balloon_alert(user, "cloak turned on.") /datum/action/bloodsucker/cloak/UsePower(mob/living/user) // Checks that we can keep using this. @@ -46,7 +46,7 @@ // Prevents running while on Cloak of Darkness if(runbound) if(user.m_intent != MOVE_INTENT_WALK) - to_chat(owner, span_warning("You attempt to run, crushing yourself.")) + owner.balloon_alert(owner, "you attempt to run, crushing yourself.") user.toggle_move_intent() user.adjustBruteLoss(rand(5,15)) @@ -69,7 +69,7 @@ if(runbound) if(was_running && user.m_intent == MOVE_INTENT_WALK) user.toggle_move_intent() - to_chat(user, span_notice("You take your Cloak of Darkness off.")) + user.balloon_alert(user, "cloak turned off.") /datum/action/bloodsucker/cloak/shadow name = "Cloak of Shadows" @@ -101,7 +101,7 @@ /obj/item/clothing/neck/yogs/sith_cloak/cloak/process() var/turf/T = get_turf(src) var/light_amount = T.get_lumcount() - if(light_amount > 0.2) + if(light_amount > LIGHTING_TILE_IS_DARK) qdel(src) STOP_PROCESSING(SSobj, src) src.visible_message(span_warning("The cape desintegrates as the light contacts it's surface!")) @@ -110,7 +110,7 @@ . = ..() var/turf/T = get_turf(owner) var/light_amount = T.get_lumcount() - if(light_amount <= 0.2) + if(light_amount <= LIGHTING_TILE_IS_DARK) if(!owner.get_item_by_slot(SLOT_NECK)) owner.equip_to_slot_or_del( new /obj/item/clothing/neck/yogs/sith_cloak/cloak(null), SLOT_NECK) diff --git a/code/modules/antagonists/bloodsuckers/powers/distress.dm b/code/modules/antagonists/bloodsuckers/powers/distress.dm index 6a9d3e081434..db9691fee97d 100644 --- a/code/modules/antagonists/bloodsuckers/powers/distress.dm +++ b/code/modules/antagonists/bloodsuckers/powers/distress.dm @@ -2,7 +2,7 @@ name = "Distress" desc = "Injure yourself, allowing you to make a desperate call for help to your Master." button_icon_state = "power_distress" - power_explanation = "Distress:\n\ + power_explanation = "Distress:\n\ Use this Power from anywhere and your Master Bloodsucker will instnatly be alerted of your location." power_flags = NONE check_flags = NONE diff --git a/code/modules/antagonists/bloodsuckers/powers/feed.dm b/code/modules/antagonists/bloodsuckers/powers/feed.dm index 5a1e4fea305d..73fdb376a535 100644 --- a/code/modules/antagonists/bloodsuckers/powers/feed.dm +++ b/code/modules/antagonists/bloodsuckers/powers/feed.dm @@ -1,240 +1,140 @@ +#define FEED_NOTICE_RANGE 2 +#define FEED_DEFAULT_TIMER (10 SECONDS) + /datum/action/bloodsucker/feed name = "Feed" - desc = "Draw the heartsblood of living victims in your grasp. You will break the Masquerade if seen feeding." + desc = "Feed blood off of a living creature." button_icon_state = "power_feed" - power_explanation = "Feed:\n\ + power_explanation = "Feed:\n\ + Feed can't be used until you reach your first Bloodsucker level.\n\ Activate Feed while next to someone and you will begin to feed blood off of them.\n\ - If passively grabbed, you will feed faster than default.\n\ - If aggressively grabbed, along with drinking even faster, your victim will additionally be put to sleep.\n\ - You cannot talk while feeding, as your mouth is full of blood.\n\ - Feeding off of someone until they die will cause you to lose Humanity.\n\ - If you are seen feeding off of someone (2 tiles) while your target is grabbed, you will break the Masquerade.\n\ - Higher levels will increase the feeding's speed." + The time needed before you start feeding speeds up the higher level you are.\n\ + Feeding off of someone while you have them aggressively grabbed will put them to sleep.\n\ + While feeding, you can't speak, as your mouth is covered.\n\ + Feeding while nearby (2 tiles away from) a mortal who is unaware of Bloodsuckers' existence, will cause a Masquerade Infraction\n\ + If you get too many Masquerade Infractions, you will break the Masquerade.\n\ + If you are in desperate need of blood, mice can be fed off of, at a cost." power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS - purchase_flags = BLOODSUCKER_CAN_BUY + purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER bloodcost = 0 - cooldown = 3 SECONDS - - ///Amount of times we were seen Feeding. If seen 3 times, we broke the Masquerade. - var/feeds_noticed = 0 - ///Distance before silent feeding is noticed. - var/notice_range = 2 - ///Check if we were noticed Feeding. - var/was_noticed = FALSE - ///So we can validate more than just the guy we're grappling. - var/mob/living/feed_target - ///If you started grappled, then ending it will end your Feed. - var/target_grappled = FALSE - ///Am I Silent? - var/amSilent = FALSE - ///How much Blood did I drink? This is used for logs - var/amount_taken = 0 - ///The initial wait before you start drinking blood. - var/feed_time - ///Quantity to take per tick, based on Silent/frenzied or not. - var/blood_take_mult - /// CHECKS - To prevent spam. - var/warning_target_inhuman = FALSE - var/warning_target_dead = FALSE - var/warning_full = FALSE - var/warning_target_bloodvol = 99999 - var/was_alive = FALSE + cooldown = 15 SECONDS + ///Amount of blood taken, reset after each Feed. Used for logging. + var/blood_taken = 0 + ///The amount of Blood a target has since our last feed, this loops and lets us not spam alerts of low blood. + var/warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL + ///Reference to the target we've fed off of + var/datum/weakref/target_ref + ///Are we feeding with aggresive grab or not? + var/silent_feed = TRUE /datum/action/bloodsucker/feed/CheckCanUse(mob/living/carbon/user) . = ..() if(!.) return FALSE - - // Wearing mask + if(target_ref) //already sucking blood. + return FALSE + if(!level_current) + owner.balloon_alert(owner, "too weak!") + to_chat(owner, span_warning("You can't use [src] until you level up.")) + return FALSE if(user.is_mouth_covered()) - to_chat(owner, span_warning("Your mouth is covered!")) + owner.balloon_alert(owner, "mouth covered!") return FALSE - // Find my Target! + //Find target, it will alert what the problem is, if any. if(!find_target()) return FALSE - // DONE! return TRUE -/// Called twice: validating a subtle victim, or validating your grapple victim. -/datum/action/bloodsucker/feed/proc/ValidateTarget(mob/living/target) - // Must have Target. - if(!target)//|| !ismob(target) - to_chat(owner, span_warning("You must be next to or grabbing a victim to feed from them.")) - return FALSE - // Not even living! - if(!isliving(target) || issilicon(target)) - to_chat(owner, span_warning("You may only feed from living beings.")) - return FALSE - // Check for other animals (Supposed to be after Mouse so Mouse can skip over it) - else if(!iscarbon(target)) - to_chat(owner, span_warning("Such simple beings cannot be fed off of.")) - return FALSE - // Has no blood to take! - else if(target.blood_volume <= 0) - to_chat(owner, span_warning("Your victim has no blood to take.")) +/datum/action/bloodsucker/feed/ContinueActive(mob/living/user, mob/living/target) + if(!target) return FALSE - // Bloodsuckers can be fed off of if they are grabbed more than Passively. - if(IS_BLOODSUCKER(target) && target == owner.pulling && owner.grab_state <= GRAB_PASSIVE) - to_chat(owner, span_warning("Other Bloodsuckers will not fall for your subtle approach.")) + if(!user.Adjacent(target)) return FALSE - if(ishuman(target)) - var/mob/living/carbon/human/target_user = target - if(!target_user.can_inject(owner, BODY_ZONE_HEAD, 1) && target == owner.pulling && owner.grab_state < GRAB_AGGRESSIVE) - to_chat(owner, span_warning("Their suit is too thick to feed through.")) - return FALSE - if(NOBLOOD in target_user.dna.species.species_traits)// || owner.get_blood_id() != target.get_blood_id()) - to_chat(owner, span_warning("Your victim's blood is not suitable for you to take.")) - return FALSE return TRUE -/// If I'm not grabbing someone, find me someone nearby. -/datum/action/bloodsucker/feed/proc/find_target() - // Default - feed_target = null - target_grappled = FALSE - // If you are pulling a mob, that's your target. If you don't like it, then release them. - if(owner.pulling && ismob(owner.pulling)) - // Check grapple target Valid - if(!ValidateTarget(owner.pulling)) // Grabbed targets display error. - return FALSE - target_grappled = TRUE - feed_target = owner.pulling - return TRUE - // Find Targets - var/list/mob/living/seen_mobs = list() - for(var/mob/living/watchers in view(1, owner) - owner) - if(!isliving(watchers)) - continue - seen_mobs |= watchers - // None Seen! - if(!seen_mobs.len) - to_chat(owner, span_warning("You must be next to or grabbing a victim to feed from them.")) - return FALSE - // Check Valids... - var/list/targets_valid = list() - var/list/targets_dead = list() - for(var/mob/living/watchers in seen_mobs) - // Check adjecent Valid target - if(watchers != owner && ValidateTarget(watchers)) // Do NOT display errors. We'll be doing this again in CheckCanUse(), which will rule out grabbed targets. - // Prioritize living, but remember dead as backup - if(watchers.stat < DEAD) - targets_valid |= watchers - else - targets_dead |= watchers - // No Living? Try dead. - if(!targets_valid.len && targets_dead.len) - targets_valid = targets_dead - // No Targets - if(!targets_valid.len) - // Did I see targets? Then display at least one error - if(seen_mobs.len > 1) - to_chat(owner, span_warning("None of these are valid targets to feed from subtly.")) - else - ValidateTarget(seen_mobs[1]) - return FALSE - else - feed_target = pick(targets_valid) - return TRUE - -/datum/action/bloodsucker/feed/ActivatePower() - . = ..() +/datum/action/bloodsucker/feed/DeactivatePower() var/mob/living/user = owner - // Checks: Step 1 - Am I SECRET or LOUD? - if(!bloodsuckerdatum_power.frenzied && (!target_grappled || owner.grab_state <= GRAB_PASSIVE)) // && iscarbon(target) // Non-carbons (animals) not passive. They go straight into aggressive. - amSilent = TRUE + if(target_ref) + var/mob/living/feed_target = target_ref.resolve() + log_combat(user, feed_target, "fed on blood", addition="(and took [blood_taken] blood)") + user.balloon_alert(owner, "feed stopped") + to_chat(user, span_notice("You slowly release [feed_target].")) + if(feed_target.stat == DEAD) + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankkilled", /datum/mood_event/drankkilled) + bloodsuckerdatum_power.AddHumanityLost(10) + + target_ref = null + warning_target_bloodvol = BLOOD_VOLUME_MAX_LETHAL + blood_taken = 0 + REMOVE_TRAIT(user, TRAIT_IMMOBILIZED, FEED_TRAIT) + REMOVE_TRAIT(user, TRAIT_MUTE, FEED_TRAIT) + return ..() - // Checks: Step 2 - Is it a Mouse? - if(istype(feed_target, /mob/living/simple_animal/mouse)) - if(bloodsuckerdatum_power.my_clan == CLAN_TOREADOR) - to_chat(user, span_danger("I am not going to drink blood of a lesser lifeform.")) - DeactivatePower() - return - var/mob/living/simple_animal/mouse_target = feed_target +/datum/action/bloodsucker/feed/ActivatePower() + var/mob/living/feed_target = target_ref.resolve() + if(istype(feed_target, /mob/living/simple_animal/mouse || istype(feed_target, /mob/living/simple_animal/hostile/rat))) + to_chat(owner, span_notice("You recoil at the taste of a lesser lifeform.")) + if(bloodsuckerdatum_power.my_clan.blood_drink_type != BLOODSUCKER_DRINK_INHUMANELY) + SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad) + bloodsuckerdatum_power.AddHumanityLost(1) bloodsuckerdatum_power.AddBloodVolume(25) - to_chat(user, span_notice("You recoil at the taste of a lesser lifeform.")) - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad) - bloodsuckerdatum_power.AddHumanityLost(1) DeactivatePower() - mouse_target.adjustBruteLoss(20) + feed_target.death() return - // Checks: Step 3 - How fast should I be and how much should I drink? - var/feed_time_multiplier + var/feed_timer = clamp(round(FEED_DEFAULT_TIMER / (1.25 * level_current)), 1, FEED_DEFAULT_TIMER) if(bloodsuckerdatum_power.frenzied) - blood_take_mult = 2 - feed_time_multiplier = 8 - else if(!amSilent) - blood_take_mult = 1 - feed_time_multiplier = 25 - (2.5 * level_current) - else - blood_take_mult = 0.3 - feed_time_multiplier = 45 - (2.5 * level_current) - feed_time = max(8, feed_time_multiplier) - // Let's check if our target is alive - was_alive = feed_target.stat < DEAD && ishuman(feed_target) + feed_timer = 2 SECONDS - // Send pre-pull message - if(amSilent) - to_chat(owner, span_notice("You quietly lean towards [feed_target]")) - else - to_chat(owner, span_notice("You pull [feed_target] close to you!")) - - // Start the countdown - if(!do_mob(user, feed_target, feed_time, NONE, TRUE)) + owner.balloon_alert(owner, "feeding off [feed_target]...") + if(!do_after(owner, feed_timer, feed_target, NONE, TRUE)) + owner.balloon_alert(owner, "interrupted!") DeactivatePower() - to_chat(owner, span_danger("Your feeding was interrupted!")) return - - // Give them the effects (Depending on if we are silent or not) - if(!amSilent) - // Sleep & paralysis. - ApplyVictimEffects(feed_target, first_hit = TRUE) - // Pull target to you if they don't take up space. + if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE) + if(!IS_BLOODSUCKER(feed_target) && !IS_VASSAL(feed_target) && !IS_MONSTERHUNTER(feed_target)) + feed_target.Unconscious((5 + level_current) SECONDS) if(!feed_target.density) - feed_target.Move(user.loc) - user.visible_message( - span_warning("[user] closes [user.p_their()] mouth around [feed_target]'s neck!"), - span_warning("You sink your fangs into [feed_target]'s neck."), - ) - if(amSilent) - var/deadmessage = feed_target.stat == DEAD ? "" : " [feed_target.p_they(TRUE)] looks dazed, and will not remember this." - user.visible_message( - span_notice("[user] puts [feed_target]'s wrist up to [user.p_their()] mouth."), \ - span_notice("You slip your fangs into [feed_target]'s wrist.[deadmessage]"), \ - vision_distance = notice_range, ignored_mobs = feed_target) // Only people who AREN'T the target will notice this action. - - // Check if we have anyone watching - If there is one, we broke the Masquerade. - for(var/mob/living/watchers in viewers(notice_range, owner) - owner - feed_target) - // Are they someone who will actually report our behavior? - if(watchers.client \ - && !watchers.has_unlimited_silicon_privilege \ - && watchers.stat != DEAD \ - && watchers.eye_blind == 0 \ - && watchers.eye_blurry == 0 \ - && !IS_BLOODSUCKER(watchers) \ - && !IS_VASSAL(watchers) \ - && !HAS_TRAIT(watchers, TRAIT_BLOODSUCKER_HUNTER)) - was_noticed = TRUE - break - if(was_noticed && !target_grappled) - feeds_noticed++ - to_chat(owner, span_danger("Someone may have noticed...")) - if(!bloodsuckerdatum_power.broke_masquerade) - to_chat(user, span_cultbold("You broke the Masquerade [feeds_noticed] time(s), if you break it 3 times, you become a criminal to the Bloodsucker's Cause!")) + feed_target.Move(owner.loc) + silent_feed = FALSE //no more mr nice guy + owner.visible_message( + span_warning("[owner] closes [owner.p_their()] mouth around [feed_target]'s neck!"), + span_warning("You sink your fangs into [feed_target]'s neck.")) else - to_chat(owner, span_notice("You think no one saw you...")) + // Only people who AREN'T the target will notice this action. + var/dead_message = feed_target.stat != DEAD ? " [feed_target.p_they(TRUE)] looks dazed, and will not remember this." : "" + owner.visible_message( + span_notice("[owner] puts [feed_target]'s wrist up to [owner.p_their()] mouth."), \ + span_notice("You slip your fangs into [feed_target]'s wrist.[dead_message]"), \ + vision_distance = FEED_NOTICE_RANGE, ignored_mobs = feed_target) + + //check if we were seen + for(var/mob/living/watchers in oviewers(FEED_NOTICE_RANGE) - feed_target) + if(!watchers.client) + continue + if(watchers.has_unlimited_silicon_privilege) + continue + if(watchers.stat >= DEAD) + continue + if(HAS_TRAIT(watchers, TRAIT_BLIND) || HAS_TRAIT(watchers, TRAIT_NEARSIGHT)) + continue + if(IS_BLOODSUCKER(watchers) || IS_VASSAL(watchers) || HAS_TRAIT(watchers.mind, TRAIT_BLOODSUCKER_HUNTER)) + continue + owner.balloon_alert(owner, "feed noticed!") + bloodsuckerdatum_power.give_masquerade_infraction() + break - // FEEEEEEEEED!! // - ADD_TRAIT(user, TRAIT_MUTE, BLOODSUCKER_TRAIT) // My mouth is full! - user.Immobilize(10 SECONDS) // Prevents spilling blood accidentally. + ADD_TRAIT(owner, TRAIT_MUTE, FEED_TRAIT) + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, FEED_TRAIT) + return ..() /datum/action/bloodsucker/feed/UsePower(mob/living/user) + var/mob/living/feed_target = target_ref.resolve() if(!ContinueActive(user, feed_target)) - if(amSilent) - to_chat(user, span_warning("Your feeding has been interrupted... but [feed_target.p_they()] didn't seem to notice you.")) - DeactivatePower() + if(silent_feed) + owner.balloon_alert(owner, "interrupted...") else - to_chat(user, span_warning("Your feeding has been interrupted!")) + owner.balloon_alert(owner, "interrupted!") user.visible_message( span_warning("[user] is ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!"), span_warning("Your teeth are ripped from [feed_target]'s throat. [feed_target.p_their(TRUE)] blood sprays everywhere!")) @@ -242,7 +142,7 @@ if(iscarbon(feed_target)) var/mob/living/carbon/carbon_target = feed_target carbon_target.bleed(15) - playsound(get_turf(feed_target), 'sound/effects/splat.ogg', 40, 1) + playsound(get_turf(feed_target), 'sound/effects/splat.ogg', 40, TRUE) if(ishuman(feed_target)) var/mob/living/carbon/human/target_user = feed_target var/obj/item/bodypart/head_part = target_user.get_bodypart(BODY_ZONE_HEAD) @@ -252,104 +152,97 @@ user.add_mob_blood(feed_target) // Put target's blood on us. The donor goes in the ( ) feed_target.add_mob_blood(feed_target) feed_target.apply_damage(10, BRUTE, BODY_ZONE_HEAD, wound_bonus = CANT_WOUND) - INVOKE_ASYNC(feed_target, /mob.proc/emote, "scream") - DeactivatePower() + INVOKE_ASYNC(feed_target, TYPE_PROC_REF(/mob, emote), "scream") + DeactivatePower() return - - /////////////////////////////////////////////////////////// - // Handle Feeding! User & Victim Effects (per tick) - bloodsuckerdatum_power.HandleFeeding(feed_target, blood_take_mult, level_current) - amount_taken += amSilent ? 0.3 : 1 - if(!amSilent) - ApplyVictimEffects(feed_target) - - /////////////////////////////////////////////////////////// - // MOOD EFFECTS // - // Drank good blood? - GOOD - if(amount_taken > 5 && feed_target.stat < DEAD && ishuman(feed_target)) + var/feed_strength_mult = 0 + if(bloodsuckerdatum_power.frenzied) + feed_strength_mult = 2 + else if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE) + feed_strength_mult = 1 + else + feed_strength_mult = 0.3 + blood_taken += bloodsuckerdatum_power.handle_feeding(feed_target, feed_strength_mult, level_current) + if(feed_strength_mult > 5 && feed_target.stat < DEAD) SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood) - // Dead Blood? - BAD + if(bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY && !feed_target.mind) + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_bad) if(feed_target.stat >= DEAD) - if(ishuman(feed_target)) - SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_dead) - if(!warning_target_dead) - to_chat(user, span_notice("Your victim is dead. [feed_target.p_their(TRUE)] blood barely nourishes you.")) - warning_target_dead = TRUE - - // Blood Remaining? (Carbons/Humans only) - else if(!IS_BLOODSUCKER(feed_target)) + SEND_SIGNAL(user, COMSIG_ADD_MOOD_EVENT, "drankblood", /datum/mood_event/drankblood_dead) + if(!IS_BLOODSUCKER(feed_target)) if(feed_target.blood_volume <= BLOOD_VOLUME_BAD(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_BAD(feed_target)) - to_chat(owner, span_danger("Your victim's blood is fatally low!")) + owner.balloon_alert(owner, "your victim's blood is fatally low!") else if(feed_target.blood_volume <= BLOOD_VOLUME_OKAY(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_OKAY(feed_target)) - to_chat(owner, span_danger("Your victim's blood is dangerously low.")) + owner.balloon_alert(owner, "your victim's blood is dangerously low.") else if(feed_target.blood_volume <= BLOOD_VOLUME_SAFE(feed_target) && warning_target_bloodvol > BLOOD_VOLUME_SAFE(feed_target)) - to_chat(owner, span_danger("Your victim's blood is at an unsafe level.")) - warning_target_bloodvol = feed_target.blood_volume // If we had a warning to give, it's been given by now. - // Full? - if(user.blood_volume >= bloodsuckerdatum_power.max_blood_volume && !warning_full) - to_chat(owner, span_notice("You are full, further blood will be wasted.")) - warning_full = TRUE - // Done? + owner.balloon_alert(owner, "your victim's blood is at an unsafe level.") + warning_target_bloodvol = feed_target.blood_volume + + if(user.blood_volume >= bloodsuckerdatum_power.max_blood_volume) + owner.balloon_alert(owner, "you are full...") + DeactivatePower() + return if(feed_target.blood_volume <= 0) + owner.balloon_alert(owner, "out of blood...") DeactivatePower() - to_chat(owner, span_notice("You have bled your victim dry...")) return - - // Blood Gulp Sound owner.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE) - if(!amSilent) + //play sound to target to show they're dying. + if(owner.pulling == feed_target && owner.grab_state >= GRAB_AGGRESSIVE) feed_target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE) -/// Check if we killed our target -/datum/action/bloodsucker/feed/proc/CheckKilledTarget(mob/living/target) - if(target && target.stat >= DEAD && ishuman(target)) - SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "drankkilled", /datum/mood_event/drankkilled) - bloodsuckerdatum_power.AddHumanityLost(10) - -/// NOTE: We only care about pulling if target started off that way. Mostly only important for Aggressive feed. -/datum/action/bloodsucker/feed/ContinueActive(mob/living/user, mob/living/target) - if(!target) +/datum/action/bloodsucker/feed/proc/find_target() + if(owner.pulling && isliving(owner.pulling)) + if(!can_feed_from(owner.pulling, give_warnings = TRUE)) + return FALSE + target_ref = WEAKREF(owner.pulling) + return TRUE + var/list/close_living_mobs = list() + var/list/close_dead_mobs = list() + for(var/mob/living/near_targets in oview(1, owner)) + if(!owner.Adjacent(near_targets)) + continue + if(near_targets.stat) + close_living_mobs |= near_targets + else + close_dead_mobs |= near_targets + //Check living first + for(var/mob/living/suckers in close_living_mobs) + if(can_feed_from(suckers)) + target_ref = WEAKREF(suckers) + return TRUE + //If not, check dead + for(var/mob/living/suckers in close_dead_mobs) + if(can_feed_from(suckers)) + target_ref = WEAKREF(suckers) + return TRUE + //No one to suck blood from. + return FALSE + +/datum/action/bloodsucker/feed/proc/can_feed_from(mob/living/target, give_warnings = FALSE) + if(istype(target, /mob/living/simple_animal/mouse) || istype(target, /mob/living/simple_animal/hostile/rat)) + if(bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY) + if(give_warnings) + to_chat(owner, span_warning("The thought of feeding off of a dirty rat leaves your stomach aching.")) + return FALSE + return TRUE + //Mice check done, only humans are otherwise allowed + if(!ishuman(target)) return FALSE - if(!user.Adjacent(target)) + var/mob/living/carbon/human/target_user = target + if(!(target_user.dna?.species) || !(target_user.mob_biotypes & MOB_ORGANIC)) + if(give_warnings) + to_chat(owner, span_warning("Your victim has no blood to take.")) + return FALSE + if(!target_user.can_inject(owner, BODY_ZONE_HEAD)) + if(give_warnings) + to_chat(owner, span_warning("Their suit is too thick to feed through.")) return FALSE - if(target_grappled && !user.pulling) + if((bloodsuckerdatum_power.my_clan.blood_drink_type == BLOODSUCKER_DRINK_SNOBBY) && !target_user.mind && !bloodsuckerdatum_power.frenzied) + if(give_warnings) + to_chat(owner, span_warning("The thought of drinking blood from the mindsless leaves a distasteful feeling in your mouth.")) return FALSE return TRUE -/// Bloodsuckers not affected by "the Kiss" of another vampire -/datum/action/bloodsucker/feed/proc/ApplyVictimEffects(mob/living/target, first_hit = FALSE) - if(IS_BLOODSUCKER(target) || IS_VASSAL(target)) - return - if(first_hit) - target.Unconscious(5 SECONDS,0) - target.Paralyze(40 + 5 * level_current) - -/datum/action/bloodsucker/feed/DeactivatePower() - . = ..() // activate = FALSE - - if(feed_target) // Check: Otherwise it runtimes if you fail to feed on someone. - if(amSilent) - to_chat(owner, span_notice("You slowly release [feed_target]'s wrist." + (feed_target.stat == 0 ? " [feed_target.p_their(TRUE)] face lacks expression, like you've already been forgotten." : ""))) - else - owner.visible_message( - span_warning("[owner] unclenches their teeth from [feed_target]'s neck."), - span_warning("You retract your fangs and release [feed_target] from your bite.")) - log_combat(owner, feed_target, "fed on blood", addition="(and took [amount_taken] blood)") - // Did we kill our target? - if(was_alive) - CheckKilledTarget(feed_target) - // Only break it once we've broken it 3 times, not more. - if(feeds_noticed == 3) - bloodsuckerdatum_power.break_masquerade() - // Reset ALL checks for next time the Power is used. - amSilent = FALSE - was_noticed = FALSE - warning_target_inhuman = FALSE - warning_target_dead = FALSE - warning_full = FALSE - feed_target = null - warning_target_bloodvol = 99999 - // My mouth is no longer full - var/mob/living/O = owner - O.SetImmobilized(0) - REMOVE_TRAIT(owner, TRAIT_MUTE, BLOODSUCKER_TRAIT) +#undef FEED_NOTICE_RANGE +#undef FEED_DEFAULT_TIMER diff --git a/code/modules/antagonists/bloodsuckers/powers/fortitude.dm b/code/modules/antagonists/bloodsuckers/powers/fortitude.dm index a19682ab496a..c1b1f5849fa2 100644 --- a/code/modules/antagonists/bloodsuckers/powers/fortitude.dm +++ b/code/modules/antagonists/bloodsuckers/powers/fortitude.dm @@ -2,7 +2,7 @@ name = "Fortitude" desc = "Withstand egregious physical wounds and walk away from attacks that would stun, pierce, and dismember lesser beings." button_icon_state = "power_fortitude" - power_explanation = "Fortitude:\n\ + power_explanation = "Fortitude<:\n\ Activating Fortitude will provide pierce, stun and dismember immunity.\n\ You will additionally gain resistance to Brute and Stamina damge, scaling with level.\n\ While using Fortitude, attempting to run will crush you.\n\ @@ -19,6 +19,7 @@ /datum/action/bloodsucker/fortitude/ActivatePower() . = ..() + owner.balloon_alert(owner, "fortitude turned on.") to_chat(owner, span_notice("Your flesh, skin, and muscles become as steel.")) // Traits & Effects ADD_TRAIT(owner, TRAIT_PIERCEIMMUNE, BLOODSUCKER_TRAIT) @@ -48,7 +49,7 @@ /// Prevents running while on Fortitude if(user.m_intent != MOVE_INTENT_WALK) user.toggle_move_intent() - to_chat(user, span_warning("You attempt to run, crushing yourself.")) + user.balloon_alert(user, "you attempt to run, crushing yourself.") user.adjustBruteLoss(rand(5,15)) /// We don't want people using fortitude being able to use vehicles if(user.buckled && istype(user.buckled, /obj/vehicle)) @@ -73,6 +74,7 @@ if(was_running && bloodsucker_user.m_intent == MOVE_INTENT_WALK) bloodsucker_user.toggle_move_intent() + owner.balloon_alert(owner, "fortitude turned off.") return ..() /// Monster Hunter version @@ -92,17 +94,14 @@ additional_text = "Additionally gives you extra damage while fortitude'd and agro grab while in darkness." purchase_flags = LASOMBRA_CAN_BUY constant_bloodcost = 0.3 - var/mutable_appearance/armor_overlay /datum/action/bloodsucker/fortitude/shadow/ActivatePower() . = ..() var/mob/living/carbon/human/user = owner var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - armor_overlay = mutable_appearance('icons/obj/vamp_obj.dmi', "fortarmor") var/turf/T = get_turf(owner) var/light_amount = T.get_lumcount() - if(light_amount <= 0.2) - owner.add_overlay(armor_overlay) + if(light_amount <= LIGHTING_TILE_IS_DARK) bloodsuckerdatum.frenzygrab.teach(user, TRUE) to_chat(user, span_notice("Shadow tentacles form and attach themselves to your body, you feel as if your muscles have merged with the shadows!")) user.physiology.punchdamagehigh_bonus += 0.5 * level_current @@ -115,8 +114,7 @@ var/light_amount = T.get_lumcount() var/mob/living/carbon/user = owner var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(light_amount > 0.2) - owner.cut_overlay(armor_overlay) + if(light_amount > LIGHTING_TILE_IS_DARK) bloodsuckerdatum.frenzygrab.remove(user) to_chat(user, span_warning("As you enter in contact with the light, the tentacles dissipate!")) @@ -124,7 +122,6 @@ . = ..() var/mob/living/carbon/human/user = owner var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - owner.cut_overlay(armor_overlay) bloodsuckerdatum.frenzygrab.remove(user) user.physiology.punchdamagehigh_bonus -= 0.5 * level_current user.physiology.punchdamagelow_bonus -= 0.5 * level_current diff --git a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm index a4d322008240..b1ab62de2998 100644 --- a/code/modules/antagonists/bloodsuckers/powers/gangrel.dm +++ b/code/modules/antagonists/bloodsuckers/powers/gangrel.dm @@ -13,7 +13,7 @@ name = "Transform" desc = "Allows you to unleash your inner form and turn into something greater." button_icon_state = "power_gangrel" - power_explanation = "Transform:\n\ + power_explanation = "Transform:\n\ A gangrel only power, will turn you into a feral being depending on your blood sucked.\n\ May have unforseen consequences if used on low blood sucked, upgrades every 500 units.\n\ Some forms have special abilites to them depending on what abilites you have.\n\ @@ -24,48 +24,68 @@ /datum/action/bloodsucker/gangrel/transform/ActivatePower() var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker) var/mob/living/carbon/human/user = owner - var/datum/species/user_species = user.dna.species - var/minortransformdone = FALSE - var/mediumtransformdone = FALSE - user.Immobilize(10 SECONDS) if(!do_mob(user, user, 10 SECONDS, 1)) return - switch(bloodsuckerdatum.total_blood_drank) - if(0 to 500) - if(!minortransformdone) - if(iscatperson(user)) - user.set_species(/datum/species/lizard) - playsound(user.loc, 'sound/voice/lizard/hiss.ogg', 50) - else - user.set_species(/datum/species/human/felinid) - playsound(user.loc, 'sound/voice/feline/meow1.ogg', 50) - if(DIGITIGRADE in user_species.species_traits) - user_species.species_traits -= DIGITIGRADE - minortransformdone = TRUE - user.dna.species.punchdamagelow += 5.0 - user.dna.species.punchdamagehigh += 5.0 //stronk - user.dna.species.punchstunthreshold += 5.0 - user.dna.species.armor += 30 - to_chat(user, span_notice("You aren't strong enough to morph into something stronger! But you do certainly feel more feral and stronger than before.")) + var/list/radial_display = list() + switch(bloodsuckerdatum.total_blood_drank) //get our options + if(0 to 250) //makes the icons for the options + var/datum/radial_menu_choice/option = new + user.setDir(SOUTH) + var/icon/icon_to_mix = getFlatIcon(user) + icon_to_mix.Blend(icon('icons/mob/mutant_bodyparts.dmi', "m_ears_cat_FRONT"), ICON_OVERLAY) + option.image = icon_to_mix + option.info = "[iscatperson(user) ? "Lizard" : "Felinid"]: Increased agility and speed, but less armor." + radial_display["Lizard/Felinid"] = option + if(250 to 750) + var/datum/radial_menu_choice/option = new + var/icon/body = icon('yogstation/icons/mob/human_parts.dmi', "gorilla_r_leg") + body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_l_leg"), ICON_OVERLAY) + body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_r_arm"), ICON_OVERLAY) + body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_l_arm"), ICON_OVERLAY) + body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_r_hand"), ICON_OVERLAY) + body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_l_hand"), ICON_OVERLAY) + body.Blend(icon('yogstation/icons/mob/human_parts.dmi', "gorilla_chest_m"), ICON_OVERLAY) + option.image = body + option.info = "Gorilla: Increased durability and strength, but less speed." + radial_display["Gorilla"] = option + if(750 to INFINITY) + var/datum/radial_menu_choice/option = new + option.image = icon('icons/mob/bloodsucker_mobs.dmi', "batform") + option.info = "Turn into a giant bat simple mob with unique abilities." + radial_display["Bat"] = option + var/chosen_transform = show_radial_menu(user, user, radial_display) + transform(chosen_transform) //actually transform + . = ..() + +/datum/action/bloodsucker/gangrel/transform/proc/transform(chosen_transform) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(/datum/antagonist/bloodsucker) + var/mob/living/carbon/human/user = owner + var/datum/species/user_species = user.dna.species + switch(chosen_transform) + if("Lizard/Felinid") + if(iscatperson(user)) + user.set_species(/datum/species/lizard) + playsound(user.loc, 'sound/voice/lizard/hiss.ogg', 50) else - to_chat(user, span_notice("You still haven't evolved your ability yet.")) + user.set_species(/datum/species/human/felinid) + playsound(user.loc, 'sound/voice/feline/meow1.ogg', 50) + if(!LAZYFIND(user_species.species_traits, DIGITIGRADE)) + user_species.species_traits += DIGITIGRADE + user.dna.species.armor -= 20 //careful + user.dna.species.speedmod = -0.5 + user.dna.species.action_speed_coefficient = 0.7 bloodsuckerdatum.AddBloodVolume(75) - if(500 to 1000) - if(!mediumtransformdone) - user.set_species(/datum/species/gorilla) - playsound(user.loc, 'sound/creatures/gorilla.ogg', 50) - if(DIGITIGRADE in user_species.species_traits) - user_species.species_traits -= DIGITIGRADE - mediumtransformdone = TRUE - user.dna.species.punchdamagelow += 7.5 - user.dna.species.punchdamagehigh += 7.5 //very stronk - user.dna.species.punchstunthreshold += 7.5 - user.dna.species.armor += 35 - to_chat(owner, span_notice("You transform into a gorrila-ey beast, you feel stronger!")) - else - to_chat(owner, span_notice("You still haven't evolved your ability yet.")) - bloodsuckerdatum.AddBloodVolume(50) - if(1500 to INFINITY) + if("Gorilla") + user.set_species(/datum/species/gorilla) + playsound(user.loc, 'sound/creatures/gorilla.ogg', 50) + if(DIGITIGRADE in user_species.species_traits) + user_species.species_traits -= DIGITIGRADE + user.dna.species.punchdamagelow += 10 + user.dna.species.punchdamagehigh += 10 //very stronk + user.dna.species.punchstunthreshold += 10 + user.dna.species.armor += 40 + bloodsuckerdatum.AddBloodVolume(50) + if("Bat") var/mob/living/simple_animal/hostile/bloodsucker/giantbat/gb if(!gb || gb.stat == DEAD) gb = new /mob/living/simple_animal/hostile/bloodsucker/giantbat(user.loc) @@ -73,7 +93,7 @@ gb.bloodsucker = user user.status_flags |= GODMODE //sad! user.mind.transfer_to(gb) - var/list/bat_powers = list(new /datum/action/bloodsucker/gangrel/transform_back,) + var/list/bat_powers = list(new /datum/action/bloodsucker/gangrel/transform_back, ) for(var/datum/action/bloodsucker/power in bloodsuckerdatum.powers) if(istype(power, /datum/action/bloodsucker/targeted/haste)) bat_powers += new /datum/action/bloodsucker/targeted/haste/batdash @@ -83,28 +103,25 @@ bat_powers += new /datum/action/bloodsucker/gangrel/wingslam for(var/datum/action/bloodsucker/power in bat_powers) power.Grant(gb) - QDEL_IN(gb, 2 MINUTES) playsound(gb.loc, 'sound/items/toysqueak1.ogg', 50, TRUE) - to_chat(owner, span_notice("You transform into a fatty beast!")) - return ..() //early to not mess with vampire organs proc - bloodsuckerdatum.HealVampireOrgans() //regives you the stuff - . = ..() + return //early to not mess with vampire organs proc + + bloodsuckerdatum.heal_vampire_organs() //regives you the stuff /datum/action/bloodsucker/gangrel/transform_back name = "Transform" desc = "Regress back into a human." button_icon_state = "power_gangrel" - power_explanation = "Transform:\n\ + power_explanation = "Transform:\n\ Regress back to your humanoid form early, requires you to stand still.\n\ Beware you will not be able to transform again until the night passes!" /datum/action/bloodsucker/gangrel/transform_back/ActivatePower() - var/mob/living/user = owner - if(!do_mob(user, user, 10 SECONDS)) + if(!do_mob(owner, owner, 10 SECONDS)) return var/mob/living/simple_animal/hostile/bloodsucker/bs - qdel(owner) - qdel(bs) + if(istype(owner, bs)) + qdel(bs) . = ..() /* ////////////////||\\\\\\\\\\\\\\\\ @@ -120,7 +137,7 @@ button_icon_state = "power_baste" background_icon_state_on = "bat_power_on" background_icon_state_off = "bat_power_off" - power_explanation = "Flying Haste:\n\ + power_explanation = "Flying Haste<:\n\ Makes you dash into the air, creating a smoke cloud at the end.\n\ Helpful in situations where you either need to run away or engage in a crowd of people, works over tables.\n\ Created from your Immortal Haste ability." @@ -148,7 +165,7 @@ button_icon_state = "power_bolt" background_icon_state_on = "bat_power_on" background_icon_state_off = "bat_power_off" - power_explanation = "Blood Bolt:\n\ + power_explanation = "Blood Bolt<:\n\ Shoots a blood bolt that does moderate damage to your foes.\n\ Helpful in situations where you get outranged or just extra damage.\n\ Created from your Mesmerize ability." @@ -201,7 +218,7 @@ button_icon_state = "power_wingslam" background_icon_state_on = "bat_power_on" background_icon_state_off = "bat_power_off" - power_explanation = "Wing Slam:\n\ + power_explanation = "Wing Slam:\n\ Knocksback and immobilizes people adjacent to you.\n\ Has a low recharge time and may be helpful in meelee situations!\n\ Created from your Brawn ability." @@ -227,9 +244,9 @@ span_userdanger("You were sent flying by the flap of [user]'s wings!"), ) to_chat(user, span_warning("You flap your wings, sending [M] flying!")) - playsound(user.loc, 'sound/weapons/punch4.ogg', 60, 1, -1) + playsound(user.loc, 'sound/weapons/punch4.ogg', 60, TRUE, -1) M.adjustBruteLoss(10) - M.Knockdown(40) + M.Knockdown(4 SECONDS) user.do_attack_animation(M, ATTACK_EFFECT_SMASH) var/send_dir = get_dir(user, M) var/turf/turf_thrown_at = get_ranged_target_turf(M, send_dir, 5) @@ -250,7 +267,7 @@ button_icon_state = "power_feast" background_icon_state_on = "wolf_power_on" background_icon_state_off = "wolf_power_off" - power_explanation = "Feast:\n\ + power_explanation = "Feast:\n\ Feasting on a dead person will give you a satiation point and gib them.\n\ Satiation points are essential for overcoming frenzy, after gathering 3 you'll turn back to normal.\n\ Feasting on someone while they are alive will bite them and make them bleed.\n\ @@ -265,6 +282,20 @@ power_activates_immediately = TRUE prefire_message = "WHOM SHALL BE DEVOURED." +/datum/action/bloodsucker/targeted/feast/CheckValidTarget(atom/target_atom) + . = ..() + if(!.) + return FALSE + return isliving(target_atom) + +/datum/action/bloodsucker/targeted/feast/CheckCanTarget(atom/target_atom) + . = ..() + if(!.) + return FALSE + // Target Type: Living + if(isliving(target_atom)) + return TRUE + /datum/action/bloodsucker/targeted/feast/FireTargetedPower(atom/target_atom) if(isturf(target_atom)) return @@ -285,11 +316,11 @@ /datum/action/bloodsucker/gangrel/wolfortitude name = "Wolftitude" - desc = "Withstand egregious physical wounds and walk away from attacks that would stun, pierce, and dismember lesser beings." + desc = "WITHSTAND THEIR ATTACKS. DESTROY. THEM. ALL!" button_icon_state = "power_wort" background_icon_state_on = "wolf_power_on" background_icon_state_off = "wolf_power_off" - power_explanation = "Fortitude:\n\ + power_explanation = "Fortitude:\n\ Activating Wolftitude will provide more attack damage, and more overall health.\n\ It will give you a minor health buff while it stands, but slow you down severely.\n\ It has a decent cooldown time to allow yourself to turn it off and run away for a while.\n\ @@ -327,7 +358,7 @@ button_icon_state = "power_pounce" background_icon_state_on = "wolf_power_on" background_icon_state_off = "wolf_power_off" - power_explanation = "Pounce:\n\ + power_explanation = "Pounce:\n\ Click any player to instantly dash at them, knocking them down and paralyzing them for a short while.\n\ Additionally if they are dead you'll consume their corpse to gain satiation and get closer to leaving frenzy.\n\ Created from your Predatory Lunge ability." @@ -397,7 +428,7 @@ button_icon_state = "power_howl" background_icon_state_on = "wolf_power_on" background_icon_state_off = "wolf_power_off" - power_explanation = "Howl:\n\ + power_explanation = "Howl:\n\ Activating Howl will start up a 2 and a half second charge up.\n\ After the charge up you'll knockdown anyone adjacent to you.\n\ Additionally, you'll confuse and deafen anyone in a 3 tile range.\n\ @@ -423,11 +454,11 @@ continue if(iscarbon(target)) var/mob/living/carbon/M = target - M.confused += 15 + M.adjust_confusion(15 SECONDS) M.adjustEarDamage(0, 50) if(target.Adjacent(A)) M.Knockdown(4 SECONDS) - M.Paralyze(0.1) + M.Paralyze(0.1 SECONDS) DeactivatePower() /datum/action/bloodsucker/gangrel/rabidism @@ -436,7 +467,7 @@ button_icon_state = "power_rabid" background_icon_state_on = "wolf_power_on" background_icon_state_off = "wolf_power_off" - power_explanation = "Rabidism:\n\ + power_explanation = "Rabidism:\n\ Rabidism will deal reduced damage to everyone in range including you.\n\ During Rabidism's ten second rage you'll deal alot more damage to structures.\n\ Be aware of it's long cooldown time.\n\ @@ -479,7 +510,7 @@ icon_icon = 'icons/mob/actions/actions_gangrel_bloodsucker.dmi' background_icon_state_on = "gangrel_power_on" background_icon_state_off = "gangrel_power_off" - power_explanation = "Tear:\n\ + power_explanation = "Tear:\n\ Tear will make your first attack start up a bleeding process.\n\ Bleeding process will only work if the target stands still.\n\ When it's done it will damage the target severely." @@ -505,6 +536,10 @@ /datum/action/bloodsucker/targeted/tear/proc/Mawl(mob/living/target) var/mob/living/carbon/user = owner + + if(!do_mob(user, target, 1 SECONDS)) + return + var/datum/status_effect/saw_bleed/B = target.has_status_effect(STATUS_EFFECT_SAWBLEED) while(!target.stat) if(!target.Adjacent(user) || !do_mob(user, target, 0.8 SECONDS)) diff --git a/code/modules/antagonists/bloodsuckers/powers/gohome.dm b/code/modules/antagonists/bloodsuckers/powers/gohome.dm index 8cf6fbc165a4..c9d954ca8418 100644 --- a/code/modules/antagonists/bloodsuckers/powers/gohome.dm +++ b/code/modules/antagonists/bloodsuckers/powers/gohome.dm @@ -9,13 +9,13 @@ button_icon_state = "power_gohome" background_icon_state_on = "vamp_power_off_oneshot" background_icon_state_off = "vamp_power_off_oneshot" - power_explanation = "Vanishing Act: \n\ + power_explanation = "Vanishing Act: \n\ Activating Vanishing Act will, after a short delay, teleport the user to their Claimed Coffin. \n\ The power will cancel out if the Claimed Coffin is somehow destroyed. \n\ Immediately after activating, lights around the user will begin to flicker. \n\ Once the user teleports to their coffin, in their place will be a Rat or Bat." power_flags = BP_AM_TOGGLE|BP_AM_SINGLEUSE|BP_AM_STATIC_COOLDOWN - check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED|BP_CANT_USE_WHILE_INCAPACITATED + check_flags = BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_STAKED // You only get this once you've claimed a lair and Sol is near. purchase_flags = NONE constant_bloodcost = 2 @@ -23,7 +23,8 @@ cooldown = 100 SECONDS ///What stage of the teleportation are we in var/teleporting_stage = GOHOME_START - var/list/spawning_mobs = list( + ///The types of mobs that will drop post-teleportation. + var/static/list/spawning_mobs = list( /mob/living/simple_animal/mouse = 3, /mob/living/simple_animal/hostile/retaliate/bat = 1, ) @@ -34,13 +35,13 @@ return FALSE /// Have No Lair (NOTE: You only got this power if you had a lair, so this means it's destroyed) if(!istype(bloodsuckerdatum_power) || !bloodsuckerdatum_power.coffin) - to_chat(owner, span_warning("Your coffin has been destroyed!")) + owner.balloon_alert(owner, "coffin was destroyed!") return FALSE return TRUE /datum/action/bloodsucker/gohome/ActivatePower() . = ..() - to_chat(owner, span_notice("You focus on separating your consciousness from your physical form...")) + owner.balloon_alert(owner, "preparing to teleport...") /datum/action/bloodsucker/gohome/UsePower(mob/living/user) . = ..() diff --git a/code/modules/antagonists/bloodsuckers/powers/masquerade.dm b/code/modules/antagonists/bloodsuckers/powers/masquerade.dm index a564836bdfbd..4a01120aad49 100644 --- a/code/modules/antagonists/bloodsuckers/powers/masquerade.dm +++ b/code/modules/antagonists/bloodsuckers/powers/masquerade.dm @@ -14,7 +14,7 @@ name = "Masquerade" desc = "Feign the vital signs of a mortal, and escape both casual and medical notice as the monster you truly are." button_icon_state = "power_human" - power_explanation = "Masquerade:\n\ + power_explanation = "Masquerade:\n\ Activating Masquerade will forge your identity to be practically identical to that of a human;\n\ - You lose nearly all Bloodsucker benefits, including healing, sleep, radiation, crit, virus and cold immunity.\n\ - Your eyes turn to that of a regular human as your heart begins to beat.\n\ @@ -23,7 +23,7 @@ At the end of a Masquerade, you will re-gain your Vampiric abilities, as well as lose any Disease & Gene you might have." power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN check_flags = BP_CANT_USE_IN_FRENZY|BP_AM_COSTLESS_UNCONSCIOUS - purchase_flags = BLOODSUCKER_CAN_BUY + purchase_flags = BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER bloodcost = 10 cooldown = 5 SECONDS constant_bloodcost = 0.1 @@ -36,27 +36,28 @@ to_chat(user, span_warning("Your vampiric healing is halted while imitating life.")) // Remove Clan-specific stuff var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - switch(bloodsuckerdatum.my_clan) - if(CLAN_TZIMISCE) - set_antag_hud(user, "bloodsucker") - if(CLAN_GANGREL) - if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans - var/obj/item/clothing/neck/neckdrip = user.get_item_by_slot(SLOT_NECK) - if(istype(neckdrip, /obj/item/clothing/neck/wolfcollar)) - theqdeld += neckdrip - if(bloodsuckerdatum.clanprogress >= 2) - var/obj/item/earsdrip = user.get_item_by_slot(SLOT_EARS) - if(istype(earsdrip, /obj/item/radio/headset/wolfears)) - theqdeld += earsdrip - if(bloodsuckerdatum.clanprogress >= 3) - var/obj/item/clothing/gloves/glovesdrip = user.get_item_by_slot(SLOT_GLOVES) - if(istype(glovesdrip, /obj/item/clothing/gloves/wolfclaws)) - theqdeld += glovesdrip - if(bloodsuckerdatum.clanprogress >= 4) - var/obj/item/clothing/shoes/shoesdrip = user.get_item_by_slot(SLOT_SHOES) - if(istype(shoesdrip , /obj/item/clothing/shoes/wolflegs)) - theqdeld += shoesdrip - QDEL_LIST(theqdeld) + if(bloodsuckerdatum.my_clan) + switch(bloodsuckerdatum.my_clan.get_clan()) + if(CLAN_TZIMISCE) + bloodsuckerdatum.antag_hud_name = "bloodsucker" + if(CLAN_GANGREL) + if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans + var/obj/item/clothing/neck/neckdrip = user.get_item_by_slot(SLOT_NECK) + if(istype(neckdrip, /obj/item/clothing/neck/wolfcollar)) + theqdeld += neckdrip + if(bloodsuckerdatum.clanprogress >= 2) + var/obj/item/earsdrip = user.get_item_by_slot(SLOT_EARS) + if(istype(earsdrip, /obj/item/radio/headset/wolfears)) + theqdeld += earsdrip + if(bloodsuckerdatum.clanprogress >= 3) + var/obj/item/clothing/gloves/glovesdrip = user.get_item_by_slot(SLOT_GLOVES) + if(istype(glovesdrip, /obj/item/clothing/gloves/wolfclaws)) + theqdeld += glovesdrip + if(bloodsuckerdatum.clanprogress >= 4) + var/obj/item/clothing/shoes/shoesdrip = user.get_item_by_slot(SLOT_SHOES) + if(istype(shoesdrip , /obj/item/clothing/shoes/wolflegs)) + theqdeld += shoesdrip + QDEL_LIST(theqdeld) // Remove Bloodsucker traits REMOVE_TRAIT(user, TRAIT_NOHARDCRIT, BLOODSUCKER_TRAIT) REMOVE_TRAIT(user, TRAIT_NOSOFTCRIT, BLOODSUCKER_TRAIT) @@ -76,7 +77,7 @@ eyes.flash_protect = initial(eyes.flash_protect) var/obj/item/organ/heart/vampheart/vampheart = user.getorganslot(ORGAN_SLOT_HEART) if(istype(vampheart)) - vampheart.FakeStart() + vampheart.fake_start_heart() user.apply_status_effect(STATUS_EFFECT_MASQUERADE) /datum/action/bloodsucker/masquerade/DeactivatePower() @@ -110,26 +111,27 @@ disease.cure() // Adds Clan-specific stuff var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - switch(bloodsuckerdatum.my_clan) - if(CLAN_TZIMISCE) - set_antag_hud(user, "tzimisce") - if(CLAN_GANGREL) - if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans - var/obj/item/clothing/neck/previousdrip = user.get_item_by_slot(SLOT_NECK) - user.dropItemToGround(previousdrip) - user.equip_to_slot_or_del(new /obj/item/clothing/neck/wolfcollar(user), SLOT_NECK) - if(bloodsuckerdatum.clanprogress >= 2) - var/obj/item/clothing/ears/previousdrip = user.get_item_by_slot(SLOT_EARS) - user.dropItemToGround(previousdrip) - user.equip_to_slot_or_del(new /obj/item/radio/headset/wolfears(user), SLOT_EARS) - if(bloodsuckerdatum.clanprogress >= 3) - var/obj/item/clothing/gloves/previousdrip = user.get_item_by_slot(SLOT_GLOVES) - user.dropItemToGround(previousdrip) - user.equip_to_slot_or_del(new /obj/item/clothing/gloves/wolfclaws(user), SLOT_GLOVES) - if(bloodsuckerdatum.clanprogress >= 4) - var/obj/item/clothing/shoes/previousdrip = user.get_item_by_slot(SLOT_SHOES) - user.dropItemToGround(previousdrip) - user.equip_to_slot_or_del(new /obj/item/clothing/shoes/wolflegs(user), SLOT_SHOES) + if(bloodsuckerdatum.my_clan) + switch(bloodsuckerdatum.my_clan.get_clan()) + if(CLAN_TZIMISCE) + bloodsuckerdatum.antag_hud_name = "tzimisce" + if(CLAN_GANGREL) + if(bloodsuckerdatum.clanprogress >= 1) // change this if we get more stuff to include other clans + var/obj/item/clothing/neck/previousdrip = user.get_item_by_slot(SLOT_NECK) + user.dropItemToGround(previousdrip) + user.equip_to_slot_or_del(new /obj/item/clothing/neck/wolfcollar(user), SLOT_NECK) + if(bloodsuckerdatum.clanprogress >= 2) + var/obj/item/clothing/ears/previousdrip = user.get_item_by_slot(SLOT_EARS) + user.dropItemToGround(previousdrip) + user.equip_to_slot_or_del(new /obj/item/radio/headset/wolfears(user), SLOT_EARS) + if(bloodsuckerdatum.clanprogress >= 3) + var/obj/item/clothing/gloves/previousdrip = user.get_item_by_slot(SLOT_GLOVES) + user.dropItemToGround(previousdrip) + user.equip_to_slot_or_del(new /obj/item/clothing/gloves/wolfclaws(user), SLOT_GLOVES) + if(bloodsuckerdatum.clanprogress >= 4) + var/obj/item/clothing/shoes/previousdrip = user.get_item_by_slot(SLOT_SHOES) + user.dropItemToGround(previousdrip) + user.equip_to_slot_or_del(new /obj/item/clothing/shoes/wolflegs(user), SLOT_SHOES) to_chat(user, span_notice("Your heart beats one final time, while your skin dries out and your icy pallor returns.")) /** diff --git a/code/modules/antagonists/bloodsuckers/powers/olfaction.dm b/code/modules/antagonists/bloodsuckers/powers/olfaction.dm index e498e95bd2c3..50f696358b08 100644 --- a/code/modules/antagonists/bloodsuckers/powers/olfaction.dm +++ b/code/modules/antagonists/bloodsuckers/powers/olfaction.dm @@ -5,7 +5,7 @@ name = "Sanguine Olfaction" desc = "Smells blood." button_icon_state = "power_olfac" - power_explanation = "Olfaction:\n\ + power_explanation = "Olfaction:\n\ Here's the sniffer.\n\ You shouldn't see this text, please scream and cry in the discord chat or ooc about it after the round if you do.\n\ Or make a bug report." @@ -15,7 +15,7 @@ /datum/action/bloodsucker/olfaction/acquire_scent name = "Sanguine Olfaction" desc = "Acquire a scent from the environment immediately around you to track." - power_explanation = "Sanguine Olfaction:\n\ + power_explanation = "Sanguine Olfaction:\n\ Activating this power will search around you in a 1-tile radius for bloodied objects and floors.\n\ If these objects are covered in blood from another humanoid being, you will see the option to track one of those scents.\n\ Selecting one of those scents will grant you a new ability to follow that scent. This will create a visible trail to follow to the owner of that blood.\n\ @@ -133,7 +133,7 @@ sensitive = TRUE tracking_flags = TRACKING_SCENT - power_explanation = "Transcendent Olfaction:\n\ + power_explanation = "Transcendent Olfaction:\n\ Activating this power will search all objects and items in a 1-tile radius around you for scents.\n\ If these objects or items have been used by humanoid beings and still have their scent, you will see the option to track one of those scents.\n\ Selecting one of those scents will grant you a new ability to follow that scent. This will create a visible trail to follow to the owner of that scent.\n\ @@ -147,7 +147,7 @@ name = "Follow the Scent" desc = "Begin following the scent of your target." button_icon_state = "power_olfac" - power_explanation = "Follow the Scent:\n\ + power_explanation = "Follow the Scent:\n\ Activating this power will create a trail you can follow to the target scent you selected with Olfaction.\n\ During this time, the target you're tracking will also leave a trail behind them as they move.\n\ WARNING: The path created for you to follow may bring you to doors you can't open and areas you don't have access to. If this happens, try again later or from a new area. \n\ diff --git a/code/modules/antagonists/bloodsuckers/powers/recuperate.dm b/code/modules/antagonists/bloodsuckers/powers/recuperate.dm index 51a7a0bc0d4b..cd06789f9152 100644 --- a/code/modules/antagonists/bloodsuckers/powers/recuperate.dm +++ b/code/modules/antagonists/bloodsuckers/powers/recuperate.dm @@ -3,7 +3,7 @@ name = "Sanguine Recuperation" desc = "Slowly heals you overtime using your master's blood, in exchange for some of your own blood and effort." button_icon_state = "power_recup" - power_explanation = "Recuperate:\n\ + power_explanation = "Recuperate:\n\ Activating this Power will begin to heal your wounds.\n\ You will heal Brute and Toxin damage, at the cost of Stamina damage, and blood from both you and your Master.\n\ If you aren't a bloodless race, you will additionally heal Burn damage.\n\ @@ -34,7 +34,7 @@ var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(user) vassaldatum.master.AddBloodVolume(-1) - user.Jitter(5) + user.adjust_jitter(5 SECONDS) user.adjustStaminaLoss(bloodcost * 1.1) user.adjustBruteLoss(-2.5) user.adjustToxLoss(-2, forced = TRUE) diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm index 6057734f8e33..849234f123a4 100644 --- a/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/_powers_targeted.dm @@ -4,7 +4,7 @@ var/obj/effect/proc_holder/bloodsucker/bs_proc_holder var/target_range = 99 - var/prefire_message = "" + var/prefire_message ///Most powers happen the moment you click. Some, like Mesmerize, require time and shouldn't cost you if they fail. var/power_activates_immediately = TRUE ///Is this power LOCKED due to being used? @@ -26,13 +26,13 @@ return FALSE ActivatePower() - UpdateButtonIcon() - // Create & Link Targeting Proc + UpdateButtons() + // Create & Link Targeting Proc, change when proc holder destroy PR gets ported var/mob/living/user = owner if(user.ranged_ability) user.ranged_ability.remove_ranged_ability() bs_proc_holder.add_ranged_ability(user) - if(prefire_message != "") + if(prefire_message) to_chat(owner, span_announce("[prefire_message]")) return TRUE @@ -41,7 +41,7 @@ UnregisterSignal(owner, COMSIG_LIVING_BIOLOGICAL_LIFE) active = FALSE DeactivateRangedAbility() - UpdateButtonIcon() + UpdateButtons() // ..() // we don't want to pay cost here /// Only Turned off when CLICK is disabled...aka, when you successfully clicked diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm index f8baa3b71dd1..53c58dbc1654 100644 --- a/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/brawn.dm @@ -2,7 +2,7 @@ name = "Brawn" desc = "Snap restraints, break lockers and doors, or deal terrible damage with your bare hands." button_icon_state = "power_strength" - power_explanation = "Brawn:\n\ + power_explanation = "Brawn:\n\ Click any person to bash into them, break restraints you have or knocking a grabber down. Only one of these can be done per use.\n\ Punching a Cyborg will heavily EMP them in addition to deal damage.\n\ At level 3, you get the ability to break closets open, additionally can both break restraints AND knock a grabber down in the same use.\n\ @@ -49,7 +49,7 @@ span_warning("[closet] tears apart as you bash it open from within!"), ) to_chat(user, span_warning("We bash [closet] wide open!")) - addtimer(CALLBACK(src, .proc/break_closet, user, closet), 1) + addtimer(CALLBACK(src, PROC_REF(break_closet), user, closet), 0.1 SECONDS) used = TRUE // Remove both Handcuffs & Legcuffs @@ -60,8 +60,8 @@ span_warning("[user] discards their restraints like it's nothing!"), span_warning("We break through our restraints!"), ) - user.clear_cuffs(cuffs, TRUE) - user.clear_cuffs(legcuffs, TRUE) + user.clear_cuffs(cuffs, TRUE, TRUE) + user.clear_cuffs(legcuffs, TRUE, TRUE) used = TRUE // Remove Straightjackets @@ -122,7 +122,7 @@ if(isliving(target_atom)) var/mob/living/target = target_atom var/mob/living/carbon/carbonuser = user - var/hitStrength = carbonuser.dna.species.punchdamagehigh * 1.25 + 2 + var/hitStrength = carbonuser.dna.species.punchdamagehigh * 1.25 + level_current // Knockdown! var/powerlevel = min(5, 1 + level_current) if(rand(5 + powerlevel) >= 5) @@ -179,22 +179,22 @@ . = ..() if(!.) // Disable range notice for Brawn. return FALSE - // Must outside Closet to target anyone! + // Must be outside Closet to target anyone! if(!isturf(owner.loc)) return FALSE // Target Type: Living if(isliving(target_atom)) return TRUE // Target Type: Door - else if(istype(target_atom, /obj/machinery/door)) + if(istype(target_atom, /obj/machinery/door)) if(level_current < 4) - to_chat(owner, span_warning("You need [4 - level_current] more levels to be able to break open the [target_atom]!")) + owner.balloon_alert(owner, "you need [4 - level_current] more levels!") return FALSE return TRUE // Target Type: Locker - else if(istype(target_atom, /obj/structure/closet)) + if(istype(target_atom, /obj/structure/closet)) if(level_current < 3) - to_chat(owner, span_warning("You need [3 - level_current] more levels to be able to break open the [target_atom]!")) + owner.balloon_alert(owner, "you need [3 - level_current] more levels!") return FALSE return TRUE return FALSE diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm index d17c4f75f2b1..3fabd7c02bef 100644 --- a/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/haste.dm @@ -7,7 +7,7 @@ name = "Immortal Haste" desc = "Dash somewhere with supernatural speed. Those nearby may be knocked away, stunned, or left empty-handed." button_icon_state = "power_speed" - power_explanation = "Immortal Haste:\n\ + power_explanation = "Immortal Haste:\n\ Click anywhere to immediately dash towards that location.\n\ The Power will not work if you are lying down, in no gravity, or are aggressively grabbed.\n\ Anyone in your way during your Haste will be knocked down and Payalyzed, moreso if they are using Flow.\n\ @@ -19,7 +19,8 @@ cooldown = 12 SECONDS target_range = 15 power_activates_immediately = TRUE - var/list/hit //current hit, set while power is in use as we can't pass the list as an extra calling argument in registersignal. + /// Current hit, set while power is in use as we can't pass the list as an extra calling argument in registersignal. + var/list/hit = list() /// If set, uses this speed in deciseconds instead of world.tick_lag var/speed_override @@ -93,9 +94,9 @@ for(var/datum/action/bloodsucker/power in all_targets.actions) if(power.active) power.DeactivatePower() - all_targets.Jitter(20) - all_targets.confused = max(8, all_targets.confused) - all_targets.stuttering = max(8, all_targets.stuttering) + all_targets.set_timed_status_effect(8 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) + all_targets.set_timed_status_effect(8 SECONDS, /datum/status_effect/confusion, only_if_higher = TRUE) + all_targets.adjust_timed_status_effect(8 SECONDS, /datum/status_effect/speech/stutter) all_targets.Knockdown(10 + level_current * 5) // Re-knock them down, the first one didn't work due to stunimmunity /datum/action/bloodsucker/targeted/haste/shadow @@ -123,5 +124,5 @@ if(iscarbon(target)) var/mob/living/carbon/M = target to_chat(M, span_danger("As a figure passes by, you feel your head spike up!")) - M.confused += 4 - M.adjustEarDamage(0, 15) \ No newline at end of file + M.adjust_confusion(4 SECONDS) + M.adjustEarDamage(0, 15) diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/lasombra.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/lasombra.dm new file mode 100644 index 000000000000..7df059b10696 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/lasombra.dm @@ -0,0 +1,103 @@ +/datum/action/bloodsucker/targeted/lasombra + name = "Shadow Control" + desc = "Submit shadows to your bidding, making darkness much scarier than before." + button_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi' + icon_icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi' + background_icon_state_on = "lasombra_power_on" + background_icon_state_off = "lasombra_power_off" + button_icon_state = "power_shadow" + power_explanation = "Shadow Control:\n\ + Shadow Control allows you to do different things based on level:\n\ + Level 1 - Click lights to instantly break them;\n\ + Level 2 - Click a person near darkness to shut off any lights they have.\n\ + Level 3 - Click doors to bolt them down for a while, scales with level;\n\ + Level 4 - Click tiles to make a temporary fence on them that blocks movement, scales with level." + power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN + check_flags = BP_CANT_USE_IN_FRENZY + purchase_flags = LASOMBRA_CAN_BUY + bloodcost = 20 + cooldown = 15 SECONDS + +/datum/action/bloodsucker/targeted/lasombra/CheckValidTarget(atom/target_atom) + . = ..() + if(!.) + return FALSE + return isliving(target_atom) || istype(target_atom, /obj/machinery/door) || istype(target_atom, /obj/machinery/light) || isopenturf(target_atom) + +/datum/action/bloodsucker/targeted/lasombra/CheckCanTarget(atom/target_atom) + . = ..() + if(isopenturf(target_atom)) + if(level_current < 4) + owner.balloon_alert(owner, "you need [4 - level_current] more levels!") + return FALSE + return TRUE + if(istype(target_atom, /obj/machinery/door)) + if(level_current < 3) + owner.balloon_alert(owner, "you need [3 - level_current] more levels!") + return FALSE + return TRUE + if(isliving(target_atom)) + if(level_current < 2) + owner.balloon_alert(owner, "you need 1 more level!") + return FALSE + return TRUE + if(istype(target_atom, /obj/machinery/light)) + return TRUE + return FALSE + +/datum/action/bloodsucker/targeted/lasombra/FireTargetedPower(atom/target_atom) + . = ..() + if(istype(target_atom, /obj/machinery/light)) + for(var/obj/machinery/light/light_bulbs in range(5, target_atom)) //break nearby lights + light_bulbs.on = TRUE + light_bulbs.break_light_tube() + + if(isliving(target_atom)) + var/mob/living/L = target_atom + if(isethereal(L)) + L.emp_act(EMP_LIGHT) + for(var/obj/item/O in L.GetAllContents()) + if(O.light_range && O.light_power) + disintegrate(O) + if(L.pulling && L.pulling.light_range && isitem(L.pulling)) + disintegrate(L.pulling) + + if(istype(target_atom, /obj/machinery/door)) + var/obj/structure/window/shadow/full/friend = new /obj/structure/window/shadow(get_turf(target_atom)) + QDEL_IN(friend, (2 + level_current) SECONDS) + target_atom.visible_message(span_warning("Shadows leap at the door, blocking it!")) + + if(isopenturf(target_atom)) + var/set_direction = get_dir(owner, target_atom) + var/obj/structure/window/shadow/friend = new /obj/structure/window/shadow(get_turf(target_atom)) + friend.dir = set_direction + QDEL_IN(friend, (3 + level_current) SECONDS) + target_atom.visible_message(span_warning("Shadows harden into a translucent wall, blocking passage!")) + +/datum/action/bloodsucker/targeted/lasombra/proc/disintegrate(obj/item/O) + if(istype(O, /obj/item/pda)) + var/obj/item/pda/PDA = O + PDA.set_light_on(FALSE) + PDA.update_icon() + O.visible_message(span_danger("The light in [PDA] shorts out!")) + else + O.visible_message(span_danger("[O] is disintegrated by [src]!")) + O.burn() + playsound(src, 'sound/items/welder.ogg', 50, 1) + +/obj/structure/window/shadow + name = "shadow barrier" + desc = "Basic shadow fence meant to stop idiots like you from passing." + icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi' + icon_state = "shadowfence" + can_be_unanchored = FALSE + +/obj/structure/window/shadow/can_be_rotated() + return FALSE + +/obj/structure/window/shadow/full + name = "shadow barrier" //original + desc = "Not a window." + icon = 'icons/mob/actions/actions_lasombra_bloodsucker.dmi' + icon_state = "shadowlock" + fulltile = TRUE diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm index 70ab7c29b1d6..eae7380ac764 100644 --- a/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/lunge.dm @@ -1,23 +1,30 @@ /datum/action/bloodsucker/targeted/lunge name = "Predatory Lunge" - desc = "Spring at a humanoid to grapple them without warning, or tear the dead's heart out. Attacks from concealment or the rear may even knock them down if strong enough." + desc = "Spring at your target to grapple them without warning, or tear the dead's heart out. Attacks from concealment or the rear may even knock them down if strong enough." button_icon_state = "power_lunge" - power_explanation = "Predatory Lunge:\n\ - Click any player to instantly dash at them if at level 2 or more, aggressively grabbing them.\n\ - If below level 2, you will have to charge your lunge for a while. During this time you'll have to stand still for lunge to work.\n\ - You cannot use the Power if you are aggressively grabbed.\n\ - If the target is wearing riot gear or is a Monster Hunter, you will merely passively grab them.\n\ - If grabbed from behind or from the darkness (Cloak of Darkness counts) with a power level at or above 4, will additionally knock the target down.\n\ - Higher levels will increase the knockdown dealt to enemies." - power_flags = BP_AM_TOGGLE + power_explanation = "Predatory Lunge:\n\ + Click any player to start spinning wildly and, after a short delay, dash at them.\n\ + When lunging at someone, you will grab them, immediately starting off at aggressive.\n\ + Riot gear and Monster Hunters are protected and will only be passively grabbed.\n\ + You cannot use the Power if you are already grabbing someone, or are being grabbed.\n\ + If you grab from behind, or from darkness (Cloak of Darkness works), you will knock the target down.\n\ + If used on a dead body, will tear their heart out.\n\ + Higher levels increase the knockdown dealt to enemies.\n\ + At level 4, you will no longer spin, but you will be limited to tackling from only 6 tiles away." + power_flags = NONE check_flags = BP_CANT_USE_IN_TORPOR|BP_CANT_USE_IN_FRENZY|BP_CANT_USE_WHILE_INCAPACITATED|BP_CANT_USE_WHILE_UNCONSCIOUS purchase_flags = BLOODSUCKER_CAN_BUY|VASSAL_CAN_BUY bloodcost = 10 cooldown = 10 SECONDS - target_range = 6 power_activates_immediately = FALSE var/casting = FALSE //yogs - special snowflake!!! +/datum/action/bloodsucker/targeted/lunge/upgrade_power() + . = ..() + //range is lowered when you get stronger. + if(level_current > 3) + target_range = 6 + /* * Level 1: Grapple level 2 * Level 2: Grapple 3 from Behind @@ -28,9 +35,12 @@ . = ..() if(!.) return FALSE - /// Are we being grabbed? + // Are we being grabbed? if(user.pulledby && user.pulledby.grab_state >= GRAB_AGGRESSIVE) - to_chat(user, span_warning("You're being grabbed!")) + owner.balloon_alert(user, "grabbed!") + return FALSE + if(user.pulling) + owner.balloon_alert(user, "grabbing someone!") return FALSE return TRUE @@ -57,45 +67,46 @@ return FALSE return TRUE +/datum/action/bloodsucker/targeted/lunge/CheckCanDeactivate() + return !(datum_flags & DF_ISPROCESSING) //only if you aren't lunging + /datum/action/bloodsucker/targeted/lunge/FireTargetedPower(atom/target_atom) . = ..() - var/mob/living/user = owner - var/mob/living/carbon/target = target_atom - var/turf/targeted_turf = get_turf(target) - owner.face_atom(target_atom) - if(level_current < 2 && !prepare_target_lunge(target_atom)) - PowerActivatedSuccessfully() + if(level_current > 3) + do_lunge(target_atom) return - user.Immobilize(10 SECONDS) - var/safety = get_dist(user, targeted_turf) * 3 + 1 - var/consequetive_failures = 0 - while(--safety && !target.Adjacent(user)) - if(!step_to(user, targeted_turf)) - consequetive_failures++ - if(consequetive_failures >= 3) // If 3 steps don't work, just stop. - break - lunge_end(target) - PowerActivatedSuccessfully() + prepare_target_lunge(target_atom) + return TRUE + +///Starts processing the power and prepares the lunge by spinning, calls lunge at the end of it. /datum/action/bloodsucker/targeted/lunge/proc/prepare_target_lunge(atom/target_atom) - casting = TRUE; - to_chat(owner, span_notice("You prepare to lunge!")) + casting = TRUE + START_PROCESSING(SSprocessing, src) + owner.balloon_alert(owner, "lunge started!") //animate them shake - var/base_x = owner.pixel_x - var/base_y = owner.pixel_y + var/base_x = owner.base_pixel_x + var/base_y = owner.base_pixel_y animate(owner, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS, loop = -1) for(var/i in 1 to 25) var/x_offset = base_x + rand(-3, 3) var/y_offset = base_y + rand(-3, 3) animate(pixel_x = x_offset, pixel_y = y_offset, time = 0.1 SECONDS) - if(!do_after(owner, 4 SECONDS, target_atom, extra_checks = CALLBACK(src, .proc/CheckCanTarget, target_atom))) - animate(owner, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS) - casting = FALSE + if(!do_after(owner, 4 SECONDS, stayStill = FALSE, extra_checks = CALLBACK(src, PROC_REF(CheckCanTarget), target_atom))) + end_target_lunge(base_x, base_y) + return FALSE + + end_target_lunge() + do_lunge(target_atom) + return TRUE + +///When preparing to lunge ends, this clears it up. +/datum/action/bloodsucker/targeted/lunge/proc/end_target_lunge(base_x, base_y) animate(owner, pixel_x = base_x, pixel_y = base_y, time = 0.1 SECONDS) casting = FALSE - return TRUE + STOP_PROCESSING(SSprocessing, src) /datum/action/bloodsucker/targeted/lunge/process() ..() @@ -103,34 +114,47 @@ return if(prob(75)) owner.spin(8, 1) - owner.visible_message( - span_warning("[owner] spins wildly!"), - span_notice("You spin!"), - ) + owner.balloon_alert_to_viewers("spins wildly!", "you spin!") return do_smoke(0, owner.loc, smoke_type = /obj/effect/particle_effect/fluid/smoke/transparent) -/datum/action/bloodsucker/targeted/lunge/proc/lunge_end(atom/hit_atom) +///Actually lunges the target, then calls lunge end. +/datum/action/bloodsucker/targeted/lunge/proc/do_lunge(atom/hit_atom) + var/turf/targeted_turf = get_turf(hit_atom) + + var/safety = get_dist(owner, targeted_turf) * 3 + 1 + var/consequetive_failures = 0 + while(--safety && !hit_atom.Adjacent(owner)) + if(!step_to(owner, targeted_turf)) + consequetive_failures++ + if(consequetive_failures >= 3) // If 3 steps don't work, just stop. + break + + lunge_end(hit_atom, targeted_turf) + +/datum/action/bloodsucker/targeted/lunge/proc/lunge_end(atom/hit_atom, turf/target_turf) + PowerActivatedSuccessfully() + // Am I next to my target to start giving the effects? + if(!owner.Adjacent(hit_atom)) + return + var/mob/living/user = owner var/mob/living/carbon/target = hit_atom - var/turf/target_turf = get_turf(target) -// Am I next to my target to start giving the effects? - if(!user.Adjacent(target)) - return + // Did I slip or get knocked unconscious? - if(!(user.mobility_flags & MOBILITY_STAND)) + if(!(user.mobility_flags & MOBILITY_STAND) || user.incapacitated()) var/send_dir = get_dir(user, target_turf) new /datum/forced_movement(user, get_ranged_target_turf(user, send_dir, 1), 1, FALSE) user.spin(10) return // Is my target a Monster hunter? var/mob/living/carbon/human/H = target - if(IS_MONSTERHUNTER(target) || H.is_shove_knockdown_blocked()) - to_chat(owner, span_danger("You get pushed away!")) - H.grabbedby(owner) + if(IS_MONSTERHUNTER(target) || H?.is_shove_knockdown_blocked()) + owner.balloon_alert(owner, "you get pushed away!") + target.grabbedby(owner) return - to_chat(owner, span_danger("You lunge at [target]!")) + owner.balloon_alert(owner, "you lunge at [target]!") if(target.stat == DEAD) var/obj/item/bodypart/chest = target.get_bodypart(BODY_ZONE_CHEST) var/datum/wound/slash/moderate/crit_wound = new @@ -138,19 +162,19 @@ owner.visible_message( span_warning("[owner] tears into [target]'s chest!"), span_warning("You tear into [target]'s chest!")) + var/obj/item/organ/heart/myheart_now = locate() in target.internal_organs if(myheart_now) myheart_now.Remove(target) user.put_in_hands(myheart_now) - return - //Grab now - target.grabbedby(owner) - target.grippedby(owner, instant = TRUE) - // Did we knock them down? - if(level_current >= 4 && (!is_A_facing_B(target, owner) || owner.alpha <= 40)) - target.Knockdown(10 + level_current * 5) - target.Paralyze(0.1) + else + target.grabbedby(owner) + target.grippedby(owner, instant = TRUE) + // Did we knock them down? + if(!is_A_facing_B(target, owner) || owner.alpha <= 40) + target.Knockdown(10 + level_current * 5) + target.Paralyze(0.1) /datum/action/bloodsucker/targeted/lunge/DeactivatePower() var/mob/living/O = owner diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm index 9b7422d43939..2eae27502674 100644 --- a/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/mesmerize.dm @@ -11,7 +11,7 @@ name = "Mesmerize" desc = "Dominate the mind of a mortal who can see your eyes." button_icon_state = "power_mez" - power_explanation = "Mesmerize:\n\ + power_explanation = "Mesmerize:\n\ Click any player to attempt to mesmerize them. This process takes 5 seconds and will be interrupted on movement.\n\ You cannot wear anything covering your face, and both parties must be facing eachother. Obviously, both parties need to not be blind. \n\ If your target is already mesmerized or a Monster Hunter, the Power will fail.\n\ diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm index a6b9dffc3182..16dfdfb3243d 100644 --- a/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/trespass.dm @@ -2,7 +2,7 @@ name = "Trespass" desc = "Become mist and advance two tiles in one direction. Useful for skipping past doors and barricades." button_icon_state = "power_tres" - power_explanation = "Trespass:\n\ + power_explanation = "Trespass:\n\ Click anywhere from 1-2 tiles away from you to teleport.\n\ This power goes through all obstacles except Walls.\n\ Higher levels decrease the sound played from using the Power, and increase the speed of the transition." diff --git a/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm b/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm index 98f7bb400f1e..fa87afa2b6aa 100644 --- a/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm +++ b/code/modules/antagonists/bloodsuckers/powers/targeted/tzimisce.dm @@ -5,21 +5,20 @@ /datum/action/bloodsucker/targeted/dice name = "Dice" desc = "Slice, cut, sever. The Flesh obeys as my fingers lay touch on it." + button_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi' + icon_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi' + background_icon_state = "tzimisce_power_off" + background_icon_state_on = "tzimisce_power_on" + background_icon_state_off = "tzimisce_power_off" button_icon_state = "power_dice" - power_explanation = "Dice:\n\ + power_explanation = "Dice:\n\ Use on a dead corpse to extract muscle from it to be able to feed it to a vassalrack.\n\ This won't take long and is your primary source of muscle acquiring, necessary for future endeavours.\n\ This ability takes well to leveling up, higher levels will increase your mastery over a person's flesh while using the ability for it's combat purpose.\n\ You shouldn't use this on your allies.." power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN bloodcost = 10 - button_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi' - icon_icon = 'icons/mob/actions/actions_tzimisce_bloodsucker.dmi' - background_icon_state = "tzimisce_power_off" - background_icon_state_on = "tzimisce_power_on" - background_icon_state_off = "tzimisce_power_off" purchase_flags = TZIMISCE_CAN_BUY - power_flags = BP_AM_TOGGLE|BP_AM_STATIC_COOLDOWN check_flags = BP_AM_COSTLESS_UNCONSCIOUS target_range = 1 cooldown = 10 SECONDS @@ -54,14 +53,14 @@ /obj/item/muscle/examine(mob/user) . = ..() var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan == CLAN_TZIMISCE) - . += span_cult("By looking at it you comprehend that it would yield [size] points for ritual usage.") + if(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_FLESH) + . += span_cult("It will yield [size] points for ritual usage.") /obj/item/muscle/attackby(obj/item/I, mob/user, params) // handles muscle crafting var/newsize = 0 var/quantity = 1 var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(!(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan == CLAN_TZIMISCE)) + if(!(IS_BLOODSUCKER(user) && bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_FLESH)) return if(istype(I, /obj/item/muscle)) var/obj/item/muscle/muscle2 = I @@ -179,4 +178,4 @@ #undef SIZE_SMALL #undef SIZE_MEDIUM -#undef SIZE_BIG \ No newline at end of file +#undef SIZE_BIG diff --git a/code/modules/antagonists/bloodsuckers/powers/vassal_fold.dm b/code/modules/antagonists/bloodsuckers/powers/vassal_fold.dm new file mode 100644 index 000000000000..363ab4a21f57 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/powers/vassal_fold.dm @@ -0,0 +1,91 @@ +/datum/action/bloodsucker/vassal_blood + name = "Help Vassal" + desc = "Bring an ex-Vassal back into the fold." + button_icon_state = "power_torpor" + power_explanation = "Help Vassal:\n\ + Use this power while you have an ex-Vassal grabbed to bring them back into the fold. \ + Right-Click will show the status of all Vassals." + power_flags = NONE + check_flags = NONE + purchase_flags = NONE + bloodcost = 10 + cooldown = 10 SECONDS + + ///Bloodbag we have in our hands. + var/obj/item/reagent_containers/blood/bloodbag + ///Weakref to a target we're bringing into the fold. + var/datum/weakref/target_ref + ///If we are analyze or helping + var/analyzing + +/datum/action/bloodsucker/vassal_blood/CheckCanUse(mob/living/carbon/user) + . = ..() + if(!.) + return FALSE + var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/ex_vassal) + if(revenge_vassal) + return FALSE + + if(tgui_alert(owner, "What would you like to do?", "Lost and Found", list("Analyze", "Help")) == "Analyze") + if(!revenge_vassal.ex_vassals.len) + owner.balloon_alert(owner, "no vassals!") + return FALSE + analyzing = TRUE + return TRUE + + if(owner.pulling && isliving(owner.pulling)) + var/mob/living/pulled_target = owner.pulling + var/datum/antagonist/ex_vassal/former_vassal = pulled_target.mind.has_antag_datum(/datum/antagonist/ex_vassal) + if(!former_vassal) + owner.balloon_alert(owner, "not a former vassal!") + return FALSE + target_ref = WEAKREF(owner.pulling) + return TRUE + + var/blood_bag = locate(/obj/item/reagent_containers/blood) in user.held_items + if(!blood_bag) + owner.balloon_alert(owner, "blood bag needed!") + return FALSE + if(istype(blood_bag, /obj/item/reagent_containers/blood/o_minus/bloodsucker)) + owner.balloon_alert(owner, "already bloodsucker blood!") + + bloodbag = blood_bag + return TRUE + +/datum/action/bloodsucker/vassal_blood/ActivatePower() + . = ..() + var/datum/antagonist/vassal/revenge/revenge_vassal = owner.mind.has_antag_datum(/datum/antagonist/vassal/revenge) + if(analyzing) + for(var/datum/antagonist/ex_vassal/former_vassals as anything in revenge_vassal.ex_vassals) + var/information = "[former_vassals.owner.current]" + information += " - has [round(COOLDOWN_TIMELEFT(former_vassals, blood_timer) / 600)] minutes left of Blood" + var/turf/open/floor/target_area = get_area(owner) + if(target_area) + information += " - currently at [target_area]." + if(former_vassals.owner.current.stat >= DEAD) + information += " - DEAD." + + to_chat(owner, "[information]") + + DeactivatePower() + return + + if(target_ref) + var/mob/living/target = target_ref.resolve() + var/datum/antagonist/ex_vassal/former_vassal = target.mind.has_antag_datum(/datum/antagonist/ex_vassal) + if(!former_vassal || former_vassal.revenge_vassal) + target_ref = null + return + if(do_after(owner, 5 SECONDS, target)) + former_vassal.return_to_fold(revenge_vassal) + target_ref = null + DeactivatePower() + return + + if(bloodbag) + var/mob/living/living_owner = owner + living_owner.blood_volume -= 150 + QDEL_NULL(bloodbag) + var/obj/item/reagent_containers/blood/o_minus/bloodsucker/new_bag = new(owner.loc) + owner.put_in_active_hand(new_bag) + DeactivatePower() diff --git a/code/modules/antagonists/bloodsuckers/powers/veil.dm b/code/modules/antagonists/bloodsuckers/powers/veil.dm index de1b1b0ab183..0197313051a4 100644 --- a/code/modules/antagonists/bloodsuckers/powers/veil.dm +++ b/code/modules/antagonists/bloodsuckers/powers/veil.dm @@ -2,13 +2,13 @@ name = "Veil of Many Faces" desc = "Disguise yourself in the illusion of another identity." button_icon_state = "power_veil" - power_explanation = "Veil of Many Faces:\n\ + power_explanation = "Veil of Many Faces:\n\ Activating Veil of Many Faces will shroud you in smoke and forge you a new identity.\n\ Your name and appearance will be completely randomized, and turning the ability off again will undo it all.\n\ Clothes, gear, and Security/Medical HUD status is kept the same while this power is active." power_flags = BP_AM_TOGGLE check_flags = BP_CANT_USE_IN_FRENZY - purchase_flags = VASSAL_CAN_BUY|BLOODSUCKER_CAN_BUY + purchase_flags = VASSAL_CAN_BUY|BLOODSUCKER_CAN_BUY|BLOODSUCKER_DEFAULT_POWER bloodcost = 15 constant_bloodcost = 0.1 cooldown = 10 SECONDS diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm index af70eb1406cb..466683d15ec6 100644 --- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm +++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_coffin.dm @@ -1,4 +1,4 @@ -/datum/antagonist/bloodsucker/proc/ClaimCoffin(obj/structure/closet/crate/claimed) +/datum/antagonist/bloodsucker/proc/claim_coffin(obj/structure/closet/crate/claimed, area/current_area) // ALREADY CLAIMED if(claimed.resident) if(claimed.resident == owner.current) @@ -6,6 +6,12 @@ else to_chat(owner, "This [claimed.name] has already been claimed by another.") return FALSE + if(!LAZYFIND(GLOB.the_station_areas, current_area)) + claimed.balloon_alert(owner.current, "not part of station!") + return + // This is my Lair + coffin = claimed + bloodsucker_lair_area = current_area if(!(/datum/crafting_recipe/vassalrack in owner?.learned_recipes)) owner.teach_crafting_recipe(/datum/crafting_recipe/vassalrack) owner.teach_crafting_recipe(/datum/crafting_recipe/candelabrum) @@ -13,22 +19,13 @@ owner.teach_crafting_recipe(/datum/crafting_recipe/meatcoffin) owner.teach_crafting_recipe(/datum/crafting_recipe/staketrap) owner.teach_crafting_recipe(/datum/crafting_recipe/woodenducky) - if(my_clan != CLAN_TZIMISCE) // better things to do + if(my_clan?.get_clan() != CLAN_TZIMISCE) // better things to do owner.teach_crafting_recipe(/datum/crafting_recipe/bloodaltar) to_chat(owner, span_danger("You learned new recipes - You can view them in the Structure and Weaponry section of the crafting menu!")) - // This is my Lair - coffin = claimed - lair = get_area(claimed) - to_chat(owner, span_userdanger("You have claimed the [claimed] as your place of immortal rest! Your lair is now [lair].")) + to_chat(owner, span_userdanger("You have claimed the [claimed] as your place of immortal rest! Your lair is now [bloodsucker_lair_area].")) to_chat(owner, span_announce("Bloodsucker Tip: Find new lair recipes in the structure tab of the Crafting Menu, including the Persuasion Rack for converting crew into Vassals and the Blood Altar which lets you gain two tasks per night to Rank Up.")) return TRUE -/// From crate.dm -/obj/structure/closet/crate - var/mob/living/resident /// This lets bloodsuckers claim any "crate" as a Coffin. - var/pryLidTimer = 25 SECONDS - breakout_time = 20 SECONDS - /obj/structure/closet/crate/coffin/examine(mob/user) . = ..() if(user == resident) @@ -43,11 +40,11 @@ icon_state = "coffin" icon = 'icons/obj/vamp_obj.dmi' breakout_time = 30 SECONDS - pryLidTimer = 20 SECONDS + pry_lid_timer = 20 SECONDS resistance_flags = NONE material_drop = /obj/item/stack/sheet/metal material_drop_amount = 2 - armor = list("melee" = 50, "bullet" = 20, "laser" = 30, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + armor = list(MELEE = 50, BULLET = 20, LASER = 30, ENERGY = 0, BOMB = 50, BIO = 0, RAD = 0, FIRE = 70, ACID = 60) /obj/structure/closet/crate/coffin/securecoffin name = "secure coffin" @@ -57,11 +54,11 @@ open_sound = 'sound/effects/coffin_open.ogg' close_sound = 'sound/effects/coffin_close.ogg' breakout_time = 35 SECONDS - pryLidTimer = 35 SECONDS + pry_lid_timer = 35 SECONDS resistance_flags = FIRE_PROOF | LAVA_PROOF | ACID_PROOF material_drop = /obj/item/stack/sheet/metal material_drop_amount = 2 - armor = list("melee" = 35, "bullet" = 20, "laser" = 20, "energy" = 0, "bomb" = 100, "bio" = 0, "rad" = 100, "fire" = 100, "acid" = 100) + armor = list(MELEE = 35, BULLET = 20, LASER = 20, ENERGY = 0, BOMB = 100, BIO = 0, RAD = 100, FIRE = 100, ACID = 100) /obj/structure/closet/crate/coffin/meatcoffin name = "meat coffin" @@ -72,10 +69,10 @@ open_sound = 'sound/effects/footstep/slime1.ogg' close_sound = 'sound/effects/footstep/slime1.ogg' breakout_time = 25 SECONDS - pryLidTimer = 20 SECONDS + pry_lid_timer = 20 SECONDS material_drop = /obj/item/reagent_containers/food/snacks/meat/slab material_drop_amount = 3 - armor = list("melee" = 70, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + armor = list(MELEE = 70, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 70, BIO = 0, RAD = 0, FIRE = 70, ACID = 60) /obj/structure/closet/crate/coffin/metalcoffin name = "metal coffin" @@ -86,25 +83,31 @@ open_sound = 'sound/effects/pressureplate.ogg' close_sound = 'sound/effects/pressureplate.ogg' breakout_time = 25 SECONDS - pryLidTimer = 30 SECONDS + pry_lid_timer = 30 SECONDS material_drop = /obj/item/stack/sheet/metal - armor = list("melee" = 40, "bullet" = 15, "laser" = 50, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 50, "fire" = 70, "acid" = 60) - + armor = list(MELEE = 40, BULLET = 15, LASER = 50, ENERGY = 0, BOMB = 10, BIO = 0, RAD = 50, FIRE = 70, ACID = 60) + ////////////////////////////////////////////// /// NOTE: This can be any coffin that you are resting AND inside of. -/obj/structure/closet/crate/coffin/proc/ClaimCoffin(mob/living/claimant) - // Bloodsucker Claim +/obj/structure/closet/crate/coffin/proc/claim_coffin(mob/living/claimant, area/current_area) var/datum/antagonist/bloodsucker/bloodsuckerdatum = claimant.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum) - // Successfully claimed? - if(bloodsuckerdatum.ClaimCoffin(src)) - resident = claimant - anchored = TRUE - START_PROCESSING(SSprocessing, src) + // Successfully claimed? + if(bloodsuckerdatum?.claim_coffin(src, current_area)) + resident = claimant + anchored = TRUE + START_PROCESSING(SSprocessing, src) + +/obj/structure/closet/crate/coffin/examine(mob/user) + . = ..() + if(user == resident) + . += span_cult("This is your Claimed Coffin.") + . += span_cult("Rest in it while injured to enter Torpor. Entering it with unspent Ranks will allow you to spend one.") + . += span_cult("Alt-Click while inside the Coffin to Lock/Unlock.") + . += span_cult("Alt-Click while outside of your Coffin to Unclaim it, unwrenching it and all your other structures as a result.") /obj/structure/closet/crate/coffin/Destroy() - UnclaimCoffin() + unclaim_coffin() STOP_PROCESSING(SSprocessing, src) return ..() @@ -113,19 +116,11 @@ if(!.) return FALSE if(user in src) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(!bloodsuckerdatum) - return FALSE - if(bloodsuckerdatum.lair != get_area(bloodsuckerdatum.coffin)) - if(bloodsuckerdatum.coffin) - bloodsuckerdatum.coffin.UnclaimCoffin() - var/list/turf/area_turfs = get_area_turfs(bloodsuckerdatum.lair) + var/list/turf/area_turfs = get_area_turfs(get_area(src)) // Create Dirt etc. var/turf/T_Dirty = pick(area_turfs) if(T_Dirty && !T_Dirty.density) // Default: Dirt - // CHECK: Cobweb already there? - //if (!locate(var/obj/effect/decal/cleanable/cobweb) in T_Dirty) // REMOVED! Cleanables don't stack. // STEP ONE: COBWEBS // CHECK: Wall to North? var/turf/check_N = get_step(T_Dirty, NORTH) @@ -133,37 +128,14 @@ // CHECK: Wall to West? var/turf/check_W = get_step(T_Dirty, WEST) if(istype(check_W, /turf/closed/wall)) - new /obj/effect/decal/cleanable/cobweb (T_Dirty) + new /obj/effect/decal/cleanable/cobweb(T_Dirty) // CHECK: Wall to East? var/turf/check_E = get_step(T_Dirty, EAST) if(istype(check_E, /turf/closed/wall)) - new /obj/effect/decal/cleanable/cobweb/cobweb2 (T_Dirty) - // STEP TWO: DIRT - new /obj/effect/decal/cleanable/dirt (T_Dirty) - // Find Animals in Area -/* if(rand(0,2) == 0) - var/mobCount = 0 - var/mobMax = clamp(area_turfs.len / 25, 1, 4) - for(var/turf/lair_turfs in area_turfs) - if(!lair_turfs) - continue - var/mob/living/simple_animal/SA = locate() in lair_turfs - if(SA) - mobCount++ - if(mobCount >= mobMax) // Already at max - break - Spawn One - if(mobCount < mobMax) -// Seek Out Location - while(area_turfs.len > 0) - var/turf/lair_turfs = pick(area_turfs) // We use while&pick instead of a for/loop so it's random, rather than from the top of the list. - if(lair_turfs && !lair_turfs.density) - var/mob/living/simple_animal/selected_simplemob = /mob/living/simple_animal/mouse // pick(/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse, /mob/living/simple_animal/hostile/retaliate/bat) //prob(300) /mob/living/simple_animal/mouse, - new selected_simplemob(lair_turfs) - break - area_turfs -= lair_turfs*/ + new /obj/effect/decal/cleanable/cobweb/cobweb2(T_Dirty) + new /obj/effect/decal/cleanable/dirt(T_Dirty) -/obj/structure/closet/crate/proc/UnclaimCoffin(manual = FALSE) +/obj/structure/closet/crate/proc/unclaim_coffin(manual = FALSE) // Unanchor it (If it hasn't been broken, anyway) anchored = FALSE if(!resident || !resident.mind) @@ -172,7 +144,7 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = resident.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(bloodsuckerdatum && bloodsuckerdatum.coffin == src) bloodsuckerdatum.coffin = null - bloodsuckerdatum.lair = null + bloodsuckerdatum.bloodsucker_lair_area = null for(var/obj/structure/bloodsucker/bloodsucker_structure in get_area(src)) if(bloodsucker_structure.owner == resident) bloodsucker_structure.unbolt() @@ -205,29 +177,38 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(!bloodsuckerdatum) return FALSE + var/area/current_area = get_area(src) if(!bloodsuckerdatum.coffin && !resident) - switch(input("Do you wish to claim this as your coffin? [get_area(src)] will be your lair, and you will learn to craft new structures.","Claim Lair") in list("Yes", "No")) + switch(tgui_alert(user, "Do you wish to claim this as your coffin? [current_area] will be your lair.", "Claim Lair", list("Yes", "No"))) if("Yes") - ClaimCoffin(user) - LockMe(user) - bloodsuckerdatum.SpendRank() + claim_coffin(user, current_area) + if("No") + return + LockMe(user) + //Level up if possible. + if(!bloodsuckerdatum.my_clan) + to_chat(user, span_notice("You must enter a Clan to rank up.")) + return + if(bloodsuckerdatum.my_clan.rank_up_type == BLOODSUCKER_RANK_UP_NORMAL) + bloodsuckerdatum.SpendRank() /// You're in a Coffin, everything else is done, you're likely here to heal. Let's offer them the oppertunity to do so. - bloodsuckerdatum.Check_Begin_Torpor() + bloodsuckerdatum.check_begin_torpor() return TRUE /// You cannot weld or deconstruct an owned coffin. Only the owner can destroy their own coffin. /obj/structure/closet/crate/coffin/attackby(obj/item/item, mob/user, params) - if(resident) - if(user != resident) - if(istype(item, cutting_tool)) - to_chat(user, span_notice("This is a much more complex mechanical structure than you thought. You don't know where to begin cutting [src].")) - return - if(anchored && istype(item, /obj/item/wrench)) - to_chat(user, span_danger("The coffin won't come unanchored from the floor.[user == resident ? " You can Alt Click to unclaim and unwrench your Coffin." : ""]")) + if(!resident) + return ..() + if(user != resident) + if(istype(item, cutting_tool)) + to_chat(user, span_notice("This is a much more complex mechanical structure than you thought. You don't know where to begin cutting [src].")) return + if(anchored && (item.tool_behaviour == TOOL_WRENCH)) + to_chat(user, span_danger("The coffin won't come unanchored from the floor.[user == resident ? " You can Alt-Click to unclaim and unwrench your Coffin." : ""]")) + return - if(locked && istype(item, /obj/item/crowbar)) - var/pry_time = pryLidTimer * item.toolspeed // Pry speed must be affected by the speed of the tool. + if(locked && (item.tool_behaviour == TOOL_CROWBAR)) + var/pry_time = pry_lid_timer * item.toolspeed // Pry speed must be affected by the speed of the tool. user.visible_message( span_notice("[user] tries to pry the lid off of [src] with [item]."), span_notice("You begin prying the lid off of [src] with [item]. This should take about [DisplayTimeText(pry_time)].")) @@ -248,13 +229,15 @@ return if(user == resident && user.Adjacent(src)) - switch(input("Do you wish to unclaim your coffin?","Unclaim Lair") in list("Yes", "No")) + balloon_alert(user, "unclaim coffin?") + var/static/list/unclaim_options = list( + "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"), + "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no")) + var/unclaim_response = show_radial_menu(user, src, unclaim_options, radius = 36, require_near = TRUE) + switch(unclaim_response) if("Yes") - UnclaimCoffin(TRUE) - LockMe(user) - if("No") - return - return TRUE + unclaim_coffin(TRUE) + /obj/structure/closet/crate/proc/LockMe(mob/user, inLocked = TRUE) if(user == resident) if(!broken) diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm index 1a2401b8ba04..5a1d7950508e 100644 --- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm +++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_crypt.dm @@ -43,7 +43,7 @@ /// If a Bloodsucker tries to wrench it in place, yell at them. if(item.tool_behaviour == TOOL_WRENCH && !anchored && IS_BLOODSUCKER(user)) user.playsound_local(null, 'sound/machines/buzz-sigh.ogg', 40, FALSE, pressure_affected = FALSE) - to_chat(user, span_announce("* Bloodsucker Tip: Examine the Persuasion Rack to understand how it functions!")) + to_chat(user, span_announce("* Bloodsucker Tip: Examine Bloodsucker structures to understand how they function!")) return . = ..() @@ -52,15 +52,22 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) /// Claiming the Rack instead of using it? if(istype(bloodsuckerdatum) && !owner) - if(!bloodsuckerdatum.lair) + if(!bloodsuckerdatum.bloodsucker_lair_area) to_chat(user, span_danger("You don't have a lair. Claim a coffin to make that location your lair.")) return FALSE - if(bloodsuckerdatum.lair != get_area(src)) - to_chat(user, span_danger("You may only activate this structure in your lair: [bloodsuckerdatum.lair].")) + if(bloodsuckerdatum.bloodsucker_lair_area != get_area(src)) + to_chat(user, span_danger("You may only activate this structure in your lair: [bloodsuckerdatum.bloodsucker_lair_area].")) return FALSE - /// Menu for securing your Persuasion rack in place. - switch(input("Do you wish to secure [src] here?") in list("Yes", "No")) + /// Radial menu for securing your Persuasion rack in place. + to_chat(user, span_notice("Do you wish to secure [src] here?")) + var/static/list/secure_options = list( + "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"), + "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no")) + var/secure_response = show_radial_menu(user, src, secure_options, radius = 36, require_near = TRUE) + if(!secure_response) + return FALSE + switch(secure_response) if("Yes") user.playsound_local(null, 'sound/items/ratchet.ogg', 70, FALSE, pressure_affected = FALSE) bolt(user) @@ -71,7 +78,13 @@ /obj/structure/bloodsucker/AltClick(mob/user) . = ..() if(user == owner && user.Adjacent(src)) - switch(input("Unbolt [src]?") in list("Yes", "No")) + balloon_alert(user, "unbolt [src]?") + var/static/list/unclaim_options = list( + "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"), + "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no") + ) + var/unclaim_response = show_radial_menu(user, src, unclaim_options, radius = 36, require_near = TRUE) + switch(unclaim_response) if("Yes") unbolt(user) @@ -87,10 +100,11 @@ climbable = TRUE pass_flags = LETPASSTHROW can_buckle = FALSE - var/task_completed = FALSE var/sacrifices = 0 var/sacrificialtask = FALSE var/organ_name = "" + var/suckamount = 0 + var/heartamount = 0 Ghost_desc = "This is a Blood Altar, where bloodsuckers can get two tasks per night to get more ranks." Vamp_desc = "This is a Blood Altar, which allows you to do two tasks per day to advance your ranks.\n\ Interact with the Altar by clicking on it after it's bolted to get a task.\n\ @@ -113,18 +127,29 @@ /obj/structure/bloodsucker/bloodaltar/attack_hand(mob/user, list/modifiers) . = ..() if(!.) - return FALSE - if(!IS_BLOODSUCKER(user)) + return + if(!IS_BLOODSUCKER(user)) //not bloodsucker to_chat(user, span_warning("You can't figure out how this works.")) - return FALSE + return var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum.altar_uses >= ALTAR_RANKS_PER_DAY) + if(bloodsuckerdatum.altar_uses >= ALTAR_RANKS_PER_DAY) //used the altar already to_chat(user, span_notice("You have done all tasks for the night, come back tomorrow for more.")) return + if(!check_completion(user) && bloodsuckerdatum.current_task) //not done but has a task? put them on their way + to_chat(user, span_warning("You already have a rank up task!")) + return + var/want_rank = tgui_alert("Do you want to gain a task? This will cost 50 Blood.", "Task Manager", list("Yes", "No")) + if(want_rank != "Yes" || QDELETED(src)) + return + generate_task(user) //generate + +/obj/structure/bloodsucker/bloodaltar/proc/generate_task(mob/living/user) var/task //just like amongus - var/suckamount = bloodsuckerdatum.task_blood_required - var/heartamount = bloodsuckerdatum.task_heart_required - if(suckamount == 0 && heartamount == 0) // Generate random amounts if we don't already have them set + var/mob/living/carbon/crewmate = user + var/datum/antagonist/bloodsucker/bloodsuckerdatum = crewmate.mind.has_antag_datum(/datum/antagonist/bloodsucker) + suckamount = bloodsuckerdatum.task_blood_required + heartamount = bloodsuckerdatum.task_heart_required + if(!suckamount && !heartamount) // Generate random amounts if we don't already have them set switch(bloodsuckerdatum.bloodsucker_level + bloodsuckerdatum.bloodsucker_level_unspent) if(0 to 3) suckamount = rand(100, 200) @@ -135,49 +160,42 @@ if(8 to INFINITY) suckamount = rand(500, 600) heartamount = rand(5,6) - if(bloodsuckerdatum.task_blood_drank >= suckamount || sacrifices >= heartamount) - task_completed = TRUE - if(task_completed) - bloodsuckerdatum.task_memory = null - bloodsuckerdatum.current_task = FALSE - bloodsuckerdatum.bloodsucker_level_unspent++ - bloodsuckerdatum.altar_uses++ - bloodsuckerdatum.task_blood_drank = 0 - bloodsuckerdatum.task_blood_required = 0 - bloodsuckerdatum.task_heart_required = 0 - sacrifices = 0 - to_chat(user, span_notice("You have sucessfully done a task and gained a rank!")) - task_completed = FALSE - sacrificialtask = FALSE - return - if(bloodsuckerdatum.current_task) - to_chat(user, span_warning("You already have a rank up task!")) + if(crewmate.blood_volume < 50) + to_chat(user, span_danger("You don't have enough blood to gain a task!")) return - if(!bloodsuckerdatum.current_task) - var/want_rank = alert("Do you want to gain a task? This will cost 50 Blood.", "Task Manager", "Yes", "No") - if(want_rank == "No" || QDELETED(src)) - return - var/mob/living/carbon/C = user - if(C.blood_volume < 50) - to_chat(user, span_danger("You don't have enough blood to gain a task!")) - return - C.blood_volume -= 50 - switch(rand(1, 3)) - if(1,2) - bloodsuckerdatum.task_blood_required = suckamount - task = "Suck [suckamount] units of pure blood." - if(3) - bloodsuckerdatum.task_heart_required = heartamount - task = "Sacrifice [heartamount] hearts by using them on the altar." - sacrificialtask = TRUE - bloodsuckerdatum.task_memory += "Current Rank Up Task: [task]
" - bloodsuckerdatum.current_task = TRUE - to_chat(user, span_boldnotice("You have gained a new Task! [task] Remember to collect it by using the blood altar!")) + crewmate.blood_volume -= 50 + switch(rand(1, 3)) + if(1,2) + bloodsuckerdatum.task_blood_required = suckamount + task = "Suck [suckamount] units of pure blood." + if(3) + bloodsuckerdatum.task_heart_required = heartamount + task = "Sacrifice [heartamount] hearts by using them on the altar." + sacrificialtask = TRUE + bloodsuckerdatum.task_memory += "Current Rank Up Task: [task]
" + bloodsuckerdatum.current_task = TRUE + to_chat(user, span_boldnotice("You have gained a new Task! [task] Remember to collect it by using the blood altar!")) + +/obj/structure/bloodsucker/bloodaltar/proc/check_completion(mob/living/user) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) + if(bloodsuckerdatum.task_blood_drank < suckamount || sacrifices < heartamount) + return FALSE + bloodsuckerdatum.task_memory = null + bloodsuckerdatum.current_task = FALSE + bloodsuckerdatum.bloodsucker_level_unspent++ + bloodsuckerdatum.altar_uses++ + bloodsuckerdatum.task_blood_drank = 0 + bloodsuckerdatum.task_blood_required = 0 + bloodsuckerdatum.task_heart_required = 0 + sacrifices = 0 + to_chat(user, span_notice("You have sucessfully done a task and gained a rank!")) + sacrificialtask = FALSE + return TRUE /obj/structure/bloodsucker/bloodaltar/examine(mob/user) . = ..() if(sacrificialtask) - if(sacrifices > 0) + if(sacrifices) . += span_boldnotice("It currently contains [sacrifices] [organ_name].") else return ..() @@ -191,7 +209,7 @@ to_chat(usr, span_warning("This type of organ doesn't have blood to sustain the altar!")) return ..() organ_name = H.name - to_chat(usr, span_notice("You feed the heart to the altar!")) + balloon_alert(user, "heart fed!") qdel(H) sacrifices++ return @@ -205,9 +223,10 @@ desc = "This seem to hold a bit of significance." icon_state = "restingplace" var/awoken = FALSE + can_buckle = TRUE Ghost_desc = "This is a Resting Place, where Lasombra bloodsucker can ascend their powers." Vamp_desc = "This is a Resting Place, which allows you to ascend your powers by gaining points using your ranks or blood.\n\ - Interact with the Altar by clicking on it after you have fed it a abyssal essence, acquirable through influences.\n\ + Interact with the Altar by clicking on it after you have fed it a abyssal essence, acquirable through influences or sacrifices done on it.\n\ Remember most ascended powers have benefits if used in the dark.\n\ It only seems to speak to elders of 4 or higher ranks." Vassal_desc = "This is the resting place, where your master does rituals to ascend their bloodsucking powers.\n\ @@ -215,28 +234,17 @@ Hunter_desc = "This is a blood altar, where monsters ascend their powers to shadowy levels.\n\ They normally need ranks or blood in exchange for power, forcing them to move out of their lair and weakening them." -/obj/item/abyssal_essence - name = "abyssal essence" - desc = "As you glare at the abyssal essence, you feel it glaring back." - icon = 'icons/obj/vamp_obj.dmi' - icon_state = "abyssal_essence" - item_state = "abyssal_essence" - throwforce = 0 - w_class = WEIGHT_CLASS_TINY - throw_speed = 3 - throw_range = 7 - pressure_resistance = 10 - /obj/structure/bloodsucker/bloodaltar/restingplace/deconstruct(disassembled = TRUE) . = ..() - new /obj/item/abyssal_essence(src.loc) + if(awoken) + new /obj/item/bloodsucker/abyssal_essence(loc) qdel(src) /obj/structure/bloodsucker/bloodaltar/restingplace/attackby(obj/item/H, mob/user, params) if(!IS_BLOODSUCKER(user) && !IS_VASSAL(user)) return ..() if(!awoken) - if(istype(H, /obj/item/abyssal_essence)) + if(istype(H, /obj/item/bloodsucker/abyssal_essence)) to_chat(usr, span_notice("As you touch [src] with the [H], you start sensing something different coming from [src]!")) qdel(H) awoken = TRUE @@ -251,11 +259,11 @@ if(INTERACTING_WITH(user, src)) return if(user.mind in src.siphoners) - to_chat(user, span_danger("You have already harvested this shard!")) + balloon_alert(user, "already harvested!") return - to_chat(user, span_danger("You start to harvest the energy of [src]...")) + balloon_alert(user, "harvesting...") if(do_after(user, 10 SECONDS, src)) - user.put_in_hands(new /obj/item/abyssal_essence) + user.put_in_hands(new /obj/item/bloodsucker/abyssal_essence) to_chat(user, span_notice("You finish harvesting the energy of [src]!")) src.siphoners |= user.mind @@ -263,8 +271,12 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(!IS_BLOODSUCKER(user)) return ..() - if(bloodsuckerdatum.my_clan == CLAN_LASOMBRA) - if(bloodsuckerdatum.clanpoints > 0) + if(!anchored) + return ..() + if(LAZYLEN(buckled_mobs)) + do_sacrifice(buckled_mobs, user) + if(bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_SHADOWS) + if(bloodsuckerdatum.clanpoints) var/list/upgradablepowers = list() var/list/unupgradablepowers = list(/datum/action/bloodsucker/feed, /datum/action/bloodsucker/masquerade, /datum/action/bloodsucker/veil) for(var/datum/action/bloodsucker/power as anything in bloodsuckerdatum.powers) @@ -272,31 +284,35 @@ upgradablepowers += power if(is_type_in_list(power, unupgradablepowers)) upgradablepowers -= power - var/choice = input(usr, "What Power do you wish to ascend? This resets the powers level.", "Darkness Manager") in upgradablepowers + var/choice = tgui_input_list(usr, "What Power do you wish to ascend? This resets the powers level.", "Darkness Manager", upgradablepowers) if(!choice) return if((locate(upgradablepowers[choice]) in bloodsuckerdatum.powers)) return - if(istype(choice, /datum/action/bloodsucker/targeted/brawn)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/brawn/shadow) - if(istype(choice, /datum/action/bloodsucker/targeted/haste)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/haste/shadow) - if(istype(choice, /datum/action/bloodsucker/fortitude)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/fortitude/shadow) // i hate this - if(istype(choice, /datum/action/bloodsucker/targeted/mesmerize)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/mesmerize/shadow) - if(istype(choice, /datum/action/bloodsucker/targeted/trespass)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/trespass/shadow) - if(istype(choice, /datum/action/bloodsucker/targeted/lunge)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/targeted/lunge/shadow) - if(istype(choice, /datum/action/bloodsucker/cloak/)) - bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/cloak/shadow) - bloodsuckerdatum.powers -= choice + var/datum/action/bloodsucker/granted = null + switch(choice) + if(/datum/action/bloodsucker/targeted/brawn) + granted = new /datum/action/bloodsucker/targeted/brawn/shadow + if(/datum/action/bloodsucker/targeted/haste) + granted = new /datum/action/bloodsucker/targeted/haste/shadow + if(/datum/action/bloodsucker/fortitude) + granted = new /datum/action/bloodsucker/fortitude/shadow // i hate this + if(/datum/action/bloodsucker/targeted/mesmerize) + granted = new /datum/action/bloodsucker/targeted/mesmerize/shadow + if(/datum/action/bloodsucker/targeted/trespass) + granted = new /datum/action/bloodsucker/targeted/trespass/shadow + if(/datum/action/bloodsucker/targeted/lunge) + granted = new /datum/action/bloodsucker/targeted/lunge/shadow + if(/datum/action/bloodsucker/cloak/) + granted = new /datum/action/bloodsucker/cloak/shadow + bloodsuckerdatum.BuyPower(granted) + var/datum/action/bloodsucker/now_level_it_up = LAZYFIND(bloodsuckerdatum.powers, granted) + now_level_it_up.level_current = rand(3, 4) qdel(choice) to_chat(user, span_boldnotice("You have ascended [choice]!")) bloodsuckerdatum.clanpoints-- return - if(bloodsuckerdatum.bloodsucker_level >= 4 ) + if(bloodsuckerdatum.bloodsucker_level >= 4) if(!awoken) //don't want this to affect power upgrading if you make another one to_chat(user, span_cult("Seems like you need a direct link to the abyss to awaken [src]. Maybe searching a spacial influence would yield something.")) return @@ -318,11 +334,11 @@ if(8 to INFINITY) to_chat(user, span_notice("You have evolved all abilities possible.")) return - var/want_clantask = alert("Do you want to spend a rank to gain a shadowpoint? This will cost [rankspent] ranks.", "Dark Manager", "Yes", "No") + var/want_clantask = tgui_alert("Do you want to spend a rank to gain a shadowpoint? This will cost [rankspent] ranks.", "Dark Manager", list("Yes", "No")) if(want_clantask == "No" || QDELETED(src)) return if(bloodsuckerdatum.bloodsucker_level_unspent < rankspent) - var/another_shot = alert("It seems like you don't have enough ranks, spend 550 blood instead?", "Dark Manager", "Yes", "No") + var/another_shot = tgui_alert("It seems like you don't have enough ranks, spend 550 blood instead?", "Dark Manager", list("Yes", "No")) if(another_shot == "No" || QDELETED(src)) return var/mob/living/carbon/C = user @@ -337,12 +353,199 @@ return return ..() -//////////////////////////////////////////////////// +/obj/structure/bloodsucker/bloodaltar/restingplace/proc/do_sacrifice(list/pig, mob/living/carbon/user) + var/mob/living/carbon/sacrifice = pick(pig) + if(!sacrifice.mind) + balloon_alert(user, "not worthy to sacrifice!") + return + if(sacrifice.stat == DEAD) + balloon_alert(user, "[sacrifice.p_theyre()] already dead...") + return + balloon_alert(user, "starting sacrifice...") + if(!do_after(user, 10 SECONDS, sacrifice)) + balloon_alert(user, "interrupted!") + return + playsound(get_turf(sacrifice), 'sound/weapons/slash.ogg', 50, TRUE, -1) + sacrifice.adjustBruteLoss(200) + balloon_alert(user, "sucess!") + new /obj/item/bloodsucker/abyssal_essence(get_turf(src)) + +#define METALLIMIT 50 + +/obj/structure/bloodsucker/moldingstone + name = "molding stone" + desc = "Not made of marble, but will have to do." + icon_state = "molding_stone" + anchored = FALSE + density = TRUE + can_buckle = TRUE + buckle_lying = 180 + var/metal = 0 + +/obj/structure/bloodsucker/moldingstone/examine(mob/user) + . = ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) + if(bloodsuckerdatum.my_clan?.control_type >= BLOODSUCKER_CONTROL_METAL) + if(metal) + . += span_boldnotice("It currently contains [metal] metal to use in sculpting.") + else + return ..() -/*/obj/structure/bloodsucker/bloodstatue +/obj/structure/bloodsucker/moldingstone/bolt() + . = ..() + anchored = TRUE + +/obj/structure/bloodsucker/moldingstone/unbolt() + . = ..() + anchored = FALSE + +/obj/structure/bloodsucker/moldingstone/update_icon() + cut_overlays() + switch(metal) + if(1 to 5) + add_overlay("metal") + if(6 to 20) + add_overlay("metal_2") + if(21 to 50) + add_overlay("metal_3") + +/obj/structure/bloodsucker/moldingstone/attackby(obj/item/I, mob/user, params) + if(!anchored) + return + var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) + if(!bloodsuckerdatum) + return ..() + if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_METAL) + return ..() + if(istype(I, /obj/item/stack/sheet/metal)) + if(metal >= METALLIMIT) + balloon_alert(user, "full!") + return + var/obj/item/stack/sheet/metal/M = I + if(metal + M.amount > METALLIMIT) + M.use(METALLIMIT - metal) + metal = METALLIMIT + else + metal = M.amount + qdel(M) + balloon_alert(user, "added [metal] metal") + if(istype(I, /obj/item/bloodsucker/chisel)) + start_sculpiting(user) + update_icon() + +/obj/structure/bloodsucker/moldingstone/proc/start_sculpiting(mob/living/artist) + if(metal < 10) + balloon_alert(artist, "not enough metal!") + return + var/list/possible_statues = list() + for(var/statues_available as anything in subtypesof(/obj/structure/bloodsucker/bloodstatue)) + possible_statues[initial(statues_available)] = statues_available + var/obj/structure/bloodsucker/bloodstatue/what_type = tgui_input_list(artist, "What kind of statue would you like to make?", "Artist Manual", possible_statues) + if(!do_after(artist, 10 SECONDS, src)) + artist.balloon_alert(artist, "ruined!") + metal -= rand(5, 10) + update_icon() + + return + artist.balloon_alert(artist, "done, a masterpiece!") + new what_type(get_turf(src)) + +/obj/structure/bloodsucker/moldingstone/CtrlClick(mob/user) + if(!anchored) + return ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) + if(!bloodsuckerdatum) + return ..() + if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_METAL) + return ..() + if(metal) + var/count = input("How much metal would you like to retrieve from [src]?","Fine Metal", metal) as null | num + if(count > METALLIMIT) + count = METALLIMIT + if(count > metal) + count = metal + metal -= count + new /obj/item/stack/sheet/metal(get_turf(user), count) + else + to_chat(user, span_warning("There's no metal to retrieve in [src.]")) + update_icon() +#undef METALLIMIT + +/obj/structure/bloodsucker/bloodstatue name = "bloody countenance" desc = "It looks upsettingly familiar..." -/obj/structure/bloodsucker/bloodportrait + icon_state = "statue" + +/obj/structure/bloodsucker/bloodstatue/ComponentInitialize() + . = ..() + AddComponent(/datum/component/art, 30) + +/obj/structure/bloodsucker/bloodstatue/bolt() + . = ..() + anchored = TRUE + START_PROCESSING(SSprocessing, src) + +/obj/structure/bloodsucker/bloodstatue/unbolt() + . = ..() + anchored = FALSE + STOP_PROCESSING(SSprocessing, src) + +/obj/structure/bloodsucker/bloodstatue/command + name = "captain bust" + desc = "It fills you with an eerie sense of patriotism." + icon_state = "statue_command" + +/obj/structure/bloodsucker/bloodstatue/command/attack_hand(mob/user, list/modifiers) //get rid of area requirement + var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) + if(istype(bloodsuckerdatum) && !owner) + if(!bloodsuckerdatum.bloodsucker_lair_area) + to_chat(user, span_danger("You don't have a lair. Claim a coffin to make that location your lair.")) + return FALSE + to_chat(user, span_notice("Do you wish to secure [src] here?")) + var/static/list/secure_options = list( + "Yes" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_yes"), + "No" = image(icon = 'icons/mob/radial.dmi', icon_state = "radial_no")) + var/secure_response = show_radial_menu(user, src, secure_options, radius = 36, require_near = TRUE) + if(!secure_response) + return FALSE + switch(secure_response) + if("Yes") + user.playsound_local(null, 'sound/items/ratchet.ogg', 70, FALSE, pressure_affected = FALSE) + bolt(user) + return FALSE + return FALSE + return TRUE + +/obj/structure/bloodsucker/bloodstatue/command/bolt() + . = ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner?.mind?.has_antag_datum(/datum/antagonist/bloodsucker) + var/area/current_area = get_area(src) + if(current_area == bloodsuckerdatum.bloodsucker_lair_area) + return + bloodsuckerdatum.bloodsucker_lair_area.contained_turfs += current_area.contained_turfs + +/obj/structure/bloodsucker/bloodstatue/command/unbolt() + . = ..() + var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner?.mind?.has_antag_datum(/datum/antagonist/bloodsucker) + var/area/current_area = get_area(src) + if(current_area == bloodsuckerdatum.bloodsucker_lair_area) + return + bloodsuckerdatum.bloodsucker_lair_area.turfs_to_uncontain += current_area.contained_turfs + +/obj/structure/bloodsucker/bloodstatue/greytide + name = "greytider bust" + desc = "Despite its simple attire it looks quite menancing." + icon_state = "statue_assist" + +/obj/structure/bloodsucker/bloodstatue/greytide/process() + for(var/mob/living/carbon/carbon_in_area in get_area(src)) + if(IS_VASSAL(carbon_in_area) || IS_BLOODSUCKER(carbon_in_area) || carbon_in_area.has_status_effect(STATUS_EFFECT_EXPOSED)) + continue + carbon_in_area.apply_status_effect(STATUS_EFFECT_EXPOSED) + +//////////////////////////////////////////////////// + +/*/obj/structure/bloodsucker/bloodportrait name = "oil portrait" desc = "A disturbingly familiar face stares back at you. Those reds don't seem to be painted in oil..." /obj/structure/bloodsucker/bloodbrazier @@ -436,18 +639,13 @@ Vamp_desc = "This is the Vassal rack, which allows you to thrall crewmembers into loyal minions in your service.\n\ Simply click and hold on a victim, and then drag their sprite on the vassal rack. Click on help intent on the vassal rack to unbuckle them.\n\ To convert into a Vassal, repeatedly click on the persuasion rack while NOT on help intent. The time required scales with the tool in your off hand. This costs Blood to do.\n\ - Once you have Vassals ready, you are able to select a Favorite Vassal;\n\ - Click the Rack as a Vassal is buckled onto it to turn them into your Favorite. This can only be done once, so choose carefully!\n\ - This process costs 150 Blood to do, and will make your Vassal unable to be deconverted, outside of you reaching Final Death." + Vassals can be turned into special ones by continuing to torture them once converted." Vassal_desc = "This is the vassal rack, which allows your master to thrall crewmembers into their minions.\n\ Aid your master in bringing their victims here and keeping them secure.\n\ You can secure victims to the vassal rack by click dragging the victim onto the rack while it is secured." Hunter_desc = "This is the vassal rack, which monsters use to brainwash crewmembers into their loyal slaves.\n\ They usually ensure that victims are handcuffed, to prevent them from running away.\n\ Their rituals take time, allowing us to disrupt it." - /// So we can't spam buckle people onto the rack - var/use_lock = FALSE - var/mob/buckled /// Resets on each new character to be added to the chair. Some effects should lower it... var/convert_progress = 3 /// Mindshielded and Antagonists willingly have to accept you as their Master. @@ -471,8 +669,8 @@ /obj/structure/bloodsucker/vassalrack/examine(mob/user) . = ..() var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum.my_clan == CLAN_TZIMISCE) - if(meat_amount > 0) + if(bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_FLESH) + if(meat_amount) . += span_boldnotice("It currently contains [meat_points] points to use in rituals.") . += span_boldnotice("You can add meat points to the rack by using muscle, acquired from Dicing corpses, on it.") else @@ -495,19 +693,15 @@ to_chat(user, span_announce("* Bloodsucker Tip: Examine the Persuasion Rack to understand how it functions!")) return // Default checks - if(!isliving(movable_atom) || !living_target.Adjacent(src) || living_target == user || !isliving(user) || use_lock || has_buckled_mobs() || user.incapacitated() || living_target.buckled) + if(!isliving(movable_atom) || !living_target.Adjacent(src) || living_target == user || !isliving(user) || has_buckled_mobs() || user.incapacitated() || living_target.buckled) return // Don't buckle Silicon to it please. if(issilicon(living_target)) to_chat(user, span_danger("You realize that Silicon cannot be vassalized, therefore it is useless to buckle them.")) return - // Good to go - Buckle them! - use_lock = TRUE if(do_mob(user, living_target, 5 SECONDS)) attach_victim(living_target, user) - use_lock = FALSE -/// Attempt Release (Owner vs Non Owner) /obj/structure/bloodsucker/vassalrack/proc/attach_victim(mob/living/target, mob/living/user) // Standard Buckle Check target.forceMove(get_turf(src)) @@ -518,7 +712,7 @@ span_boldnotice("You secure [target] tightly in place. They won't escape you now."), ) - playsound(src.loc, 'sound/effects/pop_expl.ogg', 25, 1) + playsound(loc, 'sound/effects/pop_expl.ogg', 25, 1) density = TRUE update_icon() @@ -527,80 +721,81 @@ disloyalty_confirm = FALSE disloyalty_offered = FALSE -/// Attempt Unbuckle +/// Attempt Release (Owner vs Non Owner) /obj/structure/bloodsucker/vassalrack/user_unbuckle_mob(mob/living/buckled_mob, mob/user) - if(!IS_BLOODSUCKER(user) || !IS_VASSAL(user)) - if(buckled_mob == user) - buckled_mob.visible_message( - span_danger("[user] tries to release themself from the rack!"), - span_danger("You attempt to release yourself from the rack!"), - span_hear("You hear a squishy wet noise."), - ) - else - buckled_mob.visible_message( - span_danger("[user] tries to pull [buckled_mob] from the rack!"), - span_danger("[user] tries to pull [buckled_mob] from the rack!"), - span_hear("You hear a squishy wet noise."), - ) - // Monster hunters are used to this sort of stuff, they know how they work, which includes breaking others out - var/breakout_timer = IS_MONSTERHUNTER(user) ? 20 SECONDS : 10 SECONDS - if(!do_mob(user, buckled_mob, breakout_timer)) + if(IS_BLOODSUCKER(user) || IS_VASSAL(user)) + return ..() + + if(buckled_mob == user) + user.visible_message( + span_danger("[user] tries to release themself from the rack!"), + span_danger("You attempt to release yourself from the rack!"), + span_hear("You hear a squishy wet noise.")) + if(!do_after(user, 20 SECONDS, user)) return - unbuckle_mob(buckled_mob) - . = ..() + else + buckled_mob.visible_message( + span_danger("[user] tries to pull [buckled_mob] rack!"), + span_danger("[user] tries to pull [buckled_mob] rack!"), + span_hear("You hear a squishy wet noise.")) + if(!do_after(user, 10 SECONDS, buckled_mob)) + return + + return ..() /obj/structure/bloodsucker/vassalrack/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) . = ..() if(!.) return FALSE - src.visible_message(span_danger("[buckled_mob][buckled_mob.stat == DEAD ? "'s corpse" : ""] slides off of the rack.")) + visible_message(span_danger("[buckled_mob][buckled_mob.stat == DEAD ? "'s corpse" : ""] slides off of the rack.")) density = FALSE - buckled_mob.Paralyze(3 SECONDS) + buckled_mob.Paralyze(2 SECONDS) update_icon() - use_lock = FALSE // Failsafe return TRUE /obj/structure/bloodsucker/vassalrack/attack_hand(mob/user, list/modifiers) . = ..() if(!.) return FALSE - var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) // Is there anyone on the rack & If so, are they being tortured? - if(use_lock || !has_buckled_mobs()) + if(!has_buckled_mobs()) return FALSE + var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) var/mob/living/carbon/buckled_carbons = pick(buckled_mobs) if(user.a_intent == INTENT_HELP) if(istype(bloodsuckerdatum)) unbuckle_mob(buckled_carbons) return FALSE - else - user_unbuckle_mob(buckled_carbons, user) - return + user_unbuckle_mob(buckled_carbons, user) + return + if(!bloodsuckerdatum.my_clan) + to_chat(user, span_warning("You can't vassalize people until you enter a Clan (Through your Antagonist UI button)")) + user.balloon_alert(user, "join a clan first!") + return /// If I'm not a Bloodsucker, try to unbuckle them. var/datum/antagonist/vassal/vassaldatum = IS_VASSAL(buckled_carbons) // Are they our Vassal, or Dead? if(buckled_carbons.stat == DEAD) - if(bloodsuckerdatum.my_clan != CLAN_TZIMISCE) - to_chat(user, span_warning("[buckled_carbons] is dead!")) + if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_FLESH) + balloon_alert(user, "[buckled_carbons.p_theyre()] dead!") return do_ritual(user, buckled_carbons) return - if(istype(vassaldatum) && vassaldatum.master == bloodsuckerdatum) - // Can we assign a Favorite Vassal? - if(istype(vassaldatum) && !bloodsuckerdatum.has_favorite_vassal) - offer_favorite_vassal(user, buckled_carbons) - else if(bloodsuckerdatum.my_clan == CLAN_TZIMISCE) - do_ritual(user, buckled_carbons) - use_lock = FALSE + if(vassaldatum && (vassaldatum in bloodsuckerdatum.vassals)) + SEND_SIGNAL(bloodsuckerdatum.my_clan, BLOODSUCKER_PRE_MAKE_FAVORITE, bloodsuckerdatum, vassaldatum) + return + else if(bloodsuckerdatum.my_clan?.control_type == BLOODSUCKER_CONTROL_FLESH) + do_ritual(user, buckled_carbons) return // Not our Vassal, but Alive & We're a Bloodsucker, good to torture! torture_victim(user, buckled_carbons) #define MEATLIMIT 3 + /obj/structure/bloodsucker/vassalrack/attackby(obj/item/I, mob/user, params) //Tzimisce stuff var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum.my_clan != CLAN_TZIMISCE) + if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_FLESH) return ..() // only gamers if(istype(I, /obj/item/muscle)) if(meat_amount >= MEATLIMIT) @@ -624,37 +819,37 @@ /obj/structure/bloodsucker/vassalrack/update_icon() cut_overlays() - if(bigmeat > 0) + if(bigmeat) add_overlay("bigmeat_[bigmeat]") - if(intermeat > 0) + if(intermeat) add_overlay("mediummeat_[intermeat]") add_overlay("smallmeat_[intermeat]") - if(mediummeat > 0) + if(mediummeat) add_overlay("mediummeat_[mediummeat + intermeat]") - if(smallmeat > 0) + if(smallmeat) add_overlay("smallmeat_[smallmeat + intermeat]") /obj/structure/bloodsucker/vassalrack/CtrlClick(mob/user) if(!anchored) return ..() var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum.my_clan != CLAN_TZIMISCE) + if(bloodsuckerdatum.my_clan?.control_type < BLOODSUCKER_CONTROL_FLESH) return ..() - if(meat_amount > 0) - if(smallmeat > 0) + if(meat_amount) + if(smallmeat) new /obj/item/muscle/small(user.drop_location()) smallmeat-- meat_points -= 1 - if(mediummeat > 0) + if(mediummeat) new /obj/item/muscle/medium(user.drop_location()) mediummeat-- meat_points -= 2 - if(intermeat > 0) + if(intermeat) new /obj/item/muscle/medium(user.drop_location()) new /obj/item/muscle/small(user.drop_location()) intermeat-- meat_points -= 3 - if(bigmeat > 0) + if(bigmeat) new /obj/item/muscle/big(user.drop_location()) bigmeat-- meat_points -= 4 @@ -670,67 +865,64 @@ */ /obj/structure/bloodsucker/vassalrack/proc/torture_victim(mob/living/user, mob/living/target) + if(INTERACTING_WITH(user, target)) + balloon_alert(user, "already interacting!") + return var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - /// Prep... - use_lock = TRUE - /// Conversion Process - if(convert_progress > 0) - to_chat(user, span_notice("You spill some blood and prepare to initiate [target] into your service.")) - bloodsuckerdatum.AddBloodVolume(-text2num(TORTURE_BLOOD_COST)) - if(!do_torture(user,target)) - to_chat(user, span_danger("The ritual has been interrupted!")) + if(IS_VASSAL(target)) + var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal) + if(!vassaldatum.master.broke_masquerade) + balloon_alert(user, "someone else's vassal!") + return + + var/disloyalty_requires = RequireDisloyalty(user, target) + if(disloyalty_requires == VASSALIZATION_BANNED) + balloon_alert(user, "can't be vassalized!") + return + + // Conversion Process + if(convert_progress) + balloon_alert(user, "spilling blood...") + bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST) + if(!do_torture(user, target)) + balloon_alert(user, "interrupted!") + return + bloodsuckerdatum.AddBloodVolume(-TORTURE_BLOOD_HALF_COST) + // Prevent them from unbuckling themselves as long as we're torturing. + target.Paralyze(1 SECONDS) + convert_progress-- + + // We're done? Let's see if they can be Vassal. + if(convert_progress) + balloon_alert(user, "needs more persuasion...") + return + + if(disloyalty_requires == VASSALIZATION_DISLOYAL) + balloon_alert(user, "has external loyalties! more persuasion required!") else - /// Prevent them from unbuckling themselves as long as we're torturing. - target.Paralyze(1 SECONDS) - convert_progress-- - /// We're done? Let's see if they can be Vassal. - if(convert_progress <= 0) - if(IS_VASSAL(target)) - var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal) - if(!vassaldatum.master.broke_masquerade) - to_chat(user, span_boldwarning("[target] is under the spell of another Bloodsucker!")) - return - if(RequireDisloyalty(user, target)) - to_chat(user, span_boldwarning("[target] has external loyalties! [target.p_they(TRUE)] will require more persuasion to break [target.p_them()] to your will!")) - else - to_chat(user, span_notice("[target] looks ready for the Dark Communion.")) - /// Otherwise, we're not done, we need to persuade them some more. - else - to_chat(user, span_notice("[target] could use [convert_progress == 1 ? "a little" : "some"] more persuasion.")) - use_lock = FALSE + balloon_alert(user, "ready for communion!") return - /// Check: Mindshield & Antag - if(!disloyalty_confirm && RequireDisloyalty(user, target)) - if(!do_disloyalty(user,target)) - to_chat(user, span_danger("The ritual has been interrupted!")) + + if(!disloyalty_confirm && (disloyalty_requires == VASSALIZATION_DISLOYAL)) + if(!do_disloyalty(user, target)) + return else if(!disloyalty_confirm) - to_chat(user, span_danger("[target] refuses to give into your persuasion. Perhaps a little more?")) + balloon_alert(user, "refused persuasion!") else - to_chat(user, span_notice("[target] looks ready for the Dark Communion.")) - use_lock = FALSE + balloon_alert(user, "ready for communion!") return - user.visible_message( - span_notice("[user] marks a bloody smear on [target]'s forehead and puts a wrist up to [target.p_their()] mouth!"), - span_notice("You paint a bloody marking across [target]'s forehead, place your wrist to [target.p_their()] mouth, and subject [target.p_them()] to the Dark Communion."), - ) - if(!do_mob(user, src, 5 SECONDS)) - to_chat(user, span_danger("The ritual has been interrupted!")) - use_lock = FALSE + + user.balloon_alert_to_viewers("smears blood...", "painting bloody marks...", ) + if(!do_after(user, 5 SECONDS, target)) + balloon_alert(user, "interrupted!") return if(HAS_TRAIT(target, TRAIT_MINDSHIELD)) to_chat(user, span_danger("They're mindshielded! Break their mindshield with a candelabrum or surgery before continuing!")) return /// Convert to Vassal! - bloodsuckerdatum.AddBloodVolume(-text2num(TORTURE_CONVERSION_COST)) - if(bloodsuckerdatum && bloodsuckerdatum.attempt_turn_vassal(target)) - bloodsuckerdatum.bloodsucker_level_unspent++ - user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE) - target.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE) - target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE) - target.Jitter(15) - INVOKE_ASYNC(target, /mob.proc/emote, "laugh") - //remove_victim(target) // Remove on CLICK ONLY! - use_lock = FALSE + bloodsuckerdatum.AddBloodVolume(-TORTURE_CONVERSION_COST) + if(bloodsuckerdatum.make_vassal(target)) + SEND_SIGNAL(bloodsuckerdatum.my_clan, BLOODSUCKER_MADE_VASSAL, user, target) /obj/structure/bloodsucker/vassalrack/proc/do_torture(mob/living/user, mob/living/carbon/target, mult = 1) /// Fifteen seconds if you aren't using anything. Shorter with weapons and such. @@ -765,7 +957,7 @@ /// Minimum 5 seconds. torture_time = max(5 SECONDS, torture_time SECONDS) /// Now run process. - if(!do_mob(user, target, torture_time * mult)) + if(!do_after(user, torture_time * mult, target)) return FALSE /// Success? if(held_item) @@ -775,70 +967,43 @@ span_danger("[user] performs a ritual, spilling some of [target]'s blood from their [target_string] and shaking them up!"), span_userdanger("[user] performs a ritual, spilling some blood from your [target_string], shaking you up!"), ) - INVOKE_ASYNC(target, /mob.proc/emote, "scream") - target.Jitter(5) + INVOKE_ASYNC(target, TYPE_PROC_REF(/mob, emote), "scream") + target.adjust_jitter(5 SECONDS) target.apply_damages(brute = torture_dmg_brute, burn = torture_dmg_burn, def_zone = (selected_bodypart ? selected_bodypart.body_zone : null)) // take_overall_damage(6,0) return TRUE /// Offer them the oppertunity to join now. /obj/structure/bloodsucker/vassalrack/proc/do_disloyalty(mob/living/user, mob/living/target) - spawn(10) - /// Are we still torturing? Did we cancel? Are they still here? - if(use_lock && target && target.client) - to_chat(user, span_notice("[target] has been given the opportunity for servitude. You await their decision...")) - var/alert_text = "You are being tortured! Do you want to give in and pledge your undying loyalty to [user]?" - alert_text += "\n\nYou will not lose your current objectives, but they come second to the will of your new master!" - to_chat(target, span_cultlarge("THE HORRIBLE PAIN! WHEN WILL IT END?!")) - var/list/torture_icons = list( - "Accept" = image(icon = 'icons/mob/actions/actions_bloodsucker.dmi', icon_state = "power_recup"), - "Refuse" = image(icon = 'icons/obj/weapons/baton.dmi', icon_state = "stunbaton_active") - ) - var/torture_response = show_radial_menu(target, src, torture_icons, radius = 36, require_near = TRUE) - switch(torture_response) - if("Accept") - disloyalty_accept(target) - else - disloyalty_refuse(target) - if(!do_torture(user,target, 2)) + if(!target || !target.client) + balloon_alert(user, "target has no mind!") return FALSE + if(disloyalty_offered) + return FALSE + + disloyalty_offered = TRUE + to_chat(user, span_notice("[target] has been given the opportunity for servitude. You await their decision...")) + var/alert_response = tgui_alert( + user = target, \ + message = "You are being tortured! Do you want to give in and pledge your undying loyalty to [user]? \n\ + You will not lose your current objectives, but they come second to the will of your new master!", \ + title = "THE HORRIBLE PAIN! WHEN WILL IT END?!", + buttons = list("Accept", "Refuse"), + timeout = 10 SECONDS, \ + autofocus = TRUE, \ + ) + switch(alert_response) + if("Accept") + disloyalty_confirm = TRUE + else + to_chat(target, span_notice("You refuse to give in! You will not break!")) + disloyalty_offered = FALSE - // NOTE: We only remove loyalties when we're CONVERTED! return TRUE /obj/structure/bloodsucker/vassalrack/proc/RequireDisloyalty(mob/living/user, mob/living/target) var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(user) return bloodsuckerdatum.AmValidAntag(target) -/obj/structure/bloodsucker/vassalrack/proc/disloyalty_accept(mob/living/target) - // FAILSAFE: Still on the rack? - if(!(locate(target) in buckled_mobs)) - return - // NOTE: You can say YES after torture. It'll apply to next time. - disloyalty_confirm = TRUE - -/obj/structure/bloodsucker/vassalrack/proc/disloyalty_refuse(mob/living/target) - // FAILSAFE: Still on the rack? - if(!(locate(target) in buckled_mobs)) - return - // Failsafe: You already said YES. - if(disloyalty_confirm) - return - to_chat(target, span_notice("You refuse to give in! You will not break!")) - - -/obj/structure/bloodsucker/vassalrack/proc/offer_favorite_vassal(mob/living/carbon/human/user, mob/living/target) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) - var/datum/antagonist/vassal/vassaldatum = target.mind.has_antag_datum(/datum/antagonist/vassal) - - switch(input("Would you like to turn this Vassal into your completely loyal Servant? This costs 150 Blood to do. You cannot undo this.") in list("Yes", "No")) - if("Yes") - user.blood_volume -= 150 - bloodsuckerdatum.has_favorite_vassal = TRUE - vassaldatum.make_favorite(user) - else - to_chat(user, span_danger("You decide not to turn [target] into your Favorite Vassal.")) - use_lock = FALSE - /obj/structure/bloodsucker/vassalrack/proc/do_ritual(mob/living/user, mob/living/target) var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(!target.mind) @@ -850,26 +1015,24 @@ /// Due to the checks leding up to this, if they fail this, they're dead & Not our vassal. if(!IS_VASSAL(target)) to_chat(user, span_notice("Do you wish to rebuild this body? This will remove any restraints they might have, and will cost 150 Blood!")) - var/revive_response = alert(usr, "Would you like to revive [target]?", "Ghetto Medbay", "Yes", "No") - switch(revive_response) - if("Yes") - if(!do_mob(user, src, 7 SECONDS)) - to_chat(user, span_danger("The ritual has been interrupted!")) - return - if(prob(15) && bloodsuckerdatum.bloodsucker_level <= 2) - to_chat(user, span_danger("Something has gone terribly wrong! You have accidentally turned [target] into a High-Functioning Zombie!")) - to_chat(target, span_announce("As Blood drips over your body, your heart fails to beat... But you still wake up.")) - H.set_species(/datum/species/zombie) - else - to_chat(user, span_danger("You have brought [target] back from the Dead!")) - to_chat(target, span_announce("As Blood drips over your body, your heart begins to beat... You live again!")) - B.blood_volume -= 150 - target.revive(full_heal = TRUE, admin_revive = FALSE) + var/revive_response = tgui_alert(usr, "Would you like to revive [target]?", "Ghetto Medbay", list("Yes", "No")) + if(revive_response == "Yes") + if(!do_mob(user, src, 7 SECONDS)) + to_chat(user, span_danger("The ritual has been interrupted!")) return + if(prob(50) && bloodsuckerdatum.bloodsucker_level <= 10) + to_chat(user, span_danger("Something has gone terribly wrong! You have accidentally turned [target] into a High-Functioning Zombie!")) + to_chat(target, span_announce("As Blood drips over your body, your heart fails to beat... But you still wake up.")) + H.set_species(/datum/species/zombie) + else + to_chat(user, span_danger("You have brought [target] back from the Dead!")) + to_chat(target, span_announce("As Blood drips over your body, your heart begins to beat... You live again!")) + B.blood_volume -= 150 + target.revive(full_heal = TRUE, admin_revive = FALSE) + return to_chat(user, span_danger("You decide not to revive [target].")) // Unbuckle them now. unbuckle_mob(B) - use_lock = FALSE return var/list/races = list(HUSK_MONSTER) switch(bloodsuckerdatum.bloodsucker_level) @@ -884,7 +1047,7 @@ races += TRIPLECHEST_MONSTER var/list/options = list() options = races - var/answer = input(user, "We have the chance to mutate our Vassal, how should we mutilate their corpse? This will cost us blood.", "What do we do with our Vassal?") in options + var/answer = tgui_input_list(user, "We have the chance to mutate our Vassal, how should we mutilate their corpse? This will cost us blood.", "What do we do with our Vassal?", options) var/meat_cost = 0 var/blood_gained if(!answer) @@ -1061,7 +1224,7 @@ /// We dont want Bloodsuckers or Vassals affected by this if(IS_VASSAL(nearly_people) || IS_BLOODSUCKER(nearly_people)) continue - nearly_people.hallucination += 5 + nearly_people.adjust_hallucinations(5 SECONDS) if(nearly_people.getStaminaLoss() >= 100) continue if(nearly_people.getStaminaLoss() >= 60) @@ -1132,10 +1295,10 @@ /obj/structure/bloodsucker/candelabrum/proc/remove_loyalties(mob/living/target, mob/living/user) // Find Mindshield implant & destroy, takes a good while. - if(HAS_TRAIT(target, TRAIT_MINDSHIELD)) - for(var/obj/item/implant/mindshield/L in target) - if(L) - qdel(L) + for(var/obj/item/implant/all_implants as anything in target.implants) + if(all_implants.type == /obj/item/implant/mindshield) + all_implants.removed(target, silent = TRUE) + /// Attempt Unbuckle /obj/structure/bloodsucker/candelabrum/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE) . = ..() diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm index ae51241b2321..9b2c4fa376d6 100644 --- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm +++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_life.dm @@ -1,10 +1,20 @@ +///How much Blood it costs to live. +#define BLOODSUCKER_PASSIVE_BLOOD_DRAIN 0.1 + /// Runs from COMSIG_LIVING_BIOLOGICAL_LIFE, handles Bloodsucker constant proccesses. /datum/antagonist/bloodsucker/proc/LifeTick() + SIGNAL_HANDLER + + if(isbrain(owner.current)) + return if(!owner && !owner.current) - INVOKE_ASYNC(src, .proc/HandleDeath) + INVOKE_ASYNC(src, PROC_REF(HandleDeath)) return + if(HAS_TRAIT(owner.current, TRAIT_NODEATH)) + check_end_torpor() + if(istype(owner.current, /mob/living/simple_animal/hostile/bloodsucker)) return @@ -13,40 +23,41 @@ // Deduct Blood if(owner.current.stat == CONSCIOUS && !HAS_TRAIT(owner.current, TRAIT_NODEATH)) - INVOKE_ASYNC(src, .proc/AddBloodVolume, passive_blood_drain) // -.1 currently - if(HandleHealing(1)) - if((COOLDOWN_FINISHED(src, bloodsucker_spam_healing)) && owner.current.blood_volume > 0) + INVOKE_ASYNC(src, PROC_REF(AddBloodVolume), -BLOODSUCKER_PASSIVE_BLOOD_DRAIN) // -.1 currently + if(HandleHealing()) + if((COOLDOWN_FINISHED(src, bloodsucker_spam_healing)) && bloodsucker_blood_volume) to_chat(owner.current, span_notice("The power of your blood begins knitting your wounds...")) COOLDOWN_START(src, bloodsucker_spam_healing, BLOODSUCKER_SPAM_HEALING) // Standard Updates - INVOKE_ASYNC(src, .proc/HandleDeath) - INVOKE_ASYNC(src, .proc/HandleStarving) - INVOKE_ASYNC(src, .proc/HandleTorpor) - - if(my_clan == CLAN_TOREADOR && owner.current.stat != DEAD) - for(var/datum/antagonist/vassal/vassal in vassals) - if(vassal.master != src) - continue - if(!vassal.owner.current || vassal.owner.current == DEAD) - continue - if(get_dist(get_turf(owner.current), get_turf(vassal.owner.current)) > 5) - continue - SEND_SIGNAL(vassal.owner.current, COMSIG_ADD_MOOD_EVENT, /datum/mood_event/toreador_vassal) - + INVOKE_ASYNC(src, PROC_REF(HandleDeath)) + INVOKE_ASYNC(src, PROC_REF(HandleStarving)) + INVOKE_ASYNC(src, PROC_REF(update_blood)) -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// BLOOD -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + INVOKE_ASYNC(src, PROC_REF(update_hud)) + if(my_clan) + SEND_SIGNAL(my_clan, BLOODSUCKER_HANDLE_LIFE, src) + +/** + * ## BLOOD STUFF + */ /datum/antagonist/bloodsucker/proc/AddBloodVolume(value) - owner.current.blood_volume = clamp(owner.current.blood_volume + value, 0, max_blood_volume) - update_hud() + bloodsucker_blood_volume = clamp(bloodsucker_blood_volume + value, 0, max_blood_volume) + +/datum/antagonist/bloodsucker/proc/on_examine(datum/source, mob/examiner, examine_text) + SIGNAL_HANDLER + + if(!iscarbon(source)) + return + var/mob/living/carbon/carbon_source = source + var/vamp_examine = carbon_source.return_vamp_examine(examiner) + examine_text += vamp_examine /datum/antagonist/bloodsucker/proc/AddHumanityLost(value) if(humanity_lost >= 500) to_chat(owner.current, span_warning("You hit the maximum amount of lost Humanity, you are far from Human.")) return - if(my_clan == CLAN_TOREADOR) + if(my_clan?.get_clan() == CLAN_TOREADOR) if(humanity_lost >= TOREADOR_MAX_HUMANITY_LOSS) to_chat(owner.current, span_warning("Your morals prevent you from becoming more inhuman.")) SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, /datum/mood_event/toreador_inhuman2) @@ -56,21 +67,15 @@ to_chat(owner.current, span_warning("You feel as if you lost some of your humanity, you will now enter Frenzy at [FRENZY_THRESHOLD_ENTER + humanity_lost * 10] Blood.")) /// mult: SILENT feed is 1/3 the amount -/datum/antagonist/bloodsucker/proc/HandleFeeding(mob/living/carbon/target, mult=1, power_level) +/datum/antagonist/bloodsucker/proc/handle_feeding(mob/living/carbon/target, mult=1, power_level) // Starts at 15 (now 8 since we doubled the Feed time) var/feed_amount = 15 + (power_level * 2) var/blood_taken = min(feed_amount, target.blood_volume) * mult target.blood_volume -= blood_taken - // Simple Animals lose a LOT of blood, and take damage. This is to keep cats, cows, and so forth from giving you insane amounts of blood. - if(!ishuman(target)) - target.blood_volume -= (blood_taken / max(target.mob_size, 0.1)) * 3.5 // max() to prevent divide-by-zero - target.apply_damage_type(blood_taken / 3.5) // Don't do too much damage, or else they die and provide no blood nourishment. - if(target.blood_volume <= 0) - target.blood_volume = 0 - target.death(0) + /////////// // Shift Body Temp (toward Target's temp, by volume taken) - owner.current.bodytemperature = ((owner.current.blood_volume * owner.current.bodytemperature) + (blood_taken * target.bodytemperature)) / (owner.current.blood_volume + blood_taken) + owner.current.bodytemperature = ((bloodsucker_blood_volume * owner.current.bodytemperature) + (blood_taken * target.bodytemperature)) / (bloodsucker_blood_volume + blood_taken) // our volume * temp, + their volume * temp, / total volume /////////// // Reduce Value Quantity @@ -97,17 +102,15 @@ to_chat(owner, span_warning("[target] is catatonic and won't yield any usable blood for tasks!")) return blood_taken -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// HEALING - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * ## HEALING + */ /// Constantly runs on Bloodsucker's LifeTick, and is increased by being in Torpor/Coffins /datum/antagonist/bloodsucker/proc/HandleHealing(mult = 1) var/actual_regen = bloodsucker_regen_rate + additional_regen // Don't heal if I'm staked or on Masquerade (+ not in a Coffin). Masqueraded Bloodsuckers in a Coffin however, will heal. - if(owner.current.AmStaked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && my_clan != CLAN_TOREADOR)) + if(owner.current.am_staked() || (HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && my_clan?.get_clan() != CLAN_TOREADOR)) return FALSE owner.current.adjustCloneLoss(-1 * (actual_regen * 4) * mult, 0) owner.current.adjustOrganLoss(ORGAN_SLOT_BRAIN, -1 * (actual_regen * 4) * mult) //adjustBrainLoss(-1 * (actual_regen * 4) * mult, 0) @@ -120,7 +123,7 @@ /// Checks if you're in a coffin here, additionally checks for Torpor right below it. var/amInCoffin = istype(user.loc, /obj/structure/closet/crate/coffin) if(amInCoffin && HAS_TRAIT(user, TRAIT_NODEATH)) - if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && my_clan != CLAN_TOREADOR) + if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE) && my_clan?.get_clan() != CLAN_TOREADOR) to_chat(user, span_warning("You will not heal while your Masquerade ability is active.")) return fireheal = min(user.getFireLoss_nonProsthetic(), actual_regen) @@ -147,10 +150,10 @@ var/list/missing = user.get_missing_limbs() if(missing.len && user.blood_volume < limb_regen_cost + 5) return FALSE - for(var/targetLimbZone in missing) // 1) Find ONE Limb and regenerate it. - user.regenerate_limb(targetLimbZone, FALSE) // regenerate_limbs() <--- If you want to EXCLUDE certain parts, do it like this ----> regenerate_limbs(0, list("head")) + for(var/missing_limb in missing) //Find ONE Limb and regenerate it. + user.regenerate_limb(missing_limb, FALSE) AddBloodVolume(limb_regen_cost) - var/obj/item/bodypart/missing_bodypart = user.get_bodypart(targetLimbZone) // 2) Limb returns Damaged + var/obj/item/bodypart/missing_bodypart = user.get_bodypart(missing_limb) // 2) Limb returns Damaged missing_bodypart.brute_dam = 60 to_chat(user, span_notice("Your flesh knits as it regrows your [missing_bodypart]!")) playsound(user, 'sound/magic/demon_consume.ogg', 50, TRUE) @@ -167,7 +170,7 @@ * This is called on Bloodsucker's Assign, and when they end Torpor. */ -/datum/antagonist/bloodsucker/proc/HealVampireOrgans() +/datum/antagonist/bloodsucker/proc/heal_vampire_organs() var/mob/living/carbon/bloodsuckeruser = owner.current // Step 1 - Fix basic things, husk and organs. @@ -190,9 +193,11 @@ current_eyes.sight_flags = SEE_MOBS current_eyes.see_in_dark = 8 current_eyes.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - if(my_clan == CLAN_LASOMBRA && ishuman(bloodsuckeruser)) + current_eyes.setOrganDamage(0) //making sure + if(my_clan?.get_clan() == CLAN_LASOMBRA && ishuman(bloodsuckeruser)) var/mob/living/carbon/human/bloodsucker = bloodsuckeruser - bloodsucker.eye_color = "f00" + bloodsucker.eye_color = BLOODCULT_EYE + bloodsucker.physiology.brute_mod *= 0 //making sure bloodsuckeruser.update_body() bloodsuckeruser.update_sight() @@ -206,8 +211,7 @@ var/list/bad_organs = list( bloodsuckeruser.getorgan(/obj/item/organ/body_egg), bloodsuckeruser.getorgan(/obj/item/organ/zombie_infection)) - for(var/tumors in bad_organs) - var/obj/item/organ/yucky_organs = tumors + for(var/obj/item/organ/yucky_organs as anything in bad_organs) if(!istype(yucky_organs)) continue yucky_organs.Remove(bloodsuckeruser) @@ -227,12 +231,16 @@ if(!owner.current || !get_turf(owner.current)) FinalDeath() return - // Fire Damage? (above double health) - if(owner.current.getFireLoss() >= owner.current.maxHealth * 2.5) + // Fire Damage and Fledgeling? (above double health) + if(owner.current.getFireLoss() >= owner.current.maxHealth * 2 && bloodsucker_level < 4) + FinalDeath() + return + // Fire Damage and Daytime? + if(owner.current.getFireLoss() >= owner.current.maxHealth * 2 && (SSsunlight.sunlight_active || frenzied)) FinalDeath() return // Staked while "Temp Death" or Asleep - if(owner.current.StakeCanKillMe() && owner.current.AmStaked()) + if(owner.current.StakeCanKillMe() && owner.current.am_staked()) FinalDeath() return // Not organic/living? (Zombie/Skeleton/Plasmaman) @@ -244,90 +252,98 @@ var/mob/living/carbon/human/dead_bloodsucker = owner.current if(!HAS_TRAIT(dead_bloodsucker, TRAIT_NODEATH)) to_chat(dead_bloodsucker, span_danger("Your immortal body will not yet relinquish your soul to the abyss. You enter Torpor.")) - Check_Begin_Torpor(TRUE) + check_begin_torpor(TRUE) + /datum/antagonist/bloodsucker/proc/HandleStarving() // I am thirsty for blood! // Nutrition - The amount of blood is how full we are. - owner.current.set_nutrition(min(owner.current.blood_volume, NUTRITION_LEVEL_FED)) + owner.current.set_nutrition(min(bloodsucker_blood_volume, NUTRITION_LEVEL_FED)) // BLOOD_VOLUME_GOOD: [336] - Pale // handled in bloodsucker_integration.dm // BLOOD_VOLUME_EXIT: [560] - Exit Frenzy (If in one) This is high because we want enough to kill the poor soul they feed off of. - if(owner.current.blood_volume >= (FRENZY_THRESHOLD_EXIT + humanity_lost * 10) && frenzied) + if(bloodsucker_blood_volume >= (FRENZY_THRESHOLD_EXIT + humanity_lost * 10) && frenzied) owner.current.remove_status_effect(STATUS_EFFECT_FRENZY) // BLOOD_VOLUME_BAD: [224] - Jitter - if(owner.current.blood_volume < BLOOD_VOLUME_BAD(owner.current) && prob(0.5) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE)) - owner.current.Jitter(3) + if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD(owner.current) && prob(0.5) && !HAS_TRAIT(owner.current, TRAIT_NODEATH) && !HAS_TRAIT(owner.current, TRAIT_MASQUERADE)) + owner.current.adjust_jitter(3 SECONDS) // BLOOD_VOLUME_SURVIVE: [122] - Blur Vision - if(owner.current.blood_volume < BLOOD_VOLUME_SURVIVE(owner.current)) - owner.current.blur_eyes(8 - 8 * (owner.current.blood_volume / BLOOD_VOLUME_BAD(owner.current))) + if(bloodsucker_blood_volume < BLOOD_VOLUME_SURVIVE(owner.current)) + owner.current.blur_eyes((8 - 8 * (bloodsucker_blood_volume / BLOOD_VOLUME_BAD(owner.current)))* 2 SECONDS) // The more blood, the better the Regeneration, get too low blood, and you enter Frenzy. - if(owner.current.blood_volume < (FRENZY_THRESHOLD_ENTER + humanity_lost * 10) && !frenzied) + if(bloodsucker_blood_volume < (FRENZY_THRESHOLD_ENTER + humanity_lost * 10) && !frenzied) if(!iscarbon(owner.current)) return - if(owner.current.stat == DEAD) - HandleDeath() - return - enter_frenzy() - else if(owner.current.blood_volume < BLOOD_VOLUME_BAD(owner.current)) + owner.current.apply_status_effect(/datum/status_effect/frenzy) + else if(bloodsucker_blood_volume < BLOOD_VOLUME_BAD(owner.current)) additional_regen = 0.1 - else if(owner.current.blood_volume < BLOOD_VOLUME_OKAY(owner.current)) + else if(bloodsucker_blood_volume < BLOOD_VOLUME_OKAY(owner.current)) additional_regen = 0.2 - else if(owner.current.blood_volume < BLOOD_VOLUME_NORMAL(owner.current)) + else if(bloodsucker_blood_volume < BLOOD_VOLUME_NORMAL(owner.current)) additional_regen = 0.3 - else if(owner.current.blood_volume < BS_BLOOD_VOLUME_MAX_REGEN) + else if(bloodsucker_blood_volume < BS_BLOOD_VOLUME_MAX_REGEN) additional_regen = 0.4 else additional_regen = 0.5 -/datum/antagonist/bloodsucker/proc/enter_frenzy() - if(my_clan == CLAN_GANGREL) - var/mob/living/carbon/user = owner.current - switch(frenzies) - if(0) - owner.current.apply_status_effect(STATUS_EFFECT_FRENZY) - return - if(1) - to_chat(owner, span_warning("You start feeling hungrier, you feel like a normal frenzy won't satiate it enough anymore.")) - owner.current.apply_status_effect(STATUS_EFFECT_FRENZY) - return - if(2 to INFINITY) - AddBloodVolume(FRENZY_THRESHOLD_EXIT + humanity_lost * 10 - user.blood_volume) //so it doesn't happen multiple times and refills your blood when you get out again - if(!do_mob(user, user, 2 SECONDS, TRUE)) - return - playsound(user.loc, 'sound/weapons/slash.ogg', 25, TRUE) - to_chat(user, span_warning("You skin rips and tears.")) - if(!do_mob(user, user, 1 SECONDS, TRUE)) - return - playsound(user.loc, 'sound/weapons/slashmiss.ogg', 25, TRUE) - to_chat(user, span_warning("You heart pumps blackened blood into your veins as your skin turns into fur.")) - if(!do_mob(user, user, 1 SECONDS, TRUE)) - return - playsound(user.loc, 'sound/weapons/slice.ogg', 25, TRUE) - to_chat(user, span_boldnotice("YOU HAVE AWOKEN.")) - var/mob/living/simple_animal/hostile/bloodsucker/werewolf/ww - if(!ww || ww.stat == DEAD) - ww = new /mob/living/simple_animal/hostile/bloodsucker/werewolf(user.loc) - user.forceMove(ww) - ww.bloodsucker = user - user.status_flags |= GODMODE - user.mind.transfer_to(ww) - var/list/wolf_powers = list(new /datum/action/bloodsucker/targeted/feast,) - for(var/datum/action/bloodsucker/power in powers) - if(istype(power, /datum/action/bloodsucker/fortitude)) - wolf_powers += new /datum/action/bloodsucker/gangrel/wolfortitude - if(istype(power, /datum/action/bloodsucker/targeted/lunge)) - wolf_powers += new /datum/action/bloodsucker/targeted/pounce - if(istype(power, /datum/action/bloodsucker/cloak)) - wolf_powers += new /datum/action/bloodsucker/gangrel/howl - if(istype(power, /datum/action/bloodsucker/targeted/trespass)) - wolf_powers += new /datum/action/bloodsucker/gangrel/rabidism - for(var/datum/action/bloodsucker/power in wolf_powers) - power.Grant(ww) - frenzies++ - else - owner.current.apply_status_effect(STATUS_EFFECT_FRENZY) +/// Cycle through all vamp antags and check if they're inside a closet. +/datum/antagonist/bloodsucker/proc/handle_sol() + SIGNAL_HANDLER + if(!owner) + return + + if(!istype(owner.current.loc, /obj/structure)) + if(COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn)) + if(bloodsucker_level > 0) + to_chat(owner, span_userdanger("The solar flare sets your skin ablaze!")) + else + to_chat(owner, span_userdanger("The solar flare scalds your neophyte skin!")) + COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol + + if(owner.current.fire_stacks <= 0) + owner.current.fire_stacks = 0 + if(bloodsucker_level > 0) + owner.current.adjust_fire_stacks(0.2 + bloodsucker_level / 10) + owner.current.ignite_mob() + owner.current.adjustFireLoss(2 + (bloodsucker_level / 2)) + owner.current.updatehealth() + SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_2) + return + + if(istype(owner.current.loc, /obj/structure/closet/crate/coffin)) // Coffins offer the BEST protection + if(owner.current.am_staked() && COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn)) + to_chat(owner.current, span_userdanger("You are staked! Remove the offending weapon from your heart before sleeping.")) + COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol + if(!HAS_TRAIT(owner.current, TRAIT_NODEATH)) + check_begin_torpor(TRUE) + SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/coffinsleep) + return + + if(COOLDOWN_FINISHED(src, bloodsucker_spam_sol_burn)) // Closets offer SOME protection + to_chat(owner, span_warning("Your skin sizzles. [owner.current.loc] doesn't protect well against UV bombardment.")) + COOLDOWN_START(src, bloodsucker_spam_sol_burn, BLOODSUCKER_SPAM_SOL) //This should happen twice per Sol + owner.current.adjustFireLoss(0.5 + (bloodsucker_level / 4)) + owner.current.updatehealth() + SEND_SIGNAL(owner.current, COMSIG_ADD_MOOD_EVENT, "vampsleep", /datum/mood_event/daylight_1) + +/datum/antagonist/bloodsucker/proc/give_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message) + SIGNAL_HANDLER + if(!owner) + return + to_chat(owner, vampire_warning_message) + + switch(danger_level) + if(DANGER_LEVEL_FIRST_WARNING) + owner.current.playsound_local(null, 'sound/effects/griffin_3.ogg', 50, 1) + if(DANGER_LEVEL_SECOND_WARNING) + owner.current.playsound_local(null, 'sound/effects/griffin_5.ogg', 50, 1) + if(DANGER_LEVEL_THIRD_WARNING) + owner.current.playsound_local(null, 'sound/effects/alert.ogg', 75, 1) + if(DANGER_LEVEL_SOL_ROSE) + owner.current.playsound_local(null, 'sound/ambience/ambimystery.ogg', 100, 1) + if(DANGER_LEVEL_SOL_ENDED) + owner.current.playsound_local(null, 'sound/spookoween/ghosty_wind.ogg', 90, 1) /** * # Torpor @@ -337,60 +353,62 @@ * You cannot manually exit Torpor, it is instead entered/exited by: * * Torpor is triggered by: - * - Being in a Coffin while Sol is on, dealt with by /HandleTorpor() + * - Being in a Coffin while Sol is on, dealt with by Sol * - Entering a Coffin with more than 10 combined Brute/Burn damage, dealt with by /closet/crate/coffin/close() [bloodsucker_coffin.dm] * - Death, dealt with by /HandleDeath() * Torpor is ended by: - * - Having less than 10 Brute damage while OUTSIDE of your Coffin while it isnt Sol, dealt with by /HandleTorpor() - * - Having less than 10 Brute & Burn Combined while INSIDE of your Coffin while it isnt Sol, dealt with by /HandleTorpor() + * - Having less than 10 Brute damage while OUTSIDE of your Coffin while it isnt Sol. + * - Having less than 10 Brute & Burn Combined while INSIDE of your Coffin while it isnt Sol. * - Sol being over, dealt with by /sunlight/process() [bloodsucker_daylight.dm] */ -/datum/antagonist/bloodsucker/proc/HandleTorpor() - if(!owner.current) - return - if(istype(owner.current.loc, /obj/structure/closet/crate/coffin)) - if(!HAS_TRAIT(owner.current, TRAIT_NODEATH)) - /// Staked? Dont heal - if(owner.current.AmStaked()) - to_chat(owner.current, span_userdanger("You are staked! Remove the offending weapon from your heart before sleeping.")) - return - /// Otherwise, check if it's Sol, to enter Torpor. - if(clan.bloodsucker_sunlight.amDay) - Check_Begin_Torpor(TRUE) - if(HAS_TRAIT(owner.current, TRAIT_NODEATH)) // Check so I don't go insane. - Check_End_Torpor() - -/datum/antagonist/bloodsucker/proc/Check_Begin_Torpor(SkipChecks = FALSE) +/** + * # Assigning Sol + * + * Sol is the sunlight, during this period, all Bloodsuckers must be in their coffin, else they burn. + */ + +/// Start Sol, called when someone is assigned Bloodsucker +/datum/antagonist/bloodsucker/proc/check_start_sunlight() + if(length(get_antag_minds(/datum/antagonist/bloodsucker))) + message_admins("New Sol has been created due to Bloodsucker assignment.") + SSsunlight.can_fire = TRUE + +/// End Sol, if you're the last Bloodsucker +/datum/antagonist/bloodsucker/proc/check_cancel_sunlight() + if(!length(get_antag_minds(/datum/antagonist/bloodsucker))) + message_admins("Sol has been deleted due to the lack of Bloodsuckers") + SSsunlight.can_fire = FALSE + +/datum/antagonist/bloodsucker/proc/check_begin_torpor(SkipChecks = FALSE) /// Are we entering Torpor via Sol/Death? Then entering it isnt optional! if(SkipChecks) - Torpor_Begin() - return + torpor_begin() var/mob/living/carbon/user = owner.current var/total_brute = user.getBruteLoss_nonProsthetic() var/total_burn = user.getFireLoss_nonProsthetic() var/total_damage = total_brute + total_burn /// Checks - Not daylight & Has more than 10 Brute/Burn & not already in Torpor - if(!clan.bloodsucker_sunlight.amDay && total_damage >= 10 && !HAS_TRAIT(owner.current, TRAIT_NODEATH)) - Torpor_Begin() + if(!SSsunlight.sunlight_active && total_damage >= 10 && !HAS_TRAIT(owner.current, TRAIT_NODEATH)) + torpor_begin() -/datum/antagonist/bloodsucker/proc/Check_End_Torpor() +/datum/antagonist/bloodsucker/proc/check_end_torpor() var/mob/living/carbon/user = owner.current var/total_brute = user.getBruteLoss_nonProsthetic() var/total_burn = user.getFireLoss_nonProsthetic() var/total_damage = total_brute + total_burn // You are in a Coffin, so instead we'll check TOTAL damage, here. if(istype(user.loc, /obj/structure/closet/crate/coffin)) - if(!clan.bloodsucker_sunlight.amDay && total_damage <= 10) - Torpor_End() + if(!SSsunlight.sunlight_active && total_damage <= 10) + torpor_end() // You're not in a Coffin? We won't check for low Burn damage - else if(!clan.bloodsucker_sunlight.amDay && total_brute <= 10) + else if(!SSsunlight.sunlight_active && total_brute <= 10) // You're under 10 brute, but over 200 Burn damage? Don't exit Torpor, to prevent spam revival/death. Only way out is healing that Burn. if(total_burn >= 199) return - Torpor_End() + torpor_end() -/datum/antagonist/bloodsucker/proc/Torpor_Begin() +/datum/antagonist/bloodsucker/proc/torpor_begin() var/mob/living/carbon/human/bloodsucker = owner.current to_chat(owner.current, span_notice("You enter the horrible slumber of deathless Torpor. You will heal until you are renewed.")) /// Force them to go to sleep @@ -401,33 +419,49 @@ ADD_TRAIT(owner.current, TRAIT_DEATHCOMA, BLOODSUCKER_TRAIT) ADD_TRAIT(owner.current, TRAIT_RESISTLOWPRESSURE, BLOODSUCKER_TRAIT) bloodsucker.physiology.brute_mod *= 0 - owner.current.Jitter(0) + bloodsucker.physiology.burn_mod *= 0.75 + owner.current.set_timed_status_effect(0 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE) /// Disable ALL Powers DisableAllPowers() -/datum/antagonist/bloodsucker/proc/Torpor_End() +/datum/antagonist/bloodsucker/proc/torpor_end() var/mob/living/carbon/human/bloodsucker = owner.current owner.current.grab_ghost() to_chat(owner.current, span_warning("You have recovered from Torpor.")) - if(my_clan == CLAN_LASOMBRA) - bloodsucker.physiology.brute_mod *= 0 - else - bloodsucker.physiology.brute_mod = initial(bloodsucker.physiology.brute_mod) + bloodsucker.physiology.brute_mod = initial(bloodsucker.physiology.brute_mod) + bloodsucker.physiology.burn_mod = initial(bloodsucker.physiology.brute_mod) REMOVE_TRAIT(owner.current, TRAIT_RESISTLOWPRESSURE, BLOODSUCKER_TRAIT) REMOVE_TRAIT(owner.current, TRAIT_DEATHCOMA, BLOODSUCKER_TRAIT) REMOVE_TRAIT(owner.current, TRAIT_FAKEDEATH, BLOODSUCKER_TRAIT) REMOVE_TRAIT(owner.current, TRAIT_NODEATH, BLOODSUCKER_TRAIT) ADD_TRAIT(owner.current, TRAIT_SLEEPIMMUNE, BLOODSUCKER_TRAIT) - HealVampireOrgans() + heal_vampire_organs() + + if(my_clan) + SEND_SIGNAL(my_clan, BLOODSUCKER_EXIT_TORPOR, owner.current) + +/// Makes your blood_volume look like your bloodsucker blood, unless you're Masquerading. +/datum/antagonist/bloodsucker/proc/update_blood() + //If we're on Masquerade, we appear to have full blood, unless we are REALLY low, in which case we don't look as bad. + if(HAS_TRAIT(owner.current, TRAIT_MASQUERADE)) + switch(bloodsucker_blood_volume) + if(BLOOD_VOLUME_OKAY(owner.current) to INFINITY) // 336 and up, we are perfectly fine. + owner.current.blood_volume = initial(bloodsucker_blood_volume) + if(BLOOD_VOLUME_BAD(owner.current) to BLOOD_VOLUME_OKAY(owner.current)) // 224 to 336 + owner.current.blood_volume = BLOOD_VOLUME_SAFE(owner.current) + else // 224 and below + owner.current.blood_volume = BLOOD_VOLUME_OKAY(owner.current) + return + owner.current.blood_volume = bloodsucker_blood_volume /// Gibs the Bloodsucker, roundremoving them. /datum/antagonist/bloodsucker/proc/FinalDeath() - FreeAllVassals() // If we have no body, end here. if(!owner.current) return - DisableAllPowers() + free_all_vassals() + DisableAllPowers(forced = TRUE) if(!iscarbon(owner.current)) owner.current.gib(TRUE, FALSE, FALSE) return @@ -437,19 +471,24 @@ owner.current.unequip_everything() user.remove_all_embedded_objects() playsound(owner.current, 'sound/effects/tendril_destroyed.ogg', 40, TRUE) - // Elders get dusted, Fledglings get gibbed + if(my_clan) + var/unique_death = SEND_SIGNAL(my_clan, BLOODSUCKER_FINAL_DEATH, owner.current) + if(unique_death & DONT_DUST) + return + + // Elders get dusted, Fledglings get gibbed. if(bloodsucker_level >= 4) - owner.current.visible_message( - span_warning("[owner.current]'s skin crackles and dries, their skin and bones withering to dust. A hollow cry whips from what is now a sandy pile of remains."), + user.visible_message( + span_warning("[user]'s skin crackles and dries, their skin and bones withering to dust. A hollow cry whips from what is now a sandy pile of remains."), span_userdanger("Your soul escapes your withering body as the abyss welcomes you to your Final Death."), span_hear("You hear a dry, crackling sound.")) - addtimer(CALLBACK(owner.current, /mob/living.proc/dust), 5 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) + addtimer(CALLBACK(user, /mob/living.proc/dust), 5 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) else - owner.current.visible_message( - span_warning("[owner.current]'s skin bursts forth in a spray of gore and detritus. A horrible cry echoes from what is now a wet pile of decaying meat."), + user.visible_message( + span_warning("[user]'s skin bursts forth in a spray of gore and detritus. A horrible cry echoes from what is now a wet pile of decaying meat."), span_userdanger("Your soul escapes your withering body as the abyss welcomes you to your Final Death."), span_hear("You hear a wet, bursting sound.")) - owner.current.gib(TRUE, FALSE, FALSE) + user.gib(TRUE, FALSE, FALSE) // Bloodsuckers moodlets // diff --git a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm index e69ee3913808..cbe6d872d6d3 100644 --- a/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm +++ b/code/modules/antagonists/bloodsuckers/structures/bloodsucker_recipes.dm @@ -3,6 +3,7 @@ ///////////////////////// /// Coffins /// ///////////////////////// + /datum/crafting_recipe/blackcoffin name = "Black Coffin" result = /obj/structure/closet/crate/coffin/blackcoffin @@ -52,6 +53,7 @@ //////////////////////////// /// Structures /// //////////////////////////// + /datum/crafting_recipe/bloodaltar name = "Blood Altar" result = /obj/structure/bloodsucker/bloodaltar @@ -117,6 +119,18 @@ category = CAT_STRUCTURES always_available = FALSE +/datum/crafting_recipe/moldingstone + name = "Molding Stone" + result = /obj/structure/bloodsucker/moldingstone + tools = list(TOOL_WELDER, /obj/item/bloodsucker/chisel) + reqs = list( + /obj/item/stack/sheet/metal = 5, + /obj/item/stack/rods = 6, + ) + time = 8 SECONDS + category = CAT_STRUCTURES + always_available = FALSE + /* /datum/crafting_recipe/bloodthrone name = "Blood Throne" @@ -147,6 +161,7 @@ //////////////////////// /// Stakes /// //////////////////////// + /datum/crafting_recipe/stake name = "Stake" result = /obj/item/stake @@ -190,3 +205,30 @@ category = CAT_WEAPONRY subcategory = CAT_WEAPON always_available = FALSE + +//////////////////////// +/// Tools /// +//////////////////////// + +/datum/crafting_recipe/chisel + name = "Chisel" + result = /obj/item/bloodsucker/chisel + tools = list(TOOL_WELDER, TOOL_SCREWDRIVER) + reqs = list( + /obj/item/stack/sheet/metal = 3 + ) + time = 5 SECONDS + category = CAT_TOOLS + always_available = FALSE + +/*/datum/crafting_recipe/bloodybrush + name = "Artist's Brush" + result = /obj/item/bloodsucker/bloodybrush + tools = list(TOOL_HATCHET) + reqs = list( + /obj/item/stack/sheet/mineral/wood = 2, + /obj/item/stack/sheet/cloth = 1, + ) + time = 5 SECONDS + category = CAT_TOOLS + always_available = FALSE*/ diff --git a/code/modules/antagonists/bloodsuckers/subsystem_sunlight.dm b/code/modules/antagonists/bloodsuckers/subsystem_sunlight.dm new file mode 100644 index 000000000000..e95eaf1992eb --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/subsystem_sunlight.dm @@ -0,0 +1,90 @@ +/// 40 - 65 seconds +#define TIME_BLOODSUCKER_DAY (rand(40, 65)) +/// 10 - 15 minutes +#define TIME_BLOODSUCKER_NIGHT 720 +/// 1.5 minutes +#define TIME_BLOODSUCKER_DAY_WARN 90 +/// 30 seconds +#define TIME_BLOODSUCKER_DAY_FINAL_WARN 30 +/// 5 seconds +#define TIME_BLOODSUCKER_BURN_INTERVAL 5 + +///How much time Sol can be 'off' by, keeping the time inconsistent. +#define TIME_BLOODSUCKER_SOL_DELAY 180 + +SUBSYSTEM_DEF(sunlight) + name = "Sol" + can_fire = FALSE + wait = 2 SECONDS + flags = SS_NO_INIT | SS_BACKGROUND | SS_TICKER + + ///If the Sun is currently out our not. + var/sunlight_active = FALSE + ///The time between the next cycle, randomized every night. + var/time_til_cycle = TIME_BLOODSUCKER_NIGHT + ///If Bloodsucker levels for the night has been given out yet. + var/issued_XP = FALSE + + +/datum/controller/subsystem/sunlight/fire(resumed = FALSE) + time_til_cycle-- + if(sunlight_active) + if(time_til_cycle) + SEND_SIGNAL(src, COMSIG_SOL_RISE_TICK) + if(!issued_XP && time_til_cycle <= 15) + issued_XP = TRUE + SEND_SIGNAL(src, COMSIG_SOL_RANKUP_BLOODSUCKERS) + if(time_til_cycle <= 1) + sunlight_active = FALSE + issued_XP = FALSE + //randomize the next sol timer + time_til_cycle = round(rand((TIME_BLOODSUCKER_NIGHT-TIME_BLOODSUCKER_SOL_DELAY), (TIME_BLOODSUCKER_NIGHT+TIME_BLOODSUCKER_SOL_DELAY)), 1) + message_admins("BLOODSUCKER NOTICE: Daylight Ended. Resetting to Night (Lasts for [round(time_til_cycle / 60, 1)] minutes.") + SEND_SIGNAL(src, COMSIG_SOL_END) + warn_daylight( + danger_level = DANGER_LEVEL_SOL_ENDED, + vampire_warning_message = span_announce("The solar flare has ended, and the daylight danger has passed... for now."), + vassal_warning_message = span_announce("The solar flare has ended, and the daylight danger has passed... for now."), + ) + return + + switch(time_til_cycle) + if(TIME_BLOODSUCKER_DAY_WARN) + SEND_SIGNAL(src, COMSIG_SOL_NEAR_START) + warn_daylight( + danger_level = DANGER_LEVEL_FIRST_WARNING, + vampire_warning_message = span_danger("Solar Flares will bombard the station with dangerous UV radiation in [TIME_BLOODSUCKER_DAY_WARN / 60] minutes. Prepare to seek cover in a coffin or closet."), + ) + if(TIME_BLOODSUCKER_DAY_FINAL_WARN) + message_admins("BLOODSUCKER NOTICE: Daylight beginning in [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds.") + warn_daylight( + danger_level = DANGER_LEVEL_SECOND_WARNING, + vampire_warning_message = span_userdanger("Solar Flares are about to bombard the station! You have [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds to find cover!"), + vassal_warning_message = span_danger("In [TIME_BLOODSUCKER_DAY_FINAL_WARN] seconds, your master will be at risk of a Solar Flare. Make sure they find cover!"), + ) + if(TIME_BLOODSUCKER_BURN_INTERVAL) + warn_daylight( + danger_level = DANGER_LEVEL_THIRD_WARNING, + vampire_warning_message = span_userdanger("Seek cover, for Sol rises!"), + ) + if(NONE) + sunlight_active = TRUE + //set the timer to countdown daytime now. + time_til_cycle = TIME_BLOODSUCKER_DAY + message_admins("BLOODSUCKER NOTICE: Daylight Beginning (Lasts for [round(TIME_BLOODSUCKER_DAY / 60, 1)] minutes.") + warn_daylight( + danger_level = DANGER_LEVEL_SOL_ROSE, + vampire_warning_message = span_userdanger("Solar flares bombard the station with deadly UV light! Stay in cover for the next [round(TIME_BLOODSUCKER_DAY / 60, 1)] minutes or risk Final Death!"), + vassal_warning_message = span_userdanger("Solar flares bombard the station with UV light!"), + ) + +/datum/controller/subsystem/sunlight/proc/warn_daylight(danger_level, vampire_warning_message, vassal_warning_message) + SEND_SIGNAL(src, COMSIG_SOL_WARNING_GIVEN, danger_level, vampire_warning_message, vassal_warning_message) + +#undef TIME_BLOODSUCKER_SOL_DELAY + +#undef TIME_BLOODSUCKER_DAY +#undef TIME_BLOODSUCKER_NIGHT +#undef TIME_BLOODSUCKER_DAY_WARN +#undef TIME_BLOODSUCKER_DAY_FINAL_WARN +#undef TIME_BLOODSUCKER_BURN_INTERVAL diff --git a/code/modules/antagonists/bloodsuckers/vassal.dm b/code/modules/antagonists/bloodsuckers/vassal.dm deleted file mode 100644 index 545e49a5fae4..000000000000 --- a/code/modules/antagonists/bloodsuckers/vassal.dm +++ /dev/null @@ -1,224 +0,0 @@ -#define VASSAL_SCAN_MIN_DISTANCE 5 -#define VASSAL_SCAN_MAX_DISTANCE 500 -/// 2s update time. -#define VASSAL_SCAN_PING_TIME (2 SECONDS) - -/datum/antagonist/vassal - name = "\improper Vassal" - roundend_category = "vassals" - antagpanel_category = "Bloodsucker" - job_rank = ROLE_BLOODSUCKER - show_in_roundend = FALSE - - /// The Master Bloodsucker's antag datum. - var/datum/antagonist/bloodsucker/master - var/datum/game_mode/blooodsucker - /// List of all Purchased Powers, like Bloodsuckers. - var/list/datum/action/powers = list() - /// The favorite vassal gets unique features. - var/favorite_vassal = FALSE - /// Bloodsucker levels, but for Vassals. - var/vassal_level - -/datum/antagonist/vassal/antag_panel_data() - return "Master : [master.owner.name]" - -/datum/antagonist/vassal/apply_innate_effects(mob/living/mob_override) - var/mob/living/current_mob = mob_override || owner.current - current_mob.apply_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) - -/datum/antagonist/vassal/remove_innate_effects(mob/living/mob_override) - var/mob/living/current_mob = mob_override || owner.current - current_mob.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) - - -/datum/antagonist/vassal/on_gain() - /// Enslave them to their Master - if(master) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.owner.has_antag_datum(/datum/antagonist/bloodsucker) - if(bloodsuckerdatum) - bloodsuckerdatum.vassals |= src - owner.enslave_mind_to_creator(master.owner.current) - owner.current.log_message("has been vassalized by [master.owner.current]!", LOG_ATTACK, color="#960000") - /// Give Recuperate Power - BuyPower(new /datum/action/bloodsucker/recuperate) - /// Give Objectives - var/datum/objective/bloodsucker/vassal/vassal_objective = new - vassal_objective.owner = owner - objectives += vassal_objective - /// Give Vampire Language & Hud - owner.current.grant_all_languages(FALSE, FALSE, TRUE) - owner.current.grant_language(/datum/language/vampiric) - update_vassal_icons_added(owner.current) - . = ..() - -/datum/antagonist/vassal/on_removal() - //Free them from their Master - if(master && master.owner) - master.vassals -= src - owner.enslaved_to = null - for(var/all_status_traits in owner.current.status_traits) - REMOVE_TRAIT(owner.current, all_status_traits, BLOODSUCKER_TRAIT) - //Remove Recuperate Power - while(powers.len) - var/datum/action/bloodsucker/power = pick(powers) - powers -= power - power.Remove(owner.current) - //Remove Language & Hud - owner.current.remove_language(/datum/language/vampiric) - update_vassal_icons_removed(owner.current) - UnregisterSignal(master, COMSIG_BLOODSUCKER_RANKS_SPENT) - return ..() - -/datum/antagonist/vassal/on_body_transfer(mob/living/old_body, mob/living/new_body) - . = ..() - for(var/datum/action/bloodsucker/all_powers as anything in powers) - all_powers.Remove(old_body) - all_powers.Grant(new_body) - - -/datum/antagonist/vassal/proc/add_objective(datum/objective/added_objective) - objectives += added_objective - -/datum/antagonist/vassal/proc/remove_objectives(datum/objective/removed_objective) - objectives -= removed_objective - -/datum/antagonist/vassal/greet() - . = ..() - to_chat(owner, span_userdanger("You are now the mortal servant of [master.owner.current], a Bloodsucker!")) - to_chat(owner, span_boldannounce("The power of [master.owner.current.p_their()] immortal blood compels you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.\n\ - You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed.")) - owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE) - antag_memory += "You have been vassalized, becoming the mortal servant of [master.owner.current], a bloodsucking vampire!
" - /// Message told to your Master. - to_chat(master.owner, span_userdanger("[owner.current] has become addicted to your immortal blood. [owner.current.p_they(TRUE)] [owner.current.p_are()] now your undying servant!")) - master.owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE) - -/datum/antagonist/vassal/farewell() - owner.current.visible_message( - span_deconversion_message("[owner.current]'s eyes dart feverishly from side to side, and then stop. [owner.current.p_they(TRUE)] seem[owner.current.p_s()] calm, \ - like [owner.current.p_they()] [owner.current.p_have()] regained some lost part of [owner.current.p_them()]self."), - ) - to_chat(owner, span_deconversion_message("With a snap, you are no longer enslaved to [master.owner]! You breathe in heavily, having regained your free will.")) - owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE) - /// Message told to your (former) Master. - if(master && master.owner) - to_chat(master.owner, span_cultbold("You feel the bond with your vassal [owner.current] has somehow been broken!")) - -/// Called when we are made into the Favorite Vassal -/datum/antagonist/vassal/proc/make_favorite(mob/living/master) - // Default stuff for all - favorite_vassal = TRUE - set_antag_hud(owner.current, "vassal6") - to_chat(master, span_danger("You have turned [owner.current] into your Favorite Vassal! They will no longer be deconverted upon Mindshielding!")) - to_chat(owner, span_notice("As Blood drips over your body, you feel closer to your Master... You are now the Favorite Vassal!")) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.mind.has_antag_datum(/datum/antagonist/bloodsucker) - var/mob/living/carbon/human/vassal = owner.current - switch(bloodsuckerdatum.my_clan) - if(CLAN_GANGREL) - var/obj/effect/proc_holder/spell/targeted/shapeshift/bat/batform = new - owner.AddSpell(batform) - if(CLAN_LASOMBRA) - if(ishuman(owner.current)) - vassal.see_in_dark = 8 - vassal.eye_color = "f00" - var/list/powers = list(new /obj/effect/proc_holder/spell/targeted/lesser_glare, new /obj/effect/proc_holder/spell/targeted/shadowwalk) - for(var/obj/effect/proc_holder/spell/targeted/power in powers) - owner.AddSpell(power) - if(CLAN_TZIMISCE) - if(!do_mob(master, owner.current, 1 SECONDS, TRUE)) - return - playsound(vassal.loc, 'sound/weapons/slash.ogg', 50, TRUE, -1) - if(!do_mob(master, owner.current, 1 SECONDS, TRUE)) - return - playsound(vassal.loc, 'sound/effects/splat.ogg', 50, TRUE) - vassal.set_species(/datum/species/szlachta) - if(CLAN_TOREADOR) - BuyPower(/datum/action/bloodsucker/targeted/mesmerize) - RegisterSignal(master, COMSIG_BLOODSUCKER_RANKS_SPENT, .proc/toreador_levelup_mesmerize) - -/datum/antagonist/vassal/proc/toreador_levelup_mesmerize() //Don't need stupid args - for(var/datum/action/bloodsucker/targeted/mesmerize/mesmerize_power in powers) - if(!istype(mesmerize_power)) - continue - mesmerize_power.level_current = max(master.bloodsucker_level, 1) - -/// If we weren't created by a bloodsucker, then we cannot be a vassal (assigned from antag panel) -/datum/antagonist/vassal/can_be_owned(datum/mind/new_owner) - if(!master) - return FALSE - return ..() - -/// Used for Admin removing Vassals. -/datum/mind/proc/remove_vassal() - var/datum/antagonist/vassal/selected_vassal = has_antag_datum(/datum/antagonist/vassal) - if(selected_vassal) - remove_antag_datum(/datum/antagonist/vassal) - -/// When a Bloodsucker gets FinalDeath, all Vassals are freed - This is a Bloodsucker proc, not a Vassal one. -/datum/antagonist/bloodsucker/proc/FreeAllVassals() - for(var/datum/antagonist/vassal/all_vassals in vassals) - // Skip over any Bloodsucker Vassals, they're too far gone to have all their stuff taken away from them - if(all_vassals.owner.has_antag_datum(/datum/antagonist/bloodsucker)) - continue - remove_vassal(all_vassals.owner) - -/// Called by FreeAllVassals() -/datum/antagonist/bloodsucker/proc/remove_vassal(datum/mind/vassal) - vassal.remove_antag_datum(/datum/antagonist/vassal) - -/// Used when your Master teaches you a new Power. -/datum/antagonist/vassal/proc/BuyPower(datum/action/bloodsucker/power) - powers += power - power.Grant(owner.current) - -/datum/antagonist/vassal/proc/LevelUpPowers() - for(var/datum/action/bloodsucker/power in powers) - power.level_current++ - -/** - * # Vassal Pinpointer - * - * Pinpointer that points to their Master's location at all times. - * Unlike the Monster hunter one, this one is permanently active, and has no power needed to activate it. - */ - -/atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition - name = "Blood Bond" - desc = "You always know where your master is." - -/datum/status_effect/agent_pinpointer/vassal_edition - id = "agent_pinpointer" - alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition - minimum_range = VASSAL_SCAN_MIN_DISTANCE - tick_interval = VASSAL_SCAN_PING_TIME - duration = -1 - range_fuzz_factor = 0 - -/datum/status_effect/agent_pinpointer/vassal_edition/on_creation(mob/living/new_owner, ...) - ..() - var/datum/antagonist/vassal/antag_datum = new_owner.mind.has_antag_datum(/datum/antagonist/vassal) - scan_target = antag_datum?.master?.owner?.current - -/datum/status_effect/agent_pinpointer/vassal_edition/scan_for_target() - return - -/datum/status_effect/agent_pinpointer/vassal_edition/Destroy() - if(scan_target) - to_chat(owner, span_notice("You've lost your master's trail.")) - return ..() - -/** - * # HUD - */ -/datum/antagonist/vassal/proc/update_vassal_icons_added(mob/living/vassal, icontype = "vassal") - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_BLOODSUCKER] - hud.join_hud(vassal) - /// Located in icons/mob/hud.dmi - set_antag_hud(vassal, icontype) - /// FULP ADDITION! Check prepare_huds in mob.dm to see why. - -/datum/antagonist/vassal/proc/update_vassal_icons_removed(mob/living/vassal) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_BLOODSUCKER] - hud.leave_hud(vassal) - set_antag_hud(vassal, null) diff --git a/code/modules/antagonists/bloodsuckers/vassal/bloodsucker_conversion.dm b/code/modules/antagonists/bloodsuckers/vassal/bloodsucker_conversion.dm new file mode 100644 index 000000000000..5da4e1a125f7 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/vassal/bloodsucker_conversion.dm @@ -0,0 +1,135 @@ +/** + * Checks if the target has antag datums and, if so, + * are they allowed to be Vassalized, or not, or banned. + * Args: + * target - The person we check for antag datums. + */ +/datum/antagonist/bloodsucker/proc/AmValidAntag(mob/target) + if(!target.mind) + return + + if(target.mind.unconvertable) + return VASSALIZATION_BANNED + + var/vassalization_status = VASSALIZATION_ALLOWED + for(var/datum/antagonist/antag_datum as anything in target.mind.antag_datums) + if(antag_datum.type in vassal_banned_antags) + return VASSALIZATION_BANNED + vassalization_status = VASSALIZATION_DISLOYAL + return vassalization_status + +/** + * # can_make_vassal + * Checks if the person is allowed to turn into the Bloodsucker's + * Vassal, ensuring they are a player and valid. + * If they are a Vassal themselves, will check if their master + * has broken the Masquerade, to steal them. + * Args: + * conversion_target - Person being vassalized + */ +/datum/antagonist/bloodsucker/proc/can_make_vassal(mob/living/conversion_target) + if(!iscarbon(conversion_target) || conversion_target.stat > UNCONSCIOUS) + return FALSE + // No Mind! + if(!conversion_target.mind) + to_chat(owner.current, span_danger("[conversion_target] isn't self-aware enough to be made into a Vassal.")) + return FALSE + if(!AmValidAntag(conversion_target)) + to_chat(owner.current, span_danger("[conversion_target] resists the power of your blood to dominate their mind!")) + return FALSE + var/mob/living/master = conversion_target.mind.enslaved_to?.resolve() + if(!master || (master == owner.current)) + return TRUE + var/datum/antagonist/bloodsucker/bloodsuckerdatum = master.mind.has_antag_datum(/datum/antagonist/bloodsucker) + if(bloodsuckerdatum?.broke_masquerade) + //vassal stealing + return TRUE + to_chat(owner.current, span_danger("[conversion_target]'s mind is overwhelmed with too much external force to put your own!")) + return FALSE + +/** + * First will check if the target can be turned into a Vassal, if so then it will + * turn them into one, log it, sync their minds, then updates the Rank + * Args: + * conversion_target - The person converted. + */ +/datum/antagonist/bloodsucker/proc/make_vassal(mob/living/conversion_target) + if(!can_make_vassal(conversion_target)) + return FALSE + + //Check if they used to be a Vassal and was stolen. + var/datum/antagonist/vassal/old_vassal = conversion_target.mind.has_antag_datum(/datum/antagonist/vassal) + if(old_vassal) + conversion_target.mind.remove_antag_datum(/datum/antagonist/vassal) + + var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.has_antag_datum(/datum/antagonist/bloodsucker) + bloodsuckerdatum.SelectTitle(am_fledgling = FALSE) + + //set the master, then give the datum. + var/datum/antagonist/vassal/vassaldatum = new(conversion_target.mind) + vassaldatum.master = bloodsuckerdatum + conversion_target.mind.add_antag_datum(vassaldatum) + + message_admins("[conversion_target] has become a Vassal, and is enslaved to [owner.current].") + log_admin("[conversion_target] has become a Vassal, and is enslaved to [owner.current].") + return TRUE + +/* + * # can_make_bloodsucker + * + * MIND Helper proc that ensures the person can be a Bloodsucker, + * without actually giving the antag datum to them. + * Args: + * creator - Person attempting to convert them. + */ +/datum/mind/proc/can_make_bloodsucker(datum/mind/creator) + var/mob/living/user = current + if(!(user.mob_biotypes & MOB_ORGANIC)) + if(creator) + to_chat(creator, span_danger("[user]'s DNA isn't compatible!")) + return FALSE + return TRUE + +/* + * # make_bloodsucker + * + * MIND Helper proc that turns the person into a Bloodsucker + * Args: + * creator - Person attempting to convert them. + */ +/datum/mind/proc/make_bloodsucker(datum/mind/creator) + var/datum/antagonist/bloodsuckerdatum = add_antag_datum(/datum/antagonist/bloodsucker) + if(bloodsuckerdatum && creator) + message_admins("[src] has become a Bloodsucker, and was created by [creator].") + log_admin("[src] has become a Bloodsucker, and was created by [creator].") + return bloodsuckerdatum + +/datum/mind/proc/remove_bloodsucker() + var/datum/antagonist/bloodsucker/removed_bloodsucker = has_antag_datum(/datum/antagonist/bloodsucker) + if(removed_bloodsucker) + remove_antag_datum(/datum/antagonist/bloodsucker) + special_role = null + +/** + * # Assigning Bloodsucker status + * + * Here we assign the Bloodsuckers themselves, ensuring they arent Plasmamen + */ + +/datum/mind/proc/prepare_bloodsucker(datum/mind/convertee, datum/mind/converter) + var/mob/living/carbon/human/user = convertee.current + //Yogs start -- fixes plasmaman vampires + var/species_type = convertee?.current?.client.prefs.read_preference(/datum/preference/choiced/species) + var/datum/species/species = new species_type + + var/noblood = (NOBLOOD in species.species_traits) + qdel(species) + + if(noblood) + user.set_species(/datum/species/human) + user.apply_pref_name(/datum/preference/name/real_name, user.client) + // Check for Fledgeling + if(converter) + message_admins("[convertee] has become a Bloodsucker, and was created by [converter].") + log_admin("[convertee] has become a Bloodsucker, and was created by [converter].") + return TRUE diff --git a/code/modules/antagonists/bloodsuckers/vassal/favorite_vassal.dm b/code/modules/antagonists/bloodsuckers/vassal/favorite_vassal.dm new file mode 100644 index 000000000000..1e257bcbe383 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/vassal/favorite_vassal.dm @@ -0,0 +1,23 @@ +/** + * Favorite Vassal + * + * Gets some cool abilities depending on the Clan. + */ +/datum/antagonist/vassal/favorite + name = "\improper Favorite Vassal" + show_in_antagpanel = FALSE + antag_hud_name = "vassal6" + special_type = FAVORITE_VASSAL + vassal_description = "The Favorite Vassal gets unique abilities over other Vassals depending on your Clan \ + and becomes completely immune to Mindshields. If part of Ventrue, this is the Vassal you will rank up." + + ///Bloodsucker levels, but for Vassals, used by Ventrue. + var/vassal_level + +/datum/antagonist/vassal/favorite/on_gain() + . = ..() + SEND_SIGNAL(master.my_clan, BLOODSUCKER_MAKE_FAVORITE, src, master) + +///Set the Vassal's rank to their Bloodsucker level +/datum/antagonist/vassal/favorite/proc/set_vassal_level(mob/living/carbon/human/target) + master.bloodsucker_level = vassal_level diff --git a/code/modules/antagonists/bloodsuckers/vassal/revenge_vassal.dm b/code/modules/antagonists/bloodsuckers/vassal/revenge_vassal.dm new file mode 100644 index 000000000000..54ea44e577be --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/vassal/revenge_vassal.dm @@ -0,0 +1,106 @@ +/** + * Revenge Vassal + * + * Has the goal to 'get revenge' when their Master dies. + */ +/datum/antagonist/vassal/revenge + name = "\improper Revenge Vassal" + roundend_category = "abandoned Vassals" + show_in_roundend = FALSE + show_in_antagpanel = FALSE + antag_hud_name = "vassal4" + special_type = REVENGE_VASSAL + vassal_description = "The Revenge Vassal will not deconvert on your Final Death, \ + instead they will gain all your Powers, and the objective to take revenge for your demise. \ + They additionally maintain your Vassals after your departure, rather than become aimless." + + ///all ex-vassals brought back into the fold. + var/list/datum/antagonist/ex_vassal/ex_vassals = list() + +/datum/antagonist/vassal/revenge/roundend_report() + var/list/report = list() + report += printplayer(owner) + if(objectives.len) + report += printobjectives(objectives) + + // Now list their vassals + if(ex_vassals.len) + report += "The Vassals brought back into the fold were..." + for(var/datum/antagonist/ex_vassal/all_vassals as anything in ex_vassals) + if(!all_vassals.owner) + continue + report += "[all_vassals.owner.name] the [all_vassals.owner.assigned_role]" + + return report.Join("
") + +/datum/antagonist/vassal/revenge/on_gain() + . = ..() + RegisterSignal(master.my_clan, BLOODSUCKER_FINAL_DEATH, PROC_REF(on_master_death)) + +/datum/antagonist/vassal/revenge/on_removal() + UnregisterSignal(master.my_clan, BLOODSUCKER_FINAL_DEATH) + return ..() + +/datum/antagonist/vassal/revenge/ui_static_data(mob/user) + var/list/data = list() + for(var/datum/action/bloodsucker/power as anything in powers) + var/list/power_data = list() + + power_data["power_name"] = power.name + power_data["power_explanation"] = power.power_explanation + power_data["power_icon"] = power.button_icon_state + + data["power"] += list(power_data) + + return data + ..() + +/datum/antagonist/vassal/revenge/proc/on_master_death(datum/source, mob/living/carbon/master) + SIGNAL_HANDLER + + show_in_roundend = TRUE + for(var/datum/objective/all_objectives as anything in objectives) + objectives -= all_objectives + BuyPower(new /datum/action/bloodsucker/vassal_blood) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(master) + for(var/datum/action/bloodsucker/master_powers as anything in bloodsuckerdatum.powers) + if(master_powers.purchase_flags & BLOODSUCKER_DEFAULT_POWER) + continue + master_powers.Grant(owner.current) + owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) + + var/datum/objective/survive/new_objective = new + new_objective.name = "Avenge Bloodsucker" + new_objective.explanation_text = "Avenge your Bloodsucker's death by recruiting their ex-vassals and continuing their operations." + new_objective.owner = owner + objectives += new_objective + + if(info_button_ref) + QDEL_NULL(info_button_ref) + + ui_name = "AntagInfoRevengeVassal" //give their new ui + var/datum/action/antag_info/info_button = new(src) + info_button.Grant(owner.current) + info_button_ref = WEAKREF(info_button) + +/datum/antagonist/vassal/admin_add(datum/mind/new_owner, mob/admin) + var/list/datum/mind/possible_vampires = list() + for(var/datum/antagonist/bloodsucker/bloodsuckerdatums in GLOB.antagonists) + var/datum/mind/vamp = bloodsuckerdatums.owner + if(!vamp) + continue + if(!vamp.current) + continue + if(vamp.current.stat == DEAD) + continue + possible_vampires += vamp + if(!length(possible_vampires)) + message_admins("[key_name_admin(usr)] tried vassalizing [key_name_admin(new_owner)], but there were no bloodsuckers!") + return + var/datum/mind/choice = input("Which bloodsucker should this vassal belong to?", "Bloodsucker") in possible_vampires + if(!choice) + return + log_admin("[key_name_admin(usr)] turned [key_name_admin(new_owner)] into a vassal of [key_name_admin(choice)]!") + var/datum/antagonist/bloodsucker/vampire = choice.has_antag_datum(/datum/antagonist/bloodsucker) + master = vampire + new_owner.add_antag_datum(src) + to_chat(choice, span_notice("Through divine intervention, you've gained a new vassal!")) diff --git a/code/modules/antagonists/bloodsuckers/vassal/vassal.dm b/code/modules/antagonists/bloodsuckers/vassal/vassal.dm new file mode 100644 index 000000000000..1576eb5474b4 --- /dev/null +++ b/code/modules/antagonists/bloodsuckers/vassal/vassal.dm @@ -0,0 +1,378 @@ +#define VASSAL_SCAN_MIN_DISTANCE 5 +#define VASSAL_SCAN_MAX_DISTANCE 500 +/// 2s update time. +#define VASSAL_SCAN_PING_TIME 2 SECONDS + +/datum/antagonist/vassal + name = "\improper Vassal" + roundend_category = "vassals" + antagpanel_category = "Bloodsucker" + job_rank = ROLE_BLOODSUCKER + show_in_roundend = FALSE + + /// The Master Bloodsucker's antag datum. + var/datum/antagonist/bloodsucker/master + var/datum/game_mode/blooodsucker + /// List of all Purchased Powers, like Bloodsuckers. + var/list/datum/action/powers = list() + ///Whether this vassal is already a special type of Vassal. + var/special_type = FALSE + ///Description of what this Vassal does. + var/vassal_description + +/datum/antagonist/vassal/antag_panel_data() + return "Master : [master.owner.name]" + +/datum/antagonist/vassal/apply_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/current_mob = mob_override || owner.current + current_mob.apply_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) + add_team_hud(current_mob) + +/datum/antagonist/vassal/remove_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/current_mob = mob_override || owner.current + current_mob.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) + +/datum/antagonist/vassal/add_team_hud(mob/target) + QDEL_NULL(team_hud_ref) + + team_hud_ref = WEAKREF(target.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/has_antagonist, + "antag_team_hud_[REF(src)]", + hud_image_on(target), + )) + + var/datum/atom_hud/alternate_appearance/basic/has_antagonist/hud = team_hud_ref.resolve() + + var/list/mob/living/mob_list = list() + mob_list += master.owner.current + for(var/datum/antagonist/vassal/vassal as anything in master.vassals) + mob_list += vassal.owner.current + + for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds) + if(!(antag_hud.target in mob_list)) + continue + antag_hud.show_to(target) + hud.show_to(antag_hud.target) + +/datum/antagonist/vassal/proc/on_examine(datum/source, mob/examiner, examine_text) + SIGNAL_HANDLER + + if(!iscarbon(source)) + return + var/vassal_examine = return_vassal_examine(examiner) + examine_text += vassal_examine + +/datum/antagonist/vassal/on_gain() + RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, .proc/on_examine) + /// Enslave them to their Master + if(!master || !istype(master, master)) + return + if(special_type) + if(!master.special_vassals[special_type]) + master.special_vassals[special_type] = list() + master.special_vassals[special_type] |= src + master.vassals |= src + owner.enslave_mind_to_creator(master.owner.current) + owner.current.log_message("has been vassalized by [master.owner.current]!", LOG_ATTACK, color="#960000") + /// Give Recuperate Power + BuyPower(new /datum/action/bloodsucker/recuperate) + /// Give Objectives + var/datum/objective/bloodsucker/vassal/vassal_objective = new + vassal_objective.owner = owner + objectives += vassal_objective + /// Give Vampire Language & Hud + owner.current.grant_all_languages(FALSE, FALSE, TRUE) + owner.current.grant_language(/datum/language/vampiric) + . = ..() + +/datum/antagonist/vassal/on_removal() + UnregisterSignal(owner.current, COMSIG_PARENT_EXAMINE) + //Free them from their Master + if(master && master.owner) + if(special_type && master.special_vassals[special_type]) + master.special_vassals[special_type] -= src + master.vassals -= src + owner.enslaved_to = null + for(var/all_status_traits in owner.current.status_traits) + REMOVE_TRAIT(owner.current, all_status_traits, BLOODSUCKER_TRAIT) + //Remove Recuperate Power + while(powers.len) + var/datum/action/bloodsucker/power = pick(powers) + powers -= power + power.Remove(owner.current) + //Remove Language & Hud + owner.current.remove_language(/datum/language/vampiric) + return ..() + +/datum/antagonist/vassal/on_body_transfer(mob/living/old_body, mob/living/new_body) + . = ..() + for(var/datum/action/bloodsucker/all_powers as anything in powers) + all_powers.Remove(old_body) + all_powers.Grant(new_body) + +/datum/antagonist/vassal/proc/add_objective(datum/objective/added_objective) + objectives += added_objective + +/datum/antagonist/vassal/greet() + . = ..() + if(silent) + return + to_chat(owner, span_userdanger("You are now the mortal servant of [master.owner.current], a Bloodsucker!")) + to_chat(owner, span_boldannounce("The power of [master.owner.current.p_their()] immortal blood compels you to obey [master.owner.current.p_them()] in all things, even offering your own life to prolong theirs.\n\ + You are not required to obey any other Bloodsucker, for only [master.owner.current] is your master. The laws of Nanotrasen do not apply to you now; only your vampiric master's word must be obeyed.")) + owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE) + antag_memory += "You have been vassalized, becoming the mortal servant of [master.owner.current], a bloodsucking vampire!
" + /// Message told to your Master. + to_chat(master.owner, span_userdanger("[owner.current] has become addicted to your immortal blood. [owner.current.p_they(TRUE)] [owner.current.p_are()] now your undying servant!")) + master.owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE) + +/datum/antagonist/vassal/farewell() + if(silent) + return + owner.current.visible_message( + span_deconversion_message("[owner.current]'s eyes dart feverishly from side to side, and then stop. [owner.current.p_they(TRUE)] seem[owner.current.p_s()] calm, \ + like [owner.current.p_they()] [owner.current.p_have()] regained some lost part of [owner.current.p_them()]self."), + ) + to_chat(owner, span_deconversion_message("With a snap, you are no longer enslaved to [master.owner]! You breathe in heavily, having regained your free will.")) + owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 100, FALSE, pressure_affected = FALSE) + /// Message told to your (former) Master. + if(master && master.owner) + to_chat(master.owner, span_cultbold("You feel the bond with your vassal [owner.current] has somehow been broken!")) + +/datum/antagonist/vassal/proc/toreador_levelup_mesmerize() //Don't need stupid args + for(var/datum/action/bloodsucker/targeted/mesmerize/mesmerize_power in powers) + if(!istype(mesmerize_power)) + continue + mesmerize_power.level_current = max(master.bloodsucker_level, 1) + +/// If we weren't created by a bloodsucker, then we cannot be a vassal (assigned from antag panel) +/datum/antagonist/vassal/can_be_owned(datum/mind/new_owner) + if(!master) + return FALSE + return ..() + +/// When a Bloodsucker gets FinalDeath, all Vassals are freed - This is a Bloodsucker proc, not a Vassal one. +/datum/antagonist/bloodsucker/proc/free_all_vassals() + for(var/datum/antagonist/vassal/all_vassals in vassals) + // Skip over any Bloodsucker Vassals, they're too far gone to have all their stuff taken away from them + if(all_vassals.owner.has_antag_datum(/datum/antagonist/bloodsucker)) + all_vassals.owner.current.remove_status_effect(/datum/status_effect/agent_pinpointer/vassal_edition) + continue + remove_vassal(all_vassals.owner) + if(all_vassals.special_type == REVENGE_VASSAL) + continue + all_vassals.owner.add_antag_datum(/datum/antagonist/ex_vassal) + all_vassals.owner.remove_antag_datum(/datum/antagonist/vassal) + +/// Called by FreeAllVassals() +/datum/antagonist/bloodsucker/proc/remove_vassal(datum/mind/vassal) + vassal.remove_antag_datum(/datum/antagonist/vassal) + +/// Used when your Master teaches you a new Power. +/datum/antagonist/vassal/proc/BuyPower(datum/action/bloodsucker/power) + powers += power + power.Grant(owner.current) + +/datum/antagonist/vassal/proc/LevelUpPowers() + for(var/datum/action/bloodsucker/power in powers) + power.level_current++ + +/// Called when we are made into the Favorite Vassal +/datum/antagonist/vassal/proc/make_special(datum/antagonist/vassal/vassal_type) + //store what we need + var/datum/mind/vassal_owner = owner + var/datum/antagonist/bloodsucker/bloodsuckerdatum = master + + //remove our antag datum + silent = TRUE + vassal_owner.remove_antag_datum(/datum/antagonist/vassal) + + //give our new one + var/datum/antagonist/vassal/vassaldatum = new vassal_type(vassal_owner) + vassaldatum.master = bloodsuckerdatum + vassaldatum.silent = TRUE + vassal_owner.add_antag_datum(vassaldatum) + vassaldatum.silent = FALSE + + //send alerts of completion + to_chat(master, span_danger("You have turned [vassal_owner.current] into your [vassaldatum.name]! They will no longer be deconverted upon Mindshielding!")) + to_chat(vassal_owner, span_notice("As Blood drips over your body, you feel closer to your Master... You are now the Favorite Vassal!")) + vassal_owner.current.playsound_local(null, 'sound/magic/mutate.ogg', 75, FALSE, pressure_affected = FALSE) + +/** + * # Vassal Pinpointer + * + * Pinpointer that points to their Master's location at all times. + * Unlike the Monster hunter one, this one is permanently active, and has no power needed to activate it. + */ + +/atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition + name = "Blood Bond" + desc = "You always know where your master is." + +/datum/status_effect/agent_pinpointer/vassal_edition + id = "agent_pinpointer" + alert_type = /atom/movable/screen/alert/status_effect/agent_pinpointer/vassal_edition + minimum_range = VASSAL_SCAN_MIN_DISTANCE + tick_interval = VASSAL_SCAN_PING_TIME + duration = -1 + range_fuzz_factor = 0 + +/datum/status_effect/agent_pinpointer/vassal_edition/on_creation(mob/living/new_owner, ...) + ..() + var/datum/antagonist/vassal/antag_datum = new_owner.mind.has_antag_datum(/datum/antagonist/vassal) + scan_target = antag_datum?.master?.owner?.current + +/datum/status_effect/agent_pinpointer/vassal_edition/scan_for_target() + return + +/datum/status_effect/agent_pinpointer/vassal_edition/Destroy() + if(scan_target) + to_chat(owner, span_notice("You've lost your master's trail.")) + return ..() + +#define BLOOD_TIMER_REQUIREMENT (10 MINUTES) +#define BLOOD_TIMER_HALWAY (BLOOD_TIMER_REQUIREMENT / 2) + +/datum/antagonist/ex_vassal + name = "\improper Ex-Vassal" + roundend_category = "vassals" + antagpanel_category = "Bloodsucker" + job_rank = ROLE_BLOODSUCKER + antag_hud_name = "vassal_grey" + show_in_roundend = FALSE + show_in_antagpanel = FALSE + silent = TRUE + ui_name = FALSE + hud_icon = 'yogstation/icons/mob/hud.dmi' + + ///The revenge vassal that brought us into the fold. + var/datum/antagonist/vassal/revenge/revenge_vassal + ///Timer we have to live + COOLDOWN_DECLARE(blood_timer) + +/datum/antagonist/ex_vassal/on_gain() + . = ..() + RegisterSignal(owner.current, COMSIG_PARENT_EXAMINE, .proc/on_examine) + +/datum/antagonist/ex_vassal/on_removal() + if(revenge_vassal) + revenge_vassal.ex_vassals -= src + revenge_vassal = null + blood_timer = null + return ..() + +/datum/antagonist/ex_vassal/proc/on_examine(datum/source, mob/examiner, examine_text) + SIGNAL_HANDLER + + var/datum/antagonist/vassal/revenge/vassaldatum = examiner.mind.has_antag_datum(/datum/antagonist/vassal/revenge) + if(vassaldatum && !revenge_vassal) + examine_text += span_notice("[owner.current] is an ex-vassal!") + +/datum/antagonist/ex_vassal/add_team_hud(mob/target) + QDEL_NULL(team_hud_ref) + + team_hud_ref = WEAKREF(target.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/has_antagonist, + "antag_team_hud_[REF(src)]", + hud_image_on(target), + )) + + var/datum/atom_hud/alternate_appearance/basic/has_antagonist/hud = team_hud_ref.resolve() + + var/list/mob/living/mob_list = list() + mob_list += revenge_vassal.owner.current + for(var/datum/antagonist/ex_vassal/former_vassals as anything in revenge_vassal.ex_vassals) + mob_list += former_vassals.owner.current + + for (var/datum/atom_hud/alternate_appearance/basic/has_antagonist/antag_hud as anything in GLOB.has_antagonist_huds) + if(!(antag_hud.target in mob_list)) + continue + antag_hud.show_to(target) + hud.show_to(antag_hud.target) + +/** + * Fold return + * + * Called when a Revenge bloodsucker gets a vassal back into the fold. + */ +/datum/antagonist/ex_vassal/proc/return_to_fold(datum/antagonist/vassal/revenge/mike_ehrmantraut) + revenge_vassal = mike_ehrmantraut + mike_ehrmantraut.ex_vassals += src + COOLDOWN_START(src, blood_timer, BLOOD_TIMER_REQUIREMENT) + + RegisterSignal(src, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(on_life)) + +/datum/antagonist/ex_vassal/proc/on_life(datum/source, delta_time, times_fired) + SIGNAL_HANDLER + + if(COOLDOWN_TIMELEFT(src, blood_timer) <= BLOOD_TIMER_HALWAY + 2 && COOLDOWN_TIMELEFT(src, blood_timer) >= BLOOD_TIMER_HALWAY - 2) //just about halfway + to_chat(owner.current, span_cultbold("You need new blood from your Master!")) + if(!COOLDOWN_FINISHED(src, blood_timer)) + return + to_chat(owner.current, span_cultbold("You are out of blood!")) + to_chat(revenge_vassal.owner.current, span_cultbold("[owner.current] has ran out of blood and is no longer in the fold!")) + owner.remove_antag_datum(/datum/antagonist/ex_vassal) + +/datum/antagonist/vassal/proc/give_warning(atom/source, danger_level, vampire_warning_message, vassal_warning_message) + SIGNAL_HANDLER + if(vassal_warning_message) + to_chat(owner, vassal_warning_message) + +/** + * Returns a Vassals's examine strings. + * Args: + * viewer - The person examining. + */ + +/datum/antagonist/vassal/proc/return_vassal_examine(mob/living/viewer) + if(!viewer.mind || !iscarbon(owner.current)) + return FALSE + var/mob/living/carbon/carbon_current = owner.current + // Target must be a Vassal + // Default String + var/returnString = "\[" + var/returnIcon = "" + // Vassals and Bloodsuckers recognize eachother, while Monster Hunters can see Vassals. + if(!IS_BLOODSUCKER(viewer) && !IS_VASSAL(viewer) && !IS_MONSTERHUNTER(viewer)) + return FALSE + // Am I Viewer's Vassal? + if(master.owner == viewer.mind) + returnString += "This [carbon_current.dna.species.name] bears YOUR mark!" + returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal")]" + // Am I someone ELSE'S Vassal? + else if(IS_BLOODSUCKER(viewer) || IS_MONSTERHUNTER(viewer)) + returnString += "This [carbon_current.dna.species.name] bears the mark of [master.return_full_name()][master.broke_masquerade ? " who has broken the Masquerade" : ""]" + returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal_grey")]" + // Are you serving the same master as I am? + else if(viewer.mind.has_antag_datum(/datum/antagonist/vassal) in master.vassals) + returnString += "[p_they(TRUE)] bears the mark of your Master" + returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal")]" + // You serve a different Master than I do. + else + returnString += "[p_they(TRUE)] bears the mark of another Bloodsucker" + returnIcon = "[icon2html('icons/mob/vampiric.dmi', world, "vassal_grey")]" + + returnString += "\]" // \n" Don't need spacers. Using . += "" in examine.dm does this on its own. + return returnIcon + returnString + +/** + * Bloodsucker Blood + * + * Artificially made, this must be fed to ex-vassals to keep them on their high. + */ +/datum/reagent/blood/bloodsucker + name = "Blood two" + +/datum/reagent/blood/bloodsucker/reaction_mob(mob/living/exposed_mob, methods, reac_volume, show_message, touch_protection) + var/datum/antagonist/ex_vassal/former_vassal = exposed_mob.mind.has_antag_datum(/datum/antagonist/ex_vassal) + if(former_vassal) + to_chat(exposed_mob, span_cult("You feel the blood restore you... You feel safe.")) + COOLDOWN_RESET(former_vassal, blood_timer) + COOLDOWN_START(former_vassal, blood_timer, BLOOD_TIMER_REQUIREMENT) + return ..() + +#undef BLOOD_TIMER_REQUIREMENT +#undef BLOOD_TIMER_HALWAY diff --git a/code/modules/antagonists/brainwashing/brainwashing.dm b/code/modules/antagonists/brainwashing/brainwashing.dm index 7cb3d7ece6f6..59f833a0a0d2 100644 --- a/code/modules/antagonists/brainwashing/brainwashing.dm +++ b/code/modules/antagonists/brainwashing/brainwashing.dm @@ -28,6 +28,7 @@ job_rank = ROLE_BRAINWASHED roundend_category = "brainwashed victims" show_in_antagpanel = TRUE + antag_hud_name = "brainwashed" antagpanel_category = "Other" show_name_in_check_antagonists = TRUE @@ -49,24 +50,6 @@ owner.current.clear_alert("brainwash_notif") owner.announce_objectives() -/datum/antagonist/brainwashed/apply_innate_effects(mob/living/mob_override) - . = ..() - update_traitor_icons_added() - -/datum/antagonist/brainwashed/remove_innate_effects(mob/living/mob_override) - . = ..() - update_traitor_icons_removed() - -/datum/antagonist/brainwashed/proc/update_traitor_icons_added(datum/mind/slave_mind) - var/datum/atom_hud/antag/brainwashedhud = GLOB.huds[ANTAG_HUD_BRAINWASHED] - brainwashedhud.join_hud(owner.current) - set_antag_hud(owner.current, "brainwashed") - -/datum/antagonist/brainwashed/proc/update_traitor_icons_removed(datum/mind/slave_mind) - var/datum/atom_hud/antag/brainwashedhud = GLOB.huds[ANTAG_HUD_BRAINWASHED] - brainwashedhud.leave_hud(owner.current) - set_antag_hud(owner.current, null) - /datum/antagonist/brainwashed/admin_add(datum/mind/new_owner,mob/admin) var/mob/living/carbon/C = new_owner.current if(!istype(C)) diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 768ed4565efc..c974e77406d5 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -3,6 +3,7 @@ antagpanel_category = "Brother" job_rank = ROLE_BROTHER var/special_role = ROLE_BROTHER + antag_hud_name = "brother" var/datum/team/brother_team/team antag_moodlet = /datum/mood_event/focused can_hijack = HIJACK_HIJACKER @@ -102,7 +103,6 @@ for(var/datum/mind/M in team.members) // Link the implants of all team members var/obj/item/implant/bloodbrother/T = locate() in M.current.implants I.link_implant(T) - SSticker.mode.update_brother_icons_added(owner) owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE) /datum/antagonist/brother/admin_add(datum/mind/new_owner,mob/admin) @@ -133,7 +133,7 @@ .["Convert To Traitor"] = CALLBACK(src, .proc/make_traitor) /datum/antagonist/brother/proc/make_traitor() - if(alert("Are you sure? This will turn the blood brother into a traitor with the same objectives!",,"Yes","No") != "Yes") + if(tgui_alert("Are you sure? This will turn the blood brother into a traitor with the same objectives!",,list("Yes","No")) != "Yes") return var/datum/antagonist/traitor/tot = new() diff --git a/code/modules/antagonists/changeling/cellular_emporium.dm b/code/modules/antagonists/changeling/cellular_emporium.dm index 7437cd09e54e..c226257c1c70 100644 --- a/code/modules/antagonists/changeling/cellular_emporium.dm +++ b/code/modules/antagonists/changeling/cellular_emporium.dm @@ -1,29 +1,17 @@ // cellular emporium // The place where changelings go to buy their biological weaponry. -/datum/cellular_emporium - var/name = "cellular emporium" - var/datum/antagonist/changeling/changeling +/datum/antag_menu/cellular_emporium + name = "cellular emporium" + ui_name = "CellularEmporium" -/datum/cellular_emporium/New(my_changeling) - . = ..() - changeling = my_changeling - -/datum/cellular_emporium/Destroy() - changeling = null - . = ..() - -/datum/cellular_emporium/ui_state(mob/user) - return GLOB.always_state - -/datum/cellular_emporium/ui_interact(mob/user, datum/tgui/ui) - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "CellularEmporium", name) - ui.open() - -/datum/cellular_emporium/ui_data(mob/user) +/datum/antag_menu/cellular_emporium/ui_data(mob/user) var/list/data = list() + var/datum/antagonist/changeling/changeling = antag_datum + + if(!istype(changeling)) + CRASH("changeling menu started with wrong datum.") + return var/can_readapt = changeling.canrespec var/genetic_points_remaining = changeling.geneticpoints @@ -63,9 +51,10 @@ return data -/datum/cellular_emporium/ui_act(action, params) +/datum/antag_menu/cellular_emporium/ui_act(action, params) if(..()) return + var/datum/antagonist/changeling/changeling = antag_datum switch(action) if("readapt") @@ -82,15 +71,14 @@ icon_icon = 'icons/obj/drinks.dmi' button_icon_state = "changelingsting" background_icon_state = "bg_changeling" - var/datum/cellular_emporium/cellular_emporium + var/datum/antag_menu/cellular_emporium/cellular_emporium /datum/action/innate/cellular_emporium/New(our_target) . = ..() - button.name = name - if(istype(our_target, /datum/cellular_emporium)) + if(istype(our_target, /datum/antag_menu/cellular_emporium)) cellular_emporium = our_target else - CRASH("cellular_emporium action created with non emporium") + CRASH("cellular_emporium action created with non emporium.") /datum/action/innate/cellular_emporium/Activate() cellular_emporium.ui_interact(owner) diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index bf546dbd56fe..1cdbb5d870d7 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -1,6 +1,8 @@ -#define LING_FAKEDEATH_TIME 400 //40 seconds +/// The duration of the fakedeath coma. +#define LING_FAKEDEATH_TIME 40 SECONDS #define LING_DEAD_GENETICDAMAGE_HEAL_CAP 50 //The lowest value of geneticdamage handle_changeling() can take it to while dead. -#define LING_ABSORB_RECENT_SPEECH 8 //The amount of recent spoken lines to gain on absorbing a mob +/// The number of recent spoken lines to gain on absorbing a mob +#define LING_ABSORB_RECENT_SPEECH 8 /datum/antagonist/changeling name = "Changeling" @@ -8,7 +10,9 @@ antagpanel_category = "Changeling" show_to_ghosts = TRUE job_rank = ROLE_CHANGELING + antag_hud_name = "changeling" antag_moodlet = /datum/mood_event/changeling + ui_name = "AntagInfoChangeling" var/you_are_greet = TRUE var/give_objectives = TRUE @@ -38,10 +42,18 @@ var/canrespec = FALSE//set to TRUE in absorb.dm var/changeling_speak = 0 var/datum/dna/chosen_dna + /// The currently active changeling sting. var/datum/action/changeling/sting/chosen_sting - var/datum/cellular_emporium/cellular_emporium + /// A reference to our cellular emporium datum. + var/datum/antag_menu/cellular_emporium/cellular_emporium + /// A reference to our cellular emporium action (which opens the UI for the datum). var/datum/action/innate/cellular_emporium/emporium_action + /// UI displaying how many chems we have + var/atom/movable/screen/ling/chems/lingchemdisplay + /// UI displayng our currently active sting + var/atom/movable/screen/ling/sting/lingstingdisplay + var/static/list/all_powers = typecacheof(/datum/action/changeling,TRUE) var/list/stored_snapshots = list() //list of stored snapshots @@ -89,7 +101,7 @@ if(team_mode) forge_team_objectives() forge_objectives() - remove_clownmut() + handle_clown_mutation(owner.current, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") owner.current.grant_all_languages(FALSE, FALSE, TRUE) //Grants omnitongue. We are able to transform our body after all. . = ..() @@ -104,6 +116,21 @@ remove_changeling_powers() . = ..() +/datum/antagonist/changeling/proc/on_hud_created(datum/source) + SIGNAL_HANDLER + + var/datum/hud/ling_hud = owner.current.hud_used + + lingchemdisplay = new + lingchemdisplay.hud = ling_hud + ling_hud.infodisplay += lingchemdisplay + + lingstingdisplay = new + lingstingdisplay.hud = ling_hud + ling_hud.infodisplay += lingstingdisplay + + ling_hud.show_hud(ling_hud.hud_version) + /datum/antagonist/changeling/proc/make_absorbable() var/mob/living/carbon/C = owner.current if(ishuman(C) && (NO_DNA_COPY in C.dna.species.species_traits || !C.has_dna())) @@ -114,13 +141,6 @@ else C.fully_replace_character_name(C.dna.real_name, random_unique_name(C.gender)) -/datum/antagonist/changeling/proc/remove_clownmut() - if (owner) - var/mob/living/carbon/human/H = owner.current - if(istype(H) && owner.assigned_role == "Clown") - to_chat(H, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") - H.dna.remove_mutation(CLOWNMUT) - /datum/antagonist/changeling/proc/reset_properties() changeling_speak = 0 chosen_sting = null @@ -149,11 +169,6 @@ geneticpoints = additionalpoints - //MOVE THIS - if(owner.current.hud_used && owner.current.hud_used.lingstingdisplay) - owner.current.hud_used.lingstingdisplay.icon_state = null - owner.current.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT - /datum/antagonist/changeling/proc/reset_powers() if(purchasedpowers) remove_changeling_powers() @@ -377,21 +392,46 @@ if(ishuman(C)) add_new_profile(C) -/datum/antagonist/changeling/apply_innate_effects() +/datum/antagonist/changeling/apply_innate_effects(mob/living/mob_override) //Brains optional. - var/mob/living/carbon/C = owner.current + var/mob/mob_to_tweak = mob_override || owner.current + if(!isliving(mob_to_tweak)) + return + handle_clown_mutation(mob_to_tweak, "You have evolved beyond your clownish nature, allowing you to wield weapons without harming yourself.") + RegisterSignal(mob_to_tweak, COMSIG_LIVING_BIOLOGICAL_LIFE, PROC_REF(on_life)) + var/mob/living/carbon/C = mob_to_tweak if(istype(C)) var/obj/item/organ/brain/B = C.getorganslot(ORGAN_SLOT_BRAIN) if(B) B.organ_flags &= ~ORGAN_VITAL B.decoy_override = TRUE - update_changeling_icons_added() - return -/datum/antagonist/changeling/remove_innate_effects() - update_changeling_icons_removed() - return + if(C.hud_used) + var/datum/hud/hud_used = C.hud_used + lingchemdisplay = new /atom/movable/screen/ling/chems() + lingchemdisplay.hud = hud_used + hud_used.infodisplay += lingchemdisplay + + lingstingdisplay = new /atom/movable/screen/ling/sting() + lingstingdisplay.hud = hud_used + hud_used.infodisplay += lingstingdisplay + + hud_used.show_hud(hud_used.hud_version) + else + RegisterSignal(C, COMSIG_MOB_HUD_CREATED, PROC_REF(on_hud_created)) + +/datum/antagonist/changeling/remove_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/living_mob = mob_override || owner.current + + if(living_mob.hud_used) + var/datum/hud/hud_used = living_mob.hud_used + + hud_used.infodisplay -= lingchemdisplay + hud_used.infodisplay -= lingstingdisplay + QDEL_NULL(lingchemdisplay) + QDEL_NULL(lingstingdisplay) /datum/antagonist/changeling/greet() if (you_are_greet) @@ -405,6 +445,34 @@ /datum/antagonist/changeling/farewell() to_chat(owner.current, span_userdanger("You grow weak and lose your powers! You are no longer a changeling and are stuck in your current form!")) +/** + * Signal proc for [COMSIG_LIVING_BIOLOGICAL_LIFE]. + * Handles regenerating chemicals on life ticks. + */ +/datum/antagonist/changeling/proc/on_life(datum/source, delta_time, times_fired) + SIGNAL_HANDLER + + // If dead, we only regenerate up to half chem storage. + if(owner.current.stat == DEAD) + adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * delta_time, chem_storage * 0.5) + + // If we're not dead - we go up to the full chem cap. + else + adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * delta_time) + +/* + * Adjust the chem charges of the ling by [amount] + * and clamp it between 0 and override_cap (if supplied) or chem_storage (if no override supplied) + */ +/datum/antagonist/changeling/proc/adjust_chemicals(amount, override_cap) + if(!isnum(amount)) + return + var/cap_to = isnum(override_cap) ? override_cap : chem_storage + chem_charges = clamp(chem_charges + amount, 0, cap_to) + + lingchemdisplay?.maptext = FORMAT_ANTAG_TEXT(chem_charges, COLOR_CHANGELING_CHEMICALS) + + /datum/antagonist/changeling/proc/forge_team_objectives() if(GLOB.changeling_team_objective_type) var/datum/objective/changeling_team_objective/team_objective = new GLOB.changeling_team_objective_type @@ -509,16 +577,6 @@ objectives += identity_theft escape_objective_possible = FALSE -/datum/antagonist/changeling/proc/update_changeling_icons_added() - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING] - hud.join_hud(owner.current) - set_antag_hud(owner.current, "changeling") - -/datum/antagonist/changeling/proc/update_changeling_icons_removed() - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_CHANGELING] - hud.leave_hud(owner.current) - set_antag_hud(owner.current, null) - /datum/antagonist/changeling/admin_add(datum/mind/new_owner,mob/admin) . = ..() to_chat(new_owner.current, span_boldannounce("Our powers have awoken. A flash of memory returns to us...we are [changelingID], a changeling!")) @@ -640,6 +698,15 @@ return finish_preview_icon(final_icon) +/datum/antagonist/changeling/ui_data(mob/user) + var/list/data = list() + + data["true_name"] = changelingID + data["stolen_antag_info"] = antag_memory + data["objectives"] = get_objectives() + + return data + /datum/outfit/changeling name = "Changeling" diff --git a/code/modules/antagonists/changeling/changeling_power.dm b/code/modules/antagonists/changeling/changeling_power.dm index b7251e3fac10..e312bd4ad523 100644 --- a/code/modules/antagonists/changeling/changeling_power.dm +++ b/code/modules/antagonists/changeling/changeling_power.dm @@ -36,49 +36,61 @@ the same goes for Remove(). if you override Remove(), call parent or else your p return try_to_sting(user) +/** + *Contrary to the name, this proc isn't just used by changeling stings. It handles the activation of the action and the deducation of its cost. + *The order of the proc chain is: + *can_sting(). Should this fail, the process gets aborted early. + *sting_action(). This proc usually handles the actual effect of the action. + *Should sting_action succeed the following will be done: + *sting_feedback(). Produces feedback on the performed action. Don't ask me why this isn't handled in sting_action() + *The deduction of the cost of this power. + *Returns TRUE on a successful activation. + */ /datum/action/changeling/proc/try_to_sting(mob/user, mob/target) if(!can_sting(user, target)) - return - var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling) + return FALSE + var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) if(sting_action(user, target)) sting_feedback(user, target) - c.chem_charges -= chemical_cost + changeling.adjust_chemicals(-chemical_cost) + return TRUE + return FALSE /datum/action/changeling/proc/sting_action(mob/user, mob/target) SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]")) - return 0 + return FALSE /datum/action/changeling/proc/sting_feedback(mob/user, mob/target) - return 0 + return FALSE //Fairly important to remember to return 1 on success >.< /datum/action/changeling/proc/can_sting(mob/living/user, mob/target) 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, span_warning("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, span_warning("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, span_warning("We require at least [req_dna] sample\s of compatible DNA.")) - return 0 + return FALSE if(c.trueabsorbs < req_absorbs) to_chat(user, span_warning("We require at least [req_absorbs] sample\s of DNA gained through our Absorb ability.")) if(req_stat < user.stat) to_chat(user, span_warning("We are incapacitated.")) - return 0 + return FALSE if((HAS_TRAIT(user, TRAIT_DEATHCOMA)) && (!ignores_fakedeath)) to_chat(user, span_warning("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)) - return 0 + return FALSE if(!ishuman(user) && !ismonkey(user)) return FALSE if(req_human && !ishuman(user)) diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index fa2004469f62..a630f2f15079 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -27,8 +27,6 @@ var/mob/living/carbon/target = user.pulling return changeling.can_absorb_dna(target) - - /datum/action/changeling/absorbDNA/sting_action(mob/user) var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) absorbtimer = clamp((16 - changeling.trueabsorbs) * 10, 0, 5 SECONDS) //the more people you eat, the faster you can absorb @@ -101,7 +99,7 @@ if(nlog_type & LOG_SAY) var/list/reversed = log_source[log_type] if(islist(reversed)) - say_log = reverseRange(reversed.Copy()) + say_log = reverse_range(reversed.Copy()) break if(LAZYLEN(say_log) > LING_ABSORB_RECENT_SPEECH) @@ -130,7 +128,7 @@ target_ling.geneticpoints = 0 target_ling.canrespec = 0 changeling.chem_storage += round(target_ling.chem_storage/2) - changeling.chem_charges += min(target_ling.chem_charges, changeling.chem_storage) + changeling.adjust_chemicals(round(target_ling.chem_storage/2)) target_ling.chem_charges = 0 target_ling.chem_storage = 0 changeling.absorbedcount += (target_ling.absorbedcount) @@ -138,10 +136,8 @@ target_ling.absorbedcount = 0 target_ling.was_absorbed = TRUE - - changeling.chem_charges=min(changeling.chem_charges+10, changeling.chem_storage) - changeling.isabsorbing = 0 + changeling.adjust_chemicals(10) changeling.canrespec = 1 target.death(0) diff --git a/code/modules/antagonists/changeling/powers/fakedeath.dm b/code/modules/antagonists/changeling/powers/fakedeath.dm index ed7746223498..265a2cf46423 100644 --- a/code/modules/antagonists/changeling/powers/fakedeath.dm +++ b/code/modules/antagonists/changeling/powers/fakedeath.dm @@ -20,7 +20,7 @@ name = "Reviving Stasis" desc = "We fall into a stasis, allowing us to regenerate and trick our enemies." button_icon_state = "fake_death" - UpdateButtonIcon() + UpdateButtons() chemical_cost = 15 to_chat(user, span_notice("We have revived ourselves.")) var/datum/antagonist/changeling/C = user.mind.has_antag_datum(/datum/antagonist/changeling) @@ -67,7 +67,7 @@ name = "Revive" desc = "We arise once more." button_icon_state = "revive" - UpdateButtonIcon() + UpdateButtons() chemical_cost = 0 revive_ready = TRUE diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm index 0f70a2d6e6fa..3768c4f73b16 100644 --- a/code/modules/antagonists/changeling/powers/headcrab.dm +++ b/code/modules/antagonists/changeling/powers/headcrab.dm @@ -31,7 +31,7 @@ H.Stun(20) H.blur_eyes(20) eyes.applyOrganDamage(5) - H.confused += 3 + H.adjust_confusion(3 SECONDS) for(var/mob/living/silicon/S in range(2,user)) to_chat(S, span_userdanger("Your sensors are disabled by a shower of blood!")) S.Paralyze(60) diff --git a/code/modules/antagonists/changeling/powers/lesserform.dm b/code/modules/antagonists/changeling/powers/lesserform.dm index 36856bc0fd94..9bd94bcf0804 100644 --- a/code/modules/antagonists/changeling/powers/lesserform.dm +++ b/code/modules/antagonists/changeling/powers/lesserform.dm @@ -6,7 +6,7 @@ chemical_cost = 5 dna_cost = 1 req_human = 1 - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE //Transform into a monkey. /datum/action/changeling/lesserform/sting_action(mob/living/carbon/human/user) diff --git a/code/modules/antagonists/changeling/powers/shriek.dm b/code/modules/antagonists/changeling/powers/shriek.dm index 7cee363ff333..c45a74b0c493 100644 --- a/code/modules/antagonists/changeling/powers/shriek.dm +++ b/code/modules/antagonists/changeling/powers/shriek.dm @@ -19,8 +19,8 @@ var/mob/living/carbon/C = M if(!C.mind || !C.mind.has_antag_datum(/datum/antagonist/changeling)) C.adjustEarDamage(0, 30) - C.confused += 25 - C.Jitter(50) + C.adjust_confusion(25 SECONDS) + C.adjust_jitter(50 SECONDS) else SEND_SOUND(C, sound('sound/effects/screech.ogg')) diff --git a/code/modules/antagonists/changeling/powers/tiny_prick.dm b/code/modules/antagonists/changeling/powers/tiny_prick.dm index c609428ce39b..917af6d3a848 100644 --- a/code/modules/antagonists/changeling/powers/tiny_prick.dm +++ b/code/modules/antagonists/changeling/powers/tiny_prick.dm @@ -21,17 +21,17 @@ var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) changeling.chosen_sting = src - user.hud_used.lingstingdisplay.icon = 'icons/obj/changeling.dmi' - user.hud_used.lingstingdisplay.icon_state = sting_icon - user.hud_used.lingstingdisplay.invisibility = 0 + changeling.lingstingdisplay.icon = 'icons/obj/changeling.dmi' + changeling.lingstingdisplay.icon_state = sting_icon + changeling.lingstingdisplay.invisibility = 0 /datum/action/changeling/sting/proc/unset_sting(mob/user) to_chat(user, span_warning("We retract our sting, we can't sting anyone for now.")) var/datum/antagonist/changeling/changeling = user.mind.has_antag_datum(/datum/antagonist/changeling) changeling.chosen_sting = null - user.hud_used.lingstingdisplay.icon_state = null - user.hud_used.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT + changeling.lingstingdisplay.icon_state = null + changeling.lingstingdisplay.invisibility = INVISIBILITY_ABSTRACT /mob/living/carbon/proc/unset_sting() if(mind) diff --git a/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm b/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm index 37f6f0b2d77e..529c89bbb5b0 100644 --- a/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm +++ b/code/modules/antagonists/clockcult/clock_helpers/hierophant_network.dm @@ -29,7 +29,7 @@ icon_icon = 'icons/mob/actions/actions_clockcult.dmi' button_icon_state = "hierophant" background_icon_state = "bg_clock" - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS buttontooltipstyle = "clockcult" var/title = "Servant" var/span_for_name = "heavy_brass" diff --git a/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm b/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm index adf5c4ada069..8c890a10da3a 100644 --- a/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm +++ b/code/modules/antagonists/clockcult/clock_items/clock_weapons/_call_weapon.dm @@ -6,7 +6,7 @@ icon_icon = 'icons/mob/actions/actions_clockcult.dmi' button_icon_state = "ratvarian_spear" background_icon_state = "bg_clock" - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS buttontooltipstyle = "clockcult" var/cooldown = 0 var/obj/item/clockwork/weapon/weapon_type //The type of weapon to create diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm index 632eeb4c75f0..dd5a711cfd55 100644 --- a/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm +++ b/code/modules/antagonists/clockcult/clock_items/clockwork_armor.dm @@ -117,7 +117,7 @@ user.emote("scream") user.apply_damage(15, BURN, BODY_ZONE_CHEST) user.adjust_fire_stacks(2) - user.IgniteMob() + user.ignite_mob() addtimer(CALLBACK(user, /mob/living.proc/dropItemToGround, src, TRUE), 1) /obj/item/clothing/gloves/clockwork diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm index e1e5f427e9ac..b70d1e9b5c6e 100644 --- a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm +++ b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm @@ -149,8 +149,8 @@ return 0 if(!is_servant_of_ratvar(user)) to_chat(user, span_warning("The information on [src]'s display shifts rapidly. After a moment, your head begins to pound, and you tear your eyes away.")) - user.confused += 5 - user.dizziness += 5 + user.adjust_confusion(5 SECONDS) + user.adjust_dizzy(5 SECONDS) return 0 if(busy) to_chat(user, span_warning("[src] refuses to work, displaying the message: \"[busy]!\"")) @@ -517,6 +517,6 @@ Q.name = "[quickbind_slot.name] ([Q.scripture_index])" Q.desc = quickbind_slot.quickbind_desc Q.button_icon_state = quickbind_slot.name - Q.UpdateButtonIcon() + Q.UpdateButtons() if(isliving(loc)) Q.Grant(loc) diff --git a/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm b/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm index edd0488b5cd9..dbcca00edf57 100644 --- a/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm +++ b/code/modules/antagonists/clockcult/clock_items/judicial_visor.dm @@ -47,7 +47,7 @@ to_chat(user, "[span_heavy_brass("\"Consider yourself judged, whelp.\"")]") to_chat(user, span_userdanger("You suddenly catch fire!")) user.adjust_fire_stacks(5) - user.IgniteMob() + user.ignite_mob() return 1 /obj/item/clothing/glasses/judicial_visor/dropped(mob/user) @@ -204,7 +204,7 @@ L.visible_message(span_warning("[L] is struck by a judicial explosion!"), \ "[span_heavy_brass("\"Keep an eye out, filth.\"")]\n[span_userdanger("A burst of heat crushes you against the ground!")]") L.adjust_fire_stacks(2) //sets cultist targets on fire - L.IgniteMob() + L.ignite_mob() L.adjustFireLoss(5) targetsjudged++ if(!QDELETED(L)) diff --git a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm index 9d8b47302ff5..6f72f9ffed64 100644 --- a/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm +++ b/code/modules/antagonists/clockcult/clock_scriptures/scripture_scripts.dm @@ -149,7 +149,7 @@ icon_icon = 'icons/mob/actions/actions_clockcult.dmi' button_icon_state = "clockwork_armor" background_icon_state = "bg_clock" - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS buttontooltipstyle = "clockcult" var/cooldown = 0 var/static/list/ratvarian_armor_typecache = typecacheof(list( diff --git a/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm b/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm index 53e6999f3b26..c719c062020a 100644 --- a/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm +++ b/code/modules/antagonists/clockcult/clock_structures/ocular_warden.dm @@ -79,7 +79,7 @@ last_process = world.time if(GLOB.ratvar_awakens && L) L.adjust_fire_stacks(damage_per_tick) - L.IgniteMob() + L.ignite_mob() else if(ismecha(target)) var/obj/mecha/M = target Beam(M, icon_state = "warden_beam", time = 10) //yogs: gives a beam diff --git a/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm b/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm index 8be3334257f7..62de05b8dbeb 100644 --- a/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm +++ b/code/modules/antagonists/clockcult/clock_structures/taunting_trail.dm @@ -55,8 +55,7 @@ /obj/structure/destructible/clockwork/taunting_trail/proc/affect_mob(mob/living/L) if(istype(L) && !is_servant_of_ratvar(L)) if(!L.anti_magic_check(chargecost = 0)) - L.confused = min(L.confused + 15, 50) - L.dizziness = min(L.dizziness + 15, 50) - if(L.confused >= 25) - L.Paralyze(FLOOR(L.confused * 0.8, 1)) + L.adjust_confusion_up_to(15 SECONDS, 50 SECONDS) + L.adjust_dizzy_up_to(15 SECONDS, 50 SECONDS) + L.Paralyze(FLOOR(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 0.8, 1)) take_damage(max_integrity) diff --git a/code/modules/antagonists/clockcult/clockcult.dm b/code/modules/antagonists/clockcult/clockcult.dm index 862c4d2893b5..f89b6c88a3b7 100644 --- a/code/modules/antagonists/clockcult/clockcult.dm +++ b/code/modules/antagonists/clockcult/clockcult.dm @@ -4,6 +4,7 @@ roundend_category = "clock cultists" antagpanel_category = "Clockcult" job_rank = ROLE_SERVANT_OF_RATVAR + antag_hud_name = "clockwork" show_to_ghosts = TRUE antag_moodlet = /datum/mood_event/cult var/datum/action/innate/hierophant/hierophant_network = new() @@ -62,7 +63,6 @@ /datum/antagonist/clockcult/on_gain() var/mob/living/current = owner.current SSticker.mode.servants_of_ratvar += owner - SSticker.mode.update_servant_icons_added(owner) owner.special_role = ROLE_SERVANT_OF_RATVAR owner.current.log_message("has been converted to the cult of Ratvar!", LOG_ATTACK, color="#BE8700") if(issilicon(current)) @@ -132,8 +132,10 @@ current.throw_alert("clockinfo", /atom/movable/screen/alert/clockwork/infodump) if(clockwork_ark_active() && ishuman(current)) current.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER)) + add_team_hud(current) /datum/antagonist/clockcult/remove_innate_effects(mob/living/mob_override) + . = ..() var/mob/living/current = owner.current if(istype(mob_override)) current = mob_override @@ -156,7 +158,6 @@ S.update_icons() S.show_laws() var/mob/living/temp_owner = current - ..() if(iscyborg(temp_owner)) var/mob/living/silicon/robot/R = temp_owner R.module.rebuild_modules() @@ -167,7 +168,6 @@ /datum/antagonist/clockcult/on_removal() SSticker.mode.servants_of_ratvar -= owner - SSticker.mode.update_servant_icons_removed(owner) if(!silent) owner.current.visible_message("[span_deconversion_message("[owner.current] seems to have remembered [owner.current.p_their()] true allegiance!")]", null, null, null, owner.current) to_chat(owner, span_userdanger("A cold, cold darkness flows through your mind, extinguishing the Justiciar's light and all of your memories as his servant.")) diff --git a/code/modules/antagonists/creep/creep.dm b/code/modules/antagonists/creep/creep.dm index cc887dafe65c..66c0e980e5fc 100644 --- a/code/modules/antagonists/creep/creep.dm +++ b/code/modules/antagonists/creep/creep.dm @@ -4,6 +4,7 @@ antagpanel_category = "Other" preview_outfit = /datum/outfit/obsessed job_rank = ROLE_OBSESSED + antag_hud_name = "obsessed" show_name_in_check_antagonists = TRUE roundend_category = "obsessed" silent = TRUE //not actually silent, because greet will be called by the trauma anyway. @@ -72,14 +73,13 @@ /datum/antagonist/obsessed/apply_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current - update_obsession_icons_added(M) - if(owner.current && ishuman(owner.current) && !owner.current.GetComponent(/datum/component/mood)) + if(M && ishuman(M) && !M.GetComponent(/datum/component/mood)) to_chat(owner, span_danger("You feel more aware of your condition, mood has been enabled!")) - owner.current.AddComponent(/datum/component/mood) //you fool you absolute buffoon to think you could escape + M.AddComponent(/datum/component/mood) //you fool you absolute buffoon to think you could escape /datum/antagonist/obsessed/remove_innate_effects(mob/living/mob_override) + . = ..() var/mob/living/M = mob_override || owner.current - update_obsession_icons_removed(M) var/mob/living/carbon/human/H = M if(H && !H.mood_enabled) var/datum/component/C = M.GetComponent(/datum/component/mood) @@ -328,13 +328,3 @@ explanation_text = "Steal [target.name]'s family heirloom, [steal_target] they cherish." else explanation_text = "Free Objective" - -/datum/antagonist/obsessed/proc/update_obsession_icons_added(var/mob/living/carbon/human/obsessed) - var/datum/atom_hud/antag/creephud = GLOB.huds[ANTAG_HUD_OBSESSED] - creephud.join_hud(obsessed) - set_antag_hud(obsessed, "obsessed") - -/datum/antagonist/obsessed/proc/update_obsession_icons_removed(var/mob/living/carbon/human/obsessed) - var/datum/atom_hud/antag/creephud = GLOB.huds[ANTAG_HUD_OBSESSED] - creephud.leave_hud(obsessed) - set_antag_hud(obsessed, null) diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm index ffc219b907db..2e96b2b6de8c 100644 --- a/code/modules/antagonists/cult/blood_magic.dm +++ b/code/modules/antagonists/cult/blood_magic.dm @@ -5,12 +5,6 @@ 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) @@ -21,17 +15,6 @@ 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() var/rune = FALSE var/limit = RUNELESS_MAX_BLOODCHARGE @@ -104,8 +87,6 @@ desc += "
Has [charges] use\s remaining." all_magic = BM ..() - button.locked = TRUE - button.ordered = FALSE /datum/action/innate/cult/blood_spell/Remove() if(all_magic) @@ -213,67 +194,43 @@ 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 + click_action = TRUE + enable_text = span_cult("You prepare to horrify a target...") + disable_text = span_cult("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(iscultist(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/do_ability(mob/living/caller, params, mob/living/carbon/human/clicked_on) + clicked_on.hallucination = max(clicked_on.hallucination, 120) + SEND_SOUND(caller, sound('sound/effects/ghost.ogg', FALSE, TRUE, 50)) + var/image/sparkle_image = image('icons/effects/cult_effects.dmi', clicked_on, "bloodsparkles", ABOVE_MOB_LAYER) + clicked_on.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/cult, "cult_apoc", sparkle_image, NONE) -/obj/effect/proc_holder/horror/proc/toggle(mob/user) - if(active) - remove_ranged_ability(span_cult("You dispel the magic...")) - else - add_ranged_ability(user, span_cult("You prepare to horrify a target...")) + addtimer(CALLBACK(clicked_on, TYPE_PROC_REF(/atom/, remove_alt_appearance), "cult_apoc", TRUE), 4 MINUTES, TIMER_OVERRIDE|TIMER_UNIQUE) + to_chat(caller, span_cultbold("[clicked_on] has been cursed with living nightmares!")) -/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(target in view(7, get_turf(ranged_ability_user))) - 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,/atom/.proc/remove_alt_appearance,"cult_apoc",TRUE), 2400, TIMER_OVERRIDE|TIMER_UNIQUE) - to_chat(ranged_ability_user,span_cult("[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(span_cult("You have exhausted the spell's power!")) - qdel(src) + charges-- + desc = base_desc + desc += "
Has [charges] use\s remaining." + UpdateButtons() + if(charges <= 0) + to_chat(caller, span_cult("You have exhausted the spell's power!")) + qdel(src) + + return TRUE /datum/action/innate/cult/blood_spell/veiling name = "Conceal Presence" @@ -322,7 +279,7 @@ qdel(src) desc = base_desc desc += "
Has [charges] use\s remaining." - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/cult/blood_spell/manipulation name = "Blood Rites" @@ -374,7 +331,7 @@ source.charges = uses source.desc = source.base_desc source.desc += "
Has [uses] use\s remaining." - source.UpdateButtonIcon() + source.UpdateButtons() ..() /obj/item/melee/blood_magic/attack_self(mob/living/user) @@ -403,7 +360,7 @@ else if(source) source.desc = source.base_desc source.desc += "
Has [uses] use\s remaining." - source.UpdateButtonIcon() + source.UpdateButtons() //Stun /obj/item/melee/blood_magic/stun @@ -453,7 +410,7 @@ else if(iscarbon(target)) var/mob/living/carbon/C = L C.cultslurring += 10 - C.Jitter(15) + C.adjust_jitter(15 SECONDS) if(is_servant_of_ratvar(L)) L.adjustBruteLoss(30) else if(cult.cult_risen) @@ -466,9 +423,9 @@ else if(iscarbon(target)) var/mob/living/carbon/C = L C.silent += 3 - C.stuttering += 7 + C.adjust_stutter(7 SECONDS) C.cultslurring += 7 - C.Jitter(7) + C.adjust_jitter(7 SECONDS) if(is_servant_of_ratvar(L)) L.adjustBruteLoss(20) else @@ -481,9 +438,9 @@ else if(iscarbon(target)) var/mob/living/carbon/C = L C.silent += 6 - C.stuttering += 15 + C.adjust_stutter(15 SECONDS) C.cultslurring += 15 - C.Jitter(15) + C.adjust_jitter(15 SECONDS) if(is_servant_of_ratvar(L)) L.adjustBruteLoss(15) else diff --git a/code/modules/antagonists/cult/cult.dm b/code/modules/antagonists/cult/cult.dm index eac79a76e68c..875b3f292c4e 100644 --- a/code/modules/antagonists/cult/cult.dm +++ b/code/modules/antagonists/cult/cult.dm @@ -13,6 +13,7 @@ var/datum/action/innate/cult/blood_magic/magic = new preview_outfit = /datum/outfit/cultist job_rank = ROLE_CULTIST + antag_hud_name = "cult" var/ignore_implant = FALSE var/give_equipment = FALSE var/datum/team/cult/cult_team @@ -78,7 +79,6 @@ if(give_equipment) equip_cultist(TRUE) SSticker.mode.cult += owner // Only add after they've been given objectives - SSticker.mode.update_cult_icons_added(owner) current.log_message("has been converted to the cult of Nar'Sie!", LOG_ATTACK, color="#960000") if(cult_team.blood_target && cult_team.blood_target_image && current.client) @@ -136,35 +136,30 @@ r_hand = /obj/item/melee/cultblade /datum/antagonist/cult/proc/equip_cultist(metal=TRUE) - var/mob/living/carbon/H = owner.current - if(!istype(H)) - return - if (owner.assigned_role == "Clown") - to_chat(owner, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - H.dna.remove_mutation(CLOWNMUT) - . += cult_give_item(/obj/item/melee/cultblade/dagger, H) + handle_clown_mutation(owner.current, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") + . += cult_give_item(/obj/item/melee/cultblade/dagger, owner.current) if(metal) - . += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, H) + . += cult_give_item(/obj/item/stack/sheet/runed_metal/ten, owner.current) to_chat(owner, "These will help you start the cult on this station. Use them well, and remember - you are not the only one.
") -/datum/antagonist/cult/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/mob) +/datum/antagonist/cult/proc/cult_give_item(obj/item/item_path, mob/living/carbon/human/current_mob) var/list/slots = list( "backpack" = SLOT_IN_BACKPACK, "left pocket" = SLOT_L_STORE, "right pocket" = SLOT_R_STORE ) - var/T = new item_path(mob) + var/T = new item_path(current_mob) var/item_name = initial(item_path.name) - var/where = mob.equip_in_one_of_slots(T, slots) + var/where = current_mob.equip_in_one_of_slots(T, slots) if(!where) - to_chat(mob, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).")) + to_chat(current_mob, span_userdanger("Unfortunately, you weren't able to get a [item_name]. This is very bad and you should adminhelp immediately (press F1).")) return 0 else - to_chat(mob, span_danger("You have a [item_name] in your [where].")) + to_chat(current_mob, span_danger("You have a [item_name] in your [where].")) if(where == "backpack") - SEND_SIGNAL(mob.back, COMSIG_TRY_STORAGE_SHOW, mob) + SEND_SIGNAL(current_mob.back, COMSIG_TRY_STORAGE_SHOW, current_mob) return TRUE /datum/antagonist/cult/apply_innate_effects(mob/living/mob_override) @@ -185,6 +180,8 @@ if(cult_team.cult_ascendent) cult_team.ascend(current) + add_team_hud(current) + /datum/antagonist/cult/remove_innate_effects(mob/living/mob_override) . = ..() var/mob/living/current = owner.current @@ -206,7 +203,6 @@ /datum/antagonist/cult/on_removal() SSticker.mode.cult -= owner - SSticker.mode.update_cult_icons_removed(owner) if(!silent) owner.current.visible_message("[span_deconversion_message("[owner.current] looks like [owner.current.p_theyve()] just reverted to [owner.current.p_their()] old faith!")]", null, null, null, owner.current) to_chat(owner.current, span_userdanger("An unfamiliar white light flashes through your mind, cleansing the taint of the Geometer and all your memories as her servant.")) @@ -249,6 +245,7 @@ /datum/antagonist/cult/master ignore_implant = TRUE show_in_antagpanel = FALSE //Feel free to add this later + antag_hud_name = "cultmaster" var/datum/action/innate/cult/master/finalreck/reckoning = new var/datum/action/innate/cult/master/cultmark/bloodmark = new var/datum/action/innate/cult/master/pulse/throwing = new @@ -259,11 +256,6 @@ QDEL_NULL(throwing) return ..() -/datum/antagonist/cult/master/on_gain() - . = ..() - var/mob/living/current = owner.current - set_antag_hud(current, "cultmaster") - /datum/antagonist/cult/master/greet() to_chat(owner.current, "[span_cultlarge("You are the cult's Master")]. As the cult's Master, you have a unique title and loud voice when communicating, are capable of marking \ targets, such as a location or a noncultist, to direct the cult to them, and, finally, you are capable of summoning the entire living cult to your location once.") @@ -284,6 +276,7 @@ cult_team.rise(current) if(cult_team.cult_ascendent) cult_team.ascend(current) + add_team_hud(current, /datum/antagonist/cult) /datum/antagonist/cult/master/remove_innate_effects(mob/living/mob_override) . = ..() @@ -307,8 +300,11 @@ /datum/team/cult name = "Cult" - var/blood_target + ///The blood mark target + var/atom/blood_target + ///Image of the blood mark target var/image/blood_target_image + ///Timer for the blood mark expiration var/blood_target_reset_timer var/cult_vote_called = FALSE @@ -338,7 +334,7 @@ if(B.current) SEND_SOUND(B.current, 'sound/hallucinations/i_see_you2.ogg') to_chat(B.current, span_cultlarge("The veil weakens as your cult grows, your eyes begin to glow...")) - addtimer(CALLBACK(src, .proc/rise, B.current), 200) + addtimer(CALLBACK(src, PROC_REF(rise), B.current), 20 SECONDS) cult_risen = TRUE if(ratio > CULT_ASCENDENT && !cult_ascendent) @@ -346,7 +342,7 @@ if(B.current) SEND_SOUND(B.current, 'sound/hallucinations/im_here1.ogg') to_chat(B.current, "Your cult is ascendent and the red harvest approaches - you cannot hide your true nature for much longer!!") - addtimer(CALLBACK(src, .proc/ascend, B.current), 200) + addtimer(CALLBACK(src, PROC_REF(ascend), B.current), 20 SECONDS) cult_ascendent = TRUE @@ -574,6 +570,64 @@ /datum/team/cult/is_gamemode_hero() return SSticker.mode.name == "cult" +/// 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, span_bold(span_cultlarge("[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, span_bold(span_cultlarge("The blood mark's target is lost!"))) + else + to_chat(cultist.current, span_bold(span_cultlarge("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/outfit/cultist name = "Cultist (Preview only)" diff --git a/code/modules/antagonists/cult/cult_comms.dm b/code/modules/antagonists/cult/cult_comms.dm index 56068ebdf91f..419d50d0656d 100644 --- a/code/modules/antagonists/cult/cult_comms.dm +++ b/code/modules/antagonists/cult/cult_comms.dm @@ -4,7 +4,8 @@ icon_icon = 'icons/mob/actions/actions_cult.dmi' background_icon_state = "bg_demon" buttontooltipstyle = "cult" - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED|AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS + ranged_mousepointer = 'icons/effects/mouse_pointers/cult_target.dmi' /datum/action/innate/cult/IsAvailable() if(!iscultist(owner)) @@ -215,163 +216,118 @@ 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 - ..() + click_action = TRUE + enable_text = span_cult("You prepare to mark a target for your cult. Click a target to mark them!") + disable_text = span_cult("You cease the marking ritual.") + /// The duration of the mark itself + var/cult_mark_duration = 90 SECONDS + /// The duration of the cooldown for cult marks + var/cult_mark_cooldown_duration = 2 MINUTES + /// The actual cooldown tracked of the action + COOLDOWN_DECLARE(cult_mark_cooldown) /datum/action/innate/cult/master/cultmark/IsAvailable() - if(cooldown > world.time) - if(!CM.active) - to_chat(owner, span_cultlarge("You need to wait [DisplayTimeText(cooldown - world.time)] before you can mark another target!")) + return ..() && COOLDOWN_FINISHED(src, cult_mark_cooldown) + +/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 +/datum/action/innate/cult/master/cultmark/do_ability(mob/living/caller, params, atom/clicked_on) + var/datum/antagonist/cult/cultist = caller.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) + to_chat(caller, span_cult("The cult has already designated a target!")) + return FALSE + if(cult_team.set_blood_target(clicked_on, caller, cult_mark_duration)) + unset_ranged_ability(caller, span_cult("The marking rite is complete! It will last for [DisplayTimeText(cult_mark_duration)] seconds.")) + COOLDOWN_START(src, cult_mark_cooldown, cult_mark_cooldown_duration) + UpdateButtons() + addtimer(CALLBACK(src, PROC_REF(UpdateButtons)), cult_mark_cooldown_duration + 1) + return TRUE + unset_ranged_ability(caller, span_cult("The marking rite failed!")) 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/ghostmark //Ghost version + name = "Blood Mark your Target" + 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) -/obj/effect/proc_holder/cultmark/Destroy() - attached_action = null - return ..() +/datum/action/innate/cult/ghostmark/IsAvailable() + return ..() && istype(owner, /mob/dead/observer) -/obj/effect/proc_holder/cultmark/proc/toggle(mob/user) - if(active) - remove_ranged_ability(span_cult("You cease the marking ritual.")) - else - add_ranged_ability(user, span_cult("You prepare to mark a target for your cult...")) +/datum/action/innate/cult/ghostmark/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.") -/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) + if(!COOLDOWN_FINISHED(src, cult_mark_cooldown)) + cult_team.unset_blood_target_and_timer() + to_chat(owner, span_cultbold("You have cleared the cult's blood target!")) + return TRUE + to_chat(owner, span_cultbold("The cult has already designated a target!")) + return FALSE + if(!COOLDOWN_FINISHED(src, cult_mark_cooldown)) + to_chat(owner, span_cultbold("You aren't ready to place another blood mark yet!")) return FALSE - var/datum/antagonist/cult/C = caller.mind.has_antag_datum(/datum/antagonist/cult,TRUE) + var/atom/mark_target = owner.orbiting?.parent || get_turf(owner) + if(!mark_target) + return FALSE - if(target in view(7, get_turf(ranged_ability_user))) - if(C.cult_team.blood_target) - to_chat(ranged_ability_user, span_cult("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, /mob.proc/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, span_cultlarge("[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(span_cult("The marking rite is complete! It will last for 90 seconds.")) - C.cult_team.blood_target_reset_timer = addtimer(CALLBACK(GLOBAL_PROC, .proc/reset_blood_target,C.cult_team), 900, TIMER_STOPPABLE) + if(cult_team.set_blood_target(mark_target, owner, 60 SECONDS)) + to_chat(owner, span_cultbold("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/reset_button), cult_mark_cooldown_duration + 1) return TRUE - return FALSE - -/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,span_cultlarge("The blood mark has expired!")) - B.current.client.images -= team.blood_target_image - QDEL_NULL(team.blood_target_image) - team.blood_target = null + to_chat(owner, span_cult("The marking failed!")) + 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." +/datum/action/innate/cult/ghostmark/proc/update_button_status() + if(!owner) + return -/datum/action/innate/cult/master/cultmark/ghost/IsAvailable() - if(istype(owner, /mob/dead/observer) && iscultist(owner.mind.current)) - return TRUE + if(COOLDOWN_FINISHED(src, cult_mark_duration)) + name = initial(name) + desc = initial(desc) + button_icon_state = initial(button_icon_state) 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 + name = "Clear the Blood Mark" + desc = "Remove the Blood Mark you previously set." + button_icon_state = "emp" + + UpdateButtons() -/datum/action/innate/cult/ghostmark/IsAvailable() - if(istype(owner, /mob/dead/observer) && iscultist(owner.mind.current)) - return TRUE - else - qdel(src) /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,span_cultbold("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, span_cultbold("You have cleared the cult's blood target!")) - deltimer(C.cult_team.blood_target_reset_timer) - return - else - to_chat(owner, span_cultbold("The cult has already designated a target!")) - return - if(cooldown>world.time) - to_chat(owner, span_cultbold("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, /mob.proc/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, span_cultlarge("[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,span_cultbold("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, .proc/reset_blood_target,C.cult_team), base_cooldown, TIMER_STOPPABLE) - addtimer(CALLBACK(src, .proc/reset_button), base_cooldown) + SEND_SOUND(owner, 'sound/magic/enter_blood.ogg') + to_chat(owner, span_cultbold("Your previous mark is gone - you are now ready to create a new blood mark.")) + update_button_status() //////// ELDRITCH PULSE ///////// @@ -382,82 +338,86 @@ desc = "Seize upon a fellow cultist or cult structure and teleport it to a nearby location." icon_icon = 'icons/mob/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 - ..() + click_action = TRUE + enable_text = span_cult("You prepare to tear through the fabric of reality... Click a target to sieze them!") + disable_text = span_cult("You cease your preparations.") + /// Weakref to whoever we're currently about to toss + var/datum/weakref/throwee_ref + /// Cooldown of the ability + var/pulse_cooldown_duration = 15 SECONDS + /// The actual cooldown tracked of the action + COOLDOWN_DECLARE(pulse_cooldown) /datum/action/innate/cult/master/pulse/IsAvailable() - if(!owner.mind || !owner.mind.has_antag_datum(/datum/antagonist/cult/master)) + return ..() && COOLDOWN_FINISHED(src, pulse_cooldown) + +/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, span_cultlarge("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/do_ability(mob/living/caller, params, atom/clicked_on) + var/atom/throwee = throwee_ref?.resolve() + if(QDELETED(throwee)) + to_chat(caller, span_cult("You lost your target!")) + throwee = null + throwee_ref = null + return FALSE + if(throwee) + if(get_dist(throwee, clicked_on) >= 16) + to_chat(caller, span_cult("You can't teleport [clicked_on.p_them()] that far!")) + return FALSE + var/turf/throwee_turf = get_turf(throwee) + + playsound(throwee_turf, 'sound/magic/exit_blood.ogg') + new /obj/effect/temp_visual/cult/sparks(throwee_turf, caller.dir) + throwee.visible_message( + span_warning("A pulse of magic whisks [throwee] away!"), + span_cult("A pulse of blood magic whisks you away..."), + ) + + if(!do_teleport(throwee, clicked_on, channel = TELEPORT_CHANNEL_CULT)) + to_chat(caller, span_cult("The teleport fails!")) + throwee.visible_message( + span_warning("...Except they don't go very far"), + span_cult("...Except you don't appear to have moved very far."), + ) + return FALSE -/datum/action/innate/cult/master/pulse/Activate() - PM.toggle(owner) //the important bit - return TRUE - -/obj/effect/proc_holder/pulse - active = FALSE - ranged_mousepointer = 'icons/effects/throw_target.dmi' - var/datum/action/innate/cult/master/pulse/attached_action + throwee_turf.Beam(clicked_on, icon_state = "sendbeam", time = 0.4 SECONDS) + new /obj/effect/temp_visual/cult/sparks(get_turf(clicked_on), caller.dir) + throwee.visible_message( + span_warning("[throwee] appears suddenly in a pulse of magic!"), + span_cult("...And you appear elsewhere."), + ) -/obj/effect/proc_holder/pulse/Destroy() - attached_action = null - return ..() + COOLDOWN_START(src, pulse_cooldown, pulse_cooldown_duration) + to_chat(caller, span_cult("A pulse of blood magic surges through you as you shift [throwee] through time and space.")) + caller.click_intercept = null + throwee_ref = null + UpdateButtons() + addtimer(CALLBACK(src, PROC_REF(UpdateButtons)), pulse_cooldown_duration + 1) + return TRUE -/obj/effect/proc_holder/pulse/proc/toggle(mob/user) - if(active) - remove_ranged_ability(span_cult("You cease your preparations...")) - attached_action.throwing = FALSE - else - add_ranged_ability(user, span_cult("You prepare to tear through the fabric of reality...")) + if(isliving(clicked_on)) + var/mob/living/living_clicked = clicked_on + if(!iscultist(living_clicked)) + return FALSE + SEND_SOUND(caller, sound('sound/weapons/thudswoosh.ogg')) + to_chat(caller, span_cultbold("You reach through the veil with your mind's eye and seize [clicked_on]! Click anywhere nearby to teleport [clicked_on.p_them()]!")) + throwee_ref = WEAKREF(clicked_on) + 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(target in view(7, get_turf(ranged_ability_user))) - 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,span_cult("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(span_cult("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, /mob.proc/update_action_buttons_icon), attached_action.base_cooldown) + if(istype(clicked_on, /obj/structure/destructible/cult)) + to_chat(caller, span_cultbold("You reach through the veil with your mind's eye and lift [clicked_on]! Click anywhere nearby to teleport it!")) + throwee_ref = WEAKREF(clicked_on) + return TRUE + return FALSE diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index bbdda7a0ca8c..3202f492b95c 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -414,7 +414,7 @@ to_chat(user, span_cultlarge("\"I wouldn't advise that.\"")) to_chat(user, span_warning("An overwhelming sense of nausea overpowers you!")) user.dropItemToGround(src, TRUE) - user.Dizzy(30) + user.adjust_dizzy(3 SECONDS) user.Paralyze(100) else to_chat(user, span_cultlarge("\"Trying to use things you don't own is bad, you know.\"")) @@ -466,7 +466,7 @@ to_chat(user, span_cultlarge("\"I wouldn't advise that.\"")) to_chat(user, span_warning("An overwhelming sense of nausea overpowers you!")) user.dropItemToGround(src, TRUE) - user.Dizzy(30) + user.adjust_dizzy(30) user.Paralyze(100) else to_chat(user, span_cultlarge("\"Trying to use things you don't own is bad, you know.\"")) @@ -487,7 +487,7 @@ if(!iscultist(user)) to_chat(user, span_cultlarge("\"You want to be blind, do you?\"")) user.dropItemToGround(src, TRUE) - user.Dizzy(30) + user.adjust_dizzy(30) user.Paralyze(100) user.blind_eyes(30) @@ -748,8 +748,6 @@ GLOBAL_VAR_INIT(curselimit, 0) /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() if(owner == spear.loc || cooldown > world.time) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 4b9b4f0ee944..be51a01e1f56 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -302,8 +302,8 @@ structure_check() searches for nearby cultist structures required for the invoca message_admins("[key_name_admin(C)] has taken control of ([key_name_admin(H)]) to replace a jobbanned player.") H.key = C.key H.uncuff() - H.stuttering = 0 - H.cultslurring = 0 + H.remove_status_effect(/datum/status_effect/speech/slurring/cult) + H.remove_status_effect(/datum/status_effect/speech/stutter) return TRUE /obj/effect/rune/convert/proc/do_sacrifice(mob/living/sacrificial, list/invokers) @@ -1057,7 +1057,7 @@ structure_check() searches for nearby cultist structures required for the invoca continue if(ishuman(M)) if(!iscultist(M)) - AH.remove_hud_from(M) + AH.hide_from(M) addtimer(CALLBACK(GLOBAL_PROC, .proc/hudFix, M), duration) var/image/A = image('icons/mob/mob.dmi',M,"cultist", ABOVE_MOB_LAYER) A.override = 1 @@ -1150,4 +1150,4 @@ structure_check() searches for nearby cultist structures required for the invoca var/obj/O = target.get_item_by_slot(SLOT_GLASSES) if(istype(O, /obj/item/clothing/glasses/hud/security)) var/datum/atom_hud/AH = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - AH.add_hud_to(target) + AH.show_to(target) diff --git a/code/modules/antagonists/demon/demons.dm b/code/modules/antagonists/demon/demons.dm index 5e3a55003cee..f5fb6fbf601b 100644 --- a/code/modules/antagonists/demon/demons.dm +++ b/code/modules/antagonists/demon/demons.dm @@ -65,7 +65,7 @@ else L.visible_message(span_warning("[L] continues to burn!"), span_danger("You continue to burn!")) L.adjust_fire_stacks(5) - L.IgniteMob() + L.ignite_mob() return /datum/antagonist/sinfuldemon/New() @@ -128,10 +128,7 @@ owner.current.faction += "hell" for(var/all_traits in sinfuldemon_traits) ///adds demon traits ADD_TRAIT(owner.current, all_traits, SINFULDEMON_TRAIT) - if(owner.assigned_role == "Clown" && ishuman(owner.current)) - var/mob/living/carbon/human/S = owner.current - to_chat(S, span_notice("Your infernal nature has allowed you to overcome your clownishness.")) - S.dna.remove_mutation(CLOWNMUT) + handle_clown_mutation(owner.current, "Your infernal nature has allowed you to overcome your clownishness.") switch(demonsin) if(SIN_GLUTTONY) owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/shapeshift/demon/gluttony) diff --git a/code/modules/antagonists/devil/devil.dm b/code/modules/antagonists/devil/devil.dm index d243aeff5fa5..8688b08e99e1 100644 --- a/code/modules/antagonists/devil/devil.dm +++ b/code/modules/antagonists/devil/devil.dm @@ -84,11 +84,16 @@ GLOBAL_LIST_INIT(devil_pre_title, list("Dark ", "Hellish ", "Fallen ", "Fiery ", GLOBAL_LIST_INIT(devil_title, list("Lord ", "Prelate ", "Count ", "Viscount ", "Vizier ", "Elder ", "Adept ")) GLOBAL_LIST_INIT(devil_syllable, list("hal", "ve", "odr", "neit", "ci", "quon", "mya", "folth", "wren", "geyr", "hil", "niet", "twou", "phi", "coa")) GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master", ", the Lord of all things", ", Jr.")) + +/* + * Datum + */ /datum/antagonist/devil name = "Devil" roundend_category = "devils" antagpanel_category = "Devil" job_rank = ROLE_DEVIL + antag_hud_name = "devil" show_to_ghosts = TRUE var/obligation var/ban @@ -515,15 +520,12 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master", var/laws = list("You may not use violence to coerce someone into selling their soul.", "You may not directly and knowingly physically harm a devil, other than yourself.", GLOB.lawlorify[LAW][ban], GLOB.lawlorify[LAW][obligation], "Accomplish your objectives at all costs.") robot_devil.set_law_sixsixsix(laws) sleep(1 SECONDS) - if(owner.assigned_role == "Clown" && ishuman(owner.current)) - var/mob/living/carbon/human/S = owner.current - to_chat(S, span_notice("Your infernal nature has allowed you to overcome your clownishness.")) - S.dna.remove_mutation(CLOWNMUT) - .=..() + handle_clown_mutation(owner.current, "Your infernal nature has allowed you to overcome your clownishness.") + . = ..() /datum/antagonist/devil/on_removal() to_chat(owner.current, span_userdanger("Your infernal link has been severed! You are no longer a devil!")) - .=..() + . = ..() /datum/antagonist/devil/apply_innate_effects(mob/living/mob_override) give_appropriate_spells() diff --git a/code/modules/antagonists/devil/imp/imp.dm b/code/modules/antagonists/devil/imp/imp.dm index 28a8cb1c15f2..c35eeb7b1677 100644 --- a/code/modules/antagonists/devil/imp/imp.dm +++ b/code/modules/antagonists/devil/imp/imp.dm @@ -62,6 +62,7 @@ /datum/antagonist/imp name = "Imp" antagpanel_category = "Devil" + ui_name = "AntagInfoDemon" show_in_roundend = FALSE /datum/antagonist/imp/on_gain() diff --git a/code/modules/antagonists/devil/sintouched/sintouched.dm b/code/modules/antagonists/devil/sintouched/sintouched.dm index 9983e5f59943..4b0699d1d26c 100644 --- a/code/modules/antagonists/devil/sintouched/sintouched.dm +++ b/code/modules/antagonists/devil/sintouched/sintouched.dm @@ -10,6 +10,7 @@ name = "sintouched" roundend_category = "sintouched" antagpanel_category = "Devil" + antag_hud_name = "sintouched" var/sin var/static/list/sins = list(SIN_ACEDIA,SIN_GLUTTONY,SIN_GREED,SIN_SLOTH,SIN_WRATH,SIN_ENVY,SIN_PRIDE) @@ -56,28 +57,10 @@ sin = chosen_sin . = ..() -/datum/antagonist/sintouched/apply_innate_effects(mob/living/mob_override) - . = ..() - add_hud() - -/datum/antagonist/sintouched/remove_innate_effects(mob/living/mob_override) - remove_hud() - . = ..() - -/datum/antagonist/sintouched/proc/add_hud() - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SINTOUCHED] - hud.join_hud(owner.current) - set_antag_hud(owner.current, "sintouched") - -/datum/antagonist/sintouched/proc/remove_hud() - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_SINTOUCHED] - hud.leave_hud(owner.current) - set_antag_hud(owner.current, null) - #undef SIN_ACEDIA #undef SIN_ENVY #undef SIN_GLUTTONY #undef SIN_GREED #undef SIN_PRIDE #undef SIN_SLOTH -#undef SIN_WRATH \ No newline at end of file +#undef SIN_WRATH diff --git a/code/modules/antagonists/disease/disease_mob.dm b/code/modules/antagonists/disease/disease_mob.dm index 04373100feca..ad6b35dee8a4 100644 --- a/code/modules/antagonists/disease/disease_mob.dm +++ b/code/modules/antagonists/disease/disease_mob.dm @@ -63,7 +63,7 @@ the new instance inside the host to be updated to the template's stats. SSdisease.archive_diseases[disease_template.GetDiseaseID()] = disease_template //important for stuff that uses disease IDs var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE] - my_hud.add_hud_to(src) + my_hud.show_to(src) browser = new /datum/browser(src, "disease_menu", "Adaptation Menu", 1000, 770, src) @@ -229,7 +229,7 @@ the new instance inside the host to be updated to the template's stats. MA.alpha = 200 holder.appearance = MA var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE] - my_hud.add_to_hud(V.affected_mob) + my_hud.add_atom_to_hud(V.affected_mob) to_chat(src, span_notice("A new host, [V.affected_mob.real_name], has been infected.")) @@ -245,7 +245,7 @@ the new instance inside the host to be updated to the template's stats. to_chat(src, span_notice("One of your hosts, [V.affected_mob.real_name], has been purged of your infection.")) var/datum/atom_hud/my_hud = GLOB.huds[DATA_HUD_SENTIENT_DISEASE] - my_hud.remove_from_hud(V.affected_mob) + my_hud.remove_atom_from_hud(V.affected_mob) if(following_host == V.affected_mob) follow_next() diff --git a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm index ac7f4b76f192..a3e2be386405 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_antag.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_antag.dm @@ -4,7 +4,9 @@ antagpanel_category = "Heretic" antag_moodlet = /datum/mood_event/heretics job_rank = ROLE_HERETIC + antag_hud_name = "heretic" can_hijack = HIJACK_HIJACKER + show_to_ghosts = TRUE preview_outfit = /datum/outfit/heretic var/give_equipment = TRUE var/list/researched_knowledge = list() @@ -71,7 +73,6 @@ current.log_message("has been converted to the cult of the forgotten ones!", LOG_ATTACK, color="#960000") GLOB.reality_smash_track.AddMind(owner) START_PROCESSING(SSprocessing,src) - SSticker.mode.update_heretic_icons_added(owner) if(give_equipment) equip_cultist() return ..() @@ -88,8 +89,6 @@ GLOB.reality_smash_track.RemoveMind(owner) STOP_PROCESSING(SSprocessing,src) - SSticker.mode.update_heretic_icons_removed(owner) - return ..() @@ -160,12 +159,7 @@ var/mob/living/current = owner.current if(mob_override) current = mob_override - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - if(!silent) - to_chat(traitor_mob, "Your knowledge allow you to overcome your clownish nature, allowing you to wield weapons with impunity.") - traitor_mob.dna.remove_mutation(CLOWNMUT) + handle_clown_mutation(current, "Ancient knowledge described to you has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") current.faction |= "heretics" /datum/antagonist/heretic/remove_innate_effects(mob/living/mob_override) @@ -173,16 +167,13 @@ var/mob/living/current = owner.current if(mob_override) current = mob_override - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - traitor_mob.dna.add_mutation(CLOWNMUT) current.faction -= "heretics" /datum/antagonist/heretic/get_admin_commands() . = ..() - .["Equip"] = CALLBACK(src,.proc/equip_cultist) - .["Edit Research Points (Current: [charge])"] = CALLBACK(src, .proc/admin_edit_research) - .["Give Knowledge"] = CALLBACK(src, .proc/admin_give_knowledge) + .["Equip"] = CALLBACK(src, PROC_REF(equip_cultist)) + .["Edit Research Points (Current: [charge])"] = CALLBACK(src, PROC_REF(admin_edit_research)) + .["Give Knowledge"] = CALLBACK(src, PROC_REF(admin_give_knowledge)) /datum/antagonist/heretic/proc/admin_edit_research(mob/admin) var/research2add = input(admin, "Enter an amount to change research by (Negative numbers remove research)", "Research Grant") as null|num diff --git a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm index dc64bb3bef44..8ad55fc55a77 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_effects.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_effects.dm @@ -213,7 +213,7 @@ var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(/datum/antagonist/bloodsucker) if(IS_HERETIC(human_user)) to_chat(human_user, span_boldwarning("You know better than to tempt forces out of your control!")) - if(IS_BLOODSUCKER(human_user) || bloodsuckerdatum.my_clan == CLAN_LASOMBRA) + if(IS_BLOODSUCKER(human_user) || bloodsuckerdatum.my_clan?.get_clan() == CLAN_LASOMBRA) to_chat(human_user, span_boldwarning("This shard has already been harvested!")) else var/obj/item/bodypart/arm = human_user.get_active_hand() diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm index 154bb4d09c5a..effd1d20cb69 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm @@ -58,7 +58,7 @@ background_icon_state = "bg_ecult" button_icon_state = "shatter" icon_icon = 'icons/mob/actions/actions_ecult.dmi' - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE var/mob/living/carbon/human/holder var/obj/item/gun/magic/hook/sickly_blade/sword diff --git a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm index 2edaf8d6abaa..971c8549eae6 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_knowledge.dm @@ -87,6 +87,6 @@ desc = "Starts your journey in the mansus. Allows you to select a target transmuting a living heart on a transmutation rune, create new living hearts by transmuting a heart, poppy, and pool of blood, and create new codex cicatrixes by transmuting human skin, a bible, a poppy and a pen." gain_text = "Gates of mansus open up to your mind." cost = 0 - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp) + spells_to_add = list(/datum/action/cooldown/spell/touch/mansus_grasp) unlocked_transmutations = list(/datum/eldritch_transmutation/basic, /datum/eldritch_transmutation/living_heart, /datum/eldritch_transmutation/codex_cicatrix) route = "Start" diff --git a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm index e96554d6b111..bf4fa4c34458 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_magic.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_magic.dm @@ -1,173 +1,226 @@ -/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash name = "Ashen passage" desc = "Grants a short period of incorporeality, allowing passage through walls and other obstacles." - school = "transmutation" - charge_max = 150 - range = -1 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "ash_shift" - action_background_icon_state = "bg_ecult" - jaunt_in_time = 13 - jaunt_duration = 10 + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/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 + 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/cooldown/spell/jaunt/ethereal_jaunt/ash/do_steam_effects() return +/datum/action/cooldown/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" -/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp +/datum/action/cooldown/spell/touch/mansus_grasp name = "Mansus Grasp" desc = "A powerful combat initiation spell that deals massive stamina damage. It may have other effects if you continue your research..." + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/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 - school = "evocation" - charge_max = 150 - clothes_req = FALSE - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "mansus_grasp" - action_background_icon_state = "bg_ecult" + +/datum/action/cooldown/spell/touch/mansus_grasp/can_cast_spell(feedback = TRUE) + return ..() && !!IS_HERETIC(owner) + + +/datum/action/cooldown/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( + span_danger("The spell bounces off of [victim]!"), + span_danger("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, wound_bonus = CANT_WOUND) + if(iscarbon(victim)) + var/mob/living/carbon/carbon_hit = victim + carbon_hit.adjust_timed_status_effect(4 SECONDS, /datum/status_effect/speech/slurring/heretic) + 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. Knocks the target down and deals a large amount of stamina damage alongside a small amount of brute. It may gain more interesting capabilities if you continue your research..." + 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 = "disintegrate" item_state = "disintegrate" - catchphrase = "FEAR THE BEYOND" /obj/item/melee/touch_attack/mansus_fist/ignition_effect(atom/A, mob/user) . = span_notice("[user] effortlessly snaps [user.p_their()] fingers near [A], igniting it with eldritch energies. Fucking badass!") qdel(src) -/obj/item/melee/touch_attack/mansus_fist/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - - if(!proximity_flag || target == user) - return - if(ishuman(target)) - var/mob/living/carbon/human/tar = target - if(tar.anti_magic_check()) - tar.visible_message(span_danger("Strange energies from [user]'s hand fly at [target] at an impossible velocity, but vanish before making contact!"),span_danger("Strange energies from [user]'s hand begin to crash at an impossible speed towards you, but vanish before they make contact!")) - return ..() - var/datum/mind/M = user.mind - var/datum/antagonist/heretic/cultie = M.has_antag_datum(/datum/antagonist/heretic) - - var/use_charge = FALSE - if(iscarbon(target)) - use_charge = TRUE - var/mob/living/carbon/C = target - C.adjustBruteLoss(10) - C.AdjustKnockdown(5 SECONDS) - C.adjustStaminaLoss(80) - C.silent += 5 - var/list/knowledge = cultie.get_all_knowledge() - - for(var/X in knowledge) - var/datum/eldritch_knowledge/EK = knowledge[X] - if(EK.on_mansus_grasp(target, user, proximity_flag, click_parameters)) - use_charge = TRUE - if(use_charge) - playsound(user, 'sound/items/welder.ogg', 75, TRUE) - return ..() - -/obj/effect/proc_holder/spell/aoe_turf/rust_conversion +/datum/action/cooldown/spell/aoe/rust_conversion name = "Aggressive Spread" desc = "Spread rust onto nearby turfs, possibly destroying rusted walls." - school = "transmutation" - charge_max = 300 //twice as long as mansus grasp - clothes_req = FALSE - invocation = "RUST TO RUST" - invocation_type = "whisper" - range = 3 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "corrode" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "corrode" + sound = 'sound/items/welder.ogg' -/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() + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + + invocation = "A'GRSV SPR'D" + invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + + aoe_radius = 3 + + +/datum/action/cooldown/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 -/obj/effect/proc_holder/spell/aoe_turf/rust_conversion/small +/datum/action/cooldown/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/cooldown/spell/aoe/rust_conversion/small name = "Rust Conversion" - desc = "Spreads rust onto nearby turfs." - range = 2 + desc = "Spreads rust onto nearby surfaces." + aoe_radius = 2 -/obj/effect/proc_holder/spell/targeted/touch/blood_siphon +/datum/action/cooldown/spell/pointed/blood_siphon name = "Blood Siphon" - desc = "Engulfs your arm in draining energies, absorbing the health of the first human-like being struck to heal yourself." - hand_path = /obj/item/melee/touch_attack/blood_siphon - school = "evocation" - charge_max = 150 - clothes_req = FALSE - invocation = "ETERNAL FLAMES" - invocation_type = "whisper" - action_icon = 'icons/mob/actions/actions_ecult.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/mob/actions/actions_ecult.dmi' + button_icon_state = "blood_siphon" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' -/obj/item/melee/touch_attack/blood_siphon - name = "Blood Siphon" - desc = "A sinister looking aura that distorts the flow of reality around it. It looks hungry..." - icon_state = "disintegrate" - item_state = "disintegrate" - catchphrase = "REGENERATE ME" + school = SCHOOL_FORBIDDEN + cooldown_time = 15 SECONDS -/obj/item/melee/touch_attack/blood_siphon/afterattack(atom/target, mob/user, proximity_flag, click_parameters) - if(!proximity_flag) - return - playsound(user, 'sound/magic/demon_attack1.ogg', 75, TRUE) - if(iscarbon(target)) - var/mob/living/carbon/C1 = target - if(C1.anti_magic_check()) - C1.visible_message(span_danger("The energies from [user]'s hand jump at [target], but are dispersed!"),span_danger("Something jumps off of [user]'s hand, but it disperses on contact with you!")) - return ..() - var/mob/living/carbon/C2 = user - for(var/obj/item/bodypart/bodypart in C2.bodyparts) - for(var/i in bodypart.wounds) - var/datum/wound/iter_wound = i - if(prob(50)) - continue - var/obj/item/bodypart/target_bodypart = locate(bodypart.type) in C1.bodyparts - if(!target_bodypart) - continue - iter_wound.remove_wound() - iter_wound.apply_wound(target_bodypart) - if(isliving(target)) - var/mob/living/L = target - L.adjustBruteLoss(20) - C2.adjustBruteLoss(-20) - C1.blood_volume -= 20 - if(C2.blood_volume < BLOOD_VOLUME_MAXIMUM(C2)) //we dont want to explode after all - C2.blood_volume += 20 - return ..() - -/obj/effect/proc_holder/spell/targeted/projectile/dumbfire/rust_wave + invocation = "FL'MS O'ET'RN'ITY" + invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + + cast_range = 9 + +/datum/action/cooldown/spell/pointed/blood_siphon/can_cast_spell(feedback = TRUE) + return ..() && isliving(owner) + +/datum/action/cooldown/spell/pointed/blood_siphon/is_valid_target(atom/cast_on) + return ..() && isliving(cast_on) + +/datum/action/cooldown/spell/pointed/blood_siphon/cast(mob/living/cast_on) + . = ..() + playsound(owner, 'sound/magic/demon_attack1.ogg', 75, TRUE) + if(cast_on.can_block_magic()) + owner.balloon_alert(owner, "spell blocked!") + cast_on.visible_message( + span_danger("The spell bounces off of [cast_on]!"), + span_danger("The spell bounces off of you!"), + ) + return FALSE + + cast_on.visible_message( + span_danger("[cast_on] turns pale as a red glow envelops [cast_on.p_them()]!"), + span_danger("You pale as a red glow enevelops you!"), + ) + + var/mob/living/living_owner = owner + cast_on.adjustBruteLoss(20) + living_owner.adjustBruteLoss(-20) + + if(!cast_on.blood_volume || !living_owner.blood_volume) + return TRUE + + cast_on.blood_volume -= 20 + if(living_owner.blood_volume < BLOOD_VOLUME_MAXIMUM(living_owner)) // we dont want to explode from casting + living_owner.blood_volume += 20 + + if(!iscarbon(cast_on) || !iscarbon(owner)) + return TRUE + + 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 + +// Shoots a straight line of rusty stuff ahead of the caster, what rust monsters get +/datum/action/cooldown/spell/basic_projectile/rust_wave name = "Patron's Reach" desc = "Fire a rust spreading projectile in front of you, dealing toxin damage to whatever it hits." - proj_type = /obj/item/projectile/magic/spell/rust_wave - charge_max = 350 - clothes_req = FALSE - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "rust_wave" - action_background_icon_state = "bg_ecult" - invocation = "FACE INEVITABILITY" - invocation_type = "whisper" + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "rust_wave" -/obj/item/projectile/magic/spell/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/item/projectile/magic/aoe/rust_wave + +/obj/item/projectile/magic/aoe/rust_wave name = "Patron's Reach" icon_state = "eldritch_projectile" alpha = 180 @@ -179,7 +232,7 @@ range = 15 speed = 1 -/obj/item/projectile/magic/spell/rust_wave/Moved(atom/OldLoc, Dir) +/obj/item/projectile/magic/aoe/rust_wave/Moved(atom/OldLoc, Dir) . = ..() playsound(src, 'sound/items/welder.ogg', 75, TRUE) var/list/turflist = list() @@ -197,132 +250,125 @@ 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/item/projectile/magic/spell/rust_wave/short +/datum/action/cooldown/spell/basic_projectile/rust_wave/short + name = "Lesser Patron's Reach" + projectile_type = /obj/item/projectile/magic/aoe/rust_wave/short -/obj/item/projectile/magic/spell/rust_wave/short +/obj/item/projectile/magic/aoe/rust_wave/short range = 7 speed = 2 -/obj/effect/proc_holder/spell/pointed/cleave +/datum/action/cooldown/spell/pointed/cleave name = "Cleave" desc = "Causes severe bleeding on a target and people around them" - school = "transmutation" - charge_max = 350 - clothes_req = FALSE - invocation = "RIP AND TEAR" - invocation_type = "whisper" - range = 9 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "cleave" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "cleave" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' -/obj/effect/proc_holder/spell/pointed/cleave/cast(list/targets, mob/user) - if(!targets.len) - to_chat(user, span_warning("No target found in range!")) - return FALSE - if(!can_target(targets[1], user)) - return FALSE + school = SCHOOL_FORBIDDEN + cooldown_time = 35 SECONDS + + invocation = "CL'VE" + invocation_type = INVOCATION_WHISPER - for(var/mob/living/carbon/human/C in range(1,targets[1])) - targets |= C + spell_requirements = NONE + cast_range = 9 + /// The radius of the cleave effect + var/cleave_radius = 1 + +/datum/action/cooldown/spell/pointed/cleave/is_valid_target(atom/cast_on) + return ..() && ishuman(cast_on) + +/datum/action/cooldown/spell/pointed/cleave/cast(mob/living/carbon/human/cast_on) + . = ..() + var/list/mob/living/carbon/human/nearby = list(cast_on) + for(var/mob/living/carbon/human/nearby_human in range(cleave_radius, cast_on)) + nearby += nearby_human - for(var/X in targets) - var/mob/living/carbon/human/target = X - if(target == user) + for(var/mob/living/carbon/human/victim as anything in nearby) + if(victim == owner) continue - if(target.anti_magic_check()) - to_chat(user, span_warning("The spell had no effect!")) - target.visible_message(span_danger("[target]'s veins emit a dull glow, but their magic protection repulses the blaze!"), \ + if(victim.anti_magic_check()) + to_chat(owner, span_warning("The spell had no effect!")) + victim.visible_message(span_danger("[victim]'s veins emit a dull glow, but their magic protection repulses the blaze!"), \ span_danger("You see a dull glow and feel a faint prickling sensation in your veins, but your magic protection prevents ignition!")) continue - target.visible_message(span_danger("[target]'s veins are shredded from within as an unholy blaze erupts from their blood!"), \ + victim.visible_message(span_danger("[victim]'s veins are shredded from within as an unholy blaze erupts from their blood!"), \ span_danger("You feel your skin scald as superheated blood bursts from your veins!")) var/obj/item/bodypart/bodypart = pick(target.bodyparts) var/datum/wound/slash/critical/crit_wound = new crit_wound.apply_wound(bodypart) - target.adjustFireLoss(20) + victim.apply_damage(20, BURN, wound_bonus = CANT_WOUND) new /obj/effect/temp_visual/cleave(target.drop_location()) -/obj/effect/proc_holder/spell/pointed/cleave/can_target(atom/target, mob/user, silent) - . = ..() - if(!.) - return FALSE - if(!istype(target,/mob/living/carbon/human)) - if(!silent) - to_chat(user, span_warning("You are unable to cleave [target]!")) - return FALSE - return TRUE - -/obj/effect/proc_holder/spell/pointed/cleave/long - charge_max = 650 +/datum/action/cooldown/spell/pointed/cleave/long + name = "Deeper Cleave" + cooldown_time = 65 SECONDS -/obj/effect/proc_holder/spell/pointed/touch/mad_touch +// Currently unused. +/datum/action/cooldown/spell/touch/mad_touch name = "Touch of madness" desc = "Strange energies engulf your hand, you feel even the sight of them would cause a headache if you didn't understand them." - school = "transmutation" - charge_max = 150 - clothes_req = FALSE - invocation_type = "none" - range = 2 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "mad_touch" - action_background_icon_state = "bg_ecult" + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "mad_touch" -/obj/effect/proc_holder/spell/pointed/touch/mad_touch/can_target(atom/target, mob/user, silent) - . = ..() - if(!.) + school = SCHOOL_FORBIDDEN + cooldown_time = 15 SECONDS + invocation_type = INVOCATION_NONE + spell_requirements = NONE + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND + +/datum/action/cooldown/spell/touch/mad_touch/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + if(!ishuman(victim)) + return FALSE + + var/mob/living/carbon/human/human_victim = victim + if(!human_victim.mind || IS_HERETIC(human_victim)) return FALSE - if(!istype(target,/mob/living/carbon/human)) - if(!silent) - to_chat(user, span_warning("You are unable to touch [target]!")) + + if(human_victim.can_block_magic(antimagic_flags)) + victim.visible_message( + span_danger("The spell bounces off of [victim]!"), + span_danger("The spell bounces off of you!"), + ) return FALSE + + to_chat(caster, span_warning("[human_victim.name] has been cursed!")) + SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus) 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(span_danger("The energies from [user]'s hand slide onto [target], but quickly fall off and vanish!"),span_danger("Something slides onto you from [user]'s hand, but it can't maintain contact and vanishes as it falls away!")) - return - if(target.mind && !target.mind.has_antag_datum(/datum/antagonist/heretic)) - to_chat(user,span_warning("[target.name] has been cursed!")) - SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, "gates_of_mansus", /datum/mood_event/gates_of_mansus) - -/obj/effect/proc_holder/spell/pointed/ash_final +// Currently unused - releases streams of fire around the caster. +/datum/action/cooldown/spell/pointed/ash_beams name = "Nightwatcher's Rite" - desc = "Fires 5 blasts of fire in angles away from you, dealing heavy damage to anything they hit." - school = "transmutation" - invocation = "IGNITE" - invocation_type = "whisper" - charge_max = 300 - range = 15 - clothes_req = FALSE - action_icon = 'icons/mob/actions/actions_ecult.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/mob/actions/actions_ecult.dmi' + button_icon_state = "flames" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' -/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/fire_line, user,T) - T = line_target(10, range, X, user) - INVOKE_ASYNC(src, .proc/fire_line, user,T) - T = line_target(0, range, X, user) - INVOKE_ASYNC(src, .proc/fire_line, user,T) - T = line_target(-10, range, X, user) - INVOKE_ASYNC(src, .proc/fire_line, user,T) - T = line_target(25, range, X, user) - INVOKE_ASYNC(src, .proc/fire_line, user,T) - return ..() + school = SCHOOL_FORBIDDEN + cooldown_time = 300 + invocation = "F'RE" + invocation_type = INVOCATION_WHISPER + 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/cooldown/spell/pointed/ash_beams/is_valid_target(atom/cast_on) + return TRUE + +/datum/action/cooldown/spell/pointed/ash_beams/cast(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/cooldown/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 @@ -334,7 +380,7 @@ 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/cooldown/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)) @@ -360,11 +406,12 @@ M.take_damage(45, BURN, MELEE, 1) sleep(0.15 SECONDS) -/obj/effect/proc_holder/spell/targeted/shapeshift/eldritch - invocation = "BEND MY FORM" - invocation_type = "whisper" - clothes_req = FALSE - action_background_icon_state = "bg_ecult" +/datum/action/cooldown/spell/shapeshift/eldritch + school = SCHOOL_FORBIDDEN + background_icon_state = "bg_ecult" + invocation = "SH'PE" + invocation_type = INVOCATION_WHISPER + possible_shapes = list(/mob/living/simple_animal/mouse,\ /mob/living/simple_animal/pet/dog/corgi,\ /mob/living/simple_animal/hostile/carp/megacarp,\ @@ -373,92 +420,121 @@ /mob/living/simple_animal/bot/medbot,\ /mob/living/simple_animal/pet/cat ) -/obj/effect/proc_holder/spell/targeted/emplosion/eldritch +/datum/action/cooldown/spell/emp/eldritch name = "Entropic Pulse" - invocation = "ENTROPIC PULSE" - invocation_type = "whisper" - clothes_req = FALSE - action_background_icon_state = "bg_ecult" - range = -1 - include_user = TRUE - charge_max = 300 + background_icon_state = "bg_ecult" + + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + + invocation = "E'P" + invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + emp_heavy = 6 emp_light = 10 -/obj/effect/proc_holder/spell/aoe_turf/fire_cascade - name = "Fire Cascade" - desc = "Creates a large cascading burst of flames around you." - school = "transmutation" - charge_max = 300 //twice as long as mansus grasp - clothes_req = FALSE - invocation = "CONFLAGRATE" - invocation_type = "shout" - range = 4 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "fire_ring" - action_background_icon_state = "bg_ecult" +/// Creates one, large, expanding ring of fire around the caster, which does not follow them. +/datum/action/cooldown/spell/fire_cascade + name = "Lesser Fire Cascade" + desc = "Heats the air around you." + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "fire_ring" + sound = 'sound/items/welder.ogg' -/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/cast(list/targets, mob/user = usr) - INVOKE_ASYNC(src, .proc/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/_range = 1 - for(var/i = 0, i <= max_range,i++) - for(var/turf/T in spiral_range_turfs(_range,centre)) - new /obj/effect/hotspot(T) - T.hotspot_expose(700,50,1) - for(var/mob/living/livies in T.contents - centre) - livies.adjustFireLoss(5) - _range++ - sleep(0.3 SECONDS) - -/obj/effect/proc_holder/spell/aoe_turf/fire_cascade/big - range = 6 - -/obj/effect/proc_holder/spell/targeted/telepathy/eldritch - invocation = "" - invocation_type = "whisper" - clothes_req = FALSE - action_background_icon_state = "bg_ecult" + school = SCHOOL_FORBIDDEN + cooldown_time = 30 SECONDS + invocation = "C'SC'DE" + invocation_type = INVOCATION_WHISPER + spell_requirements = NONE + + /// The radius the flames will go around the caster. + var/flame_radius = 4 -/obj/effect/proc_holder/spell/targeted/fire_sworn +/datum/action/cooldown/spell/fire_cascade/cast(atom/cast_on) + . = ..() + INVOKE_ASYNC(src, PROC_REF(fire_cascade), get_turf(cast_on), flame_radius) + +/// Spreads a huge wave of fire in a radius around us, staggered between levels +/datum/action/cooldown/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.apply_damage(5, BURN) + stoplag(0.3 SECONDS) + +/datum/action/cooldown/spell/fire_cascade/big + name = "Greater Fire Cascade" + flame_radius = 6 + +/datum/action/cooldown/spell/list_target/telepathy/eldritch + name = "Eldritch Telepathy" + school = SCHOOL_FORBIDDEN + background_icon_state = "bg_ecult" + invocation_type = INVOCATION_NONE + antimagic_flags = MAGIC_RESISTANCE|MAGIC_RESISTANCE_MIND + +/// Creates a constant Ring of Fire around the caster for a set duration of time, which follows them. +/datum/action/cooldown/spell/fire_sworn name = "Oath of Fire" desc = "Engulf yourself in a cloak of flames for a minute. The flames are harmless to you, but dangerous to anyone else." - invocation = "FUEL FOR THE FIRE" - invocation_type = "shout" - clothes_req = FALSE - action_background_icon_state = "bg_ecult" - range = -1 - include_user = TRUE - charge_max = 700 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "fire_ring" - ///how long it lasts + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "fire_ring" + + school = SCHOOL_FORBIDDEN + cooldown_time = 70 SECONDS + + invocation = "FL'MS" + invocation_type = INVOCATION_WHISPER + 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/remove, user), duration, TIMER_OVERRIDE|TIMER_UNIQUE) +/datum/action/cooldown/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 +/datum/action/cooldown/spell/fire_sworn/is_valid_target(atom/cast_on) + return isliving(cast_on) -/obj/effect/proc_holder/spell/targeted/fire_sworn/process(delta_time) +/datum/action/cooldown/spell/fire_sworn/cast(mob/living/cast_on) . = ..() - if(!has_fire_ring) + cast_on.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 - for(var/turf/T in range(1,current_user)) - new /obj/effect/hotspot(T) - T.hotspot_expose(700,250 * delta_time,1) - for(var/mob/living/L in T.contents - current_user) - L.adjustFireLoss(25 * delta_time) + + if(!isturf(owner.loc)) + return + + 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 - owner) + fried_living.apply_damage(2.5 * delta_time, BURN) /obj/effect/proc_holder/spell/targeted/worm_contract @@ -489,73 +565,123 @@ /obj/effect/temp_visual/eldritch_smoke icon = 'icons/effects/eldritch.dmi' icon_state = "smoke" - duration = 10 + duration = 1 SECONDS -/obj/effect/proc_holder/spell/targeted/fiery_rebirth +/datum/action/cooldown/spell/aoe/fiery_rebirth name = "Nightwatcher's Rebirth" - desc = "Drains the health of nearby combusting individuals, healing you 10 of each damage type for every victim. If a victim is in critical condition they will be finished off." - invocation = "ASHES TO ASHES" - invocation_type = "whisper" - clothes_req = FALSE - action_background_icon_state = "bg_ecult" - range = -1 - include_user = TRUE - charge_max = 600 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "smoke" + 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/mob/actions/actions_ecult.dmi' + button_icon_state = "smoke" -/obj/effect/proc_holder/spell/targeted/fiery_rebirth/cast(list/targets, mob/user) - if(!ishuman(user)) - return - var/mob/living/carbon/human/human_user = user - for(var/mob/living/carbon/target in view(7,user)) - if(target.stat == DEAD || !target.on_fire) + school = SCHOOL_FORBIDDEN + cooldown_time = 1 MINUTES + + invocation = "GL'RY T' TH' N'GHT'W'TCH'ER" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_HUMAN + +/datum/action/cooldown/spell/aoe/fiery_rebirth/cast(mob/living/carbon/human/cast_on) + cast_on.extinguish_mob() + return ..() + +/datum/action/cooldown/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(nearby_mob) || IS_HERETIC_MONSTER(nearby_mob)) + continue + if(nearby_mob.stat == DEAD || !nearby_mob.on_fire) continue - //This is essentially a death mark, use this to finish your opponent quicker. - if(target.InCritical()) - target.death() - target.adjustFireLoss(20) - new /obj/effect/temp_visual/eldritch_smoke(target.drop_location()) - human_user.ExtinguishMob() - human_user.adjustBruteLoss(-10, FALSE) - human_user.adjustFireLoss(-10, FALSE) - human_user.adjustStaminaLoss(-10, FALSE) - human_user.adjustToxLoss(-10, FALSE) - human_user.adjustOxyLoss(-10) - -/obj/effect/proc_holder/spell/pointed/manse_link - name = "Mansus Link" - desc = "Pierce through reality, connecting minds. Hitting someone with this spell will add them to your mansus link shortly after if uninterrupted, allowing for silent communication." - school = "transmutation" - charge_max = 300 - clothes_req = FALSE - invocation = "HEAR MY VOICE" - invocation_type = "whisper" - range = 10 - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "mansus_link" - action_background_icon_state = "bg_ecult" -/obj/effect/proc_holder/spell/pointed/manse_link/can_target(atom/target, mob/user, silent) - if(!isliving(target)) - return FALSE - return TRUE + things += nearby_mob -/obj/effect/proc_holder/spell/pointed/manse_link/cast(list/targets, mob/user) - var/mob/living/simple_animal/hostile/eldritch/raw_prophet/originator = user + return things - var/mob/living/target = targets[1] +/datum/action/cooldown/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()) - to_chat(originator, span_notice("You begin linking [target]'s mind to yours...")) - to_chat(target, span_warning("You feel your mind being pulled... connected... to something unreal...")) - if(!do_after(originator, 6 SECONDS, target)) - return - if(!originator.link_mob(target)) - to_chat(originator, span_warning("You can't seem to link [target]'s mind...")) - to_chat(target, span_warning("The foreign presence leaves your mind.")) + //This is essentially a death mark, use this to finish your opponent quicker. + if(victim.stat == UNCONSCIOUS && !HAS_TRAIT(victim, TRAIT_NODEATH)) + victim.death() + victim.apply_damage(20, BURN) + + // 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) + + +/datum/action/cooldown/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_ecult" + icon_icon = 'icons/mob/actions/actions_ecult.dmi' + button_icon_state = "mansus_link" + ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' + + school = SCHOOL_FORBIDDEN + cooldown_time = 20 SECONDS + + invocation = "PI'RC' TH' M'ND." + invocation_type = INVOCATION_SHOUT + spell_requirements = NONE + + cast_range = 7 + + /// The time it takes to link to a mob. + var/link_time = 6 SECONDS + +/datum/action/cooldown/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/cooldown/spell/pointed/manse_link/is_valid_target(atom/cast_on) + . = ..() + if(!.) + return FALSE + return isliving(cast_on) + +/datum/action/cooldown/spell/pointed/manse_link/before_cast(mob/living/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) return - to_chat(originator, span_notice("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/cooldown/spell/pointed/manse_link/proc/do_linking(mob/living/linkee) + var/datum/component/mind_linker/linker = target + if(linkee.stat == DEAD) + to_chat(owner, span_warning("They're dead!")) + return FALSE + to_chat(owner, span_notice("You begin linking [linkee]'s mind to yours...")) + to_chat(linkee, span_warning("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, span_warning("You fail to link to [linkee]'s mind.")) + to_chat(linkee, span_warning("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, span_warning("You can't seem to link to [linkee]'s mind.")) + to_chat(linkee, span_warning("The foreign presence leaves your mind.")) + return FALSE + return TRUE /datum/action/innate/mansus_speech name = "Mansus Link" @@ -592,10 +718,15 @@ var/link = FOLLOW_LINK(dead_mob, living_owner) to_chat(dead_mob, "[link] [msg]") -/obj/effect/proc_holder/spell/pointed/trigger/blind/eldritch - range = 10 - invocation = "SEE NO EVIL" - action_background_icon_state = "bg_ecult" +// Given to heretic monsters. +/datum/action/cooldown/spell/pointed/blind/eldritch + name = "Eldritch Blind" + background_icon_state = "bg_ecult" + + school = SCHOOL_FORBIDDEN + invocation = "E'E'S" + + cast_range = 10 /obj/effect/temp_visual/dir_setting/entropic icon = 'icons/effects/160x160.dmi' @@ -628,42 +759,45 @@ icon_state = "small_rune_[rand(12)]" update_icon() -/obj/effect/proc_holder/spell/cone/staggered/entropic_plume +// Shoots out in a wave-like, what rust heretics themselves get +/datum/action/cooldown/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), while also spreading rust in the path of the plume." - school = "illusion" - invocation = "GUST OF RUST" - invocation_type = "whisper" - clothes_req = FALSE - action_background_icon_state = "bg_ecult" - action_icon = 'icons/mob/actions/actions_ecult.dmi' - action_icon_state = "entropic_plume" - charge_max = 300 + background_icon_state = "bg_ecult" + icon_icon = 'icons/mob/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 + 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/cooldown/spell/cone/staggered/entropic_plume/cast(atom/cast_on) . = ..() - 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(cast_on, cast_on.dir), cast_on.dir) -/obj/effect/proc_holder/spell/cone/staggered/entropic_plume/do_turf_cone_effect(turf/target_turf, level) - . = ..() +/datum/action/cooldown/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) +/datum/action/cooldown/spell/cone/staggered/entropic_plume/do_mob_cone_effect(mob/living/victim, atom/caster, level) . = ..() if(victim.anti_magic_check() || IS_HERETIC(victim) || IS_HERETIC_MONSTER(victim)) return victim.apply_status_effect(STATUS_EFFECT_AMOK) - victim.apply_status_effect(STATUS_EFFECT_CLOUDSTRUCK, (level*10)) + victim.apply_status_effect(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/cooldown/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 diff --git a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm index 431b88dc9fb7..bf923793c85c 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_monster_antag.dm @@ -17,14 +17,12 @@ to_chat(owner, span_boldannounce("You became an Eldritch Horror!")) /datum/antagonist/heretic_monster/on_gain() - SSticker.mode.update_heretic_icons_added(owner) return ..() /datum/antagonist/heretic_monster/on_removal() if(owner) to_chat(owner, span_boldannounce("Your master is no longer [master.owner.current.real_name]")) owner = null - SSticker.mode.update_heretic_icons_removed(owner) return ..() /datum/antagonist/heretic_monster/proc/set_owner(datum/antagonist/_master) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm b/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm index 5f88fa20c9d4..cda6ac971c6f 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_transmutations.dm @@ -217,4 +217,4 @@ name = "Codex Cicatrix" required_atoms = list(/obj/item/organ/eyes,/obj/item/stack/sheet/animalhide/human,/obj/item/storage/book/bible,/obj/item/pen) result_atoms = list(/obj/item/forbidden_book) - required_shit_list = "A bible, a sheet of human skin, a pen, and a pair of eyes." \ No newline at end of file + required_shit_list = "A bible, a sheet of human skin, a pen, and a pair of eyes." diff --git a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm index d21c0a943766..d1aeb1852db3 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/ash_lore.dm @@ -36,9 +36,9 @@ if(E) E.on_effect() for(var/X in user.mind.spell_list) - if(!istype(X,/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp)) + if(!istype(X, /datum/action/cooldown/spell/touch/mansus_grasp)) continue - var/obj/effect/proc_holder/spell/targeted/touch/mansus_grasp/MG = X + var/datum/action/cooldown/spell/touch/mansus_grasp/MG = X MG.charge_counter = min(round(MG.charge_counter + MG.charge_max * 0.75),MG.charge_max) // refunds 75% of charge. /datum/eldritch_knowledge/ashen_shift @@ -46,7 +46,7 @@ gain_text = "Ash is so simple, yet so numerous. Is it possible to master it all?" desc = "Short range jaunt that can help you escape from bad situations." cost = 1 - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash) + spells_to_add = list(/datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash) route = PATH_ASH tier = TIER_1 @@ -73,7 +73,7 @@ if(isliving(target)) . = TRUE var/mob/living/living_target = target - living_target.apply_status_effect(/datum/status_effect/eldritch/ash,5) + living_target.apply_status_effect(/datum/status_effect/eldritch/ash, 5) /datum/eldritch_knowledge/blindness name = "Curse of Blindness" @@ -114,14 +114,14 @@ if(iscarbon(target)) var/mob/living/carbon/C = target C.adjust_fire_stacks(2) - C.IgniteMob() + C.ignite_mob() /datum/eldritch_knowledge/flame_birth name = "Flame Birth" gain_text = "The Nightwatcher was a man of principles, and yet he arose from the chaos he vowed to protect from." desc = "A healing spell that saps the life from those combusted nearby." cost = 1 - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/fiery_rebirth) + spells_to_add = list(/datum/action/cooldown/spell/aoe/fiery_rebirth) route = PATH_ASH tier = TIER_3 diff --git a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm index 080b05c44ac6..5afb41090218 100644 --- a/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm +++ b/code/modules/antagonists/eldritch_cult/knowledge/rust_lore.dm @@ -84,7 +84,7 @@ desc = "Spreads rust to nearby turfs. Destroys already rusted walls." gain_text = "All men wise know not to touch the bound king." cost = 1 - spells_to_add = list(/obj/effect/proc_holder/spell/aoe_turf/rust_conversion) + spells_to_add = list(/datum/action/cooldown/spell/aoe/rust_conversion) route = PATH_RUST tier = TIER_2 @@ -108,7 +108,7 @@ desc = "You can now send a befuddling plume that blinds, poisons and makes enemies strike each other. Also converts the area into rust." gain_text = "Messengers of hope fear I, the Rustbringer!" cost = 1 - spells_to_add = list(/obj/effect/proc_holder/spell/cone/staggered/entropic_plume) + spells_to_add = list(/datum/action/cooldown/spell/cone/staggered/entropic_plume) route = PATH_RUST tier = TIER_3 diff --git a/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm b/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm index 0d8bb8efc8f8..0ff08f5a0397 100644 --- a/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm +++ b/code/modules/antagonists/eldritch_cult/transmutations/ash_transmutations.dm @@ -65,8 +65,13 @@ /datum/eldritch_transmutation/final/ash_final/on_finished_recipe(mob/living/user, list/atoms, loc) priority_announce("$^@&#*$^@(#&$(@&#^$&#^@# Fear The Blaze, for Ashbringer [user.real_name] has come! $^@&#*$^@(#&$(@&#^$&#^@#","#$^@&#*$^@(#&$(@&#^$&#^@#", ANNOUNCER_SPANOMALIES) set_security_level(SEC_LEVEL_GAMMA) - 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) + + var/datum/action/cooldown/spell/fire_sworn/circle_spell = new(user.mind) + circle_spell.Grant(user) + + var/datum/action/cooldown/spell/fire_cascade/big/screen_wide_fire_spell = new(user.mind) + screen_wide_fire_spell.Grant(user) + var/mob/living/carbon/human/H = user H.physiology.brute_mod *= 0.5 H.physiology.burn_mod *= 0.5 diff --git a/code/modules/antagonists/fugitive/fugitive.dm b/code/modules/antagonists/fugitive/fugitive.dm index b228bd53dca9..324423e0c753 100644 --- a/code/modules/antagonists/fugitive/fugitive.dm +++ b/code/modules/antagonists/fugitive/fugitive.dm @@ -4,18 +4,14 @@ silent = TRUE //greet called by the event show_in_antagpanel = FALSE prevent_roundtype_conversion = FALSE + antag_hud_name = "fugitive" var/datum/team/fugitive/fugitive_team var/is_captured = FALSE var/backstory = "error" preview_outfit = /datum/outfit/spacepol /datum/antagonist/fugitive/apply_innate_effects(mob/living/mob_override) - var/mob/living/M = mob_override || owner.current - update_fugitive_icons_added(M) - -/datum/antagonist/fugitive/remove_innate_effects(mob/living/mob_override) - var/mob/living/M = mob_override || owner.current - update_fugitive_icons_removed(M) + add_team_hud(mob_override || owner.current) /datum/antagonist/fugitive/on_gain() forge_objectives() @@ -90,16 +86,6 @@ return result.Join("
") -/datum/antagonist/fugitive/proc/update_fugitive_icons_added(var/mob/living/carbon/human/fugitive) - var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE] - fughud.join_hud(fugitive) - set_antag_hud(fugitive, "fugitive") - -/datum/antagonist/fugitive/proc/update_fugitive_icons_removed(var/mob/living/carbon/human/fugitive) - var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE] - fughud.leave_hud(fugitive) - set_antag_hud(fugitive, null) - /datum/action/innate/yalpcomms name = "Yalp Elor Communion" desc = "Allows talking with the brothers of Yalp Elor." diff --git a/code/modules/antagonists/fugitive/fugitive_outfits.dm b/code/modules/antagonists/fugitive/fugitive_outfits.dm index e536c40f25d4..48bb5e173da2 100644 --- a/code/modules/antagonists/fugitive/fugitive_outfits.dm +++ b/code/modules/antagonists/fugitive/fugitive_outfits.dm @@ -52,17 +52,18 @@ H.facial_hair_style = "Shaved" H.hair_color = "000" H.facial_hair_color = H.hair_color - 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(SLOT_SHOES) no_drops += H.get_item_by_slot(SLOT_W_UNIFORM) no_drops += H.get_item_by_slot(SLOT_WEAR_SUIT) no_drops += H.get_item_by_slot(SLOT_HEAD) no_drops += H.get_item_by_slot(SLOT_GLASSES) - for(var/i in no_drops) - var/obj/item/I = i - ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT) + for(var/obj/item/trait_needed as anything in no_drops) + ADD_TRAIT(trait_needed, TRAIT_NODROP, CURSED_ITEM_TRAIT) + + var/datum/action/cooldown/spell/aoe/knock/waldos_key = new(equipped_on.mind || equipped_on) + waldos_key.Grant(equipped_on) /datum/outfit/synthetic name = "Factory Error Synth" diff --git a/code/modules/antagonists/fugitive/hunter.dm b/code/modules/antagonists/fugitive/hunter.dm index c5c2e7aaa461..f7893dd879e9 100644 --- a/code/modules/antagonists/fugitive/hunter.dm +++ b/code/modules/antagonists/fugitive/hunter.dm @@ -5,17 +5,10 @@ silent = TRUE //greet called by the spawn show_in_antagpanel = FALSE prevent_roundtype_conversion = FALSE + antag_hud_name = "fugitive_hunter" var/datum/team/fugitive_hunters/hunter_team var/backstory = "error" -/datum/antagonist/fugitive_hunter/apply_innate_effects(mob/living/mob_override) - var/mob/living/M = mob_override || owner.current - update_fugitive_icons_added(M) - -/datum/antagonist/fugitive_hunter/remove_innate_effects(mob/living/mob_override) - var/mob/living/M = mob_override || owner.current - update_fugitive_icons_removed(M) - /datum/antagonist/fugitive_hunter/on_gain() forge_objectives() . = ..() @@ -168,13 +161,3 @@ result += "" return result.Join("
") - -/datum/antagonist/fugitive_hunter/proc/update_fugitive_icons_added(var/mob/living/carbon/human/fugitive) - var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE] - fughud.join_hud(fugitive) - set_antag_hud(fugitive, "fugitive") - -/datum/antagonist/fugitive_hunter/proc/update_fugitive_icons_removed(var/mob/living/carbon/human/fugitive) - var/datum/atom_hud/antag/fughud = GLOB.huds[ANTAG_HUD_FUGITIVE] - fughud.leave_hud(fugitive) - set_antag_hud(fugitive, null) diff --git a/code/modules/antagonists/golems/golem.dm b/code/modules/antagonists/golems/golem.dm index 0b7cb823d8d8..1b38a7041c45 100644 --- a/code/modules/antagonists/golems/golem.dm +++ b/code/modules/antagonists/golems/golem.dm @@ -5,7 +5,6 @@ var/old_species var/golem_species var/antag_hud - var/antag_hud_name var/removing = FALSE //whether we're already in the process of removing this antag datum (needed for remove_innate_effects() on admin_remove()) /datum/antagonist/golem/admin_add(datum/mind/new_owner,mob/admin) @@ -19,29 +18,14 @@ /datum/antagonist/golem/apply_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current - update_icons(M, "join") - -/datum/antagonist/golem/remove_innate_effects(mob/living/mob_override) - var/mob/living/M = mob_override || owner.current - update_icons(M, "leave") - -/datum/antagonist/golem/proc/update_icons(var/mob/living/carbon/human/golem, leave_or_join) - var/datum/atom_hud/antag/antagHud = GLOB.huds[antag_hud] - if(leave_or_join == "join") - antagHud.join_hud(golem) - set_antag_hud(golem, antag_hud_name) - else if(leave_or_join == "leave") - antagHud.leave_hud(golem) - set_antag_hud(golem, null) + add_team_hud(M, antag_hud) /datum/antagonist/golem/capitalist name = "Capitalist Golem" golem_species = /datum/species/golem/capitalist - antag_hud = ANTAG_HUD_CAPITALIST antag_hud_name = "capitalist" /datum/antagonist/golem/communist name = "Communist Golem" golem_species = /datum/species/golem/soviet - antag_hud = ANTAG_HUD_COMMUNIST antag_hud_name = "communist" diff --git a/code/modules/antagonists/hivemind/hivemind.dm b/code/modules/antagonists/hivemind/hivemind.dm index f6e134f73bc9..f2daef31c9a3 100644 --- a/code/modules/antagonists/hivemind/hivemind.dm +++ b/code/modules/antagonists/hivemind/hivemind.dm @@ -3,6 +3,7 @@ roundend_category = "hiveminds" antagpanel_category = "Hivemind Host" job_rank = ROLE_HIVE + antag_hud_name = "hivemind" antag_moodlet = /datum/mood_event/focused var/special_role = ROLE_HIVE var/list/hivemembers = list() @@ -214,24 +215,7 @@ ..() /datum/antagonist/hivemind/apply_innate_effects() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - if(!silent) - to_chat(traitor_mob, "The great psionic powers of the Hive lets you overcome your clownish nature, allowing you to wield weapons with impunity.") - traitor_mob.dna.remove_mutation(CLOWNMUT) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE] - hud.join_hud(owner.current) - set_antag_hud(owner.current, "hivemind") - -/datum/antagonist/hivemind/remove_innate_effects() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - traitor_mob.dna.add_mutation(CLOWNMUT) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE] - hud.leave_hud(owner.current) - set_antag_hud(owner.current, null) + handle_clown_mutation(owner.current, "The great psionic powers of the Hive lets you overcome your clownish nature, allowing you to wield weapons with impunity.") /datum/antagonist/hivemind/on_removal() //Remove all hive powers here diff --git a/code/modules/antagonists/hivemind/vessel.dm b/code/modules/antagonists/hivemind/vessel.dm index d1519a003615..29f12c95d602 100644 --- a/code/modules/antagonists/hivemind/vessel.dm +++ b/code/modules/antagonists/hivemind/vessel.dm @@ -4,6 +4,7 @@ /datum/antagonist/hivevessel name = "Awoken Vessel" job_rank = ROLE_BRAINWASHED + antag_hud_name = "hivevessel" roundend_category = "awoken vessels" show_in_antagpanel = TRUE antagpanel_category = "Other" @@ -40,24 +41,7 @@ M.add_antag_datum(vessel) /datum/antagonist/hivevessel/apply_innate_effects() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - if(!silent) - to_chat(traitor_mob, "Our newfound powers allow us to overcome our clownish nature, allowing us to wield weapons with impunity.") - traitor_mob.dna.remove_mutation(CLOWNMUT) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE] - hud.join_hud(owner.current) - set_antag_hud(owner.current, "hivevessel") - -/datum/antagonist/hivevessel/remove_innate_effects() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - traitor_mob.dna.add_mutation(CLOWNMUT) - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE] - hud.leave_hud(owner.current) - set_antag_hud(owner.current, null) + handle_clown_mutation(owner.current, "Our newfound powers allow us to overcome our clownish nature, allowing us to wield weapons with impunity.") /datum/antagonist/hivevessel/greet() to_chat(owner, span_assimilator("Your mind is suddenly opened, as you see the pinnacle of evolution...")) diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm index 70ec3d113a63..998afbbce010 100644 --- a/code/modules/antagonists/horror/horror.dm +++ b/code/modules/antagonists/horror/horror.dm @@ -76,7 +76,7 @@ RefreshAbilities() var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - hud.add_hud_to(src) + hud.show_to(src) update_horror_hud() @@ -139,7 +139,7 @@ return var/datum/hud/chemical_counter/H = hud_used var/atom/movable/screen/counter = H.chemical_counter - counter.maptext = "
[chemicals]
" + counter.maptext = FORMAT_ANTAG_TEXT(chemicals, COLOR_DARKSPAWN_PSI) /mob/living/simple_animal/horror/proc/can_use_ability() if(stat != CONSCIOUS) @@ -159,7 +159,8 @@ for(var/datum/mind/M in SSticker.minds) if(M.current && M.current.stat != DEAD) if(ishuman(M.current)) - if(M.hasSoul && (mind.enslaved_to != M.current)) + var/mob/living/master = mind.enslaved_to?.resolve() + if(M.hasSoul && (master != M.current)) possible_targets[M] = M var/list/selected_targets = list() @@ -201,7 +202,8 @@ to_chat(src, "This host doesn't have a soul!") return - if(victim == mind.enslaved_to) + var/mob/living/master = mind.enslaved_to?.resolve() + if(victim == master) to_chat(src, span_userdanger("No, not yet... We still need them...")) return @@ -345,7 +347,8 @@ return if(isliving(A)) - if(victim || A == src.mind.enslaved_to) + var/mob/living/master = mind.enslaved_to?.resolve() + if(victim || A == master) healthscan(usr, A) chemscan(usr, A) else @@ -670,7 +673,7 @@ if(nlog_type & LOG_SAY) var/list/reversed = log_source[log_type] if(islist(reversed)) - say_log = reverseRange(reversed.Copy()) + say_log = reverse_range(reversed.Copy()) break if(LAZYLEN(say_log)) for(var/spoken_memory in say_log) @@ -820,7 +823,7 @@ var/datum/action/innate/horror/action = has_ability(/datum/action/innate/horror/chameleon) if(action) action.button_icon_state = "horror_sneak_[invisible ? "true" : "false"]" - action.UpdateButtonIcon() + action.UpdateButtons() /mob/living/simple_animal/horror/proc/GrantHorrorActions() for(var/datum/action/innate/horror/ability in horrorabilities) diff --git a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm index 8ee4a8bd1ac1..81cbf21e1cba 100644 --- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm +++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm @@ -68,7 +68,7 @@ /datum/action/innate/horror/toggle_hide/Activate() B.hide() button_icon_state = "horror_hiding_[B.hiding ? "true" : "false"]" - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/horror/talk_to_horror name = "Converse with Horror" @@ -146,8 +146,8 @@ /datum/action/innate/horror/freeze_victim/Activate() B.freeze_victim() - UpdateButtonIcon() - addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 150) + UpdateButtons() + addtimer(CALLBACK(src, .proc/UpdateButtons), 150) /datum/action/innate/horror/freeze_victim/IsAvailable() if(world.time - B.used_freeze < 150) @@ -181,7 +181,7 @@ /datum/action/innate/horror/tentacle/process() ..() active = locate(/obj/item/horrortentacle) in B.victim - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/horror/tentacle/Activate() @@ -308,7 +308,7 @@ /datum/action/innate/horror/chameleon/Activate() B.go_invisible() button_icon_state = "horror_sneak_[B.invisible ? "true" : "false"]" - UpdateButtonIcon() + UpdateButtons() /datum/action/innate/horror/lube_spill name = "Lube spill" @@ -327,8 +327,8 @@ /datum/action/innate/horror/lube_spill/Activate() B.use_chemicals(chemical_cost) cooldown = world.time + 10 SECONDS - UpdateButtonIcon() - addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 10 SECONDS) + UpdateButtons() + addtimer(CALLBACK(src, .proc/UpdateButtons), 10 SECONDS) B.visible_message(span_warning("[B] spins and throws some sort of substance!"), span_notice("Your flail oily substance around you!")) flick("horror_spin", B) playsound(B, 'sound/effects/blobattack.ogg', 25, 1) diff --git a/code/modules/antagonists/monsterhunter/monsterhunter.dm b/code/modules/antagonists/monsterhunter/monsterhunter.dm index e73529574a35..0ef33de7833d 100644 --- a/code/modules/antagonists/monsterhunter/monsterhunter.dm +++ b/code/modules/antagonists/monsterhunter/monsterhunter.dm @@ -8,6 +8,7 @@ roundend_category = "Monster Hunters" antagpanel_category = "Monster Hunter" job_rank = ROLE_MONSTERHUNTER + antag_hud_name = "monsterhunter" preview_outfit = /datum/outfit/monsterhunter var/list/datum/action/powers = list() var/datum/martial_art/hunterfu/my_kungfu = new @@ -16,6 +17,7 @@ var/datum/action/bloodsucker/fortitude = new /datum/action/bloodsucker/fortitude/hunter() /datum/antagonist/monsterhunter/apply_innate_effects(mob/living/mob_override) + . = ..() var/mob/living/current_mob = mob_override || owner.current ADD_TRAIT(current_mob, TRAIT_NOSOFTCRIT, BLOODSUCKER_TRAIT) ADD_TRAIT(current_mob, TRAIT_NOCRITDAMAGE, BLOODSUCKER_TRAIT) @@ -23,6 +25,7 @@ my_kungfu.teach(current_mob, make_temporary = FALSE) /datum/antagonist/monsterhunter/remove_innate_effects(mob/living/mob_override) + . = ..() var/mob/living/current_mob = mob_override || owner.current REMOVE_TRAIT(current_mob, TRAIT_NOSOFTCRIT, BLOODSUCKER_TRAIT) REMOVE_TRAIT(current_mob, TRAIT_NOCRITDAMAGE, BLOODSUCKER_TRAIT) @@ -33,11 +36,8 @@ /datum/antagonist/monsterhunter/on_gain() //Give Monster Hunter powers - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_MHUNTER] trackvamp.Grant(owner.current) fortitude.Grant(owner.current) - hud.join_hud(owner.current) - set_antag_hud(owner.current, "monsterhunter") if(give_objectives) //Give Hunter Objective var/datum/objective/bloodsucker/monsterhunter/monsterhunter_objective = new @@ -56,11 +56,8 @@ /datum/antagonist/monsterhunter/on_removal() //Remove Monster Hunter powers - var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_MHUNTER] trackvamp.Remove(owner.current) fortitude.Remove(owner.current) - hud.leave_hud(owner.current) - set_antag_hud(owner.current, null) to_chat(owner.current, span_userdanger("Your hunt has ended: You enter retirement once again, and are no longer a Monster Hunter.")) return ..() @@ -70,7 +67,6 @@ all_powers.Remove(old_body) all_powers.Grant(new_body) - /// Mind version /datum/mind/proc/make_monsterhunter() var/datum/antagonist/monsterhunter/monsterhunterdatum = has_antag_datum(/datum/antagonist/monsterhunter) diff --git a/code/modules/antagonists/ninja/ninja.dm b/code/modules/antagonists/ninja/ninja.dm index 31b35cd0c7df..1076f0f00eda 100644 --- a/code/modules/antagonists/ninja/ninja.dm +++ b/code/modules/antagonists/ninja/ninja.dm @@ -4,6 +4,7 @@ GLOBAL_LIST_EMPTY(ninja_capture) name = "Ninja" antagpanel_category = "Ninja" job_rank = ROLE_NINJA + antag_hud_name = "ninja" show_name_in_check_antagonists = TRUE show_to_ghosts = TRUE antag_moodlet = /datum/mood_event/focused @@ -22,14 +23,12 @@ GLOBAL_LIST_EMPTY(ninja_capture) for(var/obj/item/implant/explosive/E in M.implants) if(E) RegisterSignal(E, COMSIG_IMPLANT_ACTIVATED, .proc/on_death) - update_ninja_icons_added(M) /datum/antagonist/ninja/remove_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current for(var/obj/item/implant/explosive/E in M.implants) if(E) UnregisterSignal(M, COMSIG_IMPLANT_ACTIVATED) - update_ninja_icons_removed(M) /datum/antagonist/ninja/proc/equip_space_ninja(mob/living/carbon/human/H = owner.current) return H.equipOutfit(/datum/outfit/ninja) @@ -187,13 +186,3 @@ GLOBAL_LIST_EMPTY(ninja_capture) new_owner.add_antag_datum(src) message_admins("[key_name_admin(admin)] has [adj] ninja'ed [key_name_admin(new_owner)].") log_admin("[key_name(admin)] has [adj] ninja'ed [key_name(new_owner)].") - -/datum/antagonist/ninja/proc/update_ninja_icons_added(var/mob/living/carbon/human/ninja) - var/datum/atom_hud/antag/ninjahud = GLOB.huds[ANTAG_HUD_NINJA] - ninjahud.join_hud(ninja) - set_antag_hud(ninja, "ninja") - -/datum/antagonist/ninja/proc/update_ninja_icons_removed(var/mob/living/carbon/human/ninja) - var/datum/atom_hud/antag/ninjahud = GLOB.huds[ANTAG_HUD_NINJA] - ninjahud.leave_hud(ninja) - set_antag_hud(ninja, null) diff --git a/code/modules/antagonists/nukeop/nukeop.dm b/code/modules/antagonists/nukeop/nukeop.dm index b9dac7e77672..b1594d1c2ec1 100644 --- a/code/modules/antagonists/nukeop/nukeop.dm +++ b/code/modules/antagonists/nukeop/nukeop.dm @@ -3,6 +3,7 @@ roundend_category = "syndicate operatives" //just in case antagpanel_category = "NukeOp" job_rank = ROLE_OPERATIVE + antag_hud_name = "synd" antag_moodlet = /datum/mood_event/focused show_to_ghosts = TRUE var/datum/team/nuclear/nuke_team @@ -16,24 +17,11 @@ /// In the preview icon, the nukies who are behind the leader var/preview_outfit_behind = /datum/outfit/nuclear_operative -/datum/antagonist/nukeop/proc/update_synd_icons_added(mob/living/M) - var/datum/atom_hud/antag/opshud = GLOB.huds[ANTAG_HUD_OPS] - opshud.join_hud(M) - set_antag_hud(M, "synd") - -/datum/antagonist/nukeop/proc/update_synd_icons_removed(mob/living/M) - var/datum/atom_hud/antag/opshud = GLOB.huds[ANTAG_HUD_OPS] - opshud.leave_hud(M) - set_antag_hud(M, null) - /datum/antagonist/nukeop/apply_innate_effects(mob/living/mob_override) - var/mob/living/M = mob_override || owner.current - update_synd_icons_added(M) + add_team_hud(mob_override || owner.current) ADD_TRAIT(owner, TRAIT_DISK_VERIFIER, NUKEOP_TRAIT) /datum/antagonist/nukeop/remove_innate_effects(mob/living/mob_override) - var/mob/living/M = mob_override || owner.current - update_synd_icons_removed(M) REMOVE_TRAIT(owner, TRAIT_DISK_VERIFIER, NUKEOP_TRAIT) /datum/antagonist/nukeop/proc/equip_op() diff --git a/code/modules/antagonists/revenant/revenant.dm b/code/modules/antagonists/revenant/revenant.dm index 91014f06ab39..58dc7ec0b95c 100644 --- a/code/modules/antagonists/revenant/revenant.dm +++ b/code/modules/antagonists/revenant/revenant.dm @@ -71,12 +71,27 @@ . = ..() flags_1 |= RAD_NO_CONTAMINATE_1 ADD_TRAIT(src, TRAIT_SIXTHSENSE, INNATE_TRAIT) - AddSpell(new /obj/effect/proc_holder/spell/targeted/night_vision/revenant(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/cooldown/spell/night_vision/revenant/vision = new(src) + vision.Grant(src) + + var/datum/action/cooldown/spell/list_target/telepathy/revenant/telepathy = new(src) + telepathy.Grant(src) + + // Starting spells that start locked + var/datum/action/cooldown/spell/aoe/revenant/overload/lights_go_zap = new(src) + lights_go_zap.Grant(src) + + var/datum/action/cooldown/spell/aoe/revenant/defile/windows_go_smash = new(src) + windows_go_smash.Grant(src) + + var/datum/action/cooldown/spell/aoe/revenant/blight/botany_go_mad = new(src) + botany_go_mad.Grant(src) + + var/datum/action/cooldown/spell/aoe/revenant/malfunction/shuttle_go_emag = new(src) + shuttle_go_emag.Grant(src) + random_revenant_name() LoadComponent(/datum/component/walk/jaunt) //yogs diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm index 870e48ed7d2f..3f795e32f5bd 100644 --- a/code/modules/antagonists/revenant/revenant_abilities.dm +++ b/code/modules/antagonists/revenant/revenant_abilities.dm @@ -98,103 +98,142 @@ essence_drained = 0 //Toggle night vision: lets the revenant toggle its night vision -/obj/effect/proc_holder/spell/targeted/night_vision/revenant - charge_max = 0 +/datum/action/cooldown/spell/night_vision/revenant + name = "Toggle Darkvision" panel = "Revenant Abilities" - message = span_revennotice("You toggle your night vision.") - action_icon = 'icons/mob/actions/actions_revenant.dmi' - action_icon_state = "r_nightvision" - action_background_icon_state = "bg_revenant" + background_icon_state = "bg_revenant" + icon_icon = 'icons/mob/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/cooldown/spell/list_target/telepathy/revenant name = "Revenant Transmit" panel = "Revenant Abilities" - action_icon = 'icons/mob/actions/actions_revenant.dmi' - action_icon_state = "r_transmit" - action_background_icon_state = "bg_revenant" - notice = "revennotice" - boldnotice = "revenboldnotice" - holy_check = TRUE - tinfoil_check = FALSE - -/obj/effect/proc_holder/spell/aoe_turf/revenant - clothes_req = 0 - action_icon = 'icons/mob/actions/actions_revenant.dmi' - action_background_icon_state = "bg_revenant" + background_icon_state = "bg_revenant" + + telepathy_span = "revennotice" + bold_telepathy_span = "revenboldnotice" + + antimagic_flags = MAGIC_RESISTANCE_HOLY|MAGIC_RESISTANCE_MIND + +/datum/action/cooldown/spell/aoe/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 //If it's locked and needs to be unlocked before use - 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() + background_icon_state = "bg_revenant" + icon_icon = 'icons/mob/actions/actions_revenant.dmi' + + 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 + +/datum/action/cooldown/spell/aoe/revenant/New(Target) . = ..() + if(!isrevenant(target)) + stack_trace("[type] was given to a non-revenant mob, please don't.") + qdel(src) + return + if(locked) name = "[initial(name)] ([unlock_amount]SE)" else name = "[initial(name)] ([cast_amount]E)" -/obj/effect/proc_holder/spell/aoe_turf/revenant/can_cast(mob/living/simple_animal/revenant/user = usr) - if(charge_counter < charge_max) +/datum/action/cooldown/spell/aoe/revenant/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) return FALSE - if(!istype(user)) //Badmins, no. Badmins, don't do it. - return TRUE - if(user.inhibited) + if(!isrevenant(owner)) + 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(!istype(user)) //If you're not a revenant, it works. Please, please, please don't give this to a non-revenant. - name = "[initial(name)]" - if(locked) - panel = "Revenant Abilities" - locked = FALSE - return TRUE +/datum/action/cooldown/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/cooldown/spell/aoe/revenant/before_cast(mob/living/simple_animal/revenant/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return FALSE + if(locked) - if (!user.unlock(unlock_amount)) - charge_counter = charge_max - return FALSE + if(!cast_on.unlock(unlock_amount)) + to_chat(cast_on, span_revenwarning("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(user, span_revennotice("You have unlocked [initial(name)]!")) + to_chat(owner, span_revennotice("You have unlocked [initial(name)]!")) + to_chat(cast_on, span_revennotice("You have unlocked [initial(name)]!")) panel = "Revenant Abilities" locked = FALSE - charge_counter = charge_max - return FALSE - if(!user.castcheck(-cast_amount)) - charge_counter = charge_max - return FALSE - name = "[initial(name)] ([cast_amount]E)" - 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/cooldown/spell/aoe/revenant/after_cast(mob/living/simple_animal/revenant/cast_on) + . = ..() + if(reveal_duration) + cast_on.reveal(reveal_duration) + if(stun_duration) + cast_on.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/cooldown/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 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/overload, T, user) +/datum/action/cooldown/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(span_boldwarning("[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), 2 SECONDS) + /obj/effect/proc_holder/spell/aoe_turf/revenant/overload/proc/overload(turf/T, mob/user) for(var/obj/machinery/light/L in T) @@ -207,141 +246,126 @@ new /obj/effect/temp_visual/revenant(get_turf(L)) addtimer(CALLBACK(src, .proc/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 view(shock_range, L)) - if(M == user) +/datum/action/cooldown/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, safety=TRUE) - 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(antimagic_flags)) + human_mob.electrocute_act(shock_damage, to_shock) + + 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/cooldown/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" - -/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/defile, T) + reveal_duration = 4 SECONDS + stun_duration = 2 SECONDS -/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) +/datum/action/cooldown/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) - if(!isplatingturf(T) && !istype(T, /turf/open/floor/engine/cult) && isfloorturf(T) && prob(15)) - var/turf/open/floor/floor = T - if(floor.intact && floor.floor_tile) + 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.ChangeTurf(/turf/closed/wall/rust) - if(T.type == /turf/closed/wall/r_wall && prob(10)) - new /obj/effect/temp_visual/revenant(T) - T.ChangeTurf(/turf/closed/wall/r_wall/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)) + new /obj/effect/temp_visual/revenant(victim) + victim.ChangeTurf(/turf/closed/wall/rust) + if(victim.type == /turf/closed/wall/r_wall && prob(10)) + new /obj/effect/temp_visual/revenant(victim) + victim.ChangeTurf(/turf/closed/wall/r_wall/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) - 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) - window.take_damage(rand(30,80)) - 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/cooldown/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/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) +/datum/action/cooldown/spell/aoe/revenant/malfunction/cast_on_thing_in_aoe(turf/victim, mob/living/simple_animal/revenant/caster) + for(var/mob/living/simple_animal/bot/bot in victim) if(!bot.emagged) new /obj/effect/temp_visual/revenant(bot.loc) bot.locked = FALSE bot.open = TRUE - bot.emag_act() - for(var/mob/living/carbon/human/human in T) - if(human == user) + 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(antimagic_flags)) continue to_chat(human, span_revenwarning("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) || istype(thing, /obj/machinery/particle_accelerator/control_box)) //Doesn't work on SMES and APCs or the PA control box, 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.emag_act(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) - for(var/obj/machinery/door/firedoor/window/W in T) // Fuck you atmos-locks - W.take_damage(rand(5,20)) - to_chat(user,span_notice("The shutter mechanism starts to grind its gears.")) + 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/cooldown/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/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/cooldown/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(antimagic_flags)) + to_chat(caster, span_warning("The spell had no effect on [mob]!")) continue new /obj/effect/temp_visual/revenant(mob.loc) if(iscarbon(mob)) @@ -362,15 +386,15 @@ 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) + QDEL_IN(vine, 1 SECONDS) + 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) + QDEL_IN(shroom, 1 SECONDS) + 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) diff --git a/code/modules/antagonists/revenant/revenant_blight.dm b/code/modules/antagonists/revenant/revenant_blight.dm index 05e972caeaa9..fe2dd2223dac 100644 --- a/code/modules/antagonists/revenant/revenant_blight.dm +++ b/code/modules/antagonists/revenant/revenant_blight.dm @@ -31,7 +31,7 @@ return if(prob(stage*3)) to_chat(affected_mob, span_revennotice("You suddenly feel [pick("sick and tired", "disoriented", "tired and confused", "nauseated", "faint", "dizzy")]...")) - affected_mob.confused += 8 + affected_mob.adjust_confusion(8 SECONDS) affected_mob.adjustStaminaLoss(20) new /obj/effect/temp_visual/revenant(affected_mob.loc) if(stagedamage < stage) diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm index 3df49df63945..72e23ef8aa8f 100644 --- a/code/modules/antagonists/revolution/revolution.dm +++ b/code/modules/antagonists/revolution/revolution.dm @@ -7,7 +7,7 @@ antagpanel_category = "Revolution" job_rank = ROLE_REV antag_moodlet = /datum/mood_event/revolution - var/hud_type = "rev" + antag_hud_name = "rev" var/datum/team/revolution/rev_team /datum/antagonist/rev/can_be_owned(datum/mind/new_owner) @@ -33,12 +33,11 @@ /datum/antagonist/rev/apply_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current M.grant_language(/datum/language/french, TRUE, TRUE, LANGUAGE_REVOLUTIONARY) - update_rev_icons_added(M) + add_team_hud(M, /datum/antagonist/rev) /datum/antagonist/rev/remove_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current M.remove_language(/datum/language/french, TRUE, TRUE, LANGUAGE_REVOLUTIONARY) - update_rev_icons_removed(M) /datum/antagonist/rev/proc/equip_rev() return @@ -159,7 +158,7 @@ /datum/antagonist/rev/head name = "Head Revolutionary" - hud_type = "rev_head" + antag_hud_name = "rev_head" var/remove_clumsy = FALSE var/give_flash = TRUE var/give_hud = TRUE @@ -209,16 +208,6 @@ W.on_reading_finished(owner.current) qdel(W) -/datum/antagonist/rev/proc/update_rev_icons_added(mob/living/M) - var/datum/atom_hud/antag/revhud = GLOB.huds[ANTAG_HUD_REV] - revhud.join_hud(M) - set_antag_hud(M,hud_type) - -/datum/antagonist/rev/proc/update_rev_icons_removed(mob/living/M) - var/datum/atom_hud/antag/revhud = GLOB.huds[ANTAG_HUD_REV] - revhud.leave_hud(M) - set_antag_hud(M, null) - /datum/antagonist/rev/proc/can_be_converted(mob/living/candidate) if(!candidate.mind) return FALSE @@ -281,9 +270,7 @@ if(!ishuman(H) && !ismonkey(H)) return - if(remove_clumsy && owner.assigned_role == "Clown") - to_chat(owner, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - H.dna.remove_mutation(CLOWNMUT) + handle_clown_mutation(owner.current, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") if(give_flash) var/obj/item/organ/cyberimp/arm/flash/rev/T = new diff --git a/code/modules/antagonists/santa/santa.dm b/code/modules/antagonists/santa/santa.dm index 673c9a974373..b49873a6db76 100644 --- a/code/modules/antagonists/santa/santa.dm +++ b/code/modules/antagonists/santa/santa.dm @@ -22,7 +22,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/cooldown/spell/teleport/area_teleport/wizard/santa/teleport = new(owner) + teleport.Grant(H) /datum/antagonist/santa/proc/give_objective() var/datum/objective/santa_objective = new() diff --git a/code/modules/antagonists/slaughter/slaughter_antag.dm b/code/modules/antagonists/slaughter/slaughter_antag.dm index c52e17c7a4e0..9a9ad522d12a 100644 --- a/code/modules/antagonists/slaughter/slaughter_antag.dm +++ b/code/modules/antagonists/slaughter/slaughter_antag.dm @@ -1,6 +1,7 @@ /datum/antagonist/slaughter name = "Slaughter demon" show_name_in_check_antagonists = TRUE + ui_name = "AntagInfoDemon" var/objective_verb = "Kill" var/datum/mind/summoner job_rank = ROLE_ALIEN diff --git a/code/modules/antagonists/slaughter/slaughterevent.dm b/code/modules/antagonists/slaughter/slaughterevent.dm index 2881eacbe151..03a2b12b48eb 100644 --- a/code/modules/antagonists/slaughter/slaughterevent.dm +++ b/code/modules/antagonists/slaughter/slaughterevent.dm @@ -32,17 +32,16 @@ message_admins("No valid spawn locations found, aborting...") return MAP_ERROR - /*var/obj/effect/dummy/phased_mob/holder = new /obj/effect/dummy/phased_mob((pick(spawn_locs))) //yogs start - Bloodcrawl refactor - var/mob/living/simple_animal/slaughter/S = new (holder) - S.holder = holder*/ - var/obj/effect/dummy/crawling/holder = new(pick(spawn_locs)) - var/mob/living/simple_animal/slaughter/S = new(holder) //yogs end + 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, span_bold("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/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm index 3719b40c6ddb..3ebf605ed13b 100644 --- a/code/modules/antagonists/traitor/datum_traitor.dm +++ b/code/modules/antagonists/traitor/datum_traitor.dm @@ -6,6 +6,7 @@ roundend_category = "traitors" antagpanel_category = "Traitor" job_rank = ROLE_TRAITOR + antag_hud_name = "traitor" antag_moodlet = /datum/mood_event/focused preview_outfit = /datum/outfit/traitor var/special_role = ROLE_TRAITOR @@ -42,19 +43,18 @@ ..() -/datum/antagonist/traitor/apply_innate_effects() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - if(!silent) - to_chat(traitor_mob, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") - traitor_mob.dna.remove_mutation(CLOWNMUT) +/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/silicon/ai/A = mob_override || owner.current + if(istype(A) && traitor_kind == TRAITOR_AI) + A.hack_software = TRUE + handle_clown_mutation(owner.current, "Your training has allowed you to overcome your clownish nature, allowing you to wield weapons without harming yourself.") -/datum/antagonist/traitor/remove_innate_effects() - if(owner.assigned_role == "Clown") - var/mob/living/carbon/human/traitor_mob = owner.current - if(traitor_mob && istype(traitor_mob)) - traitor_mob.dna.add_mutation(CLOWNMUT) +/datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override) + . = ..() + var/mob/living/silicon/ai/A = mob_override || owner.current + if(istype(A) && traitor_kind == TRAITOR_AI) + A.hack_software = FALSE /datum/antagonist/traitor/on_removal() //Remove malf powers. @@ -248,16 +248,6 @@ give_codewords() to_chat(owner.current, span_notice("Your employer [initial(company.name)] will be paying you an extra [initial(company.paymodifier)]x your nanotrasen paycheck.")) -/datum/antagonist/traitor/proc/update_traitor_icons_added(datum/mind/traitor_mind) - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.join_hud(owner.current) - set_antag_hud(owner.current, "traitor") - -/datum/antagonist/traitor/proc/update_traitor_icons_removed(datum/mind/traitor_mind) - var/datum/atom_hud/antag/traitorhud = GLOB.huds[ANTAG_HUD_TRAITOR] - traitorhud.leave_hud(owner.current) - set_antag_hud(owner.current, null) - /datum/antagonist/traitor/proc/finalize_traitor() switch(traitor_kind) if(TRAITOR_AI) @@ -269,20 +259,6 @@ equip(silent) owner.current.playsound_local(get_turf(owner.current), 'sound/ambience/antag/tatoralert.ogg', 100, FALSE, pressure_affected = FALSE) -/datum/antagonist/traitor/apply_innate_effects(mob/living/mob_override) - . = ..() - update_traitor_icons_added() - var/mob/living/silicon/ai/A = mob_override || owner.current - if(istype(A) && traitor_kind == TRAITOR_AI) - A.hack_software = TRUE - -/datum/antagonist/traitor/remove_innate_effects(mob/living/mob_override) - . = ..() - update_traitor_icons_removed() - var/mob/living/silicon/ai/A = mob_override || owner.current - if(istype(A) && traitor_kind == TRAITOR_AI) - A.hack_software = FALSE - /datum/antagonist/traitor/proc/give_codewords() if(!owner.current) return diff --git a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm index 8eb998c2315a..01b9371ea1e7 100644 --- a/code/modules/antagonists/traitor/equipment/Malf_Modules.dm +++ b/code/modules/antagonists/traitor/equipment/Malf_Modules.dm @@ -79,19 +79,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module)) /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 - /// The linked proc holder that contains the actual ability code - var/obj/effect/proc_holder/ranged_ai/linked_ability - /// The path of our linked ability - var/linked_ability_type - -/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 - ..() + click_action = TRUE /datum/action/innate/ai/ranged/adjust_uses(amt, silent) uses += amt @@ -102,33 +90,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module)) to_chat(owner, span_warning("[name] has run out of uses!")) if(delete_on_empty) 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 - /// Appears when the user activates the ability - var/enable_text = span_notice("Hello World!") - /// Appears when the user deactivates the ability - var/disable_text = span_danger("Goodbye Cruel World!") - 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) + QDEL_IN(src, 10 SECONDS) //let any active timers on us finish up /// The base module type, which holds info about each ability. @@ -415,45 +377,44 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module)) 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 + ranged_mousepointer = 'icons/effects/mouse_pointers/override_machine_target.dmi' + enable_text = span_notice("You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel.") + disable_text = span_notice("You release your hold on the powernet.") /datum/action/innate/ai/ranged/override_machine/New() - ..() + . = ..() desc = "[desc] It has [uses] use\s remaining." - button.desc = desc -/datum/action/innate/ai/ranged/override_machine/proc/animate_machine(obj/machinery/M) - if(M && !QDELETED(M)) - new/mob/living/simple_animal/hostile/mimic/copy/machine(get_turf(M), M, owner, 1) +/datum/action/innate/ai/ranged/override_machine/do_ability(mob/living/caller, atom/clicked_on) + if(caller.incapacitated()) + unset_ranged_ability(caller) + return FALSE + if(!istype(clicked_on, /obj/machinery)) + to_chat(caller, span_warning("You can only animate machines!")) + return FALSE + var/obj/machinery/clicked_machine = clicked_on + if(!clicked_machine.can_be_overridden() || is_type_in_typecache(clicked_machine, GLOB.blacklisted_malf_machines)) + to_chat(caller, span_warning("That machine can't be overridden!")) + return FALSE -/obj/effect/proc_holder/ranged_ai/override_machine - active = FALSE - ranged_mousepointer = 'icons/effects/override_machine_target.dmi' - enable_text = span_notice("You tap into the station's powernet. Click on a machine to animate it, or use the ability again to cancel.") - disable_text = span_notice("You release your hold on the powernet.") + caller.playsound_local(caller, 'sound/misc/interference.ogg', 50, FALSE, use_reverb = FALSE) + adjust_uses(-1) -/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, span_warning("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, span_warning("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) - if(attached_action && attached_action.uses) - attached_action.desc = "[initial(attached_action.desc)] It has [attached_action.uses] use\s remaining." - attached_action.UpdateButtonIcon() - target.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [target]!")) - addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/override_machine.proc/animate_machine, target), 50) //kabeep! - remove_ranged_ability(span_danger("Sending override signal...")) + if(uses) + desc = "[initial(desc)] It has [uses] use\s remaining." + UpdateButtons() + + clicked_machine.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [clicked_machine]!")) + addtimer(CALLBACK(src, PROC_REF(animate_machine), caller, clicked_machine), 5 SECONDS) //kabeep! + unset_ranged_ability(caller, span_danger("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) + /// Destroy RCDs: Detonates all non-cyborg RCDs on the station. /datum/AI_Module/destructive/destroy_rcd name = "Destroy RCDs" @@ -493,43 +454,46 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module)) 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 = span_notice("You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel.") disable_text = span_notice("You release your hold on the powernet.") -/obj/effect/proc_holder/ranged_ai/overload_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, span_warning("You can only overload machines!")) - return - if(is_type_in_typecache(target, GLOB.blacklisted_malf_machines)) - to_chat(ranged_ability_user, span_warning("You cannot overload that device!")) +/datum/action/innate/ai/ranged/overload_machine/New() + ..() + desc = "[desc] It has [uses] use\s remaining." + +/datum/action/innate/ai/ranged/overload_machine/proc/detonate_machine(mob/living/caller, obj/machinery/to_explode) + if(QDELETED(to_explode)) return - ranged_ability_user.playsound_local(ranged_ability_user, "sparks", 4 SECONDS, 0) - attached_action.adjust_uses(-1) - target.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [target]!")) - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.attach(target) - spark_system.set_up(1, 0, target) - addtimer(CALLBACK(attached_action, /datum/action/innate/ai/ranged/overload_machine.proc/detonate_machine, target), 4 SECONDS) //kaboom! - remove_ranged_ability(span_danger("Overcharging machine...")) + + 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/do_ability(mob/living/caller, atom/clicked_on) + if(caller.incapacitated()) + unset_ranged_ability(caller) + return FALSE + if(!ismachinery(clicked_on)) + to_chat(caller, span_warning("You can only overload machines!")) + return FALSE + var/obj/machinery/clicked_machine = clicked_on + if(is_type_in_typecache(clicked_machine, GLOB.blacklisted_malf_machines)) + to_chat(caller, span_warning("You cannot overload that device!")) + return FALSE + + caller.playsound_local(caller, "sparks", 50, 0) + adjust_uses(-1) + if(uses) + desc = "[initial(desc)] It has [uses] use\s remaining." + UpdateButtons() + + clicked_machine.audible_message(span_userdanger("You hear a loud electrical buzzing sound coming from [clicked_machine]!")) + addtimer(CALLBACK(src, PROC_REF(detonate_machine), caller, clicked_machine), 5 SECONDS) //kaboom! + unset_ranged_ability(caller, span_danger("Overcharging machine...")) return TRUE /// Blackout: Overloads a random number of lights across the station. Three uses. @@ -551,7 +515,6 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module)) /datum/action/innate/ai/blackout/New() ..() desc = "[desc] It has [uses] use\s remaining." - button.desc = desc /datum/action/innate/ai/blackout/Activate() for(var/obj/machinery/power/apc/apc in GLOB.apcs_list) @@ -564,7 +527,7 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module)) adjust_uses(-1) if(src && uses) //Not sure if not having src here would cause a runtime, so it's here to be safe desc = "[initial(desc)] It has [uses] use\s remaining." - UpdateButtonIcon() + UpdateButtons() /// Robotic Factory: Places a large machine that converts humans that go through it into cyborgs. Unlocking this ability removes shunting. /datum/AI_Module/utility/place_cyborg_transformer @@ -739,7 +702,6 @@ GLOBAL_LIST_INIT(malf_modules, subtypesof(/datum/AI_Module)) /datum/action/innate/ai/reactivate_cameras/New() ..() desc = "[desc] There are 30 reactivations remaining." - button.desc = desc /datum/action/innate/ai/reactivate_cameras/Activate() var/fixed_cameras = 0 diff --git a/code/modules/antagonists/traitor/equipment/module_picker.dm b/code/modules/antagonists/traitor/equipment/module_picker.dm index acea65cde881..6d9a19fdc201 100644 --- a/code/modules/antagonists/traitor/equipment/module_picker.dm +++ b/code/modules/antagonists/traitor/equipment/module_picker.dm @@ -126,5 +126,5 @@ else //Adding uses to an existing module action.uses += initial(action.uses) action.desc = "[initial(action.desc)] It has [action.uses] use\s remaining." - action.UpdateButtonIcon() + action.UpdateButtons() processing_time -= AM.cost diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm index f5192e31a9f5..3ef517dc9d6b 100644 --- a/code/modules/antagonists/traitor/syndicate_contract.dm +++ b/code/modules/antagonists/traitor/syndicate_contract.dm @@ -162,16 +162,16 @@ M.reagents.add_reagent(/datum/reagent/medicine/omnizine, 20) M.flash_act() - M.confused += 10 + M.adjust_confusion(10 SECONDS) M.blur_eyes(0.5 SECONDS) to_chat(M, span_warning("You feel strange...")) sleep(6 SECONDS) to_chat(M, span_warning("That pod did something to you...")) - M.Dizzy(3.5 SECONDS) + M.adjust_dizzy(3.5 SECONDS) sleep(6.5 SECONDS) to_chat(M, span_warning("Your head pounds... It feels like it's going to burst out your skull!")) M.flash_act() - M.confused += 20 + M.adjust_confusion(20 SECONDS) M.blur_eyes(3) sleep(3 SECONDS) to_chat(M, span_warning("Your head pounds...")) @@ -182,8 +182,8 @@ we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \ so it's only a matter of time before we ship you back...\"
") M.blur_eyes(1 SECONDS) - M.Dizzy(1.5 SECONDS) - M.confused += 20 + M.adjust_dizzy(1.5 SECONDS) + M.adjust_confusion(20 SECONDS) // We're returning the victim /datum/syndicate_contract/proc/returnVictim(var/mob/living/M) @@ -221,8 +221,8 @@ M.flash_act() M.blur_eyes(30) - M.Dizzy(35) - M.confused += 20 + M.adjust_dizzy(35 SECONDS) + M.adjust_confusion(20 SECONDS) new /obj/effect/DPtarget(possible_drop_loc[pod_rand_loc], return_pod) else diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index 8bd1fa6b0ba9..9138cdadaf3f 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -357,7 +357,7 @@ GiveHint(target) if(BODY_ZONE_HEAD) to_chat(user, span_notice("You smack the doll's head with your hand.")) - target.Dizzy(10) + target.adjust_dizzy(10) to_chat(target, span_warning("You suddenly feel as if your head was hit with a hammer!")) GiveHint(target,user) cooldown = world.time + cooldown_time @@ -389,7 +389,7 @@ /obj/item/voodoo/fire_act(exposed_temperature, exposed_volume) if(target) target.adjust_fire_stacks(20) - target.IgniteMob() + target.ignite_mob() GiveHint(target,1) return ..() @@ -402,7 +402,7 @@ //Warp Whistle: Provides uncontrolled long distance teleportation. -/obj/item/warpwhistle +/obj/item/warp_whistle name = "warp whistle" desc = "One toot on this whistle will send you to a far away land!" icon = 'icons/obj/wizard.dmi' @@ -410,18 +410,18 @@ var/on_cooldown = 0 //0: usable, 1: in use, 2: on cooldown var/mob/living/carbon/last_user -/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user) +/obj/item/warp_whistle/proc/interrupted(mob/living/carbon/user) if(!user || QDELETED(src) || user.notransform) on_cooldown = FALSE return TRUE return FALSE -/obj/item/warpwhistle/proc/end_effect(mob/living/carbon/user) +/obj/item/warp_whistle/proc/end_effect(mob/living/carbon/user) user.invisibility = initial(user.invisibility) user.status_flags &= ~GODMODE user.update_mobility() -/obj/item/warpwhistle/attack_self(mob/living/carbon/user) +/obj/item/warp_whistle/attack_self(mob/living/carbon/user) if(!istype(user) || on_cooldown) return on_cooldown = TRUE @@ -457,7 +457,7 @@ sleep(4 SECONDS) on_cooldown = 0 -/obj/item/warpwhistle/Destroy() +/obj/item/warp_whistle/Destroy() if(on_cooldown == 1 && last_user) //Flute got dunked somewhere in the teleport end_effect(last_user) return ..() diff --git a/code/modules/antagonists/wizard/equipment/soulstone.dm b/code/modules/antagonists/wizard/equipment/soulstone.dm index a43d1bc97079..391e551cb076 100644 --- a/code/modules/antagonists/wizard/equipment/soulstone.dm +++ b/code/modules/antagonists/wizard/equipment/soulstone.dm @@ -61,7 +61,7 @@ /obj/item/soulstone/attack(mob/living/carbon/human/M, mob/living/user) if(!iscultist(user) && !iswizard(user) && !usability) - user.Unconscious(100) + user.Unconscious(10 SECONDS) to_chat(user, span_userdanger("Your body is wracked with debilitating pain!")) return if(spent) @@ -69,13 +69,18 @@ return if(!ishuman(M))//If target is not a human. return ..() - if(iscultist(M)) - if(iscultist(user)) - to_chat(user, span_cultlarge("\"Come now, do not capture your bretheren's soul.\"")) - return + if(iscultist(M) && iscultist(user)) + to_chat(user, span_cultlarge("\"Come now, do not capture your bretheren's soul.\"")) + return if(purified && iscultist(user)) to_chat(user, span_warning("Holy magic resides within the stone, you cannot use it.")) return + /*if(theme == THEME_HOLY && IS_CULTIST(user)) + hot_potato(user) + return*/ + if(HAS_TRAIT(M, TRAIT_NO_SOUL)) + to_chat(user, span_warning("This body does not possess a soul to capture.")) + return log_combat(user, M, "captured [M.name]'s soul", src) transfer_soul("VICTIM", M, user) @@ -131,8 +136,9 @@ var/obj/item/soulstone/SS = O if(!iscultist(user) && !iswizard(user) && !SS.purified) to_chat(user, span_danger("An overwhelming feeling of dread comes over you as you attempt to place the soulstone into the shell. It would be wise to be rid of this quickly.")) - user.Dizzy(30) - return + if(isliving(user)) + var/mob/living/living_user = user + living_user.set_dizzy_if_lower(1 MINUTES) if(SS.purified && iscultist(user)) to_chat(user, span_warning("Holy magic resides within the stone, you cannot use it.")) return @@ -238,7 +244,6 @@ for(var/datum/mind/B in SSticker.mode.cult) if(B == A.mind) SSticker.mode.cult -= A.mind - SSticker.mode.update_cult_icons_removed(A.mind) qdel(T) qdel(src) else diff --git a/code/modules/antagonists/wizard/equipment/spellbook.dm b/code/modules/antagonists/wizard/equipment/spellbook.dm deleted file mode 100644 index 1968d60bc8d3..000000000000 --- a/code/modules/antagonists/wizard/equipment/spellbook.dm +++ /dev/null @@ -1,733 +0,0 @@ -/datum/spellbook_entry - var/name = "Entry Name" - - var/spell_type = null - var/desc = "" - var/category = "Offensive" - var/cost = 2 - var/refundable = TRUE - var/surplus = -1 // -1 for infinite, not used by anything atm - 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/limit //used to prevent a spellbook_entry from being bought more than X times with one wizard spellbook - var/list/no_coexistance_typecache //Used so you can't have specific spells together - -/datum/spellbook_entry/New() - ..() - no_coexistance_typecache = typecacheof(no_coexistance_typecache) - -/datum/spellbook_entry/proc/IsAvailible() // 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(book.uses= aspell.level_max) - to_chat(user, span_warning("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 - switch(aspell.spell_level) - if(1) - to_chat(user, span_notice("You have improved [aspell.name] into Efficient [aspell.name].")) - aspell.name = "Efficient [aspell.name]" - if(2) - to_chat(user, span_notice("You have further improved [aspell.name] into Quickened [aspell.name].")) - aspell.name = "Quickened [aspell.name]" - if(3) - to_chat(user, span_notice("You have further improved [aspell.name] into Free [aspell.name].")) - aspell.name = "Free [aspell.name]" - if(4) - to_chat(user, span_notice("You have further improved [aspell.name] into Instant [aspell.name].")) - aspell.name = "Instant [aspell.name]" - if(aspell.spell_level >= aspell.level_max) - to_chat(user, span_notice("This spell cannot be strengthened any further.")) - SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.spell_level]")) - return TRUE - //No same spell found - just learn it - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - user.mind.AddSpell(S) - to_chat(user, span_notice("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, span_warning("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) - qdel(S) - return cost * (spell_levels+1) - return -1 -/datum/spellbook_entry/proc/GetInfo() - if(!S) - S = new spell_type() - var/dat ="" - dat += "[initial(S.name)]" - if(S.charge_type == "recharge") - dat += " Cooldown:[S.charge_max/10]" - dat += " Cost:[cost]
" - dat += "[S.desc][desc]
" - dat += "[S.clothes_req?"Requires wizard garb.":"Can be cast without wizard garb."]
" - return dat - -/datum/spellbook_entry/fireball - name = "Fireball" - spell_type = /obj/effect/proc_holder/spell/aimed/fireball - -/datum/spellbook_entry/spell_cards - name = "Spell Cards" - spell_type = /obj/effect/proc_holder/spell/aimed/spell_cards - -/datum/spellbook_entry/rod_form - name = "Rod Form" - spell_type = /obj/effect/proc_holder/spell/targeted/rod_form - -/datum/spellbook_entry/magicm - name = "Magic Missile" - spell_type = /obj/effect/proc_holder/spell/targeted/projectile/magic_missile - category = "Defensive" - -/datum/spellbook_entry/pacify - name = "Pacify" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/pacify - -/datum/spellbook_entry/disabletech - name = "Disable Tech" - spell_type = /obj/effect/proc_holder/spell/targeted/emplosion/disable_tech - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/repulse - name = "Repulse" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/repulse - category = "Defensive" - -/datum/spellbook_entry/lightningPacket - name = "Lightning bolt! Lightning bolt!" - spell_type = /obj/effect/proc_holder/spell/targeted/conjure_item/spellpacket - category = "Defensive" - -/datum/spellbook_entry/timestop - name = "Time Stop" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/timestop - category = "Defensive" - -/datum/spellbook_entry/smoke - name = "Smoke" - spell_type = /obj/effect/proc_holder/spell/targeted/smoke - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blind - name = "Blind" - spell_type = /obj/effect/proc_holder/spell/pointed/trigger/blind - cost = 1 - -/datum/spellbook_entry/forcewall - name = "Force Wall" - spell_type = /obj/effect/proc_holder/spell/targeted/forcewall - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/blink - name = "Blink" - spell_type = /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - category = "Mobility" - -/datum/spellbook_entry/teleport - name = "Teleport" - spell_type = /obj/effect/proc_holder/spell/targeted/area_teleport/teleport - category = "Mobility" - -/datum/spellbook_entry/mutate - name = "Mutate" - spell_type = /obj/effect/proc_holder/spell/targeted/genetic/mutate - -/datum/spellbook_entry/jaunt - name = "Ethereal Jaunt" - spell_type = /obj/effect/proc_holder/spell/targeted/ethereal_jaunt - category = "Mobility" - -/datum/spellbook_entry/knock - name = "Knock" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/knock - category = "Mobility" - cost = 1 - -/datum/spellbook_entry/fleshtostone - name = "Flesh to Stone" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/flesh_to_stone - -/datum/spellbook_entry/touchofdeath //yogs start - name = "Touch of Death" - spell_type = /obj/effect/proc_holder/spell/targeted/touch/touch_of_death //yogs end - -/datum/spellbook_entry/summonitem - name = "Summon Item" - spell_type = /obj/effect/proc_holder/spell/targeted/summonitem - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/lichdom - name = "Bind Soul" - spell_type = /obj/effect/proc_holder/spell/targeted/lichdom - category = "Defensive" - cost = 4 - -/datum/spellbook_entry/teslablast - name = "Tesla Blast" - spell_type = /obj/effect/proc_holder/spell/targeted/tesla - -/datum/spellbook_entry/lightningbolt - name = "Lightning Bolt" - 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" - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - cost = 3 - no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - -/datum/spellbook_entry/arcane_barrage - name = "Arcane Barrage" - spell_type = /obj/effect/proc_holder/spell/targeted/infinite_guns/arcane_barrage - cost = 3 - no_coexistance_typecache = /obj/effect/proc_holder/spell/targeted/infinite_guns/gun - -/datum/spellbook_entry/barnyard - name = "Barnyard Curse" - spell_type = /obj/effect/proc_holder/spell/targeted/barnyardcurse - -/datum/spellbook_entry/charge - name = "Charge" - spell_type = /obj/effect/proc_holder/spell/targeted/charge - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/shapeshift - name = "Wild Shapeshift" - spell_type = /obj/effect/proc_holder/spell/targeted/shapeshift - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/tap - name = "Soul Tap" - spell_type = /obj/effect/proc_holder/spell/self/tap - category = "Assistance" - cost = 1 - -/datum/spellbook_entry/spacetime_dist - name = "Spacetime Distortion" - spell_type = /obj/effect/proc_holder/spell/spacetime_dist - category = "Defensive" - cost = 1 - -/datum/spellbook_entry/the_traps - name = "The Traps!" - spell_type = /obj/effect/proc_holder/spell/aoe_turf/conjure/the_traps - category = "Defensive" - cost = 1 - - -/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/GetInfo() - var/dat ="" - dat += "[name]" - dat += " Cost:[cost]
" - dat += "[desc]
" - if(surplus>=0) - dat += "[surplus] left.
" - return dat - -/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/soulstones - name = "Six Soul Stone Shards and the spell Artificer" - 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 artifact suit of armor that allows you to cast spells while providing more protection against attacks and the void of space. Additionally provides anti-magic protection, though this may interfere with magic you cast on yourself (i.e Mutate or wands)." - item_path = /obj/item/clothing/suit/space/hardsuit/wizard - cost = 1 - 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 - -/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/guardiancreator/wizard - category = "Assistance" - -/datum/spellbook_entry/item/guardian/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - -/datum/spellbook_entry/item/rune_crate - name = "Rune Crate" - desc = "A wizard specialized in runecrafting is offering two chests full of runes! The problem is, he mixed up the contents of them so you won't know what you will get!" - item_path = /obj/structure/closet/crate/magic - category = "Offensive" - cost = 1 - buy_word = "Order" - -/datum/spellbook_entry/item/rune_crate/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . = ..() - if(.) - new /obj/structure/closet/crate/magic(get_turf(user)) //Two crates full of magic goodies - - - -/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" - -/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" - -/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/twohanded/mjollnir - cost = 3 - -/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/twohanded/singularityhammer - -/datum/spellbook_entry/item/demon_chainsaw - name = "Demonic Chainsaw" - desc = "A demon that has taken the form of a chainsaw! This demon will grant you eternal life, so long as you feed it blood!" - item_path = /obj/item/twohanded/required/chainsaw/demon - cost = 3 -/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. It is not protected against the void of space." - item_path = /obj/item/clothing/suit/wizrobe/armor - limit = 1 - category = "Defensive" - -/datum/spellbook_entry/item/battlemage/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) - . =..() - if(.) - new /obj/item/clothing/head/wizard/armor(get_turf(user))//To complete the outfit - - -/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 - -/datum/spellbook_entry/summon - name = "Summon Stuff" - category = "Rituals" - refundable = FALSE - buy_word = "Cast" - var/active = FALSE - -/datum/spellbook_entry/summon/CanBuy(mob/living/carbon/human/user,obj/item/spellbook/book) - return ..() && !active - -/datum/spellbook_entry/summon/GetInfo() - var/dat ="" - dat += "[name]" - if(cost>0) - dat += " Cost:[cost]
" - else - dat += " No Cost
" - dat += "[desc]
" - if(active) - dat += "Already cast!
" - return dat - -/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 - -/datum/spellbook_entry/summon/ghosts/IsAvailible() - if(!SSticker.mode) - return FALSE - else - return TRUE - -/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() - active = TRUE - to_chat(user, span_notice("You have cast summon ghosts!")) - playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, 1) - return TRUE - -/datum/spellbook_entry/summon/portals - name = "Summon Portal Storm" - desc = "Terrorize the crew with a portal storm! Whatever crawls out of these portals will be a threat not just to the crew, but you as well!" - cost = 1 - -/datum/spellbook_entry/summon/portals/IsAvailible() - if(!SSticker.mode) - return FALSE - else - return TRUE - -/datum/spellbook_entry/summon/portals/Buy(mob/living/carbon/human/user, obj/item/spellbook/book) - SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name) - var/portaltype = pick("port_shocktroop","port_narsiespawn") - switch(portaltype) - if("port_shocktroop") - var/datum/round_event_control/portal_storm_syndicate/newShocktroopControl = new /datum/round_event_control/portal_storm_syndicate() - var/datum/round_event/portal_storm/syndicate_shocktroop/newShocktroopStorm = newShocktroopControl.runEvent() - newShocktroopStorm.setup() - if("port_narsiespawn") - var/datum/round_event_control/portal_storm_narsie/newNarsieControl = new /datum/round_event_control/portal_storm_narsie() - var/datum/round_event/portal_storm/portal_storm_narsie/newNarsieStorm = newNarsieControl.runEvent() - newNarsieStorm.setup() - active = TRUE - to_chat(user, span_notice("You have summoned a portal storm!")) - playsound(get_turf(user), 'sound/magic/lightningbolt.ogg', 50, 1) - return TRUE - -/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 - -/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) - active = TRUE - var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness") - if(!message) - return FALSE - curse_of_madness(user, message) - to_chat(user, span_notice("You have cast the curse of insanity!")) - playsound(user, 'sound/magic/mandswap.ogg', 50, 1) - return TRUE - -/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/tab = null - var/mob/living/carbon/human/owner - var/list/datum/spellbook_entry/entries = list() - var/list/categories = list() - -/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() - . = ..() - prepare_spells() - -/obj/item/spellbook/proc/prepare_spells() - var/entry_types = subtypesof(/datum/spellbook_entry) - /datum/spellbook_entry/item - /datum/spellbook_entry/summon - for(var/T in entry_types) - var/datum/spellbook_entry/E = new T - if(E.IsAvailible()) - entries |= E - categories |= E.category - else - qdel(E) - tab = categories[1] - -/obj/item/spellbook/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/antag_spawner/contract)) - var/obj/item/antag_spawner/contract/contract = O - if(contract.used) - to_chat(user, span_warning("The contract has been used, you can't get your points back now!")) - else - to_chat(user, span_notice("You feed the contract back into the spellbook, refunding your points.")) - uses++ - 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, span_notice("On second thought, maybe summoning a demon is a bad idea. You refund your points.")) - uses++ - for(var/datum/spellbook_entry/item/bloodbottle/BB in entries) - if(!isnull(BB.limit)) - BB.limit++ - qdel(O) - -/obj/item/spellbook/proc/GetCategoryHeader(category) - var/dat = "" - switch(category) - if("Offensive") - dat += "Spells and items geared towards debilitating and destroying.

" - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
" - dat += "For spells: the number after the spell name is the cooldown time.
" - dat += "You can reduce this number by spending more points on the spell.
" - if("Defensive") - dat += "Spells and items geared towards improving your survivability or reducing foes' ability to attack.

" - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
" - dat += "For spells: the number after the spell name is the cooldown time.
" - dat += "You can reduce this number by spending more points on the spell.
" - if("Mobility") - dat += "Spells and items geared towards improving your ability to move. It is a good idea to take at least one.

" - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
" - dat += "For spells: the number after the spell name is the cooldown time.
" - dat += "You can reduce this number by spending more points on the spell.
" - if("Assistance") - dat += "Spells and items geared towards bringing in outside forces to aid you or improving upon your other items and abilities.

" - dat += "Items are not bound to you and can be stolen. Additionally they cannot typically be returned once purchased.
" - dat += "For spells: the number after the spell name is the cooldown time.
" - dat += "You can reduce this number by spending more points on the spell.
" - if("Challenges") - dat += "The Wizard Federation typically has hard limits on the potency and number of spells brought to the station based on risk.
" - dat += "Arming the station against you will increases the risk, but will grant you one more charge for your spellbook.
" - if("Rituals") - dat += "These powerful spells change the very fabric of reality. Not always in your favour.
" - return dat - -/obj/item/spellbook/proc/wrap(content) - var/dat = "" - dat +="Spellbook" - dat += {" - - - - "} - dat += {"[content]"} - return dat - -/obj/item/spellbook/attack_self(mob/user) - if(!owner) - to_chat(user, span_notice("You bind the spellbook to yourself.")) - owner = user - return - if(user != owner) - to_chat(user, span_warning("The [name] does not recognize you as its owner and refuses to open!")) - return - user.set_machine(src) - var/dat = "" - - dat += "" - - var/datum/spellbook_entry/E - for(var/i=1,i<=entries.len,i++) - var/spell_info = "" - E = entries[i] - spell_info += E.GetInfo() - if(E.CanBuy(user,src)) - spell_info+= "[E.buy_word]
" - else - spell_info+= "Can't [E.buy_word]
" - if(E.CanRefund(user,src)) - spell_info+= "Refund
" - spell_info += "
" - if(cat_dat[E.category]) - cat_dat[E.category] += spell_info - - for(var/category in categories) - dat += "
" - dat += GetCategoryHeader(category) - dat += cat_dat[category] - dat += "
" - - user << browse(wrap(dat), "window=spellbook;size=700x500") - onclose(user, "spellbook") - return - -/obj/item/spellbook/Topic(href, href_list) - ..() - var/mob/living/carbon/human/H = usr - - if(H.stat || H.restrained()) - return - if(!ishuman(H)) - return TRUE - - if(H.mind.special_role == "apprentice") - temp = "If you got caught sneaking a peek from your teacher's spellbook, you'd likely be expelled from the Wizard Academy. Better not." - return - - var/datum/spellbook_entry/E = null - if(loc == H || (in_range(src, H) && isturf(loc))) - H.set_machine(src) - if(href_list["buy"]) - E = entries[text2num(href_list["buy"])] - if(E && E.CanBuy(H,src)) - if(E.Buy(H,src)) - if(E.limit) - E.limit-- - uses -= E.cost - else if(href_list["refund"]) - E = entries[text2num(href_list["refund"])] - if(E && E.refundable) - var/result = E.Refund(H,src) - if(result > 0) - if(!isnull(E.limit)) - E.limit += result - uses += result - else if(href_list["page"]) - tab = sanitize(href_list["page"]) - attack_self(H) - return 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 000000000000..8c8f6a11bca9 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/_entry.dm @@ -0,0 +1,232 @@ +/** + * ## 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/cooldown/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 + +/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) // 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/cooldown/spell/existing = locate(spell_type) in user.actions + if(existing) + var/before_name = existing.name + if(!existing.level_spell()) + to_chat(user, span_warning("This spell cannot be improved further!")) + return FALSE + + to_chat(user, span_notice("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/cooldown/spell/new_spell = new spell_type(user.mind || user) + new_spell.Grant(user) + to_chat(user, span_notice("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/cooldown/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/centcom/wizard_station/wizard_home = GLOB.areas_by_type[/area/centcom/wizard_station] + if(get_area(user) != wizard_home) + to_chat(user, span_warning("You can only refund spells at the wizard lair!")) + return -1 + + for(var/datum/action/cooldown/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, span_notice("\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 \ No newline at end of file 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 000000000000..e4c1fc739997 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm @@ -0,0 +1,107 @@ +// 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/cooldown/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/cooldown/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/cooldown/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/cooldown/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, span_notice("\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/cooldown/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/guardiancreator/choose/wizard + category = "Assistance" + +/datum/spellbook_entry/item/guardian/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + . = ..() + if(!.) + return + + new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) + to_chat(user, span_notice("If you are not experienced in the ways of wizardly guardians, a guide has been summoned at your feet.")) + +/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 \ No newline at end of file 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 000000000000..a65793ecfd81 --- /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\"." \ No newline at end of file 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 000000000000..130274210d75 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/defensive.dm @@ -0,0 +1,148 @@ +// Defensive wizard spells +/datum/spellbook_entry/magicm + name = "Magic Missile" + desc = "Fires several, slow moving, magic projectiles at nearby targets." + spell_type = /datum/action/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/spell/timestop + category = "Defensive" + +/datum/spellbook_entry/smoke + name = "Smoke" + desc = "Spawns a cloud of choking smoke at your location." + spell_type = /datum/action/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/spell/conjure/bee + category = "Defensive" + +/datum/spellbook_entry/duffelbag + name = "Bestow Cursed Duffel Bag" + desc = "A curse that firmly attaches a demonic duffel bag to the target's back. \ + The duffel bag will make the person it's attached to take periodical damage \ + if it is not fed regularly, and regardless of whether or not it's been fed, \ + it will slow the person wearing it down significantly." + spell_type = /datum/action/cooldown/spell/touch/duffelbag + category = "Defensive" + cost = 1 + +/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, span_notice("\A [to_equip.name] has been summoned [was_equipped ? "on your waist" : "at your feet"].")) + +/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. \ + Also grants a battlemage shield." + item_path = /obj/item/mod/control/pre_equipped/enchanted + category = "Defensive" + +/datum/spellbook_entry/item/armor/try_equip_item(mob/living/carbon/human/user, obj/item/to_equip) + var/obj/item/mod/control/mod = to_equip + var/obj/item/mod/module/storage/storage = locate() in mod.modules + var/obj/item/back = user.back + if(back) + if(!user.dropItemToGround(back)) + return + for(var/obj/item/item as anything in back.contents) + item.forceMove(storage) + if(!user.equip_to_slot_if_possible(mod, mod.slot_flags, qdel_on_fail = FALSE, disable_warning = TRUE)) + return + if(!user.dropItemToGround(user.wear_suit) || !user.dropItemToGround(user.head)) + return + mod.quick_activation() + +/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 \ No newline at end of file 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 000000000000..13400fd8c3a4 --- /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/cooldown/spell/pointed/mind_transfer + category = "Mobility" + +/datum/spellbook_entry/knock + name = "Knock" + desc = "Opens nearby doors and closets." + spell_type = /datum/action/cooldown/spell/aoe/knock + category = "Mobility" + cost = 1 + +/datum/spellbook_entry/blink + name = "Blink" + desc = "Randomly teleports you a short distance." + spell_type = /datum/action/cooldown/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/cooldown/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/cooldown/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/warp_whistle + 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" \ No newline at end of file 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 000000000000..fca0e518f706 --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/offensive.dm @@ -0,0 +1,115 @@ +// 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/cooldown/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/cooldown/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/cooldown/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/cooldown/spell/touch/smite + category = "Offensive" + +/datum/spellbook_entry/blind + name = "Blind" + desc = "Temporarily blinds a single target." + spell_type = /datum/action/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/spell/conjure_item/infinite_guns/gun + category = "Offensive" + cost = 3 + no_coexistance_typecache = list(/datum/action/cooldown/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/cooldown/spell/conjure_item/infinite_guns/arcane_barrage + category = "Offensive" + cost = 3 + no_coexistance_typecache = list(/datum/action/cooldown/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/cooldown/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/mjollnir + 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" + +/datum/spellbook_entry/item/highfrequencyblade + name = "High Frequency Blade" + desc = "An incredibly swift enchanted blade resonating at a frequency high enough to be able to slice through anything." + item_path = /obj/item/highfrequencyblade/wizard + category = "Offensive" + cost = 3 \ No newline at end of file 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 000000000000..509daa6cd21b --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/summons.dm @@ -0,0 +1,87 @@ +// 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/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 + +/datum/spellbook_entry/summon/ghosts/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) + summon_ghosts(user) + playsound(get_turf(user), 'sound/effects/ghost2.ogg', 50, TRUE) + return ..() + +/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() + // Summon Guns requires 100 threat. + var/datum/game_mode/dynamic/mode = SSticker.mode + if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) + return FALSE + // 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) + summon_guns(user, 10) + 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() + // Summon Magic requires 100 threat. + var/datum/game_mode/dynamic/mode = SSticker.mode + if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) + return FALSE + // 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) + summon_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() + // Summon Events requires 100 threat. + var/datum/game_mode/dynamic/mode = SSticker.mode + if(mode.threat_level < MINIMUM_THREAT_FOR_RITUALS) + return FALSE + // 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) + summon_events(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 + +/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 \ No newline at end of file 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 000000000000..8b35f09145be --- /dev/null +++ b/code/modules/antagonists/wizard/equipment/wizard_spellbook.dm @@ -0,0 +1,329 @@ +/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/cooldown/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, span_warning("Glowing red letters appear on the front cover...")) + to_chat(caster, span_red(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, span_notice("You bind [src] to yourself.")) + owner = user.mind + return + + if(user.mind != owner) + if(user.mind?.special_role == ROLE_WIZARD_APPRENTICE) + to_chat(user, span_warning("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, span_warning("[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, span_warning("[src] doesn't seem to want to refund [O].")) + return + if(!contract_entry.can_refund(user, src)) + to_chat(user, span_warning("You can't refund [src].")) + return + var/obj/item/antag_spawner/contract/contract = O + if(contract.used) + to_chat(user, span_warning("The contract has been used, you can't get your points back now!")) + return + + to_chat(user, span_notice("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, span_warning("[src] doesn't seem to want to refund [O].")) + return + if(!demon_entry.can_refund(user, src)) + to_chat(user, span_warning("You can't refund [O].")) + return + + to_chat(user, span_notice("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, span_warning("[src] doesn't seem to want to refund [O].")) + return + if(!demon_entry.can_refund(user, src)) + to_chat(user, span_warning("You can't refund [O].")) + return + + to_chat(user, span_notice("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["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, span_warning("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, span_warning("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, locate(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 \ No newline at end of file diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm index 1ec39021677c..53ac9f2a29d1 100644 --- a/code/modules/antagonists/wizard/wizard.dm +++ b/code/modules/antagonists/wizard/wizard.dm @@ -1,13 +1,16 @@ +/// 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" antagpanel_category = "Wizard" job_rank = ROLE_WIZARD + antag_hud_name = "wizard" antag_moodlet = /datum/mood_event/focused var/give_objectives = TRUE var/strip = TRUE //strip before equipping var/allow_rename = TRUE - var/hud_version = "wizard" var/datum/team/wizard/wiz_team //Only created if wizard summons apprentices var/move_to_lair = TRUE var/outfit_type = /datum/outfit/wizard @@ -51,7 +54,6 @@ wiz_team = new(owner) wiz_team.name = "[owner.current.real_name] team" wiz_team.master_wizard = src - update_wiz_icons_added(owner.current) /datum/antagonist/wizard/proc/send_to_lair() if(!owner || !owner.current) @@ -109,7 +111,11 @@ /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/cooldown/spell/spell in owner.current.actions) + if(spell.target == owner) + qdel(spell) + owner.current.actions -= spell return ..() /datum/antagonist/wizard/proc/equip_wizard() @@ -154,12 +160,11 @@ /datum/antagonist/wizard/apply_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current - update_wiz_icons_added(M, wiz_team ? TRUE : FALSE) //Don't bother showing the icon if you're solo wizard M.faction |= ROLE_WIZARD + add_team_hud(M) /datum/antagonist/wizard/remove_innate_effects(mob/living/mob_override) var/mob/living/M = mob_override || owner.current - update_wiz_icons_removed(M) M.faction -= ROLE_WIZARD @@ -172,7 +177,7 @@ /datum/antagonist/wizard/apprentice name = "Wizard Apprentice" - hud_version = "apprentice" + antag_hud_name = "apprentice" var/datum/mind/master var/school = APPRENTICE_DESTRUCTION outfit_type = /datum/outfit/wizard/apprentice @@ -250,20 +255,12 @@ H.equip_to_slot_or_del(new master_mob.back.type, 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)) - -/datum/antagonist/wizard/proc/update_wiz_icons_added(mob/living/wiz,join = TRUE) - var/datum/atom_hud/antag/wizhud = GLOB.huds[ANTAG_HUD_WIZ] - wizhud.join_hud(wiz) - set_antag_hud(wiz, hud_version) - -/datum/antagonist/wizard/proc/update_wiz_icons_removed(mob/living/wiz) - var/datum/atom_hud/antag/wizhud = GLOB.huds[ANTAG_HUD_WIZ] - wizhud.leave_hud(wiz) - set_antag_hud(wiz, null) - + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/jaunt = new(owner) + jaunt.Grant(H) + var/datum/action/cooldown/spell/teleport/area_teleport/wizard/teleport = new(owner) + teleport.Grant(H) + var/datum/action/cooldown/spell/teleport/radius_turf/blink/blink = new(owner) + blink.Grant(H) /datum/antagonist/wizard/academy name = "Academy Teacher" @@ -271,17 +268,19 @@ /datum/antagonist/wizard/academy/equip_wizard() . = ..() - - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/ethereal_jaunt) - owner.AddSpell(new /obj/effect/proc_holder/spell/targeted/projectile/magic_missile) - owner.AddSpell(new /obj/effect/proc_holder/spell/aimed/fireball) - - var/mob/living/M = owner.current - if(!istype(M)) + if(!isliving(owner.current)) return + var/mob/living/living_current = owner.current - var/obj/item/implant/exile/Implant = new/obj/item/implant/exile(M) - Implant.implant(M) + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/jaunt = new(owner) + jaunt.Grant(living_current) + var/datum/action/cooldown/spell/aoe/magic_missile/missile = new(owner) + missile.Grant(living_current) + var/datum/action/cooldown/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") @@ -310,12 +309,19 @@ else parts += span_redtext("The wizard has failed!") SSachievements.unlock_achievement(/datum/achievement/redtext/winlost, owner.current.client) //wizard loses, still give achievement lol - 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 += span_bold("[owner.name] used the following spells:") + parts += purchases.Join(", ") + else + parts += span_bold("[owner.name] didn't buy any spells!") return parts.Join("
") diff --git a/code/modules/antagonists/zombie/abilities/spit.dm b/code/modules/antagonists/zombie/abilities/spit.dm index e244411adfba..302a471fbfad 100644 --- a/code/modules/antagonists/zombie/abilities/spit.dm +++ b/code/modules/antagonists/zombie/abilities/spit.dm @@ -14,7 +14,7 @@ /obj/effect/proc_holder/zombie/spit/update_icon() action.button_icon_state = "alien_neurotoxin_[active]" - action.UpdateButtonIcon() + action.UpdateButtons() /obj/effect/proc_holder/zombie/spit/InterceptClickOn(mob/living/caller, params, atom/target) if(..()) diff --git a/code/modules/antagonists/zombie/zombie.dm b/code/modules/antagonists/zombie/zombie.dm index beba12770422..2b7f0e4ab99e 100644 --- a/code/modules/antagonists/zombie/zombie.dm +++ b/code/modules/antagonists/zombie/zombie.dm @@ -35,7 +35,7 @@ job_rank = ROLE_ZOMBIE var/datum/team/zombie/team - var/hud_type = "zombie" + antag_hud_name = "zombie" var/class_chosen = FALSE var/class_chosen_2 = FALSE @@ -82,13 +82,8 @@ var/mob/living/current = owner.current add_objectives() GLOB.zombies += owner - current.log_message("has been made a zombie!", LOG_ATTACK, color="#960000") - var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE] - zombie_hud.join_hud(current) - set_antag_hud(current, hud_type) - /datum/antagonist/zombie/apply_innate_effects() . = ..() @@ -105,10 +100,6 @@ /datum/antagonist/zombie/on_removal() GLOB.zombies -= owner - - var/datum/atom_hud/antag/zombie_hud = GLOB.huds[ANTAG_HUD_ZOMBIE] - zombie_hud.leave_hud(owner.current) - set_antag_hud(owner.current, null) . = ..() @@ -186,7 +177,7 @@ icon_icon = 'icons/mob/actions/actions_changeling.dmi' background_icon_state = "bg_demon" buttontooltipstyle = "cult" - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS /datum/action/innate/zombie/IsAvailable() if(!isinfected(owner)) diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm index 04057d6042db..9e242f1bd3f8 100644 --- a/code/modules/assembly/flash.dm +++ b/code/modules/assembly/flash.dm @@ -114,7 +114,7 @@ last_trigger = world.time playsound(src, 'sound/weapons/flash.ogg', 100, TRUE) set_light_on(TRUE) - addtimer(CALLBACK(src, .proc/flash_end), FLASH_LIGHT_DURATION, TIMER_OVERRIDE|TIMER_UNIQUE) + addtimer(CALLBACK(src, PROC_REF(flash_end)), FLASH_LIGHT_DURATION, TIMER_OVERRIDE|TIMER_UNIQUE) times_used++ flash_recharge() update_icon(TRUE) @@ -136,9 +136,7 @@ to_chat(M, span_disarm("[src] emits a blinding light!")) if(targeted) if(M.flash_act(1, 1)) - if(M.confused < power) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) + M.set_confusion_if_lower(power * CONFUSION_STACK_MAX_MULTIPLIER SECONDS) if(user) terrible_conversion_proc(M, user) visible_message(span_disarm("[user] blinds [M] with the flash!")) @@ -160,8 +158,7 @@ to_chat(M, span_danger("[src] fails to blind you!")) else if(M.flash_act()) - var/diff = power * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - M.confused += min(power, diff) + M.set_confusion_if_lower(power * CONFUSION_STACK_MAX_MULTIPLIER SECONDS) /obj/item/assembly/flash/attack(mob/living/M, mob/user) if(!try_use_flash(user)) @@ -175,8 +172,7 @@ log_combat(user, R, "flashed", src) update_icon(1) R.Paralyze(rand(80,120)) - var/diff = 5 * CONFUSION_STACK_MAX_MULTIPLIER - M.confused - R.confused += min(5, diff) + R.set_confusion_if_lower(5 SECONDS * CONFUSION_STACK_MAX_MULTIPLIER) R.flash_act(affect_silicon = 1) user.visible_message(span_disarm("[user] overloads [R]'s sensors with the flash!"), span_danger("You overload [R]'s sensors with the flash!")) return TRUE @@ -325,10 +321,10 @@ if(!hypnosis) to_chat(M, span_notice("The light makes you feel oddly relaxed...")) - M.confused += min(M.confused + 10, 20) - M.dizziness += min(M.dizziness + 10, 20) - M.drowsyness += min(M.drowsyness + 10, 20) - M.apply_status_effect(STATUS_EFFECT_PACIFY, 100) + M.adjust_confusion_up_to(10 SECONDS, 20 SECONDS) + M.adjust_dizzy_up_to(20 SECONDS, 40 SECONDS) + M.adjust_drowsiness_up_to(20 SECONDS, 40 SECONDS) + M.apply_status_effect(STATUS_EFFECT_PACIFY, 10 SECONDS) else M.apply_status_effect(/datum/status_effect/trance, 200, TRUE) @@ -339,7 +335,7 @@ else if(M.flash_act()) to_chat(M, span_notice("Such a pretty light...")) - M.confused += min(M.confused + 4, 20) - M.dizziness += min(M.dizziness + 4, 20) - M.drowsyness += min(M.drowsyness + 4, 20) + M.adjust_confusion_up_to(4 SECONDS, 20 SECONDS) + M.adjust_dizzy_up_to(8 SECONDS, 40 SECONDS) + M.adjust_drowsiness_up_to(8 SECONDS, 40 SECONDS) M.apply_status_effect(STATUS_EFFECT_PACIFY, 40) diff --git a/code/modules/assembly/igniter.dm b/code/modules/assembly/igniter.dm index 2264b72a0459..6c1260c559b6 100644 --- a/code/modules/assembly/igniter.dm +++ b/code/modules/assembly/igniter.dm @@ -8,7 +8,7 @@ /obj/item/assembly/igniter/suicide_act(mob/living/carbon/user) user.visible_message(span_suicide("[user] is trying to ignite [user.p_them()]self with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")) - user.IgniteMob() + user.ignite_mob() return FIRELOSS /obj/item/assembly/igniter/Initialize() diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index bf4de8017a2c..72f15ee10549 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -223,3 +223,20 @@ return /obj/item/assembly/signaler/cyborg/screwdriver_act(mob/living/user, obj/item/I) return + +/obj/item/assembly/signaler/internal + name = "internal remote signaling device" + +/obj/item/assembly/signaler/internal/ui_state(mob/user) + return GLOB.inventory_state + +/obj/item/assembly/signaler/internal/attackby(obj/item/W, mob/user, params) + return + +/obj/item/assembly/signaler/internal/screwdriver_act(mob/living/user, obj/item/I) + return + +/obj/item/assembly/signaler/internal/can_interact(mob/user) + if(ispAI(user)) + return TRUE + . = ..() diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index 9d070e6925e7..b9685943781a 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -975,7 +975,7 @@ nobliumformation = 1001 air.adjust_moles(/datum/gas/bz, -consumed_amount) else for(var/mob/living/carbon/L in location) - L.hallucination += (air.get_moles(/datum/gas/bz) * 0.7) // Yogs -- fixed accidental "path * number" + L.adjust_hallucinations(air.get_moles(/datum/gas/bz) * 0.7) // Yogs -- fixed accidental "path * number" energy_released += 100 if(energy_released) var/new_heat_capacity = air.heat_capacity() diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm index 50c7d1ca7fe3..2836c6199e60 100644 --- a/code/modules/atmospherics/machinery/datum_pipeline.dm +++ b/code/modules/atmospherics/machinery/datum_pipeline.dm @@ -202,9 +202,8 @@ /datum/pipeline/proc/return_air() . = other_airs + air - if(null in .) + if(listclearnulls(.)) stack_trace("[src] has one or more null gas mixtures, which may cause bugs. Null mixtures will not be considered in reconcile_air().") - return removeNullsFromList(.) /datum/pipeline/proc/reconcile_air() var/list/datum/gas_mixture/GL = list() diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm index e2021e93301f..1646f7671e60 100644 --- a/code/modules/awaymissions/cordon.dm +++ b/code/modules/awaymissions/cordon.dm @@ -35,24 +35,32 @@ /turf/cordon/ScrapeAway(amount, flags) return src // :devilcat: -/turf/cordon/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit) +/turf/cordon/bullet_act(obj/item/projectile/hitting_projectile, def_zone, piercing_hit) return BULLET_ACT_HIT /turf/cordon/Adjacent(atom/neighbor, atom/target, atom/movable/mover) return FALSE -/area/cordon +/// Area used in conjuction with the cordon turf to create a fully functioning world border. +/area/misc/cordon name = "CORDON" icon_state = "cordon" - dynamic_lighting = DYNAMIC_LIGHTING_DISABLED unique = TRUE noteleport = TRUE hidden = TRUE + area_flags = NOTELEPORT requires_power = FALSE -/area/cordon/Entered(atom/movable/arrived, area/old_area) +/area/misc/cordon/Entered(atom/movable/arrived, area/old_area) . = ..() for(var/mob/living/enterer as anything in arrived.get_all_contents_type(/mob/living)) to_chat(enterer, span_userdanger("This was a bad idea...")) enterer.dust(TRUE, FALSE, TRUE) + +/// This type of cordon will block ghosts from passing through it. Useful for stuff like Away Missions, where you feasibly want to block ghosts from entering to keep a certain map section a secret. +/turf/cordon/secret + name = "secret cordon (ghost blocking)" + +/turf/cordon/secret/attack_ghost(mob/dead/observer/user) + return FALSE diff --git a/code/modules/awaymissions/mission_code/Academy.dm b/code/modules/awaymissions/mission_code/Academy.dm index dae3aafe6ec4..4d169fc080a4 100644 --- a/code/modules/awaymissions/mission_code/Academy.dm +++ b/code/modules/awaymissions/mission_code/Academy.dm @@ -314,7 +314,7 @@ //Random One-use spellbook T.visible_message(span_userdanger("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(span_userdanger("A Dice Servant appears in a cloud of smoke!")) @@ -334,9 +334,8 @@ message_admins("[ADMIN_LOOKUPFLW(C)] was spawned as Dice Servant") H.key = C.key - var/obj/effect/proc_holder/spell/targeted/summonmob/S = new - S.target_mob = H - user.mind.AddSpell(S) + var/datum/action/cooldown/spell/summon_mob/summon_servant = new(user.mind || user, H) + summon_servant.Grant(user) if(17) //Tator Kit @@ -367,31 +366,45 @@ glasses = /obj/item/clothing/glasses/monocle gloves = /obj/item/clothing/gloves/color/white -/obj/effect/proc_holder/spell/targeted/summonmob +/datum/action/cooldown/spell/summon_mob name = "Summon Servant" desc = "This spell can be used to call your servant, whenever you need it." - charge_max = 100 - clothes_req = 0 + + school = SCHOOL_CONJURATION + cooldown_time = 10 SECONDS invocation = "JE VES" - invocation_type = "whisper" - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = 1 + invocation_type = INVOCATION_WHISPER + + spell_requirements = NONE + spell_max_level = 0 //cannot be improved + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 2 + + var/datum/weakref/summon_weakref - var/mob/living/target_mob + button_icon = 'icons/mob/actions/humble/actions_humble.dmi' + icon_icon = "summon_servant" - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "summon_servant" +/datum/action/cooldown/spell/summon_mob/New(Target, mob/living/summoned_mob) + . = ..() + if(summoned_mob) + summon_weakref = WEAKREF(summoned_mob) -/obj/effect/proc_holder/spell/targeted/summonmob/cast(list/targets,mob/user = usr) - if(!target_mob) +/datum/action/cooldown/spell/summon_mob/cast(atom/cast_on) + . = ..() + var/mob/living/to_summon = summon_weakref?.resolve() + if(QDELETED(to_summon)) + to_chat(cast_on, span_warning("You can't seem to summon your servant - it seems they've vanished from reality, or never existed in the first place...")) return - var/turf/Start = get_turf(user) - for(var/direction in GLOB.alldirs) - var/turf/T = get_step(Start,direction) - if(!T.density) - target_mob.Move(T) + + do_teleport( + to_summon, + get_turf(cast_on), + precision = 1, + asoundin = 'sound/magic/wand_teleport.ogg', + asoundout = 'sound/magic/wand_teleport.ogg', + channel = TELEPORT_CHANNEL_MAGIC, + ) /obj/structure/ladder/unbreakable/rune name = "\improper Teleportation Rune" diff --git a/code/modules/awaymissions/mission_code/snowdin.dm b/code/modules/awaymissions/mission_code/snowdin.dm index cff787e7edd2..003330a15a9e 100644 --- a/code/modules/awaymissions/mission_code/snowdin.dm +++ b/code/modules/awaymissions/mission_code/snowdin.dm @@ -241,7 +241,7 @@ PP.visible_message(span_warning("[L] screams in pain as [L.p_their()] [NB] melts down to the bone!"), \ span_userdanger("You scream out in pain as your [NB] melts down to the bone, leaving an eerie plasma-like glow where flesh used to be!")) if(!plasma_parts.len && !robo_parts.len) //a person with no potential organic limbs left AND no robotic limbs, time to turn them into a plasmaman - PP.IgniteMob() + PP.ignite_mob() PP.set_species(/datum/species/plasmaman) PP.visible_message(span_warning("[L] bursts into a brilliant purple flame as [L.p_their()] entire body is that of a skeleton!"), \ span_userdanger("Your senses numb as all of your remaining flesh is turned into a purple slurry, sloshing off your body and leaving only your bones to show in a vibrant purple!")) diff --git a/code/modules/cargo/bounty.dm b/code/modules/cargo/bounty.dm index 616545ba905f..755f5264b825 100644 --- a/code/modules/cargo/bounty.dm +++ b/code/modules/cargo/bounty.dm @@ -55,7 +55,7 @@ GLOBAL_LIST_EMPTY(bounties_list) setup_bounties() var/list/matched_one = FALSE - for(var/thing in reverseRange(AM.GetAllContents())) + for(var/thing in reverse_range(AM.GetAllContents())) var/matched_this = FALSE for(var/datum/bounty/B in GLOB.bounties_list) if(B.applies_to(thing)) diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 2b5c5b74fed2..6de9f8b10e87 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -348,7 +348,7 @@ if (temp_pod.effectShrapnel == TRUE) //If already doing custom damage, set back to default (no shrapnel) temp_pod.effectShrapnel = FALSE return - var/shrapnelInput = input("Please enter the type of pellet cloud you'd like to create on landing (Can be any projectile!)", "Projectile Typepath", 0) in sortList(subtypesof(/obj/projectile), /proc/cmp_typepaths_asc) + var/shrapnelInput = input("Please enter the type of pellet cloud you'd like to create on landing (Can be any projectile!)", "Projectile Typepath", 0) in sortList(subtypesof(/obj/item/projectile), /proc/cmp_typepaths_asc) if (isnull(shrapnelInput)) return var/shrapnelMagnitude = input("Enter the magnitude of the pellet cloud. This is usually a value around 1-5. Please note that Ryll-Ryll has asked me to tell you that if you go too crazy with the projectiles you might crash the server. So uh, be gentle!", "Shrapnel Magnitude", 0) as null|num diff --git a/code/modules/cargo/exports.dm b/code/modules/cargo/exports.dm index a20dab13ca8f..f31fe6b89ae2 100644 --- a/code/modules/cargo/exports.dm +++ b/code/modules/cargo/exports.dm @@ -39,7 +39,7 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they report = new // We go backwards, so it'll be innermost objects sold first - for(var/i in reverseRange(contents)) + for(var/i in reverse_range(contents)) var/atom/movable/thing = i var/obj/item/thingy = thing var/sold = FALSE diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 45b59327250d..7f71e86cd25d 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -48,7 +48,7 @@ var/stay_after_drop = FALSE var/specialised = FALSE // It's not a general use pod for cargo/admin use var/effectShrapnel = FALSE - //var/shrapnel_type = /obj/projectile/bullet/shrapnel + //var/shrapnel_type = /obj/item/projectile/bullet/shrapnel var/shrapnel_magnitude = 3 var/rubble_type //Rubble effect associated with this supplypod var/decal = "default" //What kind of extra decals we add to the pod to make it look nice diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index bc093d8e6658..22a786049a5b 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -170,3 +170,6 @@ /// Whether or not this client has standard hotkeys enabled var/hotkeys = TRUE + + /// Whether or not this client has the combo HUD enabled + var/combo_hud_enabled = FALSE diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm index a89060efd8ef..8a56059325de 100644 --- a/code/modules/client/verbs/suicide.dm +++ b/code/modules/client/verbs/suicide.dm @@ -42,7 +42,7 @@ set_suicide(TRUE) //need to be called before calling suicide_act as fuck knows what suicide_act will do with your suicider var/obj/item/held_item = get_active_held_item() if(held_item) - var/damagetype = held_item.suicide_act(src) + var/damage_type = SEND_SIGNAL(src, COMSIG_HUMAN_SUICIDE_ACT) || held_item?.suicide_act(src) if(damagetype) if(damagetype & SHAME) adjustStaminaLoss(200) diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm index b66f4b949198..4faad9612b82 100644 --- a/code/modules/clothing/chameleon.dm +++ b/code/modules/clothing/chameleon.dm @@ -211,7 +211,7 @@ update_item(picked_item) var/obj/item/thing = target thing.update_slot_icon() - UpdateButtonIcon() + UpdateButtons() /datum/action/item_action/chameleon/change/proc/update_item(obj/item/picked_item, obj/item/target = src.target) //yogs -- add support for cham hardsuits target.name = initial(picked_item.name) @@ -296,6 +296,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) . = ..() @@ -329,6 +330,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) . = ..() @@ -360,6 +362,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) . = ..() @@ -393,6 +396,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) . = ..() @@ -427,6 +431,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) . = ..() @@ -460,6 +465,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) . = ..() @@ -492,6 +498,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/helmet/space/plasmaman/chameleon/emp_act(severity) . = ..() @@ -510,9 +517,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.UpdateButtons() var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.UpdateButtonIcon() + randomise_action.UpdateButtons() /obj/item/clothing/mask/chameleon name = "gas mask" @@ -543,6 +550,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) /obj/item/clothing/mask/chameleon/emp_act(severity) . = ..() @@ -574,9 +582,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.UpdateButtons() var/datum/action/item_action/chameleon/drone/randomise/randomise_action = new(src) - randomise_action.UpdateButtonIcon() + randomise_action.UpdateButtons() /obj/item/clothing/mask/chameleon/drone/attack_self(mob/user) to_chat(user, span_notice("[src] does not have a voice changer.")) @@ -604,6 +612,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) . = ..() @@ -640,6 +649,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) . = ..() @@ -668,6 +678,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() . = ..() @@ -699,6 +710,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) . = ..() @@ -726,6 +738,7 @@ chameleon_action.chameleon_name = "PDA" chameleon_action.chameleon_blacklist = typecacheof(list(/obj/item/pda/heads, /obj/item/pda/ai, /obj/item/pda/ai/pai), only_root_path = TRUE) chameleon_action.initialize_disguises() + add_item_action(chameleon_action) /obj/item/pda/chameleon/emp_act(severity) . = ..() @@ -755,6 +768,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() . = ..() diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index b42a3b6afeb0..6d123b47824a 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -475,7 +475,7 @@ BLIND // can't see anything C.head_update(src, forced = 1) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() return TRUE /obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index 7789a74dfd3d..2808a5baa687 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -396,6 +396,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) . = ..() @@ -459,24 +460,28 @@ lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE resistance_flags = LAVA_PROOF | FIRE_PROOF clothing_flags = SCAN_REAGENTS - var/obj/effect/proc_holder/expose/expose_ability + var/datum/action/cooldown/expose/expose_ability /obj/item/clothing/glasses/godeye/Initialize() . = ..() - expose_ability = new(expose_ability) + expose_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) - user.AddAbility(expose_ability) + expose_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) - user.RemoveAbility(expose_ability) + expose_ability.Remove(user) /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) @@ -490,59 +495,42 @@ qdel(src) ..() -/obj/effect/proc_holder/expose +/datum/action/cooldown/expose name = "Expose" desc = "Expose an enemy, increasing all damage dealt to them by 15% for 10 seconds, effect is magnified on megafauna." - action_background_icon_state = "bg_demon" - action_icon = 'icons/mob/actions/actions_items.dmi' - action_icon_state = "expose" - ranged_mousepointer = 'icons/effects/mouse_pointers/expose_target.dmi' - var/cooldown_time = 1 MINUTES - COOLDOWN_DECLARE(scan_cooldown) - -/obj/effect/proc_holder/expose/on_lose(mob/living/user) - remove_ranged_ability() + background_icon_state = "bg_demon" + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "expose" -/obj/effect/proc_holder/expose/Click(location, control, params) - . = ..() - if(!isliving(usr)) - return TRUE - var/mob/living/user = usr - fire(user) - -/obj/effect/proc_holder/expose/fire(mob/living/carbon/user) - if(active) - remove_ranged_ability(span_notice("You relax your god eye.")) - else - add_ranged_ability(user, span_notice("You squint your god eye. Left-click a creature to expose it!"), TRUE) + click_to_activate = TRUE + cooldown_time = 45 SECONDS + ranged_mousepointer = 'icons/effects/mouse_pointers/expose_target.dmi' -/obj/effect/proc_holder/expose/InterceptClickOn(mob/living/caller, params, atom/target) - . = ..() - if(.) - return - if(ranged_ability_user.stat) - remove_ranged_ability() - return - if(!COOLDOWN_FINISHED(src, scan_cooldown)) - to_chat(ranged_ability_user, span_warning("You try to focus your god eye, but it's too tired. Give it some time to recharge!")) - return - if(!isliving(target) || target == ranged_ability_user) - return - var/mob/living/living_target = target - living_target.apply_status_effect(STATUS_EFFECT_EXPOSED) - living_target.Jitter(5) - to_chat(living_target, span_warning("You feel the gaze of a malevolent presence focus on you!")) - ranged_ability_user.playsound_local(get_turf(ranged_ability_user), 'sound/magic/smoke.ogg', 50, TRUE) - living_target.playsound_local(get_turf(living_target), 'sound/hallucinations/i_see_you1.ogg', 50, TRUE) - to_chat(ranged_ability_user, "You glare at [living_target], exposing them!") - COOLDOWN_START(src, scan_cooldown, cooldown_time) - addtimer(CALLBACK(src, .proc/cooldown_over, ranged_ability_user), cooldown_time) - remove_ranged_ability() +/datum/action/cooldown/expose/IsAvailable() + return ..() && isliving(owner) + +/datum/action/cooldown/expose/Activate(atom/exposed) + StartCooldown(15 SECONDS) + + if(owner.stat != CONSCIOUS) + return FALSE + if(!isliving(exposed) || exposed == owner) + owner.balloon_alert(owner, "invalid exposed!") + return FALSE + + var/mob/living/living_exposed = exposed + living_exposed.apply_status_effect(STATUS_EFFECT_EXPOSED) + living_exposed.adjust_jitter(5 SECONDS) + to_chat(living_exposed, span_warning("You feel the gaze of a malevolent presence focus on you!")) + owner.playsound_local(get_turf(ranged_ability_user), 'sound/magic/smoke.ogg', 50, TRUE) + + living_exposed.playsound_local(get_turf(living_exposed), 'sound/hallucinations/i_see_you1.ogg', 50, TRUE) + owner.balloon_alert(owner, "[living_exposed] exposed!") + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom/, balloon_alert), owner, "eye recharged"), cooldown_time) + + StartCooldown() return TRUE -/obj/effect/proc_holder/expose/proc/cooldown_over() - to_chat(usr, (span_notice("Your god eye is focused enough to expose again!"))) - /obj/item/clothing/glasses/AltClick(mob/user) if(glass_colour_type && ishuman(user)) var/mob/living/carbon/human/human_user = user diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm index 1b478834051b..1bbd3794d423 100644 --- a/code/modules/clothing/glasses/engine_goggles.dm +++ b/code/modules/clothing/glasses/engine_goggles.dm @@ -53,7 +53,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/glasses/meson/engine/attack_self(mob/user) toggle_mode(user, TRUE) diff --git a/code/modules/clothing/glasses/hud.dm b/code/modules/clothing/glasses/hud.dm index 5210757be95a..0e402e3f71d4 100644 --- a/code/modules/clothing/glasses/hud.dm +++ b/code/modules/clothing/glasses/hud.dm @@ -8,13 +8,13 @@ ..() if(hud_type && slot == SLOT_GLASSES) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) + H.show_to(user) /obj/item/clothing/glasses/hud/dropped(mob/living/carbon/human/user) ..() if(hud_type && istype(user) && user.glasses == src) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) /obj/item/clothing/glasses/hud/emp_act(severity) . = ..() @@ -123,6 +123,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) . = ..() @@ -203,7 +204,7 @@ if (hud_type) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.remove_hud_from(user) + H.hide_from(user) if (hud_type == DATA_HUD_MEDICAL_ADVANCED) hud_type = null @@ -214,7 +215,10 @@ if (hud_type) var/datum/atom_hud/H = GLOB.huds[hud_type] - H.add_hud_to(user) + H.show_to(user) + +/datum/action/item_action/switch_hud + name = "Switch HUD" /obj/item/clothing/glasses/hud/toggle/thermal name = "thermal HUD scanner" diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 7c43cbaa4162..51ffd847ca96 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -41,7 +41,7 @@ H.update_inv_head() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon(force = TRUE) + A.UpdateButtons(force = TRUE) ..() /obj/item/clothing/head/hardhat/proc/turn_on(mob/user) @@ -138,6 +138,13 @@ if(user.canUseTopic(src, BE_CLOSE)) toggle_welding_screen(user) +/obj/item/clothing/head/hardhat/weldhat/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/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) if(weldingvisortoggle(user)) playsound(src, 'sound/mecha/mechmove03.ogg', 50, 1) //Visors don't just come from nothing diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm index 91b69b9b12c6..a3434b2a0356 100644 --- a/code/modules/clothing/head/helmet.dm +++ b/code/modules/clothing/head/helmet.dm @@ -445,7 +445,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/head/helmet/stormtrooper name = "Storm Trooper Helmet" diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index 65f8d161466d..e00b48126ae1 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -430,6 +430,10 @@ icon_state = "taqiyahred" pocket_storage_component_path = /datum/component/storage/concrete/pockets/small +/datum/action/item_action/hatsky_voiceline + name = "Press Voice Button" + desc = "Engage the voice box on your Hatsky to hear a classic line from the real Officer Beepsky!" + /obj/item/clothing/head/hatsky name = "officer hatsky" desc = "A hat for true Beepsky appreciators. Not guaranteed to actually keep you safe from anything." diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 3b6f7a655cfd..3122812f5e3d 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -91,7 +91,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() to_chat(user, span_notice("Your Clown Mask has now morphed into [choice], all praise the Honkmother!")) return TRUE @@ -143,7 +143,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() to_chat(user, span_notice("Your Mime Mask has now morphed into [choice]!")) return TRUE @@ -226,7 +226,7 @@ user.update_inv_wear_mask() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() 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 8e69d17d2a2d..d5a6a2dc09c6 100644 --- a/code/modules/clothing/masks/hailer.dm +++ b/code/modules/clothing/masks/hailer.dm @@ -1,6 +1,9 @@ // **** Security gas mask **** +/datum/action/item_action/halt + name = "HALT!" + /obj/item/clothing/mask/gas/sechailer name = "security gas mask" desc = "A standard issue Security gas mask with integrated 'Compli-o-nator 3000' device. Plays over a dozen pre-recorded compliance phrases designed to get scumbags to stand still whilst you tase them. Do not tamper with the device." diff --git a/code/modules/clothing/neck/bodycamera.dm b/code/modules/clothing/neck/bodycamera.dm index 98fd5b463519..4b307719cf60 100644 --- a/code/modules/clothing/neck/bodycamera.dm +++ b/code/modules/clothing/neck/bodycamera.dm @@ -63,7 +63,7 @@ item_state = "[prefix]_bodycam_[suffix]" for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/neck/bodycam/examine(mob/user) .=..() diff --git a/code/modules/clothing/shoes/bananashoes.dm b/code/modules/clothing/shoes/bananashoes.dm index 36dcc8726d5c..c5248e4fbbc4 100644 --- a/code/modules/clothing/shoes/bananashoes.dm +++ b/code/modules/clothing/shoes/bananashoes.dm @@ -63,4 +63,4 @@ usr.update_inv_shoes() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm index cc17e9453675..bf4dd4bd703f 100644 --- a/code/modules/clothing/shoes/magboots.dm +++ b/code/modules/clothing/shoes/magboots.dm @@ -35,7 +35,7 @@ user.update_gravity(user.has_gravity()) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/shoes/magboots/negates_gravity() return clothing_flags & NOSLIP diff --git a/code/modules/clothing/shoes/miscellaneous.dm b/code/modules/clothing/shoes/miscellaneous.dm index 9761ecd86d8a..ec7f5c4464c4 100644 --- a/code/modules/clothing/shoes/miscellaneous.dm +++ b/code/modules/clothing/shoes/miscellaneous.dm @@ -324,7 +324,7 @@ desc = "They'll sure kindle something in you, and it's not childhood nostalgia..." icon_state = "kindleKicks" item_state = "kindleKicks" - actions_types = list(/datum/action/item_action/kindleKicks) + actions_types = list(/datum/action/item_action/kindle_kicks) light_system = MOVABLE_LIGHT light_range = 2 light_power = 3 @@ -544,6 +544,18 @@ xenoshoe = YES_DIGIT mutantrace_variation = MUTANTRACE_VARIATION +/datum/action/item_action/dash + name = "Dash" + desc = "Momentarily maximizes the jets of the shoes, allowing the user to dash a short distance." + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "thrust" + +/datum/action/item_action/airshoes + name = "Toggle thrust on air shoes." + desc = "Switch between walking and hovering." + icon_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "airshoes_a" + /obj/item/clothing/shoes/airshoes name = "air shoes" desc = "Footwear that uses propulsion technology to keep you above the ground and let you move faster." @@ -551,7 +563,8 @@ obj_flags = UNIQUE_RENAME //im not fucking naming them 'sonic 11's you can do that yourself ffm actions_types = list(/datum/action/item_action/airshoes, /datum/action/item_action/dash) var/airToggle = FALSE - var/obj/vehicle/ridden/scooter/airshoes/A + ///Secret vehicle that helps us move around at mach speeds + var/obj/vehicle/ridden/scooter/airshoes/shoes_of_air permeability_coefficient = 0.05 var/recharging_time = 0 var/jumpdistance = 7 //Increased distance so it might see some offensive use @@ -561,7 +574,7 @@ /obj/item/clothing/shoes/airshoes/Initialize() . = ..() - A = new/obj/vehicle/ridden/scooter/airshoes(null) + shoes_of_air = new /obj/vehicle/ridden/scooter/airshoes(null) /obj/item/clothing/shoes/airshoes/ui_action_click(mob/user, action) if(!isliving(user)) @@ -569,24 +582,24 @@ if(!istype(user.get_item_by_slot(SLOT_SHOES), /obj/item/clothing/shoes/airshoes)) to_chat(user, span_warning("You must be wearing the air shoes to use them!")) return - if(istype(action,/datum/action/item_action/airshoes)) - if(!(A.is_occupant(user))) + if(istype(action, /datum/action/item_action/airshoes)) + if(!(shoes_of_air.is_occupant(user))) airToggle = FALSE if(airToggle) - A.unbuckle_mob(user) + shoes_of_air.unbuckle_mob(user) airToggle = FALSE return - A.forceMove(get_turf(user)) - A.buckle_mob(user) + shoes_of_air.forceMove(get_turf(user)) + shoes_of_air.buckle_mob(user) airToggle = TRUE - else if(istype(action,/datum/action/item_action/dash)) + else if(istype(action, /datum/action/item_action/dash)) if(recharging_time > world.time) to_chat(user, span_warning("The boot's internal propulsion needs to recharge still!")) return var/atom/target = get_edge_target_turf(user, user.dir) //gets the user's direction if (user.throw_at(target, jumpdistance, jumpspeed, spin = FALSE, diagonals_first = TRUE)) - playsound(src, 'sound/effects/stealthoff.ogg', 50, 1, 1) + playsound(src, 'sound/effects/stealthoff.ogg', 50, TRUE, 1) user.visible_message(span_warning("[usr] dashes forward into the air!")) recharging_time = world.time + recharging_rate else @@ -594,11 +607,12 @@ /obj/item/clothing/shoes/airshoes/dropped(mob/user) if(airToggle) - A.unbuckle_mob(user) + shoes_of_air.unbuckle_mob(user) airToggle = FALSE ..() + /obj/item/clothing/shoes/airshoes/Destroy() - QDEL_NULL(A) + QDEL_NULL(shoes_of_air) . = ..() /obj/item/clothing/shoes/drip diff --git a/code/modules/clothing/spacesuits/chronosuit.dm b/code/modules/clothing/spacesuits/chronosuit.dm index cd5500a57579..c0f3bdc373b7 100644 --- a/code/modules/clothing/spacesuits/chronosuit.dm +++ b/code/modules/clothing/spacesuits/chronosuit.dm @@ -104,7 +104,7 @@ camera.remove_target_ui() camera.forceMove(user) user.reset_perspective(camera) - teleport_now.UpdateButtonIcon() + teleport_now.UpdateButtons() /obj/item/clothing/suit/space/chronos/proc/chronowalk(atom/location) var/mob/living/carbon/human/user = src.loc @@ -118,7 +118,7 @@ if(camera) camera.remove_target_ui() - teleport_now.UpdateButtonIcon() + teleport_now.UpdateButtons() var/list/nonsafe_slots = list(SLOT_BELT, SLOT_BACK) var/list/exposed = list() diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index 12b65fcc2749..3bea26f8ad1d 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -40,7 +40,7 @@ for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/head/helmet/space/hardsuit/dropped(mob/user) ..() @@ -345,7 +345,7 @@ C.head_update(src, forced = 1) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/head/helmet/space/hardsuit/syndi/proc/toggle_hardsuit_mode(mob/user) //Helmet Toggles Suit Mode if(linkedsuit) @@ -556,13 +556,13 @@ ..() if (slot == SLOT_HEAD) var/datum/atom_hud/DHUD = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - DHUD.add_hud_to(user) + DHUD.show_to(user) /obj/item/clothing/head/helmet/space/hardsuit/rd/dropped(mob/living/carbon/human/user) ..() if (user.head == src) var/datum/atom_hud/DHUD = GLOB.huds[DATA_HUD_DIAGNOSTIC_BASIC] - DHUD.remove_hud_from(user) + DHUD.hide_from(user) /obj/item/clothing/head/helmet/space/hardsuit/rd/proc/sense_explosion(datum/source, turf/epicenter, devastation_range, heavy_impact_range, light_impact_range, took, orig_dev_range, orig_heavy_range, orig_light_range) diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm index e5661390a09c..e91f8cdb112d 100644 --- a/code/modules/clothing/spacesuits/plasmamen.dm +++ b/code/modules/clothing/spacesuits/plasmamen.dm @@ -70,7 +70,7 @@ for(var/X in actions) var/datum/action/A=X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/head/helmet/space/plasmaman/proc/set_design(mob/living/carbon/human/user) if(!pref_alteration) diff --git a/code/modules/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index b2ac7b0a93d8..dfc62d84cfb3 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -106,7 +106,7 @@ for(var/mob/living/carbon/C in range(6, owner)) if(C != owner) C.adjust_fire_stacks(8) - C.IgniteMob() + C.ignite_mob() owner.fire_stacks = -1 reactivearmor_cooldown = world.time + reactivearmor_cooldown_duration return 1 diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm index 58e502388b3c..e56d70853944 100644 --- a/code/modules/clothing/suits/toggles.dm +++ b/code/modules/clothing/suits/toggles.dm @@ -43,7 +43,7 @@ hood?.forceMove(src) for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/suit/hooded/dropped() ..() @@ -65,7 +65,7 @@ H.update_inv_wear_suit() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() else RemoveHood() @@ -118,7 +118,7 @@ usr.update_inv_wear_suit() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/suit/toggle/examine(mob/user) . = ..() diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index ca942136a766..884476cb5844 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -146,6 +146,13 @@ armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 0, ACID = 0) resistance_flags = FLAMMABLE +//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/mob/actions/actions_minor_antag.dmi' + button_icon_state = "art_summon" + /obj/item/clothing/suit/wizrobe/paper name = "papier-mâché robe" // yogs -- we live in the future desc = "A robe held together by various bits of clear-tape and paste." diff --git a/code/modules/clothing/under/miscellaneous.dm b/code/modules/clothing/under/miscellaneous.dm index 1fde8cf7e107..7ca3a55957b6 100644 --- a/code/modules/clothing/under/miscellaneous.dm +++ b/code/modules/clothing/under/miscellaneous.dm @@ -836,7 +836,7 @@ for(var/X in actions) var/datum/action/A=X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/clothing/under/lampskirt/female icon_state = "lampskirt_female" diff --git a/code/modules/events/anomaly.dm b/code/modules/events/anomaly.dm index b4a6244d3db5..558ddbb6a220 100644 --- a/code/modules/events/anomaly.dm +++ b/code/modules/events/anomaly.dm @@ -30,7 +30,7 @@ allowed_areas = make_associative(GLOB.the_station_areas) - safe_area_types + unsafe_area_subtypes - return safepick(typecache_filter_list(GLOB.areas,allowed_areas)) + return pick(typecache_filter_list(GLOB.areas,allowed_areas)) /datum/round_event/anomaly/setup() impact_area = findEventArea() @@ -44,7 +44,7 @@ priority_announce("Localized energetic flux wave detected on long range scanners. Expected location of impact: [impact_area.name].", "Anomaly Alert") /datum/round_event/anomaly/start() - var/turf/T = safepick(get_area_turfs(impact_area)) + var/turf/T = pick(get_area_turfs(impact_area)) var/newAnomaly if(T) newAnomaly = new anomaly_path(T) diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index fd18e5b46479..b44c04e8e71c 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -53,7 +53,7 @@ originMachine.visible_message("[originMachine] beeps and seems lifeless.") kill() return - vendingMachines = removeNullsFromList(vendingMachines) + vendingMachines = listclearnulls(vendingMachines) if(!vendingMachines.len) //if every machine is infected for(var/obj/machinery/vending/upriser in infectedMachines) if(prob(70) && !QDELETED(upriser)) diff --git a/code/modules/events/portal_storm.dm b/code/modules/events/portal_storm.dm index 15057f8f0c72..fa4eb45ab71c 100644 --- a/code/modules/events/portal_storm.dm +++ b/code/modules/events/portal_storm.dm @@ -67,14 +67,14 @@ spawn_effects(get_random_station_turf()) if(spawn_hostile()) - var/type = safepick(hostile_types) + var/type = pick(hostile_types) hostile_types[type] = hostile_types[type] - 1 spawn_mob(type, hostiles_spawn) if(!hostile_types[type]) hostile_types -= type if(spawn_boss()) - var/type = safepick(boss_types) + var/type = pick(boss_types) boss_types[type] = boss_types[type] - 1 spawn_mob(type, boss_spawn) if(!boss_types[type]) diff --git a/code/modules/events/tzimisce.dm b/code/modules/events/tzimisce.dm index 1a2f5e60c560..34ef03212b48 100644 --- a/code/modules/events/tzimisce.dm +++ b/code/modules/events/tzimisce.dm @@ -1,9 +1,15 @@ +/datum/round_event_control/tzimisce + name = "Spawn Tzimisce" + typepath = /datum/round_event/ghost_role/tzimisce + max_occurrences = 2 + min_players = 25 + earliest_start = 45 MINUTES + /datum/round_event_control/tzimisce/bloodsucker name = "Spawn Tzimisce - Bloodsucker" max_occurrences = 1 - weight = 5 + weight = 2000 typepath = /datum/round_event/ghost_role/tzimisce/bloodsucker - max_occurrences = 2 min_players = 25 earliest_start = 30 MINUTES gamemode_whitelist = list("bloodsucker","traitorsucker") @@ -21,13 +27,7 @@ if(cancel_me) kill() return - -/datum/round_event_control/tzimisce - name = "Spawn Tzimisce" - typepath = /datum/round_event/ghost_role/tzimisce - max_occurrences = 1 - min_players = 25 - earliest_start = 45 MINUTES + try_spawning() /datum/round_event/ghost_role/tzimisce var/success_spawn = 0 @@ -61,13 +61,13 @@ Mind.add_antag_datum(/datum/antagonist/bloodsucker) var/datum/antagonist/bloodsucker/bloodsuckerdatum = tzimisce.mind.has_antag_datum(/datum/antagonist/bloodsucker) bloodsuckerdatum.bloodsucker_level_unspent += round(world.time / (15 MINUTES), 1) - bloodsuckerdatum.AssignClanAndBane(tzimisce = TRUE) + bloodsuckerdatum.assign_clan_and_bane(tzimisce = TRUE) spawned_mobs += tzimisce message_admins("[ADMIN_LOOKUPFLW(tzimisce)] has been made into a tzimisce bloodsucker an event.") log_game("[key_name(tzimisce)] was spawned as a tzimisce bloodsucker by an event.") var/datum/job/jobdatum = SSjob.GetJob(pick("Assistant", "Botanist", "Station Engineer", "Medical Doctor", "Scientist", "Cargo Technician", "Cook")) - set_antag_hud(tzimisce, "tzimisce") + bloodsuckerdatum.antag_hud_name = "tzimisce" if(SSshuttle.arrivals) SSshuttle.arrivals.QueueAnnounce(tzimisce, jobdatum.title) Mind.assigned_role = jobdatum.title //sets up the manifest properly @@ -78,6 +78,7 @@ id.update_label() GLOB.data_core.manifest_inject(tzimisce, force = TRUE) tzimisce.update_move_intent_slowdown() //prevents you from going super duper fast + announce_to_ghosts(tzimisce) return SUCCESSFUL_SPAWN diff --git a/code/modules/events/wizard/aid.dm b/code/modules/events/wizard/aid.dm index d055cc7c3d12..92747d2cd76e 100644 --- a/code/modules/events/wizard/aid.dm +++ b/code/modules/events/wizard/aid.dm @@ -1,4 +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" @@ -9,16 +10,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, span_notice("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/cooldown/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, span_notice("You suddenly feel like you never needed those garish robes in the first place...")) //--// @@ -30,29 +34,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, span_notice("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/cooldown/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, span_notice("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 c3e71ab28898..20c60128833f 100644 --- a/code/modules/events/wizard/shuffle.dm +++ b/code/modules/events/wizard/shuffle.dm @@ -79,26 +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 - 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/cooldown/spell/pointed/mind_transfer/swapper = new() - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - var/datum/effect_system/fluid_spread/smoke/smoke = new - smoke.set_up(0, location = 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/fluid_spread/smoke/smoke = new() + smoke.set_up(0, holder = alive_human, location = alive_human.loc) smoke.start() diff --git a/code/modules/fields/timestop.dm b/code/modules/fields/timestop.dm index 9315125f984e..6bd08c1b2410 100644 --- a/code/modules/fields/timestop.dm +++ b/code/modules/fields/timestop.dm @@ -28,16 +28,12 @@ freezerange = radius 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/conjure/timestop) in L.mind.spell_list) //People who can stop time are immune to its effects - immune[L] = TRUE - for(var/mob/living/simple_animal/hostile/guardian/G in GLOB.parasites) - if(G.summoner && locate(/obj/effect/proc_holder/spell/aoe_turf/conjure/timestop) in G.summoner.spell_list) //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/conjure/timestop) in G.mob_spell_list) - immune[G] = TRUE - if(G.summoner?.current) - immune[G.summoner.current] = TRUE + for(var/mob/living/to_check in GLOB.player_list) + if(HAS_TRAIT(to_check, TRAIT_TIME_STOP_IMMUNE)) + immune[to_check] = TRUE + for(var/mob/living/simple_animal/hostile/guardian/stand in GLOB.parasites) + if(stand.summoner && HAS_TRAIT(stand.summoner, TRAIT_TIME_STOP_IMMUNE)) //It would only make sense that a person's stand would also be immune. + immune[stand] = TRUE if(start) timestop() @@ -50,7 +46,7 @@ playsound(src, start_sound, 75, 1, -1) chronofield = make_field(/datum/proximity_monitor/advanced/timestop, list("current_range" = freezerange, "host" = src, "immune" = immune, "check_anti_magic" = check_anti_magic, "check_holy" = check_holy)) if(duration - start_sound_len * 2 > 0) // Needs to have enough time for both sounds to play in full - addtimer(CALLBACK(src, .proc/time_resumes), duration - start_sound_len) + addtimer(CALLBACK(src, PROC_REF(time_resumes)), duration - start_sound_len) QDEL_IN(src, duration) /obj/effect/timestop/proc/time_resumes() // toki wa ugoki dasu diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index cf1944184103..b3a68adf234a 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -1,4 +1,4 @@ -#define HAL_LINES_FILE "hallucination.json" +#define HALLUCINATION_FILE "hallucination.json" GLOBAL_LIST_INIT(hallucination_list, list( /datum/hallucination/chat = 100, @@ -24,28 +24,16 @@ GLOBAL_LIST_INIT(hallucination_list, list( /datum/hallucination/oh_yeah = 1 )) - -/mob/living/carbon/proc/handle_hallucinations() - hallucination = max(0, hallucination) - if(hallucination <= 0) - return - - hallucination-- - - if(world.time < next_hallucination) - return - - var/halpick = pickweight(GLOB.hallucination_list) - new halpick(src, FALSE) - - next_hallucination = world.time + rand(100, 600) - /mob/living/carbon/proc/set_screwyhud(hud_type) hal_screwyhud = hud_type update_health_hud() /datum/hallucination var/natural = TRUE + /// What is this hallucination's weight in the random hallucination pool? + var/random_hallucination_weight = 0 + /// Who's our next highest abstract parent type? + var/abstract_hallucination_parent = /datum/hallucination var/mob/living/carbon/target var/feedback_details //extra info for investigate @@ -54,6 +42,9 @@ GLOBAL_LIST_INIT(hallucination_list, list( target = C natural = !forced +/datum/hallucination/proc/start() + return TRUE //unfortunate + /datum/hallucination/proc/wake_and_restore() target.set_screwyhud(SCREWYHUD_NONE) target.SetSleeping(0) @@ -148,6 +139,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( #define FAKE_FLOOD_MAX_RADIUS 10 /datum/hallucination/fake_flood + random_hallucination_weight = 7 //Plasma starts flooding from the nearby vent var/turf/center var/list/flood_images = list() @@ -231,6 +223,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( target.visible_message(span_danger("[target] flails around wildly."),"[name] pounces on you!") /datum/hallucination/xeno_attack + random_hallucination_weight = 2 //Xeno crawls from nearby vent,jumps at you, and goes back in var/obj/machinery/atmospherics/components/unary/vent_pump/pump = null var/obj/effect/hallucination/simple/xeno/xeno = null @@ -278,6 +271,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( px = -32 /datum/hallucination/oh_yeah + random_hallucination_weight = 1 var/obj/effect/hallucination/simple/bubblegum/bubblegum var/image/fakebroken var/image/fakerune @@ -333,6 +327,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( return ..() /datum/hallucination/battle + random_hallucination_weight = 3 /datum/hallucination/battle/New(mob/living/carbon/C, forced = TRUE, battle_type) set waitfor = FALSE @@ -408,6 +403,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/items_other + random_hallucination_weight = 1 /datum/hallucination/items_other/New(mob/living/carbon/C, forced = TRUE, item_type) set waitfor = FALSE @@ -511,6 +507,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/delusion + random_hallucination_weight = 1 var/list/image/delusions = list() /datum/hallucination/delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300,skip_nearby = TRUE, custom_icon = null, custom_icon_file = null, custom_name = null) @@ -566,6 +563,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( return ..() /datum/hallucination/self_delusion + random_hallucination_weight = 1 var/image/delusion /datum/hallucination/self_delusion/New(mob/living/carbon/C, forced, force_kind = null , duration = 300, custom_icon = null, custom_icon_file = null, wabbajack = TRUE) //set wabbajack to false if you want to use another fake source @@ -607,6 +605,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( return ..() /datum/hallucination/bolts + random_hallucination_weight = 7 var/list/locks = list() /datum/hallucination/bolts/New(mob/living/carbon/C, forced, door_number) @@ -658,26 +657,27 @@ GLOBAL_LIST_INIT(hallucination_list, list( return FALSE /datum/hallucination/chat + random_hallucination_weight = 100 /datum/hallucination/chat/New(mob/living/carbon/C, forced = TRUE, force_radio, specific_message) set waitfor = FALSE ..() var/target_name = target.first_name() - var/speak_messages = list("[pick_list_replacements(HAL_LINES_FILE, "suspicion")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "conversation")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "greetings")][target.first_name()]!",\ - "[pick_list_replacements(HAL_LINES_FILE, "getout")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "weird")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "didyouhearthat")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "doubt")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "aggressive")]",\ - "[pick_list_replacements(HAL_LINES_FILE, "help")]!!",\ - "[pick_list_replacements(HAL_LINES_FILE, "escape")]",\ - "I'm infected, [pick_list_replacements(HAL_LINES_FILE, "infection_advice")]!") - - var/radio_messages = list("[pick_list_replacements(HAL_LINES_FILE, "people")] is [pick_list_replacements(HAL_LINES_FILE, "accusations")]!",\ + var/speak_messages = list("[pick_list_replacements(HALLUCINATION_FILE, "suspicion")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "conversation")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "greetings")][target.first_name()]!",\ + "[pick_list_replacements(HALLUCINATION_FILE, "getout")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "weird")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "didyouhearthat")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "doubt")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "aggressive")]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "help")]!!",\ + "[pick_list_replacements(HALLUCINATION_FILE, "escape")]",\ + "I'm infected, [pick_list_replacements(HALLUCINATION_FILE, "infection_advice")]!") + + var/radio_messages = list("[pick_list_replacements(HALLUCINATION_FILE, "people")] is [pick_list_replacements(HALLUCINATION_FILE, "accusations")]!",\ "Help!",\ - "[pick_list_replacements(HAL_LINES_FILE, "threat")] in [pick_list_replacements(HAL_LINES_FILE, "location")][prob(50)?"!":"!!"]",\ + "[pick_list_replacements(HALLUCINATION_FILE, "threat")] in [pick_list_replacements(HALLUCINATION_FILE, "location")][prob(50)?"!":"!!"]",\ "[pick("Where's [target.first_name()]?", "Set [target.first_name()] to arrest!")]",\ "[pick("C","Ai, c","Someone c","Rec")]all the shuttle!",\ "AI [pick("rogue", "is dead")]!!") @@ -718,6 +718,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/message + random_hallucination_weight = 60 /datum/hallucination/message/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -766,13 +767,14 @@ GLOBAL_LIST_INIT(hallucination_list, list( span_warning("You hear skittering on the ceiling."), span_warning("You see an inhumanly tall silhouette moving in the distance.")) if(prob(10)) - message_pool.Add("[pick_list_replacements(HAL_LINES_FILE, "advice")]") + message_pool.Add("[pick_list_replacements(HALLUCINATION_FILE, "advice")]") var/chosen = pick(message_pool) feedback_details += "Message: [chosen]" to_chat(target, chosen) qdel(src) /datum/hallucination/sounds + random_hallucination_weight = 5 /datum/hallucination/sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) set waitfor = FALSE @@ -829,6 +831,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/weird_sounds + random_hallucination_weight = 1 /datum/hallucination/weird_sounds/New(mob/living/carbon/C, forced = TRUE, sound_type) set waitfor = FALSE @@ -873,6 +876,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/stationmessage + random_hallucination_weight = 1 /datum/hallucination/stationmessage/New(mob/living/carbon/C, forced = TRUE, message) set waitfor = FALSE @@ -919,6 +923,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( to_chat(target, span_boldannounce("You feel reality distort for a moment...")) /datum/hallucination/hudscrew + random_hallucination_weight = 4 /datum/hallucination/hudscrew/New(mob/living/carbon/C, forced = TRUE, screwyhud_type) set waitfor = FALSE @@ -934,6 +939,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/fake_alert + random_hallucination_weight = 1 /datum/hallucination/fake_alert/New(mob/living/carbon/C, forced = TRUE, specific, duration = 15 SECONDS) set waitfor = FALSE @@ -989,6 +995,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/items + random_hallucination_weight = 1 /datum/hallucination/items/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -1051,6 +1058,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/dangerflash + random_hallucination_weight = 5 /datum/hallucination/dangerflash/New(mob/living/carbon/C, forced = TRUE, danger_type) set waitfor = FALSE @@ -1151,6 +1159,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( new /datum/hallucination/shock(target) /datum/hallucination/death + random_hallucination_weight = 1 /datum/hallucination/death/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -1179,6 +1188,8 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/fire + random_hallucination_weight = 3 + var/active = TRUE var/stage = 0 var/image/fire_overlay @@ -1230,6 +1241,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( qdel(src) /datum/hallucination/shock + random_hallucination_weight = 1 var/image/shock_image var/image/electrocution_skeleton_anim @@ -1246,13 +1258,12 @@ GLOBAL_LIST_INIT(hallucination_list, list( if(target.client) target.client.images |= shock_image target.client.images |= electrocution_skeleton_anim - addtimer(CALLBACK(src, .proc/reset_shock_animation), 40) + addtimer(CALLBACK(src, PROC_REF(reset_shock_animation)), 4 SECONDS) target.playsound_local(get_turf(src), "sparks", 100, 1) target.staminaloss += 50 - target.Stun(40) - target.jitteriness += 1000 - target.do_jitter_animation(target.jitteriness) - addtimer(CALLBACK(src, .proc/shock_drop), 20) + target.Stun(4 SECONDS) + target.do_jitter_animation(1000) + addtimer(CALLBACK(src, PROC_REF(shock_drop)), 2 SECONDS) /datum/hallucination/shock/proc/reset_shock_animation() if(target.client) @@ -1260,10 +1271,11 @@ GLOBAL_LIST_INIT(hallucination_list, list( target.client.images.Remove(electrocution_skeleton_anim) /datum/hallucination/shock/proc/shock_drop() - target.jitteriness = max(target.jitteriness - 990, 10) //Still jittery, but vastly less - target.Paralyze(60) + target.adjust_jitter(10 SECONDS) //Still jittery, but vastly less + target.Paralyze(6 SECONDS) /datum/hallucination/husks + random_hallucination_weight = 8 /datum/hallucination/husks/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE @@ -1296,6 +1308,7 @@ GLOBAL_LIST_INIT(hallucination_list, list( //hallucination projectile code in code/modules/projectiles/projectile/special.dm /datum/hallucination/stray_bullet + random_hallucination_weight = 7 /datum/hallucination/stray_bullet/New(mob/living/carbon/C, forced = TRUE) set waitfor = FALSE diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm index c5939aa507ab..74af5070fc36 100644 --- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm +++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm @@ -571,4 +571,4 @@ name = "Genius Dry Stout" desc = "A fresh bottle of stout, popularized by inhabitants of Space Ireland." icon_state = "stout_bottle" - list_reagents = list(/datum/reagent/consumable/ethanol/beer/stout = 40) \ No newline at end of file + list_reagents = list(/datum/reagent/consumable/ethanol/beer/stout = 40) diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm index e65b6017ea01..e9fc9463db46 100644 --- a/code/modules/food_and_drinks/food/snacks_pastry.dm +++ b/code/modules/food_and_drinks/food/snacks_pastry.dm @@ -705,7 +705,7 @@ contents += P update_overlays(P) P = I - clearlist(P.contents) + LAZYCLEARLIST(P.contents) return else if(contents.len) var/obj/O = contents[contents.len] diff --git a/code/modules/holodeck/area_copy.dm b/code/modules/holodeck/area_copy.dm index fcc2ed8389e6..d9471443fe18 100644 --- a/code/modules/holodeck/area_copy.dm +++ b/code/modules/holodeck/area_copy.dm @@ -1,9 +1,10 @@ //Vars that will not be copied when using /DuplicateObject -GLOBAL_LIST_INIT(duplicate_forbidden_vars,list( +GLOBAL_LIST_INIT(duplicate_forbidden_vars, list( "tag", "datum_components", "area", "type", "loc", "locs", "vars", "parent", "parent_type", "verbs", "ckey", "key", "power_supply", "contents", "reagents", "stat", "x", "y", "z", "group", "atmos_adjacent_turfs", "comp_lookup", "client_mobs_in_contents", "bodyparts", "internal_organs", "hand_bodyparts", "hud_list", - "actions", "AIStatus", "computer_id", "lastKnownIP", "implants", "tgui_shared_states" + "actions", "AIStatus", "computer_id", "lastKnownIP", "implants", "tgui_shared_states", "active_hud_list", + "important_recursive_contents", "update_on_z", )) /proc/DuplicateObject(atom/original, perfectcopy = TRUE, sameloc, atom/newloc = null, nerf, holoitem) diff --git a/code/modules/hydroponics/grown/flowers.dm b/code/modules/hydroponics/grown/flowers.dm index bc5e695fc776..118e058a5f3e 100644 --- a/code/modules/hydroponics/grown/flowers.dm +++ b/code/modules/hydroponics/grown/flowers.dm @@ -233,7 +233,7 @@ if(isliving(M)) to_chat(M, span_danger("You are lit on fire from the intense heat of the [name]!")) M.adjust_fire_stacks(seed.potency / 20) - if(M.IgniteMob()) + if(M.ignite_mob()) message_admins("[ADMIN_LOOKUPFLW(user)] set [ADMIN_LOOKUPFLW(M)] on fire with [src] at [AREACOORD(user)]") log_game("[key_name(user)] set [key_name(M)] on fire with [src] at [AREACOORD(user)]") diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm index f898ae966fa9..bc7dc006be89 100644 --- a/code/modules/hydroponics/grown/towercap.dm +++ b/code/modules/hydroponics/grown/towercap.dm @@ -263,7 +263,7 @@ else if(isliving(A)) var/mob/living/L = A L.adjust_fire_stacks(fire_stack_strength * 0.5 * delta_time) - L.IgniteMob() + L.ignite_mob() /obj/structure/bonfire/proc/Cook(delta_time = 2) var/turf/current_location = get_turf(src) @@ -274,7 +274,7 @@ else if(isliving(A)) //It's still a fire, idiot. var/mob/living/L = A L.adjust_fire_stacks(fire_stack_strength * 0.5 * delta_time) - L.IgniteMob() + L.ignite_mob() else if(G.GetComponent(/datum/component/grillable)) if(SEND_SIGNAL(G, COMSIG_ITEM_GRILLED, src) & COMPONENT_HANDLED_GRILLING) continue diff --git a/code/modules/instruments/items.dm b/code/modules/instruments/items.dm index 180dc1fa6acf..d6bc271deedc 100644 --- a/code/modules/instruments/items.dm +++ b/code/modules/instruments/items.dm @@ -213,6 +213,17 @@ icon_state = "recorder" allowed_instrument_ids = "recorder" +/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 ..() + /obj/item/instrument/harmonica name = "harmonica" desc = "For when you get a bad case of the space blues." @@ -230,7 +241,7 @@ /obj/item/instrument/harmonica/equipped(mob/M, slot) . = ..() - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + RegisterSignal(M, COMSIG_MOB_SAY, PROC_REF(handle_speech)) /obj/item/instrument/harmonica/dropped(mob/M) . = ..() diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 278709a43e84..52d3ac826704 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -75,8 +75,12 @@ var/paycheck = PAYCHECK_MINIMAL /// Where to pull money to pay people var/paycheck_department = ACCOUNT_CIV - /// Traits assigned from jobs + /// Traits added to the mind of the mob assigned this job var/list/mind_traits + + ///Lazylist of traits added to the liver of the mob assigned this job (used for the classic "cops heal from donuts" reaction, among others) + var/list/liver_traits = null + /// Display order of the job var/display_order = JOB_DISPLAY_ORDER_DEFAULT @@ -136,13 +140,17 @@ //Only override this proc //H is usually a human unless an /equip override transformed it -/datum/job/proc/after_spawn(mob/living/H, mob/M, latejoin = FALSE) - //do actions on H but send messages to M as the key may not have been transferred_yet - SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, H, M, latejoin) - if(mind_traits) - for(var/t in mind_traits) - ADD_TRAIT(H.mind, t, JOB_TRAIT) - H.mind.add_employee(/datum/corporation/nanotrasen) +/datum/job/proc/after_spawn(mob/living/spawned, mob/M, latejoin = FALSE) + SHOULD_CALL_PARENT(TRUE) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_JOB_AFTER_SPAWN, src, spawned, M, latejoin) + for(var/trait in mind_traits) + ADD_TRAIT(spawned.mind, trait, JOB_TRAIT) + + var/obj/item/organ/liver/liver = spawned.getorganslot(ORGAN_SLOT_LIVER) + if(liver) + for(var/trait in liver_traits) + ADD_TRAIT(liver, trait, JOB_TRAIT) + spawned.mind.add_employee(/datum/corporation/nanotrasen) /datum/job/proc/announce(mob/living/carbon/human/H) if(head_announce) diff --git a/code/modules/jobs/job_types/curator.dm b/code/modules/jobs/job_types/curator.dm index d4140d1e805c..45b11dec598a 100644 --- a/code/modules/jobs/job_types/curator.dm +++ b/code/modules/jobs/job_types/curator.dm @@ -20,7 +20,7 @@ base_access = list(ACCESS_LIBRARY, ACCESS_CONSTRUCTION, ACCESS_MINING_STATION) paycheck = PAYCHECK_EASY paycheck_department = ACCOUNT_CIV - + mind_traits = list(TRAIT_BLOODSUCKER_HUNTER) display_order = JOB_DISPLAY_ORDER_CURATOR minimal_character_age = 18 //Don't need to be some aged-ass fellow to know how to care for things, possessions could easily have come from parents and the like. Bloodsucker knowledge is another thing, though that's likely mostly consulted by the book diff --git a/code/modules/jobs/job_types/mime.dm b/code/modules/jobs/job_types/mime.dm index 0871df5b1ce4..3a9e55db3d6f 100644 --- a/code/modules/jobs/job_types/mime.dm +++ b/code/modules/jobs/job_types/mime.dm @@ -71,7 +71,8 @@ H.grant_language(/datum/language/french, TRUE, TRUE, LANGUAGE_MIME) if(H.mind) - H.mind.AddSpell(new /obj/effect/proc_holder/spell/targeted/mime/speak(null)) + var/datum/action/cooldown/spell/vow_of_silence/vow = new(H.mind) + vow.Grant(H) H.mind.miming = 1 /obj/item/book/mimery @@ -80,32 +81,55 @@ icon_state ="bookmime" /obj/item/book/mimery/attack_self(mob/user,) - user.set_machine(src) - var/dat = "" - 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
" - dat += "" - user << browse(dat, "window=book") - -/obj/item/book/mimery/Topic(href, href_list) - ..() - if (usr.stat || usr.restrained() || src.loc != usr) - return - if (!ishuman(usr)) + . = ..() + 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, span_notice("The book disappears into thin air.")) - qdel(src) + + var/list/spell_icons = list( + "Invisible Wall" = image(icon = 'icons/mob/actions/actions_mime.dmi', icon_state = "invisible_wall"), + "Invisible Chair" = image(icon = 'icons/mob/actions/actions_mime.dmi', icon_state = "invisible_chair"), + "Invisible Box" = image(icon = 'icons/mob/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/cooldown/spell/picked_spell_type + switch(picked_spell) + if("Invisible Wall") + picked_spell_type = /datum/action/cooldown/spell/conjure/invisible_wall + + if("Invisible Chair") + picked_spell_type = /datum/action/cooldown/spell/conjure/invisible_chair + + if("Invisible Box") + picked_spell_type = /datum/action/cooldown/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/cooldown/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, span_warning("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/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index 30270851532f..7bf603c143bc 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -33,6 +33,8 @@ paycheck = PAYCHECK_COMMAND paycheck_department = ACCOUNT_SCI + liver_traits = list(TRAIT_BALLMER_SCIENTIST) + display_order = JOB_DISPLAY_ORDER_RESEARCH_DIRECTOR minimal_character_age = 26 //Barely knows more than actual scientists, just responsibility and AI things diff --git a/code/modules/jobs/job_types/scientist.dm b/code/modules/jobs/job_types/scientist.dm index 5c2421ff9e10..41dbef1dfd18 100644 --- a/code/modules/jobs/job_types/scientist.dm +++ b/code/modules/jobs/job_types/scientist.dm @@ -17,9 +17,12 @@ added_access = list(ACCESS_ROBO_CONTROL, ACCESS_TECH_STORAGE, ACCESS_GENETICS) base_access = list(ACCESS_TOX, ACCESS_TOX_STORAGE, ACCESS_RESEARCH, ACCESS_XENOBIOLOGY, ACCESS_MECH_SCIENCE, ACCESS_MINERAL_STOREROOM) + paycheck = PAYCHECK_MEDIUM paycheck_department = ACCOUNT_SCI + liver_traits = list(TRAIT_BALLMER_SCIENTIST) + display_order = JOB_DISPLAY_ORDER_SCIENTIST minimal_character_age = 24 //Consider the level of knowledge that spans xenobio, nanites, and toxins diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index aff387ecc64b..6a5cc1c60018 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -122,7 +122,7 @@ GLOBAL_LIST_INIT(available_depts_sec, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICA else var/safety = 0 while(safety < 25) - T = safepick(get_area_turfs(destination)) + T = pick(get_area_turfs(destination)) if(T && !H.Move(T)) safety += 1 continue diff --git a/code/modules/mapping/minimap.dm b/code/modules/mapping/minimap.dm index 7ededf6ddc43..6b7293ade551 100644 --- a/code/modules/mapping/minimap.dm +++ b/code/modules/mapping/minimap.dm @@ -86,7 +86,7 @@ span_userdanger("You miss the map and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm index e3768bf4f8b8..a6f18026db34 100644 --- a/code/modules/mining/equipment/regenerative_core.dm +++ b/code/modules/mining/equipment/regenerative_core.dm @@ -143,7 +143,7 @@ add_overlay("legion_soul_crackle") for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/organ/regenerative_core/legion/go_inert() ..() diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm index a0ee74057612..a24f74de04c8 100644 --- a/code/modules/mining/fulton.dm +++ b/code/modules/mining/fulton.dm @@ -114,7 +114,7 @@ GLOBAL_LIST_EMPTY(total_extraction_beacons) if(ishuman(A)) var/mob/living/carbon/human/L = A L.SetUnconscious(0) - L.drowsyness = 0 + L.remove_status_effect(/datum/status_effect/drowsiness) L.SetSleeping(0) sleep(3 SECONDS) var/list/flooring_near_beacon = list() diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm index 6b0a588f9ea6..84314afcc2eb 100644 --- a/code/modules/mining/lavaland/necropolis_chests.dm +++ b/code/modules/mining/lavaland/necropolis_chests.dm @@ -132,7 +132,7 @@ GLOBAL_LIST_EMPTY(aide_list) to_chat(itemUser, failText) return to_chat(itemUser, span_notice("The snake, satisfied with your oath, attaches itself and the rod to your forearm with an inseparable grip. Your thoughts seem to only revolve around the core idea of helping others, and harm is nothing more than a distant, wicked memory...")) - var/datum/status_effect/hippocraticOath/effect = itemUser.apply_status_effect(STATUS_EFFECT_HIPPOCRATIC_OATH, efficiency, type) + var/datum/status_effect/hippocratic_oath/effect = itemUser.apply_status_effect(STATUS_EFFECT_HIPPOCRATIC_OATH, efficiency, type) effect.hand = usedHand activated() @@ -946,7 +946,7 @@ GLOBAL_LIST_EMPTY(aide_list) new /obj/item/melee/ghost_sword(src) if(2) new /obj/item/lava_staff(src) - new /obj/item/book/granter/spell/sacredflame(src) + new /obj/item/book/granter/action/spell/sacredflame(src) if(3) new /obj/item/dragon_egg(src) if(4) @@ -1083,9 +1083,8 @@ GLOBAL_LIST_EMPTY(aide_list) H.set_species(/datum/species/skeleton) if(3) to_chat(user, span_danger("Power courses through you! You can now shift your form at will.")) - if(user.mind) - var/obj/effect/proc_holder/spell/targeted/shapeshift/dragon/D = new - user.mind.AddSpell(D) + var/datum/action/cooldown/spell/shapeshift/dragon/dragon_shapeshift = new(user.mind || user) + dragon_shapeshift.Grant(user) if(4) to_chat(user, span_danger("You feel like you could walk straight through lava now.")) H.weather_immunities |= "lava" @@ -1308,6 +1307,19 @@ GLOBAL_LIST_EMPTY(aide_list) #define COOLDOWN_HUMAN 100 #define COOLDOWN_ANIMAL 60 #define COOLDOWN_SPLASH 100 + +/datum/action/item_action/visegrip + name = "Vise Grip" + desc = "Remotely detonate marked targets. People become rooted for 1 second. Animals become rooted for 6 seconds and take hefty damage." + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "leghold" + +/datum/action/item_action/reach + name = "Reach" + desc = "Mark those standing on blood for 10 seconds." + icon_icon = 'icons/effects/effects.dmi' + button_icon_state = "rshield" + /obj/item/melee/knuckles name = "bloody knuckles" desc = "Knuckles born of a desire for violence. Made to ensure their victims stay in the fight until there's a winner. Activating these knuckles covers several meters \ @@ -1813,9 +1825,22 @@ GLOBAL_LIST_EMPTY(aide_list) new /obj/item/prisoncube(src) //Legion -#define COOLDOWN_TAP 60 -#define COOLDOWN_BAND 200 -#define COOLDOWN_TELE 15 +#define COOLDOWN_TAP 6 SECONDS +#define COOLDOWN_BAND 20 SECONDS +#define COOLDOWN_TELE 1.5 SECONDS + +/datum/action/item_action/band + name = "Band" + desc = "Summon all your thralls to your location." + icon_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "horde" + +/*/datum/action/item_action/gambit + name = "Gambit" + desc = "Throw out your cane. If the target is weak enough to finish off, teleport to them and do it, recovering your cane in the process." + icon_icon = 'icons/mob/actions/actions_cult.dmi' + button_icon_state = "horde"*/ + /obj/item/cane/cursed name = "cursed cane" desc = "A pristine marble cane. Tapping the cane against the ground calls lesser minions to you while tapping it against a dead or dying victim will make them yours should you\ diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index 2e81ec181e5d..6a53f0d699ce 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -110,7 +110,7 @@ GLOBAL_LIST_INIT(sand_recipes, list(\ return C.adjust_blurriness(6) C.adjustStaminaLoss(15)//the pain from your eyes burning does stamina damage - C.confused += 5 + C.adjust_confusion(5 SECONDS) to_chat(C, span_userdanger("\The [src] gets into your eyes! The pain, it burns!")) qdel(src) diff --git a/code/modules/mob/dead/emote.dm b/code/modules/mob/dead/emote.dm index cbcd4a9830d5..0f93953fdf57 100644 --- a/code/modules/mob/dead/emote.dm +++ b/code/modules/mob/dead/emote.dm @@ -8,11 +8,11 @@ key_third_person = "dabs" message = "dabs." message_param = "dabs on %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/dead/dab/run_emote(mob/user, params) . = ..() var/mob/dead/observer/H = user var/light_dab_angle = rand(35,55) var/light_dab_speed = rand(3,7) - H.DabAnimation(angle = light_dab_angle , speed = light_dab_speed) \ No newline at end of file + H.DabAnimation(angle = light_dab_angle , speed = light_dab_speed) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 48f262b3dd03..9040e18eae6b 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -720,12 +720,12 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp /mob/dead/observer/proc/show_data_huds() for(var/hudtype in datahuds) var/datum/atom_hud/H = GLOB.huds[hudtype] - H.add_hud_to(src) + H.show_to(src) /mob/dead/observer/proc/remove_data_huds() for(var/hudtype in datahuds) var/datum/atom_hud/H = GLOB.huds[hudtype] - H.remove_hud_from(src) + H.hide_from(src) /mob/dead/observer/verb/toggle_data_huds() set name = "Toggle Sec/Med/Diag HUD" diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm index 405852fdd4e5..075bbe7392b5 100644 --- a/code/modules/mob/emote.dm +++ b/code/modules/mob/emote.dm @@ -42,7 +42,7 @@ /datum/emote/flip key = "flip" key_third_person = "flips" - restraint_check = TRUE + hands_use_check = TRUE mob_type_allowed_typecache = list(/mob/living, /mob/dead/observer) mob_type_ignore_stat_typecache = list(/mob/dead/observer) cooldown = 0 SECONDS @@ -79,7 +79,7 @@ /datum/emote/spin key = "spin" key_third_person = "spins" - restraint_check = TRUE + hands_use_check = TRUE mob_type_allowed_typecache = list(/mob/living, /mob/dead/observer) mob_type_ignore_stat_typecache = list(/mob/dead/observer) cooldown = 0 SECONDS @@ -109,15 +109,14 @@ return if(!iscarbon(user)) return - var/current_confusion = user.confused - if(current_confusion > BEYBLADE_PUKE_THRESHOLD) + if(user.get_timed_status_effect_duration(/datum/status_effect/confusion) > BEYBLADE_PUKE_THRESHOLD) user.vomit(BEYBLADE_PUKE_NUTRIENT_LOSS, distance = 0) return + if(prob(BEYBLADE_DIZZINESS_PROBABILITY)) - to_chat(user, "You feel woozy from spinning.") - user.Dizzy(BEYBLADE_DIZZINESS_DURATION) - if(current_confusion < BEYBLADE_CONFUSION_LIMIT) - user.confused += BEYBLADE_CONFUSION_INCREMENT + to_chat(user, span_warning("You feel woozy from spinning.")) + user.set_dizzy_if_lower(BEYBLADE_DIZZINESS_DURATION) + user.adjust_confusion_up_to(BEYBLADE_CONFUSION_INCREMENT, BEYBLADE_CONFUSION_LIMIT) #undef BEYBLADE_PUKE_THRESHOLD #undef BEYBLADE_PUKE_NUTRIENT_LOSS diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm index 2d30c1107d6c..d0294f779841 100644 --- a/code/modules/mob/living/blood.dm +++ b/code/modules/mob/living/blood.dm @@ -221,6 +221,10 @@ amount = total_amount * blood_proportion chems_amount = total_amount * (1 - blood_proportion) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = mind?.has_antag_datum(/datum/antagonist/bloodsucker) + if(!bloodsuckerdatum) + return + bloodsuckerdatum.bloodsucker_blood_volume -= amount blood_volume -= amount var/list/blood_data = get_blood_data(blood_id) diff --git a/code/modules/mob/living/brain/brain.dm b/code/modules/mob/living/brain/brain.dm index 142b52f38bba..e05e1c7fac84 100644 --- a/code/modules/mob/living/brain/brain.dm +++ b/code/modules/mob/living/brain/brain.dm @@ -17,7 +17,6 @@ OB.brainmob = src forceMove(OB) - /mob/living/brain/proc/create_dna() stored_dna = new /datum/dna/stored(src) if(!stored_dna.species) @@ -97,8 +96,6 @@ var/obj/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 ec2de0314975..e4d9c172a5af 100644 --- a/code/modules/mob/living/carbon/alien/alien.dm +++ b/code/modules/mob/living/carbon/alien/alien.dm @@ -133,8 +133,10 @@ Des: Removes all infected images from the alien. return initial(pixel_y) /mob/living/carbon/alien/proc/alien_evolve(mob/living/carbon/alien/new_xeno) - to_chat(src, span_noticealien("You begin to evolve!")) - visible_message(span_alertalien("[src] begins to twist and contort!")) + visible_message( + span_alertalien("[src] begins to twist and contort!"), + span_noticealien("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 ea3d520c157a..38cab2b30c0f 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/alien_powers.dm @@ -6,168 +6,234 @@ These are general powers. Specific powers are stored under the appropriate alien Doesn't work on other aliens/AI.*/ -/obj/effect/proc_holder/alien +/datum/action/cooldown/alien name = "Alien Power" panel = "Alien" + background_icon_state = "bg_alien" + icon_icon = 'icons/mob/actions/actions_xeno.dmi' + button_icon_state = "spell_default" + check_flags = AB_CHECK_CONSCIOUS + /// How much plasma this action uses. var/plasma_cost = 0 - var/check_turf = FALSE - has_action = TRUE - base_action = /datum/action/spell_action/alien - action_icon = 'icons/mob/actions/actions_xeno.dmi' - action_icon_state = "spell_default" - action_background_icon_state = "bg_alien" - -/obj/effect/proc_holder/alien/Initialize() + +/datum/action/cooldown/alien/IsAvailable() + . = ..() + if(!.) + return FALSE + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/carbon_owner = owner + if(carbon_owner.getPlasma() < plasma_cost) + return FALSE + + return TRUE + +/datum/action/cooldown/alien/PreActivate(atom/target) + // Parent calls Activate(), so if parent returns TRUE, + // it means the activation happened successfuly by this point . = ..() - action = new(src) + 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 + + 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(click_to_activate && carbon_owner.getPlasma() < plasma_cost) + owner.balloon_alert(owner, "not enough plasma!") + unset_click_ability(owner, refund_cooldown = FALSE) -/obj/effect/proc_holder/alien/Click() - if(!iscarbon(usr)) - return 1 - 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) - return 1 + return TRUE -/obj/effect/proc_holder/alien/on_gain(mob/living/carbon/user) - return +/datum/action/cooldown/alien/set_statpanel_format() + . = ..() + if(!islist(.)) + return -/obj/effect/proc_holder/alien/on_lose(mob/living/carbon/user) - return + .[PANEL_DISPLAY_STATUS] = "PLASMA - [plasma_cost]" -/obj/effect/proc_holder/alien/fire(mob/living/carbon/user) - return 1 +/datum/action/cooldown/alien/make_structure + /// The type of structure the action makes on use + var/obj/structure/made_structure_type -/obj/effect/proc_holder/alien/get_panel_text() +/datum/action/cooldown/alien/make_structure/IsAvailable() . = ..() - 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, span_noticealien("You must be conscious to do this.")) +/datum/action/cooldown/alien/make_structure/PreActivate(atom/target) + if(!check_for_duplicate()) return FALSE - if(user.getPlasma() < plasma_cost) - if(!silent) - to_chat(user, span_noticealien("Not enough plasma stored.")) + + if(!check_for_vents()) return FALSE - if(check_turf && (!isturf(user.loc) || isspaceturf(user.loc))) - if(!silent) - to_chat(user, span_noticealien("Bad place for a garden!")) + + return ..() + +/datum/action/cooldown/alien/make_structure/Activate(atom/target) + new made_structure_type(owner.loc) + return TRUE + +/// Checks if there's a duplicate structure in the owner's turf +/datum/action/cooldown/alien/make_structure/proc/check_for_duplicate() + var/obj/structure/existing_thing = locate(made_structure_type) in owner.loc + if(existing_thing) + owner.balloon_alert(owner, "space occupied by \a [existing_thing]!") 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/cooldown/alien/make_structure/proc/check_for_vents() + var/obj/machinery/atmospherics/components/unary/atmos_thing = locate() in owner.loc if(atmos_thing) - var/rusure = tgui_alert(user, "Laying eggs and shaping resin here would block access to [atmos_thing]. Do you want to continue?", "Blocking Atmospheric Component", list("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/cooldown/alien/make_structure/plant_weeds name = "Plant Weeds" 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 0 - user.visible_message(span_alertalien("[user] has planted some alien weeds!")) - new/obj/structure/alien/weeds/node(user.loc) - return 1 +/datum/action/cooldown/alien/make_structure/plant_weeds/Activate(atom/target) + owner.visible_message(span_alertalien("[owner] plants some alien weeds!")) + return ..() -/obj/effect/proc_holder/alien/whisper +/datum/action/cooldown/alien/whisper name = "Whisper" 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/Ms in oview(user)) - options += Ms - var/mob/living/M = input("Select who to whisper to:","Whisper to?",null) as null|mob in options - if(!M) - return 0 - if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - to_chat(user, span_noticealien("As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.")) + +/datum/action/cooldown/alien/whisper/Activate(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, span_noticealien("There's no one around to whisper to.")) + return FALSE + + var/mob/living/chosen_recipient = tgui_input_list(owner, "Select whisper recipient", "Whisper", sort_names(possible_recipients)) + if(!chosen_recipient) + return FALSE + + var/to_whisper = tgui_input_text(owner, title = "Alien Whisper") + if(QDELETED(chosen_recipient) || QDELETED(src) || QDELETED(owner) || !IsAvailable() || !to_whisper) + return FALSE + if(chosen_recipient.can_block_magic(MAGIC_RESISTANCE_MIND, charge_cost = 0)) + to_chat(owner, span_warning("As you reach into [chosen_recipient]'s mind, you are stopped by a mental blockage. It seems you've been foiled.")) return FALSE - var/msg = sanitize(to_utf8(input("Message:", "Alien Whisper") as text|null, usr)) - if(msg) - if(M.anti_magic_check(FALSE, FALSE, TRUE, 0)) - to_chat(user, span_notice("As you try to communicate with [M], you're suddenly stopped by a vision of a massive tinfoil wall that streches beyond visible range. It seems you've been foiled.")) - return - log_directed_talk(user, M, msg, LOG_SAY, tag="alien whisper") - to_chat(M, "[span_noticealien("You hear a strange, alien voice in your head...")][msg]") - to_chat(user, span_noticealien("You said: \"[msg]\" to [M]")) - for(var/ded in GLOB.dead_mob_list) - if(!isobserver(ded)) - continue - var/follow_link_user = FOLLOW_LINK(ded, user) - var/follow_link_whispee = FOLLOW_LINK(ded, M) - to_chat(ded, "[follow_link_user] [span_name("[user]")] [span_alertalien("Alien Whisper --> ")] [follow_link_whispee] [span_name("[M]")] [span_noticealien("[msg]")]") - else - return 0 - return 1 -/obj/effect/proc_holder/alien/transfer + log_directed_talk(owner, chosen_recipient, to_whisper, LOG_SAY, tag = "alien whisper") + to_chat(chosen_recipient, "[span_noticealien("You hear a strange, alien voice in your head...")][to_whisper]") + to_chat(owner, span_noticealien("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(dead_mob, owner) + var/follow_link_whispee = FOLLOW_LINK(dead_mob, chosen_recipient) + to_chat(dead_mob, "[follow_link_user] [span_name("[owner]")] [span_alertalien("Alien Whisper --> ")] [follow_link_whispee] [span_name("[chosen_recipient]")] [span_noticealien("[to_whisper]")]") + + return TRUE + +/datum/action/cooldown/alien/transfer name = "Transfer Plasma" desc = "Transfer Plasma to another alien." plasma_cost = 0 - action_icon_state = "alien_transfer" + button_icon_state = "alien_transfer" -/obj/effect/proc_holder/alien/transfer/fire(mob/living/carbon/user) +/datum/action/cooldown/alien/transfer/Activate(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 aliens_around - if(!M) - return 0 - var/amount = input("Amount:", "Transfer Plasma to [M]") as num - if (amount) - amount = min(abs(round(amount)), user.getPlasma()) - if (get_dist(user,M) <= 1) - M.adjustPlasma(amount) - user.adjustPlasma(-amount) - to_chat(M, span_noticealien("[user] has transferred [amount] plasma to you.")) - to_chat(user, span_noticealien("You transfer [amount] plasma to [M]")) - else - to_chat(user, span_noticealien("You need to be closer!")) - return - -/obj/effect/proc_holder/alien/acid + 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, span_noticealien("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 + + 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) || !IsAvailable() || isnull(amount) || amount <= 0) + return FALSE + + if(get_dist(owner, donation_target) > 1) + owner.balloon_alert(owner, "too far!") + return FALSE + + + donation_target.adjustPlasma(amount) + carbon_owner.adjustPlasma(-amount) + + to_chat(donation_target, span_noticealien("[owner] has transferred [amount] plasma to you.")) + to_chat(owner, span_noticealien("You transfer [amount] plasma to [donation_target].")) + return TRUE + +/datum/action/cooldown/alien/acid + click_to_activate = TRUE + unset_after_click = FALSE + +/datum/action/cooldown/alien/acid/corrosion name = "Corrosive Acid" desc = "Drench an object in acid, destroying it over time." + button_icon_state = "alien_acid" plasma_cost = 200 - action_icon_state = "alien_acid" + ranged_mousepointer = 'icons/effects/mouse_pointers/acid.dmi' -/obj/effect/proc_holder/alien/acid/on_gain(mob/living/carbon/user) - add_verb(user, /mob/living/carbon/proc/corrosive_acid) +/datum/action/cooldown/alien/acid/corrosion/set_click_ability(mob/on_who) + . = ..() + if(!.) + return -/obj/effect/proc_holder/alien/acid/on_lose(mob/living/carbon/user) - remove_verb(user, /mob/living/carbon/proc/corrosive_acid) + owner.balloon_alert(owner, "acid glands ready!") + on_who.update_icons() -/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(span_alertalien("[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, span_noticealien("You cannot dissolve this object.")) +/datum/action/cooldown/alien/acid/corrosion/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + if(refund_cooldown) + owner.balloon_alert(owner, "acid glands relaxed") + on_who.update_icons() - return 0 - else - to_chat(src, span_noticealien("[target] is too far away.")) - return 0 +/datum/action/cooldown/alien/acid/corrosion/PreActivate(atom/target) + if(get_dist(owner, target) > 1) + owner.balloon_alert(owner, "too far!") + return FALSE + return ..() + +/datum/action/cooldown/alien/acid/corrosion/Activate(atom/target) + if(!target.acid_act(200, 1000)) + owner.balloon_alert(owner, "cannot disolve") + return FALSE + owner.visible_message( + span_alertalien("[owner] vomits globs of vile stuff all over [target]. It begins to sizzle and melt under the bubbling mess of acid!"), + span_noticealien("You vomit globs of acid over [target]. It begins to sizzle and melt."), + ) + return TRUE /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) @@ -176,162 +242,151 @@ Doesn't work on other aliens/AI.*/ else return corrode(O,user) -/mob/living/carbon/proc/corrosive_acid(O as obj|turf in oview(1)) // right click menu verb ugh - set name = "Corrosive Acid" - - 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) - -/obj/effect/proc_holder/alien/neurotoxin +/datum/action/cooldown/alien/acid/neurotoxin name = "Spit Neurotoxin" desc = "Spits neurotoxin at someone, paralyzing them for a short time." - action_icon_state = "alien_neurotoxin_0" - active = FALSE - -/obj/effect/proc_holder/alien/neurotoxin/fire(mob/living/carbon/user) - var/message - if(active) - message = span_notice("You empty your neurotoxin gland.") - remove_ranged_ability(message) - else - message = span_notice("You prepare your neurotoxin gland. Left-click to fire at a target!") - add_ranged_ability(user, message, TRUE) + 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/cooldown/alien/acid/neurotoxin/IsAvailable() + return ..() && isturf(owner.loc) -/obj/effect/proc_holder/alien/neurotoxin/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return - var/p_cost = 50 - if(!iscarbon(ranged_ability_user) || ranged_ability_user.stat) - remove_ranged_ability() +/datum/action/cooldown/alien/acid/neurotoxin/set_click_ability(mob/on_who) + . = ..() + if(!.) return - var/mob/living/carbon/user = ranged_ability_user + owner.balloon_alert(owner, "neurotoxin glands ready!") - if(user.getPlasma() < p_cost) - to_chat(user, span_warning("You need at least [p_cost] plasma to spit.")) - remove_ranged_ability() - return + button_icon_state = "alien_neurotoxin_1" + UpdateButtons() + on_who.update_icons() - 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)) +/datum/action/cooldown/alien/acid/neurotoxin/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + if(refund_cooldown) + owner.balloon_alert(owner, "neurotoxin glands relaxed") + + button_icon_state = "alien_neurotoxin_0" + UpdateButtons() + on_who.update_icons() + +/datum/action/cooldown/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!", span_alertalien("You spit neurotoxin.")) - var/obj/item/projectile/bullet/neurotoxin/A = new /obj/item/projectile/bullet/neurotoxin(user.loc) - A.firer = user - A.preparePixelProjectile(target, user, params) - 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( + span_danger("[caller] spits neurotoxin!"), + span_alertalien("You spit neurotoxin."), + ) + var/obj/item/projectile/neurotoxin/neurotoxin = new /obj/item/projectile/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 = 1 - 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 = 0 - A.update_icons() - ..() +// Has to return TRUE, otherwise is skipped. +/datum/action/cooldown/alien/acid/neurotoxin/Activate(atom/target) + return TRUE -/obj/effect/proc_holder/alien/resin +/datum/action/cooldown/alien/make_structure/resin name = "Secrete Resin" desc = "Secrete tough malleable resin." + button_icon_state = "alien_resin" plasma_cost = 55 - check_turf = TRUE + /// A list of all structures we can make. var/list/structures = list( "resin wall" = /obj/structure/alien/resin/wall, "resin membrane" = /obj/structure/alien/resin/membrane, "resin nest" = /obj/structure/bed/nest) - action_icon_state = "alien_resin" +// Snowflake to check for multiple types of alien resin structures +/datum/action/cooldown/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, span_warning("There is already a resin structure there!")) + return FALSE -/obj/effect/proc_holder/alien/resin/fire(mob/living/carbon/user) - if(locate(/obj/structure/alien/resin) in user.loc) - to_chat(user, span_danger("There is already a resin structure there.")) - return FALSE + return TRUE - if(!check_vent_block(user)) +/datum/action/cooldown/alien/make_structure/resin/Activate(atom/target) + var/choice = show_radial_menu(owner, owner, structures, radius = 36) + if(isnull(choice) || QDELETED(src) || QDELETED(owner) || !check_for_duplicate() || !IsAvailable()) return FALSE - var/choice = input("Choose what you wish to shape.","Resin building") as null|anything in structures - if(!choice) + var/obj/structure/choice_path = structures[choice] + if(!ispath(choice_path)) return FALSE - if (!cost_check(check_turf,user)) - return FALSE - to_chat(user, span_notice("You shape a [choice].")) - user.visible_message(span_notice("[user] vomits up a thick purple substance and begins to shape it.")) - choice = structures[choice] - new choice(user.loc) + owner.visible_message( + span_notice("[owner] vomits up a thick purple substance and begins to shape it."), + span_notice("You shape a [choice] out of resin."), + ) + new choice_path(owner.loc) return TRUE -/obj/effect/proc_holder/alien/sneak +/datum/action/cooldown/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 = 1 - active = 1 - to_chat(user, span_noticealien("You blend into the shadows...")) + button_icon_state = "alien_sneak" + /// The alpha we go to when sneaking. + var/sneak_alpha = 75 + +/datum/action/cooldown/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/cooldown/alien/sneak/Activate(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) + owner.balloon_alert(owner, "you reveal yourself!") + REMOVE_TRAIT(owner, TRAIT_ALIEN_SNEAK, name) + else - user.alpha = initial(user.alpha) - user.sneaking = 0 - active = 0 - to_chat(user, span_noticealien("You reveal yourself!")) + owner.alpha = sneak_alpha + owner.balloon_alert(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 0 - 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 0 - 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() - return 1 + return FALSE + 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/cooldown/alien/ability in actions) + ability.UpdateButtons() + return TRUE /mob/living/carbon/alien/adjustPlasma(amount) . = ..() updatePlasmaDisplay() - -/mob/living/carbon/proc/usePlasma(amount) - if(getPlasma() >= amount) - adjustPlasma(-amount) - return 1 - - return 0 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 0df4efaf265e..628c2f6eba53 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/drone.dm @@ -6,40 +6,44 @@ icon_state = "aliend" /mob/living/carbon/alien/humanoid/drone/Initialize() - AddAbility(new/obj/effect/proc_holder/alien/evolve(null)) - . = ..() + var/datum/action/cooldown/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 internal_organs += new /obj/item/organ/alien/resinspinner internal_organs += new /obj/item/organ/alien/acid - ..() + return ..() -/obj/effect/proc_holder/alien/evolve +/datum/action/cooldown/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" +/datum/action/cooldown/alien/evolve_to_praetorian/IsAvailable() + . = ..() + if(!.) + return FALSE + if(!isturf(owner.loc)) + return FALSE + + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal)) + return FALSE + + var/mob/living/carbon/alien/humanoid/royal/evolver = owner + var/obj/item/organ/internal/alien/hivenode/node = evolver.getorgan(/obj/item/organ/internal/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 -/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, span_danger("Without the hivemind, you can't possibly hold the responsibility of leadership!")) - return 0 - if(node.recent_queen_death) - to_chat(user, span_danger("Your thoughts are still too scattered to take up the position of leadership.")) - return 0 - if(user.movement_type & (VENTCRAWLING)) - to_chat(user, span_danger("You cannot evolve in a pipe.")) - return 0 - if(!isturf(user.loc)) - to_chat(user, span_notice("You can't evolve here!")) - return 0 - if(!get_alien_type(/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 1 - else - to_chat(user, span_notice("We already have a living royal!")) - return 0 \ No newline at end of file +/datum/action/cooldown/alien/evolve_to_praetorian/Activate(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 \ No newline at end of file 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 303490614891..70aafb27aae7 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/praetorian.dm @@ -7,36 +7,48 @@ /mob/living/carbon/alien/humanoid/royal/praetorian/Initialize() 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/cooldown/spell/aoe/repulse/xeno/tail_whip = new(src) + tail_whip.Grant(src) + + var/datum/action/cooldown/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 internal_organs += new /obj/item/organ/alien/resinspinner internal_organs += new /obj/item/organ/alien/acid internal_organs += new /obj/item/organ/alien/neurotoxin - ..() + return ..() -/obj/effect/proc_holder/alien/royal/praetorian/evolve +/datum/action/cooldown/alien/evolve_to_queen name = "Evolve" 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, span_danger("Without the hivemind, you would be unfit to rule as queen!")) - return 0 - if(node.recent_queen_death) - to_chat(user, span_danger("You are still too burdened with guilt to evolve into a queen.")) - return 0 - if(!get_alien_type(/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 1 - else - to_chat(user, span_notice("We already have an alive queen.")) - return 0 +/datum/action/cooldown/alien/evolve_to_queen/IsAvailable() + . = ..() + if(!.) + return FALSE + + if(!isturf(owner.loc)) + return FALSE + + 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/internal/alien/hivenode/node = evolver.getorgan(/obj/item/organ/internal/alien/hivenode) + if(!node || node.recent_queen_death) + return FALSE + + return TRUE + +/datum/action/cooldown/alien/evolve_to_queen/Activate(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 \ No newline at end of file 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 7c6443cfae7b..71261ba339a5 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/caste/sentinel.dm @@ -6,11 +6,13 @@ icon_state = "aliens" /mob/living/carbon/alien/humanoid/sentinel/Initialize() - AddAbility(new /obj/effect/proc_holder/alien/sneak) + var/datum/action/cooldown/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 internal_organs += new /obj/item/organ/alien/acid internal_organs += new /obj/item/organ/alien/neurotoxin - ..() + return ..() diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index 7bbd0b63f7a8..4ef122f9219f 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -15,8 +15,6 @@ var/pounce_cooldown_time = 30 var/custom_pixel_x_offset = 0 //for admin fuckery. var/custom_pixel_y_offset = 0 - var/sneaking = 0 //For sneaky-sneaky mode and appropriate slowdown - var/drooling = 0 //For Neruotoxic spit overlays deathsound = 'sound/voice/hiss6.ogg' bodyparts = list(/obj/item/bodypart/chest/alien, /obj/item/bodypart/head/alien, /obj/item/bodypart/l_arm/alien, /obj/item/bodypart/r_arm/alien, /obj/item/bodypart/r_leg/alien, /obj/item/bodypart/l_leg/alien) @@ -113,8 +111,38 @@ return A return FALSE - /mob/living/carbon/alien/humanoid/check_breath(datum/gas_mixture/breath) - if(breath && breath.total_moles() > 0 && !sneaking) + 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, 0, -5) - ..() + return ..() + +/mob/living/carbon/alien/adult/proc/grab(mob/living/carbon/human/target) + if(target.check_block()) + target.visible_message(span_warning("[target] blocks [src]'s grab!"), \ + span_userdanger("You block [src]'s grab!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, src) + to_chat(src, span_warning("Your grab at [target] was blocked!")) + return FALSE + target.grabbedby(src) + return TRUE + +/mob/living/carbon/alien/adult/setGrabState(newstate) + if(newstate == grab_state) + return + if(newstate > GRAB_AGGRESSIVE) + newstate = GRAB_AGGRESSIVE + SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate) + . = grab_state + grab_state = newstate + switch(grab_state) // Current state. + if(GRAB_PASSIVE) + REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher. + REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + if(GRAB_AGGRESSIVE) + if(. >= GRAB_NECK) // Grab got downgraded. + REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) + else // Grab got upgraded from a passive one. + ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) + if(GRAB_NECK, GRAB_KILL) + if(. <= GRAB_AGGRESSIVE) + ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) diff --git a/code/modules/mob/living/carbon/alien/humanoid/queen.dm b/code/modules/mob/living/carbon/alien/humanoid/queen.dm index a9afd093abe5..9515f37b39d6 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/queen.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/queen.dm @@ -22,12 +22,12 @@ maxHealth = 400 health = 400 icon_state = "alienq" - var/datum/action/small_sprite/smallsprite = new/datum/action/small_sprite/queen() + var/datum/timedevent/time_to_shuttle /mob/living/carbon/alien/humanoid/royal/queen/Initialize() if(!is_centcom_level(get_turf(src))) SSshuttle.registerHostileEnvironment(src) //yogs: aliens delay shuttle - addtimer(CALLBACK(src, .proc/game_end), 30 MINUTES) //yogs: time until shuttle is freed/called + time_to_shuttle = addtimer(CALLBACK(src, PROC_REF(game_end)), 30 MINUTES, TIMER_STOPPABLE) //yogs: time until shuttle is freed/called //there should only be one queen for(var/mob/living/carbon/alien/humanoid/royal/queen/Q in GLOB.carbon_list) if(Q == src) @@ -40,121 +40,163 @@ 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/cooldown/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/cooldown/alien/promote/promotion = new(src) + promotion.Grant(src) + return ..() +/mob/living/carbon/alien/humanoid/royal/queen/get_status_tab_items() + . = ..() + if(time_to_shuttle) + . += "" + . += "Blocked Shuttle Timer: [round(timeleft(time_to_shuttle) / 600, 1)] minutes" //weird conversion but works + +/mob/living/carbon/alien/humanoid/royal/queen/proc/kill_shuttle_timer() + SSshuttle.clearHostileEnvironment(src) + if(time_to_shuttle) + deltimer(time_to_shuttle) + /mob/living/carbon/alien/humanoid/royal/queen/create_internal_organs() internal_organs += new /obj/item/organ/alien/plasmavessel/large/queen internal_organs += new /obj/item/organ/alien/resinspinner 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/game_end() if(is_centcom_level(get_turf(src))) return if(stat == DEAD) return - SSshuttle.clearHostileEnvironment(src) + kill_shuttle_timer() if(EMERGENCY_IDLE_OR_RECALLED) priority_announce("Xenomorph infestation detected: Emergency shuttle will be sent to recover any survivors, if this is in error feel free to recall.") SSshuttle.emergency.request(null, set_coefficient=0.5) /mob/living/carbon/alien/humanoid/royal/queen/death()//yogs start: dead queen doesnt stop shuttle - SSshuttle.clearHostileEnvironment(src) + kill_shuttle_timer() ..() /mob/living/carbon/alien/humanoid/royal/queen/Destroy() - SSshuttle.clearHostileEnvironment(src) + kill_shuttle_timer() ..() //yogs end //Queen verbs -/obj/effect/proc_holder/alien/lay_egg +/datum/action/cooldown/alien/make_structure/lay_egg name = "Lay Egg" 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" - -/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, span_alertalien("There's already an egg here.")) - return FALSE + made_structure_type = /obj/structure/alien/egg - if(!check_vent_block(user)) - return FALSE - - user.visible_message(span_alertalien("[user] has laid an egg!")) - new /obj/structure/alien/egg(user.loc) - return TRUE +/datum/action/cooldown/alien/make_structure/lay_egg/Activate(atom/target) + . = ..() + owner.visible_message(span_alertalien("[owner] lays an egg!")) //Button to let queen choose her praetorian. -/obj/effect/proc_holder/alien/royal/queen/promote +/datum/action/cooldown/alien/promote name = "Create Royal Parasite" desc = "Produce a royal parasite to grant one of your children the honor of being your Praetorian." - plasma_cost = 500 //Plasma cost used on promotion, not spawning the parasite. + button_icon_state = "alien_queen_promote" + /// The promotion only takes plasma when completed, not on activation. + var/promotion_plasma_cost = 500 - action_icon_state = "alien_queen_promote" +/datum/action/cooldown/alien/promote/set_statpanel_format() + . = ..() + if(!islist(.)) + return + .[PANEL_DISPLAY_STATUS] = "PLASMA - [promotion_plasma_cost]" +/datum/action/cooldown/alien/promote/IsAvailable() + . = ..() + if(!.) + return FALSE -/obj/effect/proc_holder/alien/royal/queen/promote/fire(mob/living/carbon/alien/user) - var/obj/item/queenpromote/prom - if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, span_noticealien("You already have a Praetorian!")) - return 0 - else - for(prom in user) - to_chat(user, span_noticealien("You discard [prom].")) - qdel(prom) - return 0 + var/mob/living/carbon/carbon_owner = owner + if(carbon_owner.getPlasma() < promotion_plasma_cost) + return FALSE - prom = new (user.loc) - if(!user.put_in_active_hand(prom, 1)) - to_chat(user, span_warning("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, span_noticealien("Use the royal parasite on one of your children to promote her to Praetorian!")) - return 0 + if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian)) + return FALSE + + return TRUE + +/datum/action/cooldown/alien/promote/Activate(atom/target) + var/obj/item/queen_promotion/existing_promotion = locate() in owner.held_items + if(existing_promotion) + to_chat(owner, span_noticealien("You discard [existing_promotion].")) + owner.temporarilyRemoveItemFromInventory(existing_promotion) + qdel(existing_promotion) + return TRUE -/obj/item/queenpromote + if(!owner.get_empty_held_indexes()) + to_chat(owner, span_warning("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, span_noticealien("You fail to prepare a parasite.")) + return FALSE + + to_chat(owner, span_noticealien("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() +/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/cooldown/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, span_noticealien("You may only use this with your adult, non-royal children!")) + if(!isalienadult(to_promote) || isalienroyal(to_promote)) + to_chat(queen, span_noticealien("You may only use this with your adult, non-royal children!")) return - if(get_alien_type(/mob/living/carbon/alien/humanoid/royal/praetorian/)) - to_chat(user, span_noticealien("You already have a Praetorian!")) + + if(!promotion.IsAvailable()) + to_chat(queen, span_noticealien("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, span_noticealien("You must have 500 plasma stored to use this!")) - return - - to_chat(A, span_noticealien("The queen has granted you a promotion to Praetorian!")) - user.visible_message(span_alertalien("[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, span_warning("This child must be alert and responsive to become a Praetorian!")) -/obj/item/queenpromote/attack_self(mob/user) + queen.adjustPlasma(-promotion.promotion_plasma_cost) + + to_chat(queen, span_noticealien("You have promoted [to_promote] to a Praetorian!")) + to_promote.visible_message( + span_alertalien("[to_promote] begins to expand, twist and contort!"), + span_noticealien("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, span_noticealien("You discard [src].")) qdel(src) + +/obj/item/queen_promotion/dropped(mob/user, silent) + if(!silent) + to_chat(user, span_noticealien("You discard [src].")) + return ..() \ No newline at end of file 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 256f7cdb2864..3e5fdaf31ef9 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,8 @@ for(var/I in overlays_standing) add_overlay(I) - var/asleep = IsSleeping() + var/are_we_drooling = istype(click_intercept, /datum/action/cooldown/alien/acid) + if(stat == DEAD) //If we mostly took damage from fire if(fireloss > 125) @@ -12,7 +13,7 @@ else icon_state = "alien[caste]_dead" - else if((stat == UNCONSCIOUS && !asleep) || stat == SOFT_CRIT || IsParalyzed()) + else if((stat == UNCONSCIOUS && !IsSleeping()) || stat == SOFT_CRIT || IsParalyzed()) icon_state = "alien[caste]_unconscious" else if(leap_on_click) icon_state = "alien[caste]_pounce" @@ -21,11 +22,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 31d8fcbf2221..75db59ce56fc 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -21,15 +21,18 @@ //This is fine right now, if we're adding organ specific damage this needs to be updated /mob/living/carbon/alien/larva/Initialize() - AddAbility(new/obj/effect/proc_holder/alien/hide(null)) - AddAbility(new/obj/effect/proc_holder/alien/larva_evolve(null)) - . = ..() + var/datum/action/cooldown/alien/larva_evolve/evolution = new(src) + evolution.Grant(src) + var/datum/action/cooldown/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 /mob/living/carbon/alien/larva/get_status_tab_items() . = ..() . += "Progress: [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 65b3001f5325..cb985a3b8e7b 100644 --- a/code/modules/mob/living/carbon/alien/larva/powers.dm +++ b/code/modules/mob/living/carbon/alien/larva/powers.dm @@ -1,84 +1,110 @@ -/obj/effect/proc_holder/alien/hide +/datum/action/cooldown/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/cooldown/alien/hide/Activate(atom/target) + if(owner.layer == hide_layer) + owner.layer = initial(owner.layer) + owner.visible_message( + span_notice("[owner] slowly peeks up from the ground..."), + span_noticealien("You stop hiding."), + ) + owner.layer = hide_layer + owner.visible_message( + span_name("[owner] scurries to the ground!"), + span_noticealien("You are now 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(span_name("[user] scurries to the ground!"), \ - span_noticealien("You are now hiding.")) - else - user.layer = MOB_LAYER - user.visible_message("[user] slowly peeks up from the ground...", \ - span_noticealien("You stop hiding.")) - return 1 + return TRUE - -/obj/effect/proc_holder/alien/larva_evolve +/datum/action/cooldown/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/cooldown/alien/larva_evolve/IsAvailable() + . = ..() + 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/cooldown/alien/larva_evolve/Activate(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 = span_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.") + + // 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 = span_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, span_danger("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 = span_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 = span_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) || !IsAvailable() || !alien_caste) return - if(L.movement_type & (VENTCRAWLING)) - to_chat(user, span_danger("You cannot evolve in a pipe.")) + if(alien_caste == null) return - if(L.amount_grown >= L.max_grown) //TODO ~Carn - var/totalaliens = 0 - for(var/I in GLOB.player_list) /// Scan player list for amt of xenos - var/mob/M = I - if(isalien(M) && M.stat != DEAD) - totalaliens++ - if(totalaliens == 1) /// Check if the user is the only xeno existing - /// Force them into drone - var/mob/living/carbon/alien/humanoid/new_xeno - new_xeno = new /mob/living/carbon/alien/humanoid/drone(L.loc) - L.alien_evolve(new_xeno) - 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 = tgui_alert(L, "Please choose which alien caste you shall belong to.",,list("Hunter","Sentinel","Drone")) - - if(user.incapacitated()) //something happened to us while we were choosing. - return - - if(L.movement_type & (VENTCRAWLING)) - to_chat(user, span_danger("You cannot evolve in a pipe.")) - 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, span_danger("You are not fully grown.")) - return 0 + 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]") + + larva.alien_evolve(new_xeno) + return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/carbon/alien/life.dm b/code/modules/mob/living/carbon/alien/life.dm index fbce73769415..d598ce7bb9ee 100644 --- a/code/modules/mob/living/carbon/alien/life.dm +++ b/code/modules/mob/living/carbon/alien/life.dm @@ -39,9 +39,6 @@ if(move_delay_add > 0) move_delay_add = max(0, move_delay_add - rand(1, 2)) -/mob/living/carbon/alien/handle_changeling() - return - /mob/living/carbon/alien/handle_fire()//Aliens on fire code . = ..() if(.) //if the mob isn't on fire anymore diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm index 0dfc7d3df4b7..ccc02b1e24e8 100644 --- a/code/modules/mob/living/carbon/alien/organs.dm +++ b/code/modules/mob/living/carbon/alien/organs.dm @@ -1,29 +1,6 @@ /obj/item/organ/alien icon_state = "xgibmid2" visual = FALSE - var/list/alien_powers = list() - -/obj/item/organ/alien/Initialize() - . = ..() - 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) - ..() - 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) - for(var/obj/effect/proc_holder/alien/P in alien_powers) - M.RemoveAbility(P) - ..() /obj/item/organ/alien/prepare_eat() var/obj/S = ..() @@ -37,11 +14,19 @@ 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) - var/storedPlasma = 100 + actions_types = list( + /datum/action/cooldown/alien/make_structure/plant_weeds, + /datum/action/cooldown/alien/transfer, + ) + + /// The current amount of stored plasma. + var/stored_plasma = 100 + /// The maximum plasma this organ can store. var/max_plasma = 250 + /// The rate this organ regenerates its owners health at per damage type per second. var/heal_rate = 5 + /// The rate this organ regenerates plasma at per second. var/plasma_rate = 10 /obj/item/organ/alien/plasmavessel/prepare_eat() @@ -53,7 +38,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 @@ -64,7 +49,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 @@ -73,7 +58,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/cooldown/alien/transfer) /obj/item/organ/alien/plasmavessel/synthetic name = "synthetic plasma vessel" @@ -110,7 +95,7 @@ var/mob/living/carbon/alien/A = M A.updatePlasmaDisplay() -#define QUEEN_DEATH_DEBUFF_DURATION 2400 +#define QUEEN_DEATH_DEBUFF_DURATION 4 MINUTES /obj/item/organ/alien/hivenode name = "hive node" @@ -118,8 +103,9 @@ zone = BODY_ZONE_HEAD slot = "hivenode" w_class = WEIGHT_CLASS_TINY - var/recent_queen_death = 0 //Indicates if the queen died recently, aliens are heavily weakened while this is active. - alien_powers = list(/obj/effect/proc_holder/alien/whisper) + actions_types = list(/datum/action/cooldown/alien/whisper) + /// Indicates if the queen died recently, aliens are heavily weakened while this is active. + var/recent_queen_death = FALSE /obj/item/organ/alien/hivenode/Insert(mob/living/carbon/M, special = 0) ..() @@ -144,13 +130,13 @@ owner.emote("scream") owner.Paralyze(100) - owner.jitteriness += 30 - owner.confused += 30 - owner.stuttering += 30 + owner.adjust_jitter(30 SECONDS) + owner.adjust_confusion(30 SECONDS) + owner.adjust_stutter(30 SECONDS) recent_queen_death = 1 owner.throw_alert("alien_noqueen", /atom/movable/screen/alert/alien_vulnerable) - addtimer(CALLBACK(src, .proc/clear_queen_death), QUEEN_DEATH_DEBUFF_DURATION) + addtimer(CALLBACK(src, PROC_REF(clear_queen_death)), QUEEN_DEATH_DEBUFF_DURATION) /obj/item/organ/alien/hivenode/proc/clear_queen_death() @@ -169,7 +155,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/cooldown/alien/make_structure/resin) /obj/item/organ/alien/acid @@ -177,7 +163,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/cooldown/alien/acid/corrosion) /obj/item/organ/alien/neurotoxin @@ -185,7 +171,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/cooldown/alien/acid/neurotoxin) /obj/item/organ/alien/eggsac @@ -194,4 +180,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/cooldown/alien/make_structure/lay_egg) diff --git a/code/modules/mob/living/carbon/alien/screen.dm b/code/modules/mob/living/carbon/alien/screen.dm index 1e7ff076318f..3248c923d0dd 100644 --- a/code/modules/mob/living/carbon/alien/screen.dm +++ b/code/modules/mob/living/carbon/alien/screen.dm @@ -1,7 +1,7 @@ /mob/living/carbon/alien/proc/updatePlasmaDisplay() if(hud_used) //clientless aliens - hud_used.alien_plasma_display.maptext = "
[round(getPlasma())]
" + hud_used.alien_plasma_display.maptext = FORMAT_ANTAG_TEXT(getPlasma(), COLOR_MAGENTA) //hmmmm /mob/living/carbon/alien/larva/updatePlasmaDisplay() return diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 5592bd214317..b42ed727efc0 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -299,12 +299,6 @@ /mob/living/carbon/is_muzzled() return(istype(src.wear_mask, /obj/item/clothing/mask/muzzle)) -/mob/living/carbon/hallucinating() - if(hallucination) - return TRUE - else - return FALSE - /mob/living/carbon/resist_buckle() if(restrained()) changeNext_move(CLICK_CD_BREAKOUT) @@ -357,7 +351,7 @@ cuff_resist(I) -/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 600, cuff_break = 0) +/mob/living/carbon/proc/cuff_resist(obj/item/I, breakouttime = 1 MINUTES, cuff_break = 0) if(I.item_flags & BEING_REMOVED) to_chat(src, span_warning("You're already attempting to remove [I]!")) return @@ -372,7 +366,7 @@ to_chat(src, span_warning("You fail to remove [I]!")) else if(cuff_break == FAST_CUFFBREAK) - breakouttime = 50 + breakouttime = 5 SECONDS visible_message(span_warning("[src] is trying to break [I]!")) to_chat(src, span_notice("You attempt to break [I]... (This will take around 5 seconds and you need to stand still.)")) if(do_after(src, breakouttime, src, FALSE)) @@ -385,64 +379,65 @@ I.item_flags &= ~BEING_REMOVED /mob/living/carbon/proc/uncuff() - if (handcuffed) - var/obj/item/W = handcuffed + var/obj/item/cuff = handcuffed || legcuffed + + if(!cuff) //none? fuck it we ball + changeNext_move(0) + return + + if(handcuffed) //clear handcuffs first handcuffed = null - if (buckled && buckled.buckle_requires_restraints) + if(buckled?.buckle_requires_restraints) buckled.unbuckle_mob(src) update_handcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) - if (legcuffed) - var/obj/item/W = legcuffed + + if(legcuffed) //then clear legcuffs legcuffed = null update_inv_legcuffed() - if (client) - client.screen -= W - if (W) - W.forceMove(drop_location()) - W.dropped(src) - if (W) - W.layer = initial(W.layer) - W.plane = initial(W.plane) - changeNext_move(0) -/mob/living/carbon/proc/clear_cuffs(obj/item/I, cuff_break) - if(!I.loc || buckled) - return - visible_message(span_danger("[src] manages to [cuff_break ? "break" : "remove"] [I]!")) - to_chat(src, span_notice("You successfully [cuff_break ? "break" : "remove"] [I].")) + client?.screen -= cuff //remove overlay + + cuff.forceMove(drop_location()) + cuff.dropped(src) //drop it to the ground + + if(!QDELETED(cuff)) //if it didn't delete on drop, update planes + cuff.layer = initial(handcuff.layer) + cuff.plane = initial(handcuff.plane) + + changeNext_move(0) + +/mob/living/carbon/proc/clear_cuffs(obj/item/cuff, cuff_break, ignore_buckle) + if(!cuff || !cuff.loc) + return TRUE //???, we win nonetheless + + if(buckled && !ignore_buckle) + return FALSE + + visible_message(span_danger("[src] manages to [cuff_break ? "break" : "remove"] [cuff]!")) + to_chat(src, span_notice("You successfully [cuff_break ? "break" : "remove"] [cuff].")) if(cuff_break) - . = !((I == handcuffed) || (I == legcuffed)) - qdel(I) - return + qdel(cuff) + return TRUE - else - if(I == handcuffed) - handcuffed.forceMove(drop_location()) - handcuffed.dropped(src) - handcuffed = null - if(buckled && buckled.buckle_requires_restraints) - buckled.unbuckle_mob(src) - update_handcuffed() - return - if(I == legcuffed) - legcuffed.forceMove(drop_location()) - legcuffed.dropped() - legcuffed = null - update_inv_legcuffed() - return - else - dropItemToGround(I) - return + if(cuff == handcuffed) + handcuffed.forceMove(drop_location()) + handcuffed.dropped(src) + handcuffed = null + if(buckled && buckled.buckle_requires_restraints) + buckled.unbuckle_mob(src) + update_handcuffed() + return TRUE + + if(cuff == legcuffed) + legcuffed.forceMove(drop_location()) + legcuffed.dropped() + legcuffed = null + update_inv_legcuffed() + return TRUE + + dropItemToGround(cuff) + return TRUE /mob/living/carbon/get_standard_pixel_y_offset(lying = 0) if(lying) @@ -482,14 +477,10 @@ . = ..() var/obj/item/organ/alien/plasmavessel/vessel = getorgan(/obj/item/organ/alien/plasmavessel) if(vessel) - . += "Plasma Stored: [vessel.storedPlasma]/[vessel.max_plasma]" + . += "Plasma Stored: [vessel.stored_plasma]/[vessel.max_plasma]" if(locate(/obj/item/assembly/health) in src) . += "Health: [health]" -/mob/living/carbon/get_proc_holders() - . = ..() - . += 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/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index 2ce3853bdfc9..b0f76266630a 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -301,8 +301,7 @@ do_sparks(5, TRUE, src) var/power = M.powerlevel + rand(0,3) Paralyze(power*20) - if(stuttering < power) - stuttering = power + set_stutter_if_lower(power * 2 SECONDS) if (prob(stunprob) && M.powerlevel >= 8) adjustFireLoss(M.powerlevel * rand(6,10)) updatehealth() @@ -367,29 +366,31 @@ span_userdanger("You feel a powerful shock coursing through your body!"), \ span_italics("You hear a heavy electrical crack.") \ ) - jitteriness += 1000 //High numbers for violent convulsions - do_jitter_animation(jitteriness) - stuttering += 2 - if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) - Paralyze(40) - spawn(20) - jitteriness = max(jitteriness - 990, 10) //Still jittery, but vastly less - if((!tesla_shock || (tesla_shock && siemens_coeff > 0.5)) && stun) - Paralyze(60) + do_jitter_animation(300) + adjust_stutter(4 SECONDS) + adjust_jitter(20 SECONDS) + var/should_stun = !tesla_shock || (tesla_shock && siemens_coeff > 0.5) && stun + Paralyze(4 SECONDS) + addtimer(CALLBACK(src, PROC_REF(secondary_shock), should_stun), 2 SECONDS) if(stat == DEAD && can_defib()) //yogs: ZZAPP if(!illusion && (shock_damage * siemens_coeff >= 1) && prob(80)) set_heartattack(FALSE) adjustOxyLoss(-50) adjustToxLoss(-50) revive() - INVOKE_ASYNC(src, .proc/emote, "gasp") - Jitter(100) + INVOKE_ASYNC(src, PROC_REF(emote), "gasp") + adjust_jitter(10 SECONDS) adjustOrganLoss(ORGAN_SLOT_BRAIN, 100, 199) //yogs end if(override) return override else return shock_damage +///Called slightly after electrocute act to apply a secondary stun. +/mob/living/carbon/proc/secondary_shock(should_stun) + if(should_stun) + Paralyze(6 SECONDS) + /mob/living/carbon/proc/help_shake_act(mob/living/carbon/M) if(on_fire) to_chat(M, span_warning("You can't put [p_them()] out with just your bare hands!")) @@ -513,7 +514,7 @@ var/effect_amount = intensity - ear_safety if(effect_amount > 0) if(conf_pwr) - confused += conf_pwr*effect_amount + adjust_confusion(conf_pwr*effect_amount) if(istype(ears) && (deafen_pwr || damage_pwr)) var/ear_damage = damage_pwr * effect_amount diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 8d96f588ca9b..988642c481d0 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -69,11 +69,9 @@ var/image/halbody var/obj/halitem var/hal_screwyhud = SCREWYHUD_NONE - var/next_hallucination = 0 var/cpr_time = 1 //CPR cooldown. var/damageoverlaytemp = 0 - var/drunkenness = 0 //Overall drunkenness - check handle_alcohol() in life.dm for effects var/stam_regen_start_time = 0 //used to halt stamina regen temporarily var/stam_paralyzed = FALSE //knocks you down diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm index faad4db52df2..680e99d31964 100644 --- a/code/modules/mob/living/carbon/emote.dm +++ b/code/modules/mob/living/carbon/emote.dm @@ -4,7 +4,7 @@ /datum/emote/living/carbon/airguitar key = "airguitar" message = "is strumming the air and headbanging like a safari chimp." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/blink key = "blink" @@ -20,7 +20,7 @@ key_third_person = "claps" message = "claps." muzzle_ignore = TRUE - restraint_check = TRUE + hands_use_check = TRUE emote_type = EMOTE_AUDIBLE vary = TRUE @@ -64,21 +64,21 @@ key_third_person = "pointsdown" message = "points down." message_param = "points down towards %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/roll key = "roll" key_third_person = "rolls" message = "rolls." mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/scratch key = "scratch" key_third_person = "scratches" message = "scratches." mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/screech key = "screech" @@ -91,7 +91,7 @@ key_third_person = "signs" message_param = "signs the number %t." mob_type_allowed_typecache = list(/mob/living/carbon/monkey, /mob/living/carbon/alien) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/sign/select_param(mob/user, params) . = ..() @@ -103,7 +103,7 @@ key_third_person = "signals" message_param = "raises %t fingers." mob_type_allowed_typecache = list(/mob/living/carbon/human) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/tail key = "tail" @@ -122,7 +122,7 @@ message_param = "snaps their fingers at %t." emote_type = EMOTE_AUDIBLE mob_type_allowed_typecache = list(/mob/living/carbon/human) - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/snap/get_sound(mob/living/user) return pick('sound/misc/fingersnap1.ogg', 'sound/misc/fingersnap2.ogg') diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 906258d53dcd..106cdfc31b37 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -46,9 +46,6 @@ . = ..() - dizziness = 0 - jitteriness = 0 - if(ismecha(loc)) var/obj/mecha/M = loc if(M.occupant == src) diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index cfa874f900e8..df52d3f527be 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -36,7 +36,7 @@ key_third_person = "daps" message = "sadly can't find anybody to give daps to, and daps themself. Shameful." message_param = "give daps to %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/human/eyebrow key = "eyebrow" @@ -52,7 +52,7 @@ key = "handshake" message = "shakes their own hands." message_param = "shakes hands with %t." - restraint_check = TRUE + hands_use_check = TRUE emote_type = EMOTE_AUDIBLE /datum/emote/living/carbon/hiss @@ -89,7 +89,7 @@ key_third_person = "hugs" message = "hugs themself." message_param = "hugs %t." - restraint_check = TRUE + hands_use_check = TRUE emote_type = EMOTE_AUDIBLE /datum/emote/living/carbon/human/mumble @@ -147,14 +147,14 @@ key = "raise" key_third_person = "raises" message = "raises a hand." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/human/salute key = "salute" key_third_person = "salutes" message = "salutes." message_param = "salutes to %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/carbon/human/shrug key = "shrug" diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 0788c7057be9..6df9d01d33bf 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -23,7 +23,7 @@ . = list("This is [!obscure_name ? name : "Unknown"]!") - var/vampDesc = ReturnVampExamine(user) // Fulpstation Bloodsuckers edit STARTS + var/vampDesc = return_vamp_examine(user) // Fulpstation Bloodsuckers edit STARTS var/vassDesc = ReturnVassalExamine(user) if(vampDesc != "") . += vampDesc @@ -31,7 +31,6 @@ . += vassDesc // Fulpstation Bloodsucker edit ENDS var/list/obscured = check_obscured_slots() - var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)) //uniform if(w_uniform && !(SLOT_W_UNIFORM in obscured)) @@ -112,15 +111,6 @@ //Status effects . += status_effect_examines() - //Jitters - switch(jitteriness) - if(300 to INFINITY) - . += span_warning("[t_He] [t_is] convulsing violently!") - if(200 to 300) - . += span_warning("[t_He] [t_is] extremely jittery.") - if(100 to 200) - . += span_warning("[t_He] [t_is] twitching ever so slightly.") - if(islist(dna.features) && dna.features["wings"] && dna.features["wings"] != "None") var/badwings = "" if(mind?.martial_art && istype(mind.martial_art, /datum/martial_art/ultra_violence)) @@ -368,21 +358,6 @@ msg += "The pair of holes where [t_His] eyes would be seem unnaturally dark and soulless.\n" if(!appears_dead) - if(drunkenness && !skipface) //Drunkenness - switch(drunkenness) - if(11 to 21) - msg += "[t_He] [t_is] slightly flushed.\n" - if(21.01 to 41) //.01s are used in case drunkenness ends up to be a small decimal - msg += "[t_He] [t_is] flushed.\n" - if(41.01 to 51) - msg += "[t_He] [t_is] quite flushed and [t_his] breath smells of alcohol.\n" - if(51.01 to 61) - msg += "[t_He] [t_is] very flushed and [t_his] movements jerky, with breath reeking of alcohol.\n" - if(61.01 to 91) - msg += "[t_He] look[p_s()] like a drunken mess.\n" - if(91.01 to INFINITY) - msg += "[t_He] [t_is] a shitfaced, slobbering wreck.\n" - if(src != user) if(HAS_TRAIT(user, TRAIT_EMPATH)) if (a_intent != INTENT_HELP) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 0489b91b3d4c..cb374d569491 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -867,7 +867,6 @@ regenerate_organs() remove_all_embedded_objects() set_heartattack(FALSE) - drunkenness = 0 for(var/datum/mutation/human/HM in dna.mutations) if(HM.quality != POSITIVE) dna.remove_mutation(HM.name) @@ -1345,7 +1344,7 @@ /mob/living/carbon/human/proc/hulk_stamina_check() if(dna.check_mutation(ACTIVE_HULK)) if(staminaloss < 60 && prob(1)) - confused = 7 + adjust_confusion(7 SECONDS) say("HULK SMASH!!") if(staminaloss >= 90) dna.remove_mutation(ACTIVE_HULK) @@ -1357,7 +1356,7 @@ /mob/living/carbon/human/Bump(atom/movable/AM) ..() - if(dna.check_mutation(ACTIVE_HULK) && confused && (world.time - last_bumped) > 15) + if(dna.check_mutation(ACTIVE_HULK) && has_status_effect(/datum/status_effect/confusion) && (world.time - last_bumped) > 15) Bumped(AM) return AM.attack_hulk(src) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index ce61a1cb760f..ea7fe9cc8efc 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -764,7 +764,7 @@ var/status = "" var/brutedamage = LB.brute_dam var/burndamage = LB.burn_dam - if(hallucination) + if(has_status_effect(/datum/status_effect/hallucination)) if(prob(30)) brutedamage += rand(30,40) if(prob(30)) diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index f438b5749f83..1d29edff45e8 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -4,16 +4,6 @@ // Return the item currently in the slot ID /mob/living/carbon/human/get_item_by_slot(slot_id) switch(slot_id) - if(SLOT_BACK) - return back - if(SLOT_WEAR_MASK) - return wear_mask - if(SLOT_NECK) - return wear_neck - if(SLOT_HANDCUFFED) - return handcuffed - if(SLOT_LEGCUFFED) - return legcuffed if(SLOT_BELT) return belt if(SLOT_WEAR_ID) @@ -24,8 +14,6 @@ return glasses if(SLOT_GLOVES) return gloves - if(SLOT_HEAD) - return head if(SLOT_SHOES) return shoes if(SLOT_WEAR_SUIT) @@ -38,7 +26,46 @@ return r_store if(SLOT_S_STORE) return s_store - return null + return ..() + +/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/life.dm b/code/modules/mob/living/carbon/human/life.dm index 5316630be173..b8f928dec624 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -161,10 +161,10 @@ thermal_protection = round(thermal_protection) return thermal_protection -/mob/living/carbon/human/IgniteMob() +/mob/living/carbon/human/ignite_mob() //If have no DNA or can be Ignited, call parent handling to light user //If firestacks are high enough - if(!dna || dna.species.CanIgniteMob(src)) + if(!dna || dna.species.Canignite_mob(src)) if(get_thermal_protection() > FIRE_SUIT_MAX_TEMP_PROTECT*0.95) // If they're resistant to fire (slightly undercut to make sure get_thermal_protection doesn't fuck over this achievement due to floating-point errors SSachievements.unlock_achievement(/datum/achievement/engineering/toasty,src.client) // Fear the reaper man! return ..() diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm index 69f0a59b8339..29e8b3326d14 100644 --- a/code/modules/mob/living/carbon/human/say.dm +++ b/code/modules/mob/living/carbon/human/say.dm @@ -5,10 +5,7 @@ else verb_say = dna.species.say_mod - if(slurring) - return "slurs" - else - . = ..() + . = ..() /mob/living/carbon/human/GetVoice() if(istype(wear_mask, /obj/item/clothing/mask/chameleon)) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 28afb96d41f9..e091158d01d7 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1332,7 +1332,7 @@ GLOBAL_LIST_EMPTY(features_by_species) else if(H.satiety < 0) H.satiety++ if(prob(round(-H.satiety/40))) - H.Jitter(5) + H.adjust_jitter(5 SECONDS) hunger_rate = 3 * HUNGER_FACTOR hunger_rate *= H.physiology.hunger_mod H.adjust_nutrition(-hunger_rate) @@ -1800,7 +1800,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(H.stat == CONSCIOUS) H.visible_message(span_danger("[H] has been knocked senseless!"), \ span_userdanger("[H] has been knocked senseless!")) - H.confused = max(H.confused, 20) + H.set_confusion_if_lower(20 SECONDS) H.adjust_blurriness(10) if(prob(10)) H.gain_trauma(/datum/brain_trauma/mild/concussion) @@ -2048,7 +2048,7 @@ GLOBAL_LIST_EMPTY(features_by_species) ////////// /datum/species/proc/handle_fire(mob/living/carbon/human/H, no_protection = FALSE) - if(!CanIgniteMob(H)) + if(!Canignite_mob(H)) return TRUE if(H.on_fire) //the fire tries to damage the exposed clothes and items @@ -2109,7 +2109,7 @@ GLOBAL_LIST_EMPTY(features_by_species) H.adjust_bodytemperature(BODYTEMP_HEATING_MAX + (H.fire_stacks * 2)) SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire) -/datum/species/proc/CanIgniteMob(mob/living/carbon/human/H) +/datum/species/proc/Canignite_mob(mob/living/carbon/human/H) if(HAS_TRAIT(H, TRAIT_NOFIRE)) return FALSE return TRUE @@ -2277,7 +2277,7 @@ GLOBAL_LIST_EMPTY(features_by_species) /datum/action/innate/flight name = "Toggle Flight" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_STUN + check_flags = AB_CHECK_CONSCIOUS| AB_CHECK_IMMOBILE icon_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "flight" diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm index 17b367912cde..c8862226ec77 100644 --- a/code/modules/mob/living/carbon/human/species_types/abductors.dm +++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm @@ -11,9 +11,9 @@ /datum/species/abductor/on_species_gain(mob/living/carbon/C, datum/species/old_species) . = ..() var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] - abductor_hud.add_hud_to(C) + abductor_hud.show_to(C) /datum/species/abductor/on_species_loss(mob/living/carbon/C) . = ..() var/datum/atom_hud/abductor_hud = GLOB.huds[DATA_HUD_ABDUCTOR] - abductor_hud.remove_hud_from(C) + abductor_hud.hide_from(C) 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 e046bd44b929..b0e08aef1836 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -152,7 +152,7 @@ to_chat(owner, span_notice("You ignite yourself!")) else to_chat(owner, span_warning("You try to ignite yourself, but fail!")) - H.IgniteMob() //firestacks are already there passively + H.ignite_mob() //firestacks are already there passively //Harder to hurt /datum/species/golem/diamond @@ -323,7 +323,7 @@ H.adjust_nutrition(light_amount * 10) if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) - if(light_amount > 0.2) //if there's enough light, heal + if(light_amount > LIGHTING_TILE_IS_DARK) //if there's enough light, heal H.heal_overall_damage(1,1,0, BODYPART_ORGANIC) H.adjustToxLoss(-1) H.adjustOxyLoss(-1) @@ -549,9 +549,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 + UpdateButtons() //action icon looks unavailable sleep(cooldown + 0.5 SECONDS) - UpdateButtonIcon() //action icon looks available again + UpdateButtons() //action icon looks available again //honk @@ -646,9 +646,12 @@ prefix = "Runic" special_names = null - 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/cooldown/spell/jaunt/ethereal_jaunt/shift/golem/jaunt + /// A ref to our gaze spell that we get on species gain. + var/datum/action/cooldown/spell/pointed/abyssal_gaze/abyssal_gaze + /// A ref to our dominate spell that we get on species gain. + var/datum/action/cooldown/spell/pointed/dominate/dominate /datum/species/golem/runic/random_name(gender,unique,lastname) var/edgy_first_name = pick("Razor","Blood","Dark","Evil","Cold","Pale","Black","Silent","Chaos","Deadly","Coldsteel") @@ -656,28 +659,33 @@ 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) . = ..() - C.faction |= "cult" - phase_shift = new - phase_shift.charge_counter = 0 - C.AddSpell(phase_shift) - abyssal_gaze = new - abyssal_gaze.charge_counter = 0 - C.AddSpell(abyssal_gaze) - dominate = new - dominate.charge_counter = 0 - C.AddSpell(dominate) - -/datum/species/golem/runic/on_species_loss(mob/living/carbon/C) + grant_to.faction |= "cult" + // 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.StartCooldown() + jaunt.Grant(grant_to) + + abyssal_gaze = new(grant_to) + abyssal_gaze.StartCooldown() + abyssal_gaze.Grant(grant_to) + + dominate = new(grant_to) + dominate.StartCooldown() + dominate.Grant(grant_to) + +/datum/species/golem/runic/on_species_loss(mob/living/carbon/remove_from) . = ..() - C.faction -= "cult" - if(phase_shift) - C.RemoveSpell(phase_shift) - if(abyssal_gaze) - C.RemoveSpell(abyssal_gaze) - if(dominate) - C.RemoveSpell(dominate) + grant_to.faction -= "cult" + // 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)) @@ -927,21 +935,21 @@ H.show_message(span_narsiesmall("You cringe with pain as your body rings around you!"), MSG_AUDIBLE) H.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) H.soundbang_act(2, 0, 10, 1) - H.jitteriness += 7 + M.adjust_jitter(7 SECONDS) var/distance = max(0,get_dist(get_turf(H),get_turf(M))) switch(distance) if(0 to 1) M.show_message(span_narsiesmall("GONG!"), MSG_AUDIBLE) M.playsound_local(H, 'sound/effects/gong.ogg', 100, TRUE) M.soundbang_act(1, 0, 10, 3) - M.confused += 10 - M.jitteriness += 4 + M.adjust_confusion(10 SECONDS) + M.adjust_jitter(4 SECONDS) SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) if(2 to 3) M.show_message(span_cult("GONG!"), MSG_AUDIBLE) M.playsound_local(H, 'sound/effects/gong.ogg', 75, TRUE) M.soundbang_act(1, 0, 5, 2) - M.jitteriness += 3 + M.adjust_jitter(3 SECONDS) SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "gonged", /datum/mood_event/loud_gong) else M.show_message(span_warning("GONG!"), MSG_AUDIBLE) @@ -960,8 +968,10 @@ species_traits = list(NOBLOOD,NO_UNDERWEAR,NOEYESPRITES) //no mutcolors, no eye sprites inherent_traits = list(TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_NOGUNS,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NODISMEMBER,TRAIT_NOHUNGER) - 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/cooldown/spell/conjure_item/snowball/snowball + /// A ref to our cryobeam spell we get on species gain. + var/datum/action/cooldown/spell/pointed/projectile/cryo/cryo /datum/species/golem/snow/spec_death(gibbed, mob/living/carbon/human/H) H.visible_message(span_danger("[H] turns into a pile of snow!")) @@ -972,23 +982,23 @@ new /obj/item/reagent_containers/food/snacks/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 - C.AddSpell(ball) - cryo = new - cryo.charge_counter = 0 - C.AddSpell(cryo) - -/datum/species/golem/snow/on_species_loss(mob/living/carbon/C) + snowball = new(grant_to) + snowball.StartCooldown() + snowball.Grant(grant_to) + + cryo = new(grant_to) + cryo.StartCooldown() + cryo.Grant(grant_to) + +/datum/species/golem/snow/on_species_loss(mob/living/carbon/remove_from) . = ..() C.weather_immunities -= "snow" - if(ball) - C.RemoveSpell(ball) - if(cryo) - C.RemoveSpell(cryo) + QDEL_NULL(snowball) + QDEL_NULL(cryo) + return ..() /obj/effect/proc_holder/spell/targeted/conjure_item/snowball name = "Snowball" 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 dc071276b22d..f9cc955a8dad 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -54,7 +54,7 @@ if(H.blood_volume < BLOOD_VOLUME_BAD(H)) Cannibalize_Body(H) if(regenerate_limbs) - regenerate_limbs.UpdateButtonIcon() + regenerate_limbs.UpdateButtons() /datum/species/jelly/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() @@ -472,9 +472,9 @@ /datum/species/jelly/luminescent/proc/update_slime_actions() integrate_extract.update_name() - integrate_extract.UpdateButtonIcon() - extract_minor.UpdateButtonIcon() - extract_major.UpdateButtonIcon() + integrate_extract.UpdateButtons() + extract_minor.UpdateButtons() + extract_major.UpdateButtons() /datum/species/jelly/luminescent/proc/update_glow(mob/living/carbon/C, intensity) if(intensity) @@ -516,7 +516,7 @@ name = "Eject Extract" desc = "Eject your current slime extract." -/datum/action/innate/integrate_extract/UpdateButtonIcon(status_only, force) +/datum/action/innate/integrate_extract/UpdateButtons(status_only, force) if(!species || !species.current_extract) button_icon_state = "slimeconsume" else diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 4dc35c64d61a..050b5cfd4e05 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -49,7 +49,7 @@ H.adjust_fire_stacks(0.5) if(!H.on_fire && H.fire_stacks > 0) H.visible_message(span_danger("[H]'s body reacts with the atmosphere and bursts into flames!"),span_userdanger("Your body reacts with the atmosphere and bursts into flame!")) - H.IgniteMob() + H.ignite_mob() internal_fire = TRUE else if(H.fire_stacks) diff --git a/code/modules/mob/living/carbon/human/species_types/podpeople.dm b/code/modules/mob/living/carbon/human/species_types/podpeople.dm index 4b1810dc2b71..413d40dc7e93 100644 --- a/code/modules/mob/living/carbon/human/species_types/podpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/podpeople.dm @@ -53,7 +53,7 @@ DISREGUARD THIS FILE IF YOU'RE INTENDING TO CHANGE ASPECTS OF PLAYER CONTROLLED H.adjust_nutrition(light_amount * 10) if(H.nutrition > NUTRITION_LEVEL_ALMOST_FULL) H.set_nutrition(NUTRITION_LEVEL_ALMOST_FULL) - if(light_amount > 0.2) //if there's enough light, heal + if(light_amount > LIGHTING_TILE_IS_DARK) //if there's enough light, heal H.heal_overall_damage(1,1, 0, BODYPART_ORGANIC) H.adjustOxyLoss(-1) if(H.radiation < 500) 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 8d31a813278e..f5b817e6f993 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -121,23 +121,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/cooldown/spell/jaunt/shadow_walk/our_jaunt -/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/M, special = 0) - ..() - if(M.dna.species.id != "nightmare") - M.set_species(/datum/species/shadow/nightmare) - visible_message(span_warning("[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 - - -/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/M, special = 0) - if(shadowwalk) - M.RemoveSpell(shadowwalk) +/obj/item/organ/brain/nightmare/Insert(mob/living/carbon/host, special = FALSE) ..() + if(host.dna.species.id != "nightmare") + host.set_species(/datum/species/shadow/nightmare) + visible_message(span_warning("[host] thrashes as [src] takes root in [M.p_their()] body!")) + + our_jaunt = new(host) + our_jaunt.Grant(host) +/obj/item/organ/brain/nightmare/Remove(mob/living/carbon/host, special = FALSE) + QDEL_NULL(our_jaunt) + return ..() /obj/item/organ/heart/nightmare name = "heart of darkness" @@ -186,7 +183,7 @@ return //always beating visually /obj/item/organ/heart/nightmare/process() - if(QDELETED(owner) || owner.stat != DEAD) + if(QDELETED(owner) || owner.stat != DEAD || !owner) respawn_progress = 0 return var/turf/T = get_turf(owner) 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 5a5f6c407ba5..925b5cfb6dd2 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -56,7 +56,7 @@ to_chat(C, span_danger("You don't belong here!")) C.adjustFireLoss(20) C.adjust_fire_stacks(6) - C.IgniteMob() + C.ignite_mob() /datum/species/vampire/check_species_weakness(obj/item/weapon, mob/living/attacker) if(istype(weapon, /obj/item/nullrod/whip)) diff --git a/code/modules/mob/living/carbon/human/status_procs.dm b/code/modules/mob/living/carbon/human/status_procs.dm index bba496986004..d00c37605965 100644 --- a/code/modules/mob/living/carbon/human/status_procs.dm +++ b/code/modules/mob/living/carbon/human/status_procs.dm @@ -39,14 +39,3 @@ if(.) update_hair() -/mob/living/carbon/human/set_drugginess(amount) - ..() - if(!amount) - remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_HIGH) - -/mob/living/carbon/human/adjust_drugginess(amount) - ..() - if(druggy) - grant_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_HIGH) - else - remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_HIGH) diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 1d00fbd3ea27..777e41e457e1 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -12,7 +12,32 @@ return handcuffed if(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 = 1) for(var/slot in slots) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 1830008c96e1..f94cb66644df 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -48,9 +48,6 @@ stop_sound_channel(CHANNEL_HEARTBEAT) LoadComponent(/datum/component/rot/corpse) - //Updates the number of stored chemicals for powers - handle_changeling() - if(stat != DEAD) return 1 @@ -243,19 +240,19 @@ if(SA_partialpressure > NITROGEN_NARCOSIS_PRESSURE_HIGH) // Hallucinations if(prob(15)) to_chat(src, span_userdanger("You can't think straight!")) - confused = min(SA_partialpressure/10, confused + 12) - hallucination += 5 + adjust_confusion_up_to(SA_partialpressure/10, 12 SECONDS) + adjust_hallucinations(5 SECONDS) //yogs end //BZ (Facepunch port of their Agent B) if(breath.get_moles(/datum/gas/bz)) var/bz_partialpressure = (breath.get_moles(/datum/gas/bz)/breath.total_moles())*breath_pressure /* Yogs comment-out: Smoothed BZ hallucination levels if(bz_partialpressure > 1) - hallucination += 10 + adjust_hallucinations(10 SECONDS) else if(bz_partialpressure > 0.01) - hallucination += 5 + adjust_hallucinations(5 SECONDS) */ - hallucination += round(BZ_MAX_HALLUCINATION * (1 - NUM_E ** (-BZ_LAMBDA * bz_partialpressure))) // Yogs -- Better BZ hallucination values. Keep in mind that hallucination has to be an integer value, due to how it's handled in handle_hallucination() + adjust_hallucinations(round(BZ_MAX_HALLUCINATION * (1 - NUM_E ** (-BZ_LAMBDA * bz_partialpressure)))) // Yogs -- Better BZ hallucination values. Keep in mind that hallucination has to be an integer value, due to how it's handled in handle_hallucination() //TRITIUM if(breath.get_moles(/datum/gas/tritium)) @@ -367,18 +364,6 @@ if(W.processes) // meh W.handle_process() -//todo generalize this and move hud out -/mob/living/carbon/proc/handle_changeling() - if(mind && hud_used && hud_used.lingchemdisplay) - var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - changeling.regenerate() - hud_used.lingchemdisplay.invisibility = 0 - hud_used.lingchemdisplay.maptext = "
[round(changeling.chem_charges)]
" - else - hud_used.lingchemdisplay.invisibility = INVISIBILITY_ABSTRACT - - /mob/living/carbon/handle_mutations_and_radiation() if(dna && dna.temporary_mutations.len) for(var/mut in dna.temporary_mutations) @@ -448,154 +433,12 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put /mob/living/carbon/handle_status_effects() ..() - var/restingpwr = 1 + 4 * resting - - //Dizziness - if(dizziness) - var/client/C = client - var/pixel_x_diff = 0 - var/pixel_y_diff = 0 - var/temp - var/saved_dizz = dizziness - if(C) - var/oldsrc = src - var/amplitude = dizziness*(sin(dizziness * world.time) + 1) // This shit is annoying at high strength - src = null - spawn(0) - if(C) - temp = amplitude * sin(saved_dizz * world.time) - pixel_x_diff += temp - C.pixel_x += temp - temp = amplitude * cos(saved_dizz * world.time) - pixel_y_diff += temp - C.pixel_y += temp - sleep(0.3 SECONDS) - if(C) - temp = amplitude * sin(saved_dizz * world.time) - pixel_x_diff += temp - C.pixel_x += temp - temp = amplitude * cos(saved_dizz * world.time) - pixel_y_diff += temp - C.pixel_y += temp - sleep(0.3 SECONDS) - if(C) - C.pixel_x -= pixel_x_diff - C.pixel_y -= pixel_y_diff - src = oldsrc - dizziness = max(dizziness - restingpwr, 0) - - if(drowsyness) - drowsyness = max(drowsyness - restingpwr, 0) - blur_eyes(2) - if(prob(5)) - AdjustSleeping(20) - Unconscious(100) - - //Jitteriness - if(jitteriness) - do_jitter_animation(jitteriness) - jitteriness = max(jitteriness - restingpwr, 0) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "jittery", /datum/mood_event/jittery) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "jittery") - - if(stuttering) - stuttering = max(stuttering-1, 0) - - if(slurring) - slurring = max(slurring-1,0) - if(cultslurring) cultslurring = max(cultslurring-1, 0) if(silent) silent = max(silent-1, 0) - if(druggy) - adjust_drugginess(-1) - - if(hallucination) - handle_hallucinations() - - REMOVE_TRAIT(src, TRAIT_SURGERY_PREPARED, "drunk") - if(drunkenness) - drunkenness = max(drunkenness - (drunkenness * 0.04) - 0.01, 0) - if(drunkenness >= 6) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "drunk", /datum/mood_event/drunk) - if(prob(25)) - slurring += 2 - jitteriness = max(jitteriness - 3, 0) - throw_alert("drunk", /atom/movable/screen/alert/drunk) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) - adjustBruteLoss(-0.12, FALSE) - adjustFireLoss(-0.06, FALSE) - else - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk") - clear_alert("drunk") - - if(drunkenness >= 11 && slurring < 5) - slurring += 1.2 - - if(mind && (mind.assigned_role == "Scientist" || mind.assigned_role == "Research Director")) - if(SSresearch.science_tech) - if(drunkenness >= 12.9 && drunkenness <= 13.8) - drunkenness = round(drunkenness, 0.01) - var/ballmer_percent = 0 - if(drunkenness == 13.35) // why run math if I dont have to - ballmer_percent = 1 - else - ballmer_percent = (-abs(drunkenness - 13.35) / 0.9) + 1 - if(prob(5)) - say(pick(GLOB.ballmer_good_msg), forced = "ballmer") - SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS * ballmer_percent)) - if(drunkenness > 26) // by this point you're into windows ME territory - if(prob(5)) - SSresearch.science_tech.remove_point_list(list(TECHWEB_POINT_TYPE_GENERIC = BALLMER_POINTS)) - say(pick(GLOB.ballmer_windows_me_msg), forced = "ballmer") - - if(drunkenness >= 41) - if(prob(25)) - confused += 2 - Dizzy(10) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) // effects stack with lower tiers - adjustBruteLoss(-0.3, FALSE) - adjustFireLoss(-0.15, FALSE) - ADD_TRAIT(src, TRAIT_SURGERY_PREPARED, "drunk") - - if(drunkenness >= 51) - if(prob(3)) - confused += 15 - vomit() // vomiting clears toxloss, consider this a blessing - Dizzy(25) - - if(drunkenness >= 61) - if(prob(50)) - blur_eyes(5) - if(HAS_TRAIT(src, TRAIT_DRUNK_HEALING)) - adjustBruteLoss(-0.4, FALSE) - adjustFireLoss(-0.2, FALSE) - - if(drunkenness >= 71) - blur_eyes(5) - - if(drunkenness >= 81) - adjustToxLoss(1) - if(prob(5) && !stat) - to_chat(src, span_warning("Maybe you should lie down for a bit...")) - - if(drunkenness >= 91) - adjustToxLoss(1) - adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.4) - if(prob(20) && !stat) - if(SSshuttle.emergency.mode == SHUTTLE_DOCKED && is_station_level(z)) //QoL mainly - to_chat(src, span_warning("You're so tired... but you can't miss that shuttle...")) - else - to_chat(src, span_warning("Just a quick nap...")) - Sleeping(900) - - if(drunkenness >= 101) - adjustToxLoss(2) //Let's be honest you shouldn't be alive by now - //used in human and monkey handle_environment() /mob/living/carbon/proc/natural_bodytemperature_stabilization() var/body_temperature_difference = BODYTEMP_NORMAL - bodytemperature diff --git a/code/modules/mob/living/carbon/status_procs.dm b/code/modules/mob/living/carbon/status_procs.dm index 44cd2ddc6619..528614ecc87c 100644 --- a/code/modules/mob/living/carbon/status_procs.dm +++ b/code/modules/mob/living/carbon/status_procs.dm @@ -17,26 +17,6 @@ stam_paralyzed = TRUE update_mobility() -/mob/living/carbon/adjust_drugginess(amount) - druggy = max(druggy+amount, 0) - if(druggy) - overlay_fullscreen("high", /atom/movable/screen/fullscreen/high) - throw_alert("high", /atom/movable/screen/alert/high) - SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "high", /datum/mood_event/high) - else - clear_fullscreen("high") - clear_alert("high") - SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "high") - -/mob/living/carbon/set_drugginess(amount) - druggy = max(amount, 0) - if(druggy) - overlay_fullscreen("high", /atom/movable/screen/fullscreen/high) - throw_alert("high", /atom/movable/screen/alert/high) - else - clear_fullscreen("high") - clear_alert("high") - /mob/living/carbon/adjust_disgust(amount) disgust = clamp(disgust+amount, 0, DISGUST_LEVEL_MAXEDOUT) diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm index d5a2b0de3012..abc3a78e3be2 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/damage_procs.dm @@ -98,18 +98,8 @@ if(EFFECT_IRRADIATE) if(!HAS_TRAIT(src, TRAIT_RADIMMUNE)&& !(status_flags & GODMODE)) radiation += max(effect * hit_percent, 0) - if(EFFECT_SLUR) - slurring = max(slurring,(effect * hit_percent)) - if(EFFECT_STUTTER) - if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) // stun is usually associated with stutter - stuttering = max(stuttering,(effect * hit_percent)) if(EFFECT_EYE_BLUR) blur_eyes(effect * hit_percent) - if(EFFECT_DROWSY) - drowsyness = max(drowsyness,(effect * hit_percent)) - if(EFFECT_JITTER) - if((status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) - jitteriness = max(jitteriness,(effect * hit_percent)) if(EFFECT_PARALYZE) Paralyze(effect * hit_percent) if(EFFECT_IMMOBILIZE) @@ -117,33 +107,45 @@ return TRUE -/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, blocked = 0, stamina = 0, jitter = 0, paralyze = 0, immobilize = 0) +/mob/living/proc/apply_effects(stun = 0, knockdown = 0, unconscious = 0, irradiate = 0, slur = 0, stutter = 0, eyeblur = 0, drowsy = 0, \ + blocked = 0, stamina = 0, jitter = 0, paralyze = 0, immobilize = 0) if(blocked >= 100) return FALSE if(stun) apply_effect(stun, EFFECT_STUN, blocked) + if(knockdown) apply_effect(knockdown, EFFECT_KNOCKDOWN, blocked) + if(unconscious) apply_effect(unconscious, EFFECT_UNCONSCIOUS, blocked) + if(paralyze) apply_effect(paralyze, EFFECT_PARALYZE, blocked) + if(immobilize) apply_effect(immobilize, EFFECT_IMMOBILIZE, blocked) + if(irradiate) apply_effect(irradiate, EFFECT_IRRADIATE, src.getarmor(null, RAD)) - if(slur) - apply_effect(slur, EFFECT_SLUR, blocked) - if(stutter) - apply_effect(stutter, EFFECT_STUTTER, blocked) + if(eyeblur) apply_effect(eyeblur, EFFECT_EYE_BLUR, blocked) - if(drowsy) - apply_effect(drowsy, EFFECT_DROWSY, blocked) + if(stamina) apply_damage(stamina, STAMINA, null, blocked) - if(jitter) - apply_effect(jitter, EFFECT_JITTER, blocked) + if(jitter && (status_flags & CANSTUN) && !HAS_TRAIT(src, TRAIT_STUNIMMUNE)) + adjust_jitter(jitter) + + if(slur) + adjust_slurring(slur) + + if(stutter) + adjust_stutter(stutter) + + if(drowsy) + adjust_drowsiness(drowsy) + return TRUE diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm index 24eb885605f7..c40d2f82353a 100644 --- a/code/modules/mob/living/emote.dm +++ b/code/modules/mob/living/emote.dm @@ -14,7 +14,7 @@ key_third_person = "bows" message = "bows." message_param = "bows to %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/burp key = "burp" @@ -32,7 +32,7 @@ key = "cross" key_third_person = "crosses" message = "crosses their arms." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/chuckle key = "chuckle" @@ -66,7 +66,7 @@ key = "dance" key_third_person = "dances" message = "dances around happily." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/deathgasp key = "deathgasp" @@ -126,7 +126,7 @@ key = "flap" key_third_person = "flaps" message = "flaps their wings." - restraint_check = TRUE + hands_use_check = TRUE var/wing_time = 20 /datum/emote/living/flap/run_emote(mob/user, params, type_override, intentional) @@ -146,7 +146,7 @@ key = "aflap" key_third_person = "aflaps" message = "flaps their wings ANGRILY!" - restraint_check = TRUE + hands_use_check = TRUE wing_time = 10 /datum/emote/living/frown @@ -200,7 +200,7 @@ key = "jump" key_third_person = "jumps" message = "jumps!" - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/kiss key = "kiss" @@ -250,7 +250,7 @@ key_third_person = "points" message = "points." message_param = "points at %t." - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/point/run_emote(mob/user, params, type_override, intentional) message_param = initial(message_param) // reset @@ -507,7 +507,7 @@ /datum/emote/living/circle key = "circle" key_third_person = "circles" - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/circle/run_emote(mob/user, params, type_override, intentional) . = ..() @@ -521,7 +521,7 @@ /datum/emote/living/slap key = "slap" key_third_person = "slaps" - restraint_check = TRUE + hands_use_check = TRUE /datum/emote/living/slap/run_emote(mob/user, params, type_override, intentional) . = ..() diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm index de92ff54f0a7..bff696dae333 100644 --- a/code/modules/mob/living/life.dm +++ b/code/modules/mob/living/life.dm @@ -125,8 +125,6 @@ //this updates all special effects: knockdown, druggy, stuttering, etc.. /mob/living/proc/handle_status_effects() - if(confused) - confused = max(0, confused - 1) /mob/living/proc/handle_traits() //Eyes diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 96459acce7f4..2e4a268e954e 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -4,9 +4,9 @@ name = "[name] ([rand(1, 1000)])" real_name = name var/datum/atom_hud/data/human/medical/advanced/medhud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - medhud.add_to_hud(src) + medhud.add_atom_to_hud(src) for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) faction += "[REF(src)]" GLOB.mob_living_list += src initialize_footstep() @@ -31,9 +31,6 @@ qdel(effect) else effect.be_replaced() - - if(ranged_ability) - ranged_ability.remove_ranged_ability(src) if(buckled) buckled.unbuckle_mob(src,force=1) @@ -495,6 +492,11 @@ /mob/living/is_drawable(mob/user, allowmobs = TRUE) return (allowmobs && reagents && can_inject(user)) +///Sets the current mob's health value. Do not call directly if you don't know what you are doing, use the damage procs, instead. +/mob/living/proc/set_health(new_value) + . = health + health = new_value + /mob/living/proc/updatehealth() if(status_flags & GODMODE) return @@ -562,10 +564,9 @@ reload_fullscreen() revive_guardian() . = 1 - if(mind) - for(var/S in mind.spell_list) - var/obj/effect/proc_holder/spell/spell = S - spell.updateButtonIcon() + if(IS_BLOODSUCKER(src)) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = src.mind.has_antag_datum(/datum/antagonist/bloodsucker) + bloodsuckerdatum.heal_vampire_organs() /mob/living/proc/remove_CC(should_update_mobility = TRUE) SetStun(0, FALSE) @@ -591,22 +592,14 @@ bodytemperature = BODYTEMP_NORMAL set_blindness(0) set_blurriness(0) - set_dizziness(0) cure_nearsighted() cure_blind() cure_husk() - hallucination = 0 heal_overall_damage(INFINITY, INFINITY, INFINITY, null, TRUE) //heal brute and burn dmg on both organic and robotic limbs, and update health right away. ExtinguishMob() losebreath = 0 fire_stacks = 0 - confused = 0 - dizziness = 0 - drowsyness = 0 - stuttering = 0 - slurring = 0 - jitteriness = 0 if(HAS_TRAIT_FROM(src, TRAIT_BADDNA, CHANGELING_DRAIN)) REMOVE_TRAIT(src, TRAIT_BADDNA, CHANGELING_DRAIN) var/datum/component/mood/mood = GetComponent(/datum/component/mood) @@ -616,6 +609,7 @@ stop_sound_channel(CHANNEL_HEARTBEAT) if(admin_revive) cure_fakedeath() + SEND_SIGNAL(src, COMSIG_LIVING_POST_FULLY_HEAL) //proc called by revive(), to check if we can actually ressuscitate the mob (we don't want to revive him and have him instantly die again) /mob/living/proc/can_be_revived() @@ -913,16 +907,6 @@ else if(!src.mob_negates_gravity()) step_towards(src,S) -/mob/living/proc/do_jitter_animation(jitteriness) - var/amplitude = min(4, (jitteriness/100) + 1) - var/pixel_x_diff = rand(-amplitude, amplitude) - var/pixel_y_diff = rand(-amplitude/3, amplitude/3) - var/final_pixel_x = get_standard_pixel_x_offset(lying) - var/final_pixel_y = get_standard_pixel_y_offset(lying) - animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff , time = 0.2 SECONDS, loop = 6) - animate(pixel_x = final_pixel_x , pixel_y = final_pixel_y , time = 0.2 SECONDS) - setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure to restart it in next life(). - /mob/living/proc/get_temperature(datum/gas_mixture/environment) var/loc_temp = environment ? environment.return_temperature() : T0C if(isobj(loc)) @@ -1139,7 +1123,7 @@ return BODYTEMP_NORMAL + get_body_temp_normal_change() //Mobs on Fire -/mob/living/proc/IgniteMob() +/mob/living/proc/ignite_mob() if(fire_stacks > 0 && !on_fire) on_fire = 1 src.visible_message(span_warning("[src] catches fire!"), \ @@ -1184,13 +1168,13 @@ else // If they were not fire_stacks /= 2 L.adjust_fire_stacks(fire_stacks) - if(L.IgniteMob()) // Ignite them + if(L.ignite_mob()) // Ignite them log_game("[key_name(src)] bumped into [key_name(L)] and set them on fire") else if(L.on_fire) // If they were on fire and we were not L.fire_stacks /= 2 adjust_fire_stacks(L.fire_stacks) - IgniteMob() // Ignite us + ignite_mob() // Ignite us //Mobs on Fire end @@ -1282,24 +1266,6 @@ if(!(mobility_flags & MOBILITY_USE)) drop_all_held_items() -/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() - var/list/L = list() - for(var/obj/effect/proc_holder/A in abilities) - L[++L.len] = list("[A.panel]",A.get_panel_text(),A.name,"[REF(A)]") - return L - /mob/living/lingcheck() if(mind) var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling) @@ -1395,11 +1361,6 @@ clear_fullscreen("remote_view", 0) update_pipe_vision() -/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 ("maxHealth") diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 69680ca1a816..586e27847088 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -148,7 +148,7 @@ /mob/living/fire_act() last_damage = "fire" adjust_fire_stacks(3) - IgniteMob() + ignite_mob() /mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = FALSE) if(user == src || anchored || !isturf(user.loc)) @@ -168,63 +168,65 @@ //proc to upgrade a simple pull into a more aggressive grab. /mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) - if(user.grab_state < GRAB_KILL) - user.changeNext_move(CLICK_CD_GRABBING) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - playsound(src.loc, sound_to_play, 50, 1, -1) - - if(user.grab_state) //only the first upgrade is instantaneous - var/old_grab_state = user.grab_state - var/grab_upgrade_time = instant ? 0 : 30 - visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ - span_userdanger("[user] starts to tighten [user.p_their()] grip on you!")) - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - log_combat(user, src, "attempted to neck grab", addition="neck grab") - if(GRAB_NECK) - log_combat(user, src, "attempted to strangle", addition="kill grab") - if(!do_mob(user, src, grab_upgrade_time)) - return FALSE - if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) - return FALSE - if(user.a_intent != INTENT_GRAB) - to_chat(user, "You must be on grab intent to upgrade your grab further!") - return FALSE - user.grab_state++ + if(user.grab_state >= user.max_grab) + return + user.changeNext_move(CLICK_CD_GRABBING) + var/sound_to_play = 'sound/weapons/thudswoosh.ogg' + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.dna.species.grab_sound) + sound_to_play = H.dna.species.grab_sound + playsound(src.loc, sound_to_play, 50, TRUE, -1) + + if(user.grab_state) //only the first upgrade is instantaneous + var/old_grab_state = user.grab_state + var/grab_upgrade_time = instant ? 0 : 30 + visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ + span_userdanger("[user] starts to tighten [user.p_their()] grip on you!")) switch(user.grab_state) if(GRAB_AGGRESSIVE) - var/add_log = "" - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - visible_message(span_danger("[user] has firmly gripped [src]!"), - span_danger("[user] has firmly gripped you!")) - add_log = " (pacifist)" - else - visible_message(span_danger("[user] has grabbed [src] aggressively!"), \ - span_userdanger("[user] has grabbed you aggressively!")) - drop_all_held_items() - stop_pulling() - log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") + log_combat(user, src, "attempted to neck grab", addition="neck grab") if(GRAB_NECK) - log_combat(user, src, "grabbed", addition="neck grab") - visible_message(span_danger("[user] has grabbed [src] by the neck!"),\ - span_userdanger("[user] has grabbed you by the neck!")) - update_mobility() //we fall down - if(!buckled && !density) - Move(user.loc) - if(GRAB_KILL) - last_damage = "grip marks on the neck" - log_combat(user, src, "strangled", addition="kill grab") - visible_message(span_danger("[user] is strangling [src]!"), \ - span_userdanger("[user] is strangling you!")) - update_mobility() //we fall down - if(!buckled && !density) - Move(user.loc) - user.set_pull_offsets(src, grab_state) - return TRUE + log_combat(user, src, "attempted to strangle", addition="kill grab") + if(!do_mob(user, src, grab_upgrade_time)) + return FALSE + if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) + return FALSE + if(user.a_intent != INTENT_GRAB) + to_chat(user, span_notice("You must be on grab intent to upgrade your grab further!")) + return FALSE + user.setGrabState(user.grab_state + 1) + switch(user.grab_state) + if(GRAB_AGGRESSIVE) + var/add_log = "" + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + visible_message(span_danger("[user] has firmly gripped [src]!"), + span_danger("[user] has firmly gripped you!")) + add_log = " (pacifist)" + else + visible_message(span_danger("[user] has grabbed [src] aggressively!"), \ + span_userdanger("[user] has grabbed you aggressively!")) + drop_all_held_items() + stop_pulling() + log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") + if(GRAB_NECK) + log_combat(user, src, "grabbed", addition="neck grab") + visible_message(span_danger("[user] grabs [src] by the neck!"),\ + span_userdanger("[user] grabs you by the neck!"), span_hear("You hear aggressive shuffling!"), null, user) + to_chat(user, span_danger("You grab [src] by the neck!")) + update_mobility() //we fall down + if(!buckled && !density) + Move(user.loc) + if(GRAB_KILL) + last_damage = "grip marks on the neck" + log_combat(user, src, "strangled", addition="kill grab") + visible_message(span_danger("[user] is strangling [src]!"), \ + span_userdanger("[user] is strangling you!")) + update_mobility() //we fall down + if(!buckled && !density) + Move(user.loc) + user.set_pull_offsets(src, grab_state) + return TRUE /mob/living/attack_slime(mob/living/simple_animal/slime/M) @@ -421,7 +423,7 @@ if(stat != DEAD && !is_servant_of_ratvar(src)) to_chat(src, span_userdanger("A blinding light boils you alive! Run!")) adjust_fire_stacks(20) - IgniteMob() + ignite_mob() return FALSE diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 5ca6e212cc6b..ec5c79be96c7 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -32,10 +32,6 @@ var/lying = 0 //number of degrees. DO NOT USE THIS IN CHECKS. CHECK FOR MOBILITY FLAGS INSTEAD!! var/lying_prev = 0 //last value of lying on update_mobility - var/confused = 0 //Makes the mob move in random directions. - - var/hallucination = 0 //Directly affects how long a mob will hallucinate for - var/last_special = 0 //Used by the resist verb, likely used to prevent players from bypassing next_move by logging in/out. var/timeofdeath = 0 @@ -95,12 +91,10 @@ 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() var/list/status_effects //a list of all status effects the mob has - var/druggy = 0 /// List of changes to body temperature, used by desease symtoms like fever var/list/body_temp_changes = list() @@ -113,18 +107,13 @@ var/worn_layer //use to set if you want your inhand mob sprite to be hidden or not //Speech - var/stuttering = 0 - var/slurring = 0 var/cultslurring = 0 - var/derpspeech = 0 var/lizardspeech = 0 var/list/implants = null 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? diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm index 4a7216675043..5b4098da9021 100644 --- a/code/modules/mob/living/login.dm +++ b/code/modules/mob/living/login.dm @@ -15,9 +15,6 @@ if(ventcrawler) to_chat(src, span_notice("You can ventcrawl! Use alt+click on vents to quickly travel about the station.")) - if(ranged_ability) - ranged_ability.add_ranged_ability(src, span_notice("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 c17577de1d63..a9b582c599cd 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -238,6 +238,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( return on_say_success(message,message_range,succumbed, spans, language, message_mods)//Yogs + /mob/living/proc/on_say_success(message,message_range,succumbed, spans, language, message_mods) // A helper function of stuff that is deferred to when /mob/living/say() is done and has successfully said something. if(succumbed) succumb(1) @@ -322,7 +323,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( speech_bubble_recipients.Add(M.client) var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA - INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_recipients, 30) + INVOKE_ASYNC(GLOBAL_PROC, GLOBAL_PROC_REF(flick_overlay), I, speech_bubble_recipients, 30) /mob/proc/binarycheck() return FALSE @@ -357,20 +358,7 @@ GLOBAL_LIST_INIT(special_radio_keys, list( if(HAS_TRAIT(src, TRAIT_UNINTELLIGIBLE_SPEECH)) message = unintelligize(message) - if(derpspeech) - message = derpspeech(message, stuttering) - - if(lizardspeech) - message = lizardspeech(message) - - if(stuttering) - message = stutter(message) - - if(slurring) - message = slur(message) - - if(cultslurring) - message = cultslur(message) + SEND_SIGNAL(src, COMSIG_LIVING_TREAT_MESSAGE, args) message = capitalize(message) @@ -432,15 +420,34 @@ GLOBAL_LIST_INIT(special_radio_keys, list( . = "[verb_whisper] in [p_their()] last breath" else if(message_mods[MODE_SING]) . = verb_sing - else if(stuttering) + // Any subtype of slurring in our status effects make us "slur" + else if(locate(/datum/status_effect/speech/slurring) in status_effects) + . = "slurs" + else if(has_status_effect(/datum/status_effect/speech/stutter)) . = "stammers" - else if(derpspeech) + else if(has_status_effect(/datum/status_effect/speech/stutter/derpspeech)) . = "gibbers" else . = ..() -/mob/living/whisper(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null) - say("[MODE_KEY_WHISPER][message]", bubble_type, spans, sanitize, language, ignore_spam, forced) +/** + * 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) + if(!message) + return + say("[MODE_KEY_WHISPER][message]", bubble_type, spans, sanitize, language, ignore_spam, forced, filterproof) /mob/living/get_language_holder(get_minds = TRUE) if(get_minds && mind) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index b79c3d7e8b62..eeecc1c6fdf5 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -236,7 +236,7 @@ QDEL_NULL(modularInterface) . = ..() -/mob/living/silicon/ai/IgniteMob() +/mob/living/silicon/ai/ignite_mob() fire_stacks = 0 . = ..() diff --git a/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm b/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm index aa3696c2ab09..535c6e3ae328 100644 --- a/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm +++ b/code/modules/mob/living/silicon/ai/decentralized/projects/induction.dm @@ -51,18 +51,14 @@ /datum/ai_project/induction_apc/finish() var/datum/action/innate/ai/ranged/charge_borg_or_apc/ability = add_ability(/datum/action/innate/ai/ranged/charge_borg_or_apc) - var/obj/effect/proc_holder/ranged_ai/charge_borg_or_apc/effect if(ability) - effect = ability.linked_ability - effect.works_on_apcs = TRUE - effect.attached_action.button.name = "Charge APC" - effect.attached_action.button.desc = "Click an APC to charge it by 33%" + ability.name = "Charge APC" + ability.desc = "Click an APC to charge it by 33%" else ability = locate(/datum/action/innate/ai/ranged/charge_borg_or_apc) in ai.actions - effect = ability.linked_ability - effect.works_on_apcs = TRUE - effect.attached_action.button.name = "Charge cyborg/APC" - effect.attached_action.button.desc = "Click a cyborg or APC to charge it by 33%" + ability.name = "Charge cyborg/APC" + ability.desc = "Click a cyborg or APC to charge it by 33%" + ability.works_on_apcs = TRUE /datum/action/innate/ai/ranged/charge_borg_or_apc @@ -71,43 +67,14 @@ button_icon_state = "electrified" uses = 1 delete_on_empty = FALSE - linked_ability_type = /obj/effect/proc_holder/ranged_ai/charge_borg_or_apc - -/datum/action/innate/ai/ranged/charge_borg_or_apc/proc/charge_borg_or_apc(atom/target) - if(target && !QDELETED(target)) - if(istype(target, /mob/living/silicon/robot)) - var/mob/living/silicon/robot/R = target - log_game("[key_name(usr)] charged [R.name].") - if(R.cell) - if(R.cell.charge >= R.cell.maxcharge) - to_chat(owner, span_warning("[R]'s power cell is already full!")) - return FALSE - R.charge(null, R.cell.maxcharge * 0.33) - return TRUE - else - to_chat(owner, span_warning("[R] has no powercell to charge!")) - else if(istype(target, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/APC = target - var/turf/T = get_turf(APC) - log_game("[key_name(usr)] charged [APC.name] at [AREACOORD(T)].") - if(APC.cell) - if(APC.cell.charge >= APC.cell.maxcharge) - to_chat(owner, span_warning("The APC is already fully charged!")) - return FALSE - APC.cell.give(APC.cell.maxcharge * 0.33) - return TRUE - else - to_chat(owner, span_warning("The APC has no powercell to charge!")) - -/obj/effect/proc_holder/ranged_ai/charge_borg_or_apc - active = FALSE var/works_on_borgs = FALSE var/works_on_apcs = FALSE enable_text = span_notice("You prepare bluespace induction coils. Click a borg or APC to charge its cell by 33%") disable_text = span_notice("You power down your induction coils.") -/obj/effect/proc_holder/ranged_ai/charge_borg_or_apc/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) +/datum/action/innate/ai/ranged/charge_borg_or_apc/InterceptClickOn(atom/target) + . = ..() + if(!.) return if(ranged_ability_user.incapacitated()) remove_ranged_ability() @@ -132,3 +99,29 @@ target.audible_message(span_userdanger("You hear a soothing electrical buzzing sound coming from [target]!")) remove_ranged_ability() return TRUE + +/datum/action/innate/ai/ranged/charge_borg_or_apc/proc/charge_borg_or_apc(atom/target) + if(target && !QDELETED(target)) + if(istype(target, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/R = target + log_game("[key_name(usr)] charged [R.name].") + if(R.cell) + if(R.cell.charge >= R.cell.maxcharge) + to_chat(owner, span_warning("[R]'s power cell is already full!")) + return FALSE + R.charge(null, R.cell.maxcharge * 0.33) + return TRUE + else + to_chat(owner, span_warning("[R] has no powercell to charge!")) + else if(istype(target, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/APC = target + var/turf/T = get_turf(APC) + log_game("[key_name(usr)] charged [APC.name] at [AREACOORD(T)].") + if(APC.cell) + if(APC.cell.charge >= APC.cell.maxcharge) + to_chat(owner, span_warning("The APC is already fully charged!")) + return FALSE + APC.cell.give(APC.cell.maxcharge * 0.33) + return TRUE + else + to_chat(owner, span_warning("The APC has no powercell to charge!")) diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index 6cc9aeb3dc09..0ad2cea4b45f 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -29,14 +29,14 @@ var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] var/list/old_images = hud_list[AI_DETECT_HUD] if(!ai_detector_visible) - hud.remove_from_hud(src) + hud.remove_atom_from_hud(src) QDEL_LIST(old_images) return - if(!length(hud.hudusers)) + if(!length(hud.hud_users)) return //no one is watching, do not bother updating anything - hud.remove_from_hud(src) + hud.remove_atom_from_hud(src) var/static/list/vis_contents_opaque = list() var/obj/effect/overlay/ai_detect_hud/hud_obj = vis_contents_opaque[ai_detector_color] @@ -56,7 +56,7 @@ for(var/i in (new_images.len + 1) to old_images.len) qdel(old_images[i]) hud_list[AI_DETECT_HUD] = new_images - hud.add_to_hud(src) + hud.add_atom_to_hud(src) /mob/camera/aiEye/proc/get_visible_turfs() if(!isturf(loc)) @@ -114,7 +114,7 @@ GLOB.aiEyes -= src if(ai_detector_visible) var/datum/atom_hud/ai_detector/hud = GLOB.huds[DATA_HUD_AI_DETECT] - hud.remove_from_hud(src) + hud.remove_atom_from_hud(src) var/list/L = hud_list[AI_DETECT_HUD] QDEL_LIST(L) return ..() diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm index 5c4e614d22f0..380d3e43f0b9 100644 --- a/code/modules/mob/living/silicon/pai/pai_defense.dm +++ b/code/modules/mob/living/silicon/pai/pai_defense.dm @@ -15,13 +15,13 @@ //Ask and you shall receive switch(rand(1, 3)) if(1) - stuttering = 1 + adjust_stutter(1 MINUTES / severity) to_chat(src, span_danger("Warning: Feedback loop detected in speech module.")) if(2) - slurring = 1 + adjust_slurring(INFINITY) to_chat(src, span_danger("Warning: Audio synthesizer CPU stuck.")) if(3) - derpspeech = 1 + set_derpspeech(INFINITY) to_chat(src, span_danger("Warning: Vocabulary databank corrupted.")) if(prob(40)) mind.language_holder.selected_language = get_random_spoken_language() @@ -70,7 +70,7 @@ /mob/living/silicon/pai/stripPanelEquip(obj/item/what, mob/who, where) //prevents stripping to_chat(src, span_warning("Your holochassis stutters and warps intensely as you attempt to interact with the object, forcing you to cease lest the field fail.")) -/mob/living/silicon/pai/IgniteMob(var/mob/living/silicon/pai/P) +/mob/living/silicon/pai/ignite_mob(var/mob/living/silicon/pai/P) return FALSE //No we're not flammable /mob/living/silicon/pai/proc/take_holo_damage(amount) diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index e04ac4ebc56a..a54c85a9c65c 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -250,20 +250,20 @@ secHUD = !secHUD if(secHUD) var/datum/atom_hud/sec = GLOB.huds[sec_hud] - sec.add_hud_to(src) + sec.show_to(src) else var/datum/atom_hud/sec = GLOB.huds[sec_hud] - sec.remove_hud_from(src) + sec.hide_from(src) if("medicalhud") if(href_list["toggle"]) medHUD = !medHUD if(medHUD) var/datum/atom_hud/med = GLOB.huds[med_hud] - med.add_hud_to(src) + med.show_to(src) else var/datum/atom_hud/med = GLOB.huds[med_hud] - med.remove_hud_from(src) + med.hide_from(src) if("hostscan") if(href_list["toggle"]) diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm index 32b1b74f6c5a..487c1d033d74 100644 --- a/code/modules/mob/living/silicon/robot/robot_defense.dm +++ b/code/modules/mob/living/silicon/robot/robot_defense.dm @@ -72,7 +72,7 @@ /mob/living/silicon/robot/fire_act() if(!on_fire) //Silicons don't gain stacks from hotspots, but hotspots can ignite them - IgniteMob() + ignite_mob() /mob/living/silicon/robot/emp_act(severity) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index d6931bfc28a0..5cc31efb2c76 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -43,7 +43,8 @@ var/law_change_counter = 0 var/obj/machinery/camera/builtInCamera = null var/updating = FALSE //portable camera camerachunk update - + ///Whether we have been emagged + var/emagged = FALSE var/hack_software = FALSE //Will be able to use hacking actions var/interaction_range = 7 //wireless control range ///The reference to the built-in tablet that borgs carry. @@ -55,7 +56,7 @@ GLOB.silicon_mobs += src faction += "silicon" for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) diag_hud_set_status() diag_hud_set_health() @@ -376,17 +377,17 @@ var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] var/datum/atom_hud/medsensor = GLOB.huds[med_hud] var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] - secsensor.remove_hud_from(src) - medsensor.remove_hud_from(src) - diagsensor.remove_hud_from(src) + secsensor.hide_from(src) + medsensor.hide_from(src) + diagsensor.hide_from(src) /mob/living/silicon/proc/add_sensors() var/datum/atom_hud/secsensor = GLOB.huds[sec_hud] var/datum/atom_hud/medsensor = GLOB.huds[med_hud] var/datum/atom_hud/diagsensor = GLOB.huds[d_hud] - secsensor.add_hud_to(src) - medsensor.add_hud_to(src) - diagsensor.add_hud_to(src) + secsensor.show_to(src) + medsensor.show_to(src) + diagsensor.show_to(src) /mob/living/silicon/proc/toggle_sensors(silent = FALSE) if(incapacitated()) diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm index b0986e51cf46..a9a8edc99d62 100644 --- a/code/modules/mob/living/simple_animal/bot/bot.dm +++ b/code/modules/mob/living/simple_animal/bot/bot.dm @@ -160,7 +160,7 @@ //Adds bot to the diagnostic HUD system prepare_huds() for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) diag_hud_set_bothealth() diag_hud_set_botstat() diag_hud_set_botmode() @@ -168,10 +168,10 @@ //If a bot has its own HUD (for player bots), provide it. if(data_hud_type) var/datum/atom_hud/datahud = GLOB.huds[data_hud_type] - datahud.add_hud_to(src) + datahud.show_to(src) if(path_hud) - path_hud.add_to_hud(src) - path_hud.add_hud_to(src) + path_hud.add_atom_to_hud(src) + path_hud.show_to(src) /mob/living/simple_animal/bot/update_mobility() . = ..() @@ -989,7 +989,7 @@ Pass a positive integer as an argument to override a bot's default speed. path_huds_watching_me += path_hud for(var/V in path_huds_watching_me) var/datum/atom_hud/H = V - H.remove_from_hud(src) + H.remove_atom_from_hud(src) var/list/path_images = hud_list[DIAG_PATH_HUD] QDEL_LIST(path_images) @@ -1032,7 +1032,7 @@ Pass a positive integer as an argument to override a bot's default speed. for(var/V in path_huds_watching_me) var/datum/atom_hud/H = V - H.add_to_hud(src) + H.add_atom_to_hud(src) /mob/living/simple_animal/bot/proc/increment_path() diff --git a/code/modules/mob/living/simple_animal/bot/ed209bot.dm b/code/modules/mob/living/simple_animal/bot/ed209bot.dm index 04b5c6ef1f43..351d54973efe 100644 --- a/code/modules/mob/living/simple_animal/bot/ed209bot.dm +++ b/code/modules/mob/living/simple_animal/bot/ed209bot.dm @@ -73,7 +73,7 @@ //SECHUD var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - secsensor.add_hud_to(src) + secsensor.show_to(src) /mob/living/simple_animal/bot/ed209/turn_on() . = ..() @@ -545,8 +545,8 @@ Auto Patrol[]"}, spawn(2) icon_state = "[lasercolor]ed209[on]" var/threat = 5 - C.Paralyze(100) - C.stuttering = 5 + C.Paralyze(10 SECONDS) + C.adjust_stutter(5 SECONDS) if(ishuman(C)) var/mob/living/carbon/human/H = C var/judgement_criteria = judgement_criteria() diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm index 5eaf73eb0ff5..90535b8b5345 100644 --- a/code/modules/mob/living/simple_animal/bot/honkbot.dm +++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm @@ -192,9 +192,9 @@ Maintenance panel panel is [open ? "opened" : "closed"]"}, sensor_blink() if(spam_flag == 0) if(ishuman(C)) - C.stuttering = 20 + C.adjust_stutter(20 SECONDS) C.adjustEarDamage(0, 5) //far less damage than the H.O.N.K. - C.Jitter(50) + C.adjust_jitter(50 SECONDS) C.Paralyze(60) var/mob/living/carbon/human/H = C if(client) //prevent spam from players.. @@ -213,8 +213,8 @@ Maintenance panel panel is [open ? "opened" : "closed"]"}, C.visible_message(span_danger("[src] has honked [C]!"),\ span_userdanger("[src] has honked you!")) else - C.stuttering = 20 - C.Paralyze(80) + C.adjust_stutter(20 SECONDS) + C.Paralyze(8 SECONDS) addtimer(CALLBACK(src, .proc/spam_flag_false), cooldowntime) diff --git a/code/modules/mob/living/simple_animal/bot/secbot.dm b/code/modules/mob/living/simple_animal/bot/secbot.dm index 93ff4f7f2371..199a704f8399 100644 --- a/code/modules/mob/living/simple_animal/bot/secbot.dm +++ b/code/modules/mob/living/simple_animal/bot/secbot.dm @@ -78,7 +78,7 @@ //SECHUD var/datum/atom_hud/secsensor = GLOB.huds[DATA_HUD_SECURITY_ADVANCED] - secsensor.add_hud_to(src) + secsensor.show_to(src) if(prob(5)) russian = TRUE // imported from Russia @@ -267,15 +267,13 @@ Auto Patrol: []"}, addtimer(CALLBACK(src, .proc/update_icon), 2) var/threat = 5 if(ishuman(C)) - C.stuttering = 5 - C.Paralyze(100) var/mob/living/carbon/human/H = C threat = H.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) else - C.Paralyze(100) - C.stuttering = 5 threat = C.assess_threat(judgement_criteria, weaponcheck=CALLBACK(src, .proc/check_for_weapons)) + C.Paralyze(10 SECONDS) + C.adjust_stutter(5 SECONDS) log_combat(src,C,"stunned") if(declare_arrests) var/area/location = get_area(src) diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 3949fdb7d1e4..bfc90e10c323 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -9,7 +9,7 @@ response_disarm = "flails at" response_harm = "punches" speak_chance = 1 - icon = 'icons/mob/mob.dmi' + icon = 'icons/mob/nonhuman-player/cult.dmi' speed = 0 spacewalk = TRUE a_intent = INTENT_HARM @@ -39,28 +39,30 @@ var/seeking = FALSE var/can_repair_constructs = FALSE var/can_repair_self = FALSE - var/runetype + /// Theme controls color. THEME_CULT is red THEME_WIZARD is purple and THEME_HOLY is blue + var/theme = THEME_CULT /mob/living/simple_animal/hostile/construct/Initialize() . = ..() update_health_hud() - var/spellnum = 1 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) - var/datum/action/innate/cult/create_rune/CR = new runetype(src) - CR.Grant(src) - var/pos = 2+spellnum*31 - CR.button.screen_loc = "6:[pos],4:-2" - CR.button.moved = "6:[pos],4:-2" + update_action_buttons() + + if(icon_state) + add_overlay("glow_[icon_state]_[theme]") /mob/living/simple_animal/hostile/construct/Login() ..() @@ -69,7 +71,15 @@ /mob/living/simple_animal/hostile/construct/examine(mob/user) var/t_He = p_they(TRUE) var/t_s = p_s() - . = list("This is [icon2html(src, user)] \a [src]!\n[desc]") + var/text_span + switch(theme) + if(THEME_CULT) + text_span = "cult" + if(THEME_WIZARD) + text_span = "purple" + if(THEME_HOLY) + text_span = "blue" + . = list("This is [icon2html(src, user)] \a [src]!\n[desc]") if(health < maxHealth) if(health >= maxHealth/2) . += span_warning("[t_He] look[t_s] slightly dented.") @@ -122,8 +132,8 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, name = "Juggernaut" real_name = "Juggernaut" desc = "A massive, armored construct built to spearhead attacks and soak up enemy fire." - icon_state = "behemoth" - icon_living = "behemoth" + icon_state = "juggernaut" + icon_living = "juggernaut" maxHealth = 150 health = 150 response_harm = "harmlessly punches" @@ -138,9 +148,11 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, status_flags = 0 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 + construct_spells = list( + /datum/action/cooldown/spell/forcewall/cult, + /datum/action/cooldown/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." @@ -179,8 +191,7 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, //////////////////////////Angelic-Juggernaut//////////////////////////// /mob/living/simple_animal/hostile/construct/armored/angelic - icon_state = "behemoth_angelic" - icon_living = "behemoth_angelic" + theme = THEME_HOLY loot = list(/obj/item/ectoplasm/angelic) /mob/living/simple_animal/hostile/construct/armored/noncult @@ -190,8 +201,8 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, name = "Wraith" real_name = "Wraith" desc = "A wicked, clawed shell constructed to assassinate enemies and sow chaos behind enemy lines." - icon_state = "floating" - icon_living = "floating" + icon_state = "wraith" + icon_living = "wraith" maxHealth = 65 health = 65 melee_damage_lower = 20 @@ -200,13 +211,18 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, retreat_distance = 2 //AI wraiths will move in and out of combat attacktext = "slashes" 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/cooldown/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 @@ -217,25 +233,35 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, . = ..() if(. && isnum(prev_stat)) - 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(L.InCritical() && prev_stat == CONSCIOUS) //you knocked them into critical - 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) + var/datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/jaunt = locate() in actions + if(!jaunt) + return + + var/total_refund = 0 SECONDS + // they're dead, and you killed them - full refund + if(QDELETED(living_target) || (living_target.stat == DEAD && prev_stat != DEAD)) + total_refund += jaunt.cooldown_time + // you knocked them into critical + else if(living_target.stat == UNCONSCIOUS && prev_stat == CONSCIOUS) + total_refund += crit_refund + + if(living_target.stat != DEAD && prev_stat != DEAD) + total_refund += attack_refund + + jaunt.next_use_time -= total_refund + jaunt.UpdateButtons() /mob/living/simple_animal/hostile/construct/wraith/hostile //actually hostile, will move around, hit things AIStatus = AI_ON //////////////////////////Angelic-Wraith//////////////////////////// /mob/living/simple_animal/hostile/construct/wraith/angelic - icon_state = "floating_angelic" - icon_living = "floating_angelic" + theme = THEME_HOLY loot = list(/obj/item/ectoplasm/angelic) + construct_spells = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift/angelic, + /datum/action/innate/cult/create_rune/tele, + ) /mob/living/simple_animal/hostile/construct/wraith/noncult @@ -258,17 +284,19 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, attacktext = "rams" 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) + construct_spells = list( + /datum/action/cooldown/spell/conjure/cult_floor, + /datum/action/cooldown/spell/conjure/cult_wall, + /datum/action/cooldown/spell/conjure/soulstone, + /datum/action/cooldown/spell/conjure/construct/lesser, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) runetype = /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, 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." + 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 @@ -316,27 +344,32 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, /////////////////////////////Angelic Artificer///////////////////////// /mob/living/simple_animal/hostile/construct/builder/angelic - icon_state = "artificer_angelic" - icon_living = "artificer_angelic" + theme = THEME_HOLY loot = list(/obj/item/ectoplasm/angelic) - construct_spells = list(/obj/effect/proc_holder/spell/aoe_turf/conjure/soulstone/noncult/purified, - /obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser, - /obj/effect/proc_holder/spell/targeted/projectile/magic_missile/lesser) + construct_spells = list( + /datum/action/cooldown/spell/conjure/soulstone/purified, + /datum/action/cooldown/spell/conjure/construct/lesser, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) /mob/living/simple_animal/hostile/construct/builder/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) + construct_spells = list( + /datum/action/cooldown/spell/conjure/cult_floor, + /datum/action/cooldown/spell/conjure/cult_wall, + /datum/action/cooldown/spell/conjure/soulstone/noncult, + /datum/action/cooldown/spell/conjure/construct/lesser, + /datum/action/cooldown/spell/aoe/magic_missile/lesser, + /datum/action/innate/cult/create_rune/revive, + ) /////////////////////////////Harvester///////////////////////// /mob/living/simple_animal/hostile/construct/harvester name = "Harvester" real_name = "Harvester" desc = "A long, thin construct built to herald Nar-Sie's rise. It'll be all over soon." - icon_state = "chosen" - icon_living = "chosen" + icon_state = "harvester" + icon_living = "harvester" maxHealth = 40 health = 40 sight = SEE_MOBS @@ -345,9 +378,11 @@ mob/living/simple_animal/hostile/construct/attackby(obj/item/W, mob/living/user, attack_vis_effect = ATTACK_EFFECT_SLASH attacktext = "butchers" 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, - /obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift) + construct_spells = list( + /datum/action/cooldown/spell/aoe/area_conversion, + /datum/action/cooldown/spell/forcewall/cult, + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/shift, + ) 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 diff --git a/code/modules/mob/living/simple_animal/eldritch_demons.dm b/code/modules/mob/living/simple_animal/eldritch_demons.dm index fbcf0c465415..9a86d2ba6b1d 100644 --- a/code/modules/mob/living/simple_animal/eldritch_demons.dm +++ b/code/modules/mob/living/simple_animal/eldritch_demons.dm @@ -28,21 +28,15 @@ deathmessage = "implodes into itself" faction = list("heretics") //simple_mob_flags = SILENCE_RANGED_MESSAGE + ///Innate spells that are supposed to be added when a beast is created - var/list/spells_to_add + var/list/actions_to_add /mob/living/simple_animal/hostile/eldritch/Initialize() . = ..() - add_spells() - -/** - * Add_spells - * - * Goes through spells_to_add and adds each spell to the mind. - */ -/mob/living/simple_animal/hostile/eldritch/proc/add_spells() - for(var/spell in spells_to_add) - AddSpell(new spell()) + for(var/spell in actions_to_add) + var/datum/action/cooldown/spell/new_spell = new spell(src) + new_spell.Grant(src) /mob/living/simple_animal/hostile/eldritch/raw_prophet name = "Raw Prophet" @@ -56,36 +50,35 @@ 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/pointed/trigger/blind/eldritch) + actions_to_add = list( + /datum/action/cooldown/spell/jaunt/ethereal_jaunt/ash/long, + /datum/action/cooldown/spell/list_target/telepathy/eldritch, + /datum/action/cooldown/spell/pointed/blind/eldritch, + /datum/action/innate/expand_sight, + ) - var/list/linked_mobs = list() + /// A weakref to the last target we smacked. Hitting targets consecutively does more damage. + var/datum/weakref/last_target /mob/living/simple_animal/hostile/eldritch/raw_prophet/Initialize() . = ..() - link_mob(src) - -/mob/living/simple_animal/hostile/eldritch/raw_prophet/Login() - . = ..() - client?.view_size.setTo(10) - -/mob/living/simple_animal/hostile/eldritch/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, span_notice("You feel something new enter your 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_GLOB_MOB_DEATH, COMSIG_PARENT_QDELETING) , .proc/unlink_mob) - return TRUE - -/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/unlink_mob(mob/living/mob_linked) + var/on_link_message = "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/on_unlink_message = "Your mind shatters as [src]'s Mansus Link leaves your mind." + + AddComponent(/datum/component/mind_linker, \ + network_name = "Mansus Link", \ + chat_color = "#568b00", \ + linker_action_path = /datum/action/cooldown/manse_link, \ + linker_action_path = /datum/action/cooldown/spell/pointed/manse_link, \ + link_message = on_link_message, \ + unlink_message = on_unlink_message, \ + post_unlink_callback = CALLBACK(src, PROC_REF(after_unlink)), \ + speech_action_background_icon_state = "bg_ecult", \ + ) + +/mob/living/simple_animal/hostile/eldritch/raw_prophet/proc/after_unlink(mob/living/mob_linked) if(!linked_mobs[mob_linked]) return UnregisterSignal(mob_linked, list(COMSIG_GLOB_MOB_DEATH, COMSIG_PARENT_QDELETING)) @@ -115,7 +108,7 @@ melee_damage_upper = 15 move_resist = MOVE_FORCE_OVERPOWERING+1 movement_type = GROUND - spells_to_add = list(/obj/effect/proc_holder/spell/targeted/worm_contract) + actions_to_add = list(/datum/action/cooldown/spell/worm_contract) ranged_cooldown_time = 5 ranged = TRUE rapid = 1 @@ -299,7 +292,10 @@ melee_damage_lower = 15 melee_damage_upper = 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/cooldown/spell/aoe/rust_conversion/small, + /datum/action/cooldown/spell/basic_projectile/rust_wave/short, + ) /mob/living/simple_animal/hostile/eldritch/rust_spirit/setDir(newdir) . = ..() @@ -334,7 +330,11 @@ melee_damage_lower = 15 melee_damage_upper = 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/cooldown/spell/jaunt/ethereal_jaunt/ash, + /datum/action/cooldown/spell/pointed/cleave, + /datum/action/cooldown/spell/fire_sworn, + ) /mob/living/simple_animal/hostile/eldritch/stalker name = "Flesh Stalker" @@ -348,4 +348,8 @@ melee_damage_lower = 15 melee_damage_upper = 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/cooldown/spell/jaunt/ethereal_jaunt/ash, + /datum/action/cooldown/spell/shapeshift/eldritch, + /datum/action/cooldown/spell/emp/eldritch, + ) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm index 792367c8cd90..851a966ea1c4 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm @@ -110,7 +110,7 @@ alert_drones(DRONE_NET_CONNECT) for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) if(pacifism) ADD_TRAIT(src, TRAIT_PACIFISM, JOB_TRAIT) 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 c7bcb9758482..520728b34532 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm @@ -40,8 +40,15 @@ return head if(SLOT_GENERC_DEXTROUS_STORAGE) 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/friendly/spiderbot.dm b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm index faf7660527d8..6a04525aca0e 100644 --- a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm +++ b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm @@ -30,7 +30,7 @@ var/obj/machinery/camera/camera = null var/obj/item/mmi/mmi = null var/req_access = ACCESS_ROBO_CONTROL //Access needed to pop out the brain. - var/emagged = 0 + var/emagged = FALSE var/obj/item/held_item = null //Storage for single item they can hold. /mob/living/simple_animal/spiderbot/attackby(obj/item/O, mob/user) diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm index 387d4b095533..dc2d4a0b8cfd 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm @@ -57,6 +57,17 @@ return 1 ..() +/mob/living/simple_animal/hostile/guardian/dextrous/get_item_by_slot(slot_id) + if(slot_id == ITEM_SLOT_DEX_STORAGE) + return internal_storage + return ..() + +/mob/living/simple_animal/hostile/guardian/dextrous/get_slot_by_item(obj/item/looking_for) + if(internal_storage == looking_for) + return ITEM_SLOT_DEX_STORAGE + return ..() + + /mob/living/simple_animal/hostile/guardian/dextrous/equip_to_slot(obj/item/I, slot) if(!..()) return diff --git a/code/modules/mob/living/simple_animal/guardian/types/fire.dm b/code/modules/mob/living/simple_animal/guardian/types/fire.dm index ed4e791b2347..fd9b62da0476 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/fire.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/fire.dm @@ -40,4 +40,4 @@ var/mob/living/M = AM if(!hasmatchingsummoner(M) && M != summoner && M.fire_stacks < 7) M.fire_stacks = 7 - M.IgniteMob() + M.ignite_mob() diff --git a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm index 572729a04484..546b140d3cdc 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm @@ -108,7 +108,7 @@ H.electrocution_animation(20) C.jitteriness += 1000 C.do_jitter_animation(jitteriness) - C.stuttering += 1 + C.adjust_stutter(1 SECONDS) spawn(20) if(C) C.jitteriness = max(C.jitteriness - 990, 10) diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm index 29fb548ee648..87c09d5473e9 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/support.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm @@ -18,7 +18,7 @@ /mob/living/simple_animal/hostile/guardian/healer/Initialize() . = ..() var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - medsensor.add_hud_to(src) + medsensor.show_to(src) /mob/living/simple_animal/hostile/guardian/healer/get_status_tab_items() . = ..() 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 8990ae58d3df..a6c9f974d07c 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -50,15 +50,14 @@ see_in_dark = 4 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE var/playable_spider = FALSE - var/datum/action/innate/spider/lay_web/lay_web var/directive = "" //Message passed down to children, to relay the creator's orders do_footstep = TRUE /mob/living/simple_animal/hostile/poison/giant_spider/Initialize() . = ..() - lay_web = new - lay_web.Grant(src) + var/datum/action/innate/spider/lay_web/webbing = new(src) + webbing.Grant(src) /mob/living/simple_animal/hostile/poison/giant_spider/Destroy() QDEL_NULL(lay_web) @@ -112,26 +111,25 @@ poison_type = /datum/reagent/toxin/staminatoxin var/atom/movable/cocoon_target var/fed = 0 - var/obj/effect/proc_holder/wrap/wrap - var/datum/action/innate/spider/lay_eggs/lay_eggs - var/datum/action/innate/spider/set_directive/set_directive var/static/list/consumed_mobs = list() //the tags of mobs that have been consumed by nurse spiders to lay eggs gold_core_spawnable = NO_SPAWN /mob/living/simple_animal/hostile/poison/giant_spider/nurse/Initialize() . = ..() - wrap = new - AddAbility(wrap) - lay_eggs = new - lay_eggs.Grant(src) - set_directive = new - set_directive.Grant(src) - -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/Destroy() - RemoveAbility(wrap) - QDEL_NULL(lay_eggs) - QDEL_NULL(set_directive) - return ..() + var/datum/atom_hud/datahud = GLOB.huds[health_hud] + datahud.show_to(src) + + var/datum/action/cooldown/wrap/wrapping = new(src) + wrapping.Grant(src) + + var/datum/action/innate/spider/lay_eggs/make_eggs = new(src) + make_eggs.Grant(src) + + var/datum/action/innate/spider/set_directive/give_orders = new(src) + give_orders.Grant(src) + + var/datum/action/innate/spider/comm/not_hivemind_talk = new(src) + not_hivemind_talk.Grant(src) //broodmothers are the queen of the spiders, can send messages to all them and web faster. That rare round where you get a queen spider and turn your 'for honor' players into 'r6siege' players will be a fun one. /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife @@ -142,18 +140,13 @@ icon_dead = "midwife_dead" maxHealth = 40 health = 40 - var/datum/action/innate/spider/comm/letmetalkpls gold_core_spawnable = HOSTILE_SPAWN //yogs xenobio spiders best spiders /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Initialize() . = ..() - letmetalkpls = new + var/datum/action/innate/spider/comm/letmetalkpls = new(src) letmetalkpls.Grant(src) -/mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife/Destroy() - QDEL_NULL(letmetalkpls) - return ..() - //hunters have the most poison and move the fastest, so they can find prey /mob/living/simple_animal/hostile/poison/giant_spider/hunter desc = "Furry and black, it makes you shudder to look at it. This one has sparkling purple eyes." @@ -332,7 +325,7 @@ if(L.blood_volume && (L.stat != DEAD || !consumed_mobs[L.tag])) //if they're not dead, you can consume them anyway consumed_mobs[L.tag] = TRUE fed++ - lay_eggs.UpdateButtonIcon(TRUE) + lay_eggs.UpdateButtons(TRUE) visible_message(span_danger("[src] sticks a proboscis into [L] and sucks a viscous substance out."),span_notice("You suck the nutriment out of [L], feeding you enough to lay a cluster of eggs.")) L.death() //you just ate them, they're dead. else @@ -349,140 +342,199 @@ icon_icon = 'icons/mob/actions/actions_animal.dmi' background_icon_state = "bg_alien" -/datum/action/innate/spider/lay_web +/datum/action/innate/spider/lay_web // Todo: Unify this with the genetics power name = "Spin Web" desc = "Spin a web to slow down potential prey." check_flags = AB_CHECK_CONSCIOUS button_icon_state = "lay_web" -/datum/action/innate/spider/lay_web/Activate() +/datum/action/innate/spider/lay_web/IsAvailable() + . = ..() + if(!.) + return FALSE + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/S = owner + return FALSE - if(!isturf(S.loc)) - return - var/turf/T = get_turf(S) + var/mob/living/simple_animal/hostile/poison/giant_spider/spider = owner + var/obj/structure/spider/stickyweb/web = locate() in get_turf(spider) + if(web && !spider.web_sealer) //if(web && (!spider.web_sealer || istype(web, /obj/structure/spider/stickyweb/sealed))) + to_chat(owner, span_warning("There's already a web here!")) + return FALSE - var/obj/structure/spider/stickyweb/W = locate() in T - if(W) - to_chat(S, span_warning("There's already a web here!")) - return + if(!isturf(spider.loc)) + return FALSE - if(S.busy != SPINNING_WEB) - S.busy = SPINNING_WEB - S.visible_message(span_notice("[S] begins to secrete a sticky substance."),span_notice("You begin to lay a web.")) - S.stop_automated_movement = TRUE - if(do_after(S, 4 SECONDS, T)) - if(S.busy == SPINNING_WEB && S.loc == T) - new /obj/structure/spider/stickyweb(T) - S.busy = SPIDER_IDLE - S.stop_automated_movement = FALSE + return TRUE + +/datum/action/innate/spider/lay_web/Activate() + var/turf/spider_turf = get_turf(owner) + var/mob/living/simple_animal/hostile/giant_spider/spider = owner + var/obj/structure/spider/stickyweb/web = locate() in spider_turf + if(web) + spider.visible_message( + span_notice("[spider] begins to pack more webbing onto the web."), + span_notice("You begin to seal the web."), + ) else - to_chat(S, span_warning("You're already spinning a web!")) + to_chat(spider, span_warning("You're already doing something else!")) + spider.visible_message( + span_notice("[spider] begins to secrete a sticky substance."), + span_notice("You begin to lay a web."), + ) -/obj/effect/proc_holder/wrap - name = "Wrap" - panel = "Spider" - active = FALSE - datum/action/spell_action/action = null - 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/mob/actions/actions_animal.dmi' - action_icon_state = "wrap_0" - action_background_icon_state = "bg_alien" - -/obj/effect/proc_holder/wrap/Initialize() - . = ..() - action = new(src) + spider.stop_automated_movement = TRUE -/obj/effect/proc_holder/wrap/update_icon() - action.button_icon_state = "wrap_[active]" - action.UpdateButtonIcon() + if(do_after(spider, 4 SECONDS * spider.web_speed, target = spider_turf)) + if(spider.loc == spider_turf) + if(web) + qdel(web) +// new /obj/structure/spider/stickyweb/sealed(spider_turf) + new /obj/structure/spider/stickyweb(spider_turf) -/obj/effect/proc_holder/wrap/Click() - if(!istype(usr, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return TRUE - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = usr - activate(user) - return TRUE + spider.stop_automated_movement = FALSE -/obj/effect/proc_holder/wrap/proc/activate(mob/living/user) - var/message - if(active) - message = span_notice("You no longer prepare to wrap something in a cocoon.") - remove_ranged_ability(message) - else - message = span_notice("You prepare to wrap something in a cocoon. Left-click your target to start wrapping!") - add_ranged_ability(user, message, TRUE) - return 1 +/datum/action/cooldown/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/mob/actions/actions_animal.dmi' + button_icon_state = "wrap_0" + check_flags = AB_CHECK_CONSCIOUS + click_to_activate = 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/wrap/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) +/datum/action/cooldown/wrap/IsAvailable() + . = ..() + if(!.) + return FALSE + if(owner.incapacitated()) + return FALSE + return TRUE + +/datum/action/cooldown/wrap/set_click_ability(mob/on_who) + . = ..() + if(!.) return - if(ranged_ability_user.incapacitated() || !istype(ranged_ability_user, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - remove_ranged_ability() + + to_chat(on_who, span_notice("You prepare to wrap something in a cocoon. Left-click your target to start wrapping!")) + button_icon_state = "wrap_0" + UpdateButtons() + +/datum/action/cooldown/wrap/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/user = ranged_ability_user + if(refund_cooldown) + to_chat(on_who, span_notice("You no longer prepare to wrap something in a cocoon.")) + button_icon_state = "wrap_1" + UpdateButtons() - 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, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/.proc/cocoon) - remove_ranged_ability() - return TRUE +/datum/action/cooldown/wrap/Activate(atom/to_wrap) + if(!owner.Adjacent(to_wrap)) + owner.balloon_alert(owner, "must be closer!") + return FALSE + + if(!ismob(to_wrap) && !isobj(to_wrap)) + return FALSE + + if(to_wrap == owner) + return FALSE -/obj/effect/proc_holder/wrap/on_lose(mob/living/carbon/user) - remove_ranged_ability() + if(istype(to_wrap, /mob/living/simple_animal/hostile/poison/giant_spider)) + owner.balloon_alert(owner, "can't wrap spiders!") + return FALSE + + var/atom/movable/target_movable = to_wrap + if(target_movable.anchored) + return FALSE + + StartCooldown(wrap_time) + INVOKE_ASYNC(src, PROC_REF(cocoon), to_wrap) + return TRUE + +/datum/action/cooldown/wrap/proc/cocoon(atom/movable/to_wrap) + owner.visible_message( + span_notice("[owner] begins to secrete a sticky substance around [to_wrap]."), + span_notice("You begin wrapping [to_wrap] into a cocoon."), + ) + + var/mob/living/simple_animal/animal_owner = owner + if(istype(animal_owner)) + animal_owner.stop_automated_movement = TRUE + + if(do_after(owner, wrap_time, target = to_wrap, interaction_key = INTERACTION_SPIDER_KEY)) + var/obj/structure/spider/cocoon/casing = new(to_wrap.loc) + if(isliving(to_wrap)) + var/mob/living/living_wrapped = to_wrap + // if they're not dead, you can consume them anyway + if(ishuman(living_wrapped) && (living_wrapped.stat != DEAD || !HAS_TRAIT(living_wrapped, TRAIT_SPIDER_CONSUMED))) + var/datum/action/innate/spider/lay_eggs/enriched/egg_power = locate() in owner.actions + if(egg_power) + egg_power.charges++ + egg_power.UpdateButtons() + owner.visible_message( + span_danger("[owner] sticks a proboscis into [living_wrapped] and sucks a viscous substance out."), + span_notice("You suck the nutriment out of [living_wrapped], feeding you enough to lay a cluster of enriched eggs."), + ) + + living_wrapped.death() //you just ate them, they're dead. + else + to_chat(owner, span_warning("[living_wrapped] cannot sate your hunger!")) + + to_wrap.forceMove(casing) + if(to_wrap.density || ismob(to_wrap)) + casing.icon_state = pick("cocoon_large1", "cocoon_large2", "cocoon_large3") + + if(istype(animal_owner)) + animal_owner.stop_automated_movement = TRUE /datum/action/innate/spider/lay_eggs name = "Lay Eggs" desc = "Lay a cluster of eggs, which will soon grow into more spiders. You must wrap a living being to do this." check_flags = AB_CHECK_CONSCIOUS button_icon_state = "lay_eggs" + ///How long it takes for a broodmother to lay eggs. + var/egg_lay_time = 15 SECONDS + ///The type of egg we create + //var/egg_type = /obj/effect/mob_spawn/ghost_role/spider /datum/action/innate/spider/lay_eggs/IsAvailable() - if(..()) - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return 0 - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - if(S.fed) - return 1 - return 0 + . = ..() + if(!.) + return FALSE + + if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider)) + return FALSE + var/obj/structure/spider/eggcluster/eggs = locate() in get_turf(owner) + if(eggs) + to_chat(owner, span_warning("There is already a cluster of eggs here!")) + return FALSE + + return TRUE /datum/action/innate/spider/lay_eggs/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - - var/obj/structure/spider/eggcluster/E = locate() in get_turf(S) - if(E) - to_chat(S, span_warning("There is already a cluster of eggs here!")) - else if(!S.fed) - to_chat(S, span_warning("You are too hungry to do this!")) - else if(S.busy != LAYING_EGGS) - S.busy = LAYING_EGGS - S.visible_message(span_notice("[S] begins to lay a cluster of eggs."),span_notice("You begin to lay a cluster of eggs.")) - S.stop_automated_movement = TRUE - if(do_after(S, 5 SECONDS, get_turf(S))) - if(S.busy == LAYING_EGGS) - E = locate() in get_turf(S) - if(!E || !isturf(S.loc)) - log_admin("[src] layed eggs at [S.loc]") - var/obj/structure/spider/eggcluster/C = new /obj/structure/spider/eggcluster(get_turf(S)) - if(S.ckey) - C.player_spiders = TRUE - C.directive = S.directive - C.poison_type = S.poison_type - C.poison_per_bite = S.poison_per_bite - C.faction = S.faction.Copy() - S.fed-- - UpdateButtonIcon(TRUE) - S.busy = SPIDER_IDLE - S.stop_automated_movement = FALSE + owner.visible_message( + span_notice("[owner] begins to lay a cluster of eggs."), + span_notice("You begin to lay a cluster of eggs."), + ) + + var/mob/living/simple_animal/hostile/giant_spider/spider = owner + spider.stop_automated_movement = TRUE + + if(do_after(owner, egg_lay_time, target = get_turf(owner))) + var/obj/structure/spider/eggcluster/eggs = locate() in get_turf(owner) + if(!eggs || !isturf(spider.loc)) + var/obj/structure/spider/eggcluster/eggs/new_eggs = new /obj/structure/spider/eggcluster/eggs(get_turf(spider)) + new_eggs.directive = spider.directive + new_eggs.faction = spider.faction + UpdateButtons(TRUE) + + spider.stop_automated_movement = FALSE /datum/action/innate/spider/set_directive name = "Set Directive" @@ -490,11 +542,19 @@ check_flags = AB_CHECK_CONSCIOUS button_icon_state = "directive" +/datum/action/innate/spider/set_directive/IsAvailable() + return ..() && istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider) + /datum/action/innate/spider/set_directive/Activate() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse)) - return - var/mob/living/simple_animal/hostile/poison/giant_spider/nurse/S = owner - S.directive = stripped_input(S, "Enter the new directive", "Create directive", "[S.directive]") + var/mob/living/simple_animal/hostile/giant_spider/midwife/spider = owner + + spider.directive = tgui_input_text(spider, "Enter the new directive", "Create directive", "[spider.directive]") + if(isnull(spider.directive) || QDELETED(src) || QDELETED(owner) || !IsAvailable()) + return FALSE + + message_admins("[ADMIN_LOOKUPFLW(owner)] set its directive to: '[spider.directive]'.") + log_game("[key_name(owner)] set its directive to: '[spider.directive]'.") + return TRUE /mob/living/simple_animal/hostile/poison/giant_spider/Login() . = ..() @@ -510,14 +570,13 @@ button_icon_state = "command" /datum/action/innate/spider/comm/IsAvailable() - if(!istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife)) - return FALSE - return TRUE + return ..() && istype(owner, /mob/living/simple_animal/hostile/poison/giant_spider/nurse/midwife) /datum/action/innate/spider/comm/Trigger() - var/input = stripped_input(owner, "Input a command for your legions to follow.", "Command", "") - if(QDELETED(src) || !input || !IsAvailable()) + var/input = tgui_input_text(owner, "Input a command for your legions to follow.", "Command") + if(!input || QDELETED(src) || QDELETED(owner) || !IsAvailable()) return FALSE + spider_command(owner, input) return TRUE @@ -526,12 +585,12 @@ return var/my_message my_message = span_spider("Command from [user]: [message]") - for(var/mob/living/simple_animal/hostile/poison/giant_spider/M in GLOB.spidermobs) - to_chat(M, 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") + for(var/mob/living/simple_animal/hostile/poison/giant_spider/spider as anything in GLOB.spidermobs) + to_chat(spider, my_message) + for(var/ghost in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(ghost, user) + to_chat(ghost, "[link] [my_message]") + user.log_talk(message, LOG_SAY, tag = "spider command") /mob/living/simple_animal/hostile/poison/giant_spider/handle_temperature_damage() if(bodytemperature < minbodytemp) diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm index 2e3e4afab84b..e44aaeaef356 100644 --- a/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm +++ b/code/modules/mob/living/simple_animal/hostile/jungle/seedling.dm @@ -159,7 +159,7 @@ K.transform = final living_target.adjustFireLoss(30) living_target.adjust_fire_stacks(0.2)//Just here for the showmanship - living_target.IgniteMob() + living_target.ignite_mob() playsound(living_target,'sound/weapons/sear.ogg', 50, 1) addtimer(CALLBACK(src, .proc/AttackRecovery), 5) return 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 c536c55fb915..80ccf16ac8f6 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -152,8 +152,8 @@ Difficulty: Very Hard visible_message(span_colossus("\"Die.\"")) SLEEP_CHECK_DEATH(10) - INVOKE_ASYNC(src, .proc/spiral_shoot, FALSE) - INVOKE_ASYNC(src, .proc/spiral_shoot, TRUE) + INVOKE_ASYNC(src, PROC_REF(spiral_shoot), FALSE) + INVOKE_ASYNC(src, PROC_REF(spiral_shoot), TRUE) /mob/living/simple_animal/hostile/megafauna/colossus/proc/spiral_shoot(negative = pick(TRUE, FALSE), counter_start = 8) var/turf/start_turf = get_step(src, pick(GLOB.alldirs)) @@ -242,7 +242,7 @@ Difficulty: Very Hard /obj/effect/temp_visual/at_shield/Initialize(mapload, new_target) . = ..() target = new_target - INVOKE_ASYNC(src, /atom/movable/proc/orbit, target, 0, FALSE, 0, 0, FALSE, TRUE) + INVOKE_ASYNC(src, TYPE_PROC_REF(/atom/movable/, orbit), target, 0, FALSE, 0, 0, FALSE, TRUE) /mob/living/simple_animal/hostile/megafauna/colossus/bullet_act(obj/item/projectile/P) if(!stat) @@ -691,7 +691,7 @@ Difficulty: Very Hard remove_verb(src, /mob/living/verb/pulled) remove_verb(src, /mob/verb/me_verb) var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] - medsensor.add_hud_to(src) + medsensor.show_to(src) /mob/living/simple_animal/hostile/lightgeist/ghostize() . = ..() @@ -853,8 +853,8 @@ Difficulty: Very Hard 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/escape = new(holder_animal) + escape.Grant(holder_animal) remove_verb(holder_animal, /mob/living/verb/pulled) /obj/structure/closet/stasis/dump_contents(var/kill = 1) @@ -865,7 +865,7 @@ Difficulty: Very Hard L.notransform = 0 if(holder_animal) holder_animal.mind.transfer_to(L) - L.mind.RemoveSpell(/obj/effect/proc_holder/spell/targeted/exit_possession) + holder_animal.gib() if(kill || !isanimal(loc)) L.death(0) ..() @@ -876,32 +876,28 @@ Difficulty: Very Hard /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 = "none" - max_targets = 1 - range = -1 - include_user = TRUE - selection_type = "view" - action_icon = 'icons/mob/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/mob/actions/actions_spells.dmi' + button_icon_state = "exit_possession" + +/datum/action/exit_possession/IsAvailable() + return ..() && isfloorturf(owner.loc) + + +/datum/action/exit_possession/Trigger(trigger_flags) + . = ..() + if(!.) + return FALSE + + 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/mining_mobs/basilisk.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm index 670283b278db..8a76e5f81e31 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/basilisk.dm @@ -139,7 +139,7 @@ var/mob/living/L = target if (istype(L)) L.adjust_fire_stacks(0.1) - L.IgniteMob() + L.ignite_mob() /obj/item/projectile/temp/basilisk/icewing damage = 5 diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm index a7345351c8e7..3a82af2cd7e2 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/elites/legionnaire.dm @@ -275,7 +275,7 @@ if(isliving(mover)) var/mob/living/L = mover L.adjust_fire_stacks(3) - L.IgniteMob() + L.ignite_mob() . = ..() /obj/structure/legionnaire_bonfire/Destroy() diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm index bc94e0962ec5..5c9048b455cd 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/goldgrub.dm @@ -52,15 +52,15 @@ retreat_distance = 10 minimum_distance = 10 if(will_burrow) - addtimer(CALLBACK(src, .proc/Burrow), chase_time) + addtimer(CALLBACK(src, PROC_REF(burrow)), chase_time) /mob/living/simple_animal/hostile/asteroid/goldgrub/AttackingTarget() if(wanted_objects[target.type]) - EatOre(target) + eat_ore(target) return return ..() -/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/EatOre(atom/targeted_ore) +/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/eat_ore(atom/targeted_ore) var/obj/item/stack/ore/O = targeted_ore if(length(loot) < max_loot) var/using = min(max_loot - length(loot), O.amount) @@ -72,7 +72,7 @@ search_objects = 0 visible_message(span_notice("\The [name] nibbles some of the ore and then stops. \She seems to be full!")) -/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/Burrow()//You failed the chase to kill the goldgrub in time! +/mob/living/simple_animal/hostile/asteroid/goldgrub/proc/burrow()//You failed the chase to kill the goldgrub in time! if(stat == CONSCIOUS) visible_message(span_danger("\The [name] buries into the ground, vanishing from sight!")) qdel(src) diff --git a/code/modules/mob/living/simple_animal/hostile/statue.dm b/code/modules/mob/living/simple_animal/hostile/statue.dm index b9ad906f85c2..de2d91a635c1 100644 --- a/code/modules/mob/living/simple_animal/hostile/statue.dm +++ b/code/modules/mob/living/simple_animal/hostile/statue.dm @@ -59,9 +59,13 @@ /mob/living/simple_animal/hostile/statue/Initialize(mapload, var/mob/living/creator) . = ..() // Give spells - mob_spell_list += new /obj/effect/proc_holder/spell/aoe_turf/flicker_lights(src) - mob_spell_list += new /obj/effect/proc_holder/spell/aoe_turf/blindness(src) - mob_spell_list += new /obj/effect/proc_holder/spell/targeted/night_vision(src) + + var/datum/action/cooldown/spell/aoe/flicker_lights/flicker = new(src) + flicker.Grant(src) + var/datum/action/cooldown/spell/aoe/blindness/blind = new(src) + blind.Grant(src) + var/datum/action/cooldown/spell/night_vision/night_vision = new(src) + night_vision.Grant(src) // Set creator if(creator) @@ -164,65 +168,52 @@ // Statue powers // Flicker lights -/obj/effect/proc_holder/spell/aoe_turf/flicker_lights +/datum/action/cooldown/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/cooldown/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 + + things += nearby_light -/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 + return things + +/datum/action/cooldown/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/cooldown/spell/aoe/blindness name = "Blindness" desc = "Your prey will be momentarily blind for you to advance on them." - message = span_notice("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.blind_eyes(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 = span_notice("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() + cooldown_time = 1 MINUTES + spell_requirements = NONE + aoe_radius = 14 + +/datum/action/cooldown/spell/aoe/blindness/cast(atom/cast_on) + cast_on.visible_message(span_danger("[cast_on] glares their eyes.")) + return ..() + +/datum/action/cooldown/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/cooldown/spell/aoe/blindness/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + victim.blind_eyes(4) /mob/living/simple_animal/hostile/statue/sentience_act() faction -= "neutral" diff --git a/code/modules/mob/living/simple_animal/hostile/wizard.dm b/code/modules/mob/living/simple_animal/hostile/wizard.dm index 4ba398a85400..e0107c22c1f5 100644 --- a/code/modules/mob/living/simple_animal/hostile/wizard.dm +++ b/code/modules/mob/living/simple_animal/hostile/wizard.dm @@ -29,12 +29,14 @@ retreat_distance = 3 //out of fireball range minimum_distance = 3 del_on_death = 1 - loot = list(/obj/effect/mob_spawn/human/corpse/wizard, - /obj/item/staff) + loot = list( + /obj/effect/mob_spawn/corpse/human/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/cooldown/spell/pointed/projectile/fireball/fireball + var/datum/action/cooldown/spell/teleport/radius_turf/blink/blink + var/datum/action/cooldown/spell/aoe/magic_missile/magic_missile var/next_cast = 0 @@ -42,42 +44,46 @@ /mob/living/simple_animal/hostile/wizard/Initialize() . = ..() - 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) - - 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) - - blink = new /obj/effect/proc_holder/spell/targeted/turf_teleport/blink - blink.clothes_req = 0 - blink.human_req = 0 - blink.player_lock = 0 + 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) + + magic_missile = new(src) + magic_missile.spell_requirements &= ~(SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_WIZARD_GARB|SPELL_REQUIRES_MIND) + magic_missile.Grant(src) + + 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.Trigger(null, target) + next_cast = world.time + 1 SECONDS + return + + if(magic_missile.IsAvailable()) + magic_missile.Trigger(null, target) + next_cast = world.time + 1 SECONDS + return + + if(blink.IsAvailable()) // Spam Blink when you can + blink.Trigger(null, src) + next_cast = world.time + 1 SECONDS + return /mob/living/simple_animal/hostile/academywizard //weaker wizard, only knows arcane barrage. name = "Academy Student" diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 25dc8d4b8437..edd779851c44 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -234,7 +234,7 @@ ears = headset_to_add to_chat(usr, span_notice("You fit the headset onto [src].")) - clearlist(available_channels) + LAZYCLEARLIST(available_channels) for(var/ch in headset_to_add.channels) switch(ch) if(RADIO_CHANNEL_ENGINEERING) diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index c477d5426e42..4968239cc6e8 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -159,13 +159,6 @@ stat = CONSCIOUS med_hud_set_status() -/mob/living/simple_animal/handle_status_effects() - ..() - if(stuttering) - stuttering = 0 - if(slurring) - slurring = max(slurring-1,0) - /mob/living/simple_animal/proc/handle_automated_action() set waitfor = FALSE return @@ -375,7 +368,7 @@ /mob/living/simple_animal/handle_fire() return TRUE -/mob/living/simple_animal/IgniteMob() +/mob/living/simple_animal/ignite_mob() return FALSE /mob/living/simple_animal/ExtinguishMob() diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index d1d9843963cf..8503c8463602 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -461,3 +461,164 @@ /mob/living/proc/ignore_slowdown(source) ADD_TRAIT(src, TRAIT_IGNORESLOWDOWN, source) update_movespeed(FALSE) + +/** + * Adjusts a timed status effect on the mob,taking into account any existing timed status effects. + * This can be any status effect that takes into account "duration" with their initialize arguments. + * + * Positive durations will add deciseconds to the duration of existing status effects + * or apply a new status effect of that duration to the mob. + * + * Negative durations will remove deciseconds from the duration of an existing version of the status effect, + * removing the status effect entirely if the duration becomes less than zero (less than the current world time). + * + * duration - the duration, in deciseconds, to add or remove from the effect + * effect - the type of status effect being adjusted on the mob + * max_duration - optional - if set, positive durations will only be added UP TO the passed max duration + */ +/mob/living/proc/adjust_timed_status_effect(duration, effect, max_duration) + if(!isnum(duration)) + CRASH("adjust_timed_status_effect: called with an invalid duration. (Got: [duration])") + + if(!ispath(effect, /datum/status_effect)) + CRASH("adjust_timed_status_effect: called with an invalid effect type. (Got: [effect])") + + // If we have a max duration set, we need to check our duration does not exceed it + if(isnum(max_duration)) + if(max_duration <= 0) + CRASH("adjust_timed_status_effect: Called with an invalid max_duration. (Got: [max_duration])") + + if(duration >= max_duration) + duration = max_duration + + var/datum/status_effect/existing = has_status_effect(effect) + if(existing) + if(isnum(max_duration) && duration > 0) + // Check the duration remaining on the existing status effect + // If it's greater than / equal to our passed max duration, we don't need to do anything + var/remaining_duration = existing.duration - world.time + if(remaining_duration >= max_duration) + return + + // Otherwise, add duration up to the max (max_duration - remaining_duration), + // or just add duration if it doesn't exceed our max at all + existing.duration += min(max_duration - remaining_duration, duration) + + else + existing.duration += duration + + // If the duration was decreased and is now less 0 seconds, + // qdel it / clean up the status effect immediately + // (rather than waiting for the process tick to handle it) + if(existing.duration <= world.time) + qdel(existing) + + else if(duration > 0) + apply_status_effect(effect, duration) + +/** + * Sets a timed status effect of some kind on a mob to a specific value. + * If only_if_higher is TRUE, it will only set the value up to the passed duration, + * so any pre-existing status effects of the same type won't be reduced down + * + * duration - the duration, in deciseconds, of the effect. 0 or lower will either remove the current effect or do nothing if none are present + * effect - the type of status effect given to the mob + * only_if_higher - if TRUE, we will only set the effect to the new duration if the new duration is longer than any existing duration + */ +/mob/living/proc/set_timed_status_effect(duration, effect, only_if_higher = FALSE) + if(!isnum(duration)) + CRASH("set_timed_status_effect: called with an invalid duration. (Got: [duration])") + + if(!ispath(effect, /datum/status_effect)) + CRASH("set_timed_status_effect: called with an invalid effect type. (Got: [effect])") + + var/datum/status_effect/existing = has_status_effect(effect) + if(existing) + // set_timed_status_effect to 0 technically acts as a way to clear effects, + // though remove_status_effect would achieve the same goal more explicitly. + if(duration <= 0) + qdel(existing) + return + + if(only_if_higher) + // If the existing status effect has a higher remaining duration + // than what we aim to set it to, don't downgrade it - do nothing (return) + var/remaining_duration = existing.duration - world.time + if(remaining_duration >= duration) + return + + // Set the duration accordingly + existing.duration = world.time + duration + + else if(duration > 0) + apply_status_effect(effect, duration) + +/** + * Gets how many deciseconds are remaining in + * the duration of the passed status effect on this mob. + * + * If the mob is unaffected by the passed effect, returns 0. + */ +/mob/living/proc/get_timed_status_effect_duration(effect) + if(!ispath(effect, /datum/status_effect)) + CRASH("get_timed_status_effect_duration: called with an invalid effect type. (Got: [effect])") + + var/datum/status_effect/existing = has_status_effect(effect) + if(!existing) + return 0 + // Infinite duration status effects technically are not "timed status effects" + // by name or nature, but support is included just in case. + if(existing.duration == -1) + return INFINITY + + return existing.duration - world.time + +/** + * Adjust the "drunk value" the mob is currently experiencing, + * or applies a drunk effect if the mob isn't currently drunk (or tipsy) + * + * The drunk effect doesn't have a set duration, like dizziness or drugginess, + * but instead relies on a value that decreases every status effect tick (2 seconds) by: + * 4% the current drunk_value + 0.01 + * + * A "drunk value" of 6 is the border between "tipsy" and "drunk". + * + * amount - the amount of "drunkness" to apply to the mob. + * down_to - the lower end of the clamp, when adding the value + * up_to - the upper end of the clamp, when adding the value + */ +/mob/living/proc/adjust_drunk_effect(amount, down_to = 0, up_to = INFINITY) + if(!isnum(amount)) + CRASH("adjust_drunk_effect: called with an invalid amount. (Got: [amount])") + + var/datum/status_effect/inebriated/inebriation = has_status_effect(/datum/status_effect/inebriated) + if(inebriation) + inebriation.set_drunk_value(clamp(inebriation.drunk_value + amount, down_to, up_to)) + else if(amount > 0) + apply_status_effect(/datum/status_effect/inebriated/tipsy, amount) + + +/** + * Directly sets the "drunk value" the mob is currently experiencing to the passed value, + * or applies a drunk effect with the passed value if the mob isn't currently drunk + * + * set_to - the amount of "drunkness" to set on the mob. + */ +/mob/living/proc/set_drunk_effect(set_to) + if(!isnum(set_to) || set_to < 0) + CRASH("set_drunk_effect: called with an invalid value. (Got: [set_to])") + + var/datum/status_effect/inebriated/inebriation = has_status_effect(/datum/status_effect/inebriated) + if(inebriation) + inebriation.set_drunk_value(set_to) + else if(set_to > 0) + apply_status_effect(/datum/status_effect/inebriated/tipsy, set_to) + +/// Helper to get the amount of drunkness the mob's currently experiencing. +/mob/living/proc/get_drunk_amount() + var/datum/status_effect/inebriated/inebriation = has_status_effect(/datum/status_effect/inebriated) + return inebriation?.drunk_value || 0 + +/// Helper to check if we seem to be alive or not +/mob/living/proc/appears_alive() + return health >= 0 && !HAS_TRAIT(src, TRAIT_FAKEDEATH) diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm index e8e8ce1fd808..816a2f0b299e 100644 --- a/code/modules/mob/living/taste.dm +++ b/code/modules/mob/living/taste.dm @@ -22,7 +22,7 @@ var/text_output = from.generate_taste_message(taste_sensitivity) // We dont want to spam the same message over and over again at the // person. Give it a bit of a buffer. - if(hallucination > 50 && prob(25)) + if(get_timed_status_effect_duration(/datum/status_effect/hallucination) > 100 SECONDS && prob(25)) text_output = pick("spiders","dreams","nightmares","the future","the past","victory",\ "defeat","pain","bliss","revenge","poison","time","space","death","life","truth","lies","justice","memory",\ "regrets","your soul","suffering","music","noise","blood","hunger","the american way") diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 670d303561d1..151b223f6ca5 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -24,6 +24,7 @@ /mob/Login() if(!client) return FALSE + canon_client = client add_to_player_list() lastKnownIP = client.address computer_id = client.computer_id diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index bcaec997b825..e0ec7fe1e4ec 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -96,22 +96,69 @@ tag = "mob_[next_mob_id++]" /** - * Prepare the huds for this atom - * - * Goes through hud_possible list and adds the images to the hud_list variable (if not already - * cached) - */ + * set every hud image in the given category active so other people with the given hud can see it. + * Arguments: + * * hud_category - the index in our active_hud_list corresponding to an image now being shown. + * * update_huds - if FALSE we will just put the hud_category into active_hud_list without actually updating the atom_hud datums subscribed to it + * * exclusive_hud - if given a reference to an atom_hud, will just update that hud instead of all global ones attached to that category. + * This is because some atom_hud subtypes arent supposed to work via global categories, updating normally would affect all of these which we dont want. + */ +/atom/proc/set_hud_image_active(hud_category, update_huds = TRUE, datum/atom_hud/exclusive_hud) + if(!istext(hud_category) || !hud_list?[hud_category] || active_hud_list?[hud_category]) + return FALSE + + LAZYSET(active_hud_list, hud_category, hud_list[hud_category]) + + if(!update_huds) + return TRUE + + if(exclusive_hud) + exclusive_hud.add_single_hud_category_on_atom(src, hud_category) + else + for(var/datum/atom_hud/hud_to_update as anything in GLOB.huds_by_category[hud_category]) + hud_to_update.add_single_hud_category_on_atom(src, hud_category) + + return TRUE + +///sets every hud image in the given category inactive so no one can see it +/atom/proc/set_hud_image_inactive(hud_category, update_huds = TRUE, datum/atom_hud/exclusive_hud) + if(!istext(hud_category)) + return FALSE + + if(!update_huds) + LAZYREMOVE(active_hud_list, hud_category) + return TRUE + + if(exclusive_hud) + exclusive_hud.remove_single_hud_category_on_atom(src, hud_category) + else + for(var/datum/atom_hud/hud_to_update as anything in GLOB.huds_by_category[hud_category]) + hud_to_update.remove_single_hud_category_on_atom(src, hud_category) + + LAZYREMOVE(active_hud_list, hud_category) + + return TRUE + +/** + * Prepare the huds for this atom + * + * Goes through hud_possible list and adds the images to the hud_list variable (if not already cached) + */ /atom/proc/prepare_huds() + if(hud_list) // I choose to be lienient about people calling this proc more then once + return hud_list = list() for(var/hud in hud_possible) var/hint = hud_possible[hud] - switch(hint) - if(HUD_LIST_LIST) - hud_list[hud] = list() - else - var/image/I = image('icons/mob/hud.dmi', src, "") - I.appearance_flags = RESET_COLOR|RESET_TRANSFORM - hud_list[hud] = I + + if(hint == HUD_LIST_LIST) + hud_list[hud] = list() + + else + var/image/I = image('yogstation/icons/mob/hud.dmi', src, "") + I.appearance_flags = RESET_COLOR|RESET_TRANSFORM + hud_list[hud] = I + set_hud_image_active(hud, update_huds = FALSE) //by default everything is active. but dont add it to huds to keep control. /** * Some kind of debug verb that gives atmosphere environment details @@ -283,6 +330,15 @@ /mob/proc/get_item_by_slot(slot_id) return null +/// Gets what slot the item on the mob is held in. +/// Returns null if the item isn't in any slots on our mob. +/// Does not check if the passed item is null, which may result in unexpected outcoms. +/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 restrained /mob/proc/restrained(ignore_grab) return @@ -844,29 +900,33 @@ . += "[obj_count]: [objective.explanation_text][objective.check_completion() ? " (COMPLETED)" : ""]" obj_count++ -/mob/proc/get_proc_holders() - . = list() - if(mind) - . += get_spells_for_statpanel(mind.spell_list) - . += get_spells_for_statpanel(mob_spell_list) - /** * Convert a list of spells into a displyable list for the statpanel * * Shows charge and other important info */ -/mob/proc/get_spells_for_statpanel(list/spells) - var/list/L = list() - for(var/obj/effect/proc_holder/spell/S in spells) - if(S.can_be_cast_by(src)) - switch(S.charge_type) - if("recharge") - L[++L.len] = list("[S.panel]", "[S.charge_counter/10.0]/[S.charge_max/10]", S.name, REF(S)) - if("charges") - L[++L.len] = list("[S.panel]", "[S.charge_counter]/[S.charge_max]", S.name, REF(S)) - if("holdervar") - L[++L.len] = list("[S.panel]", "[S.holder_var_type] [S.holder_var_amount]", S.name, REF(S)) - return L +/mob/proc/get_actions_for_statpanel() + var/list/data = list() + for(var/datum/action/cooldown/action in actions) + var/list/action_data = action.set_statpanel_format() + if(!length(action_data)) + return + + data += list(list( + // the panel the action gets displayed to + // in the future, this could probably be replaced with subtabs (a la admin tabs) + action_data[PANEL_DISPLAY_PANEL], + // the status of the action, - cooldown, charges, whatever + action_data[PANEL_DISPLAY_STATUS], + // the name of the action + action_data[PANEL_DISPLAY_NAME], + // a ref to the action button of this action for this mob + // it's a ref to the button specifically, instead of the action itself, + // because statpanel href calls click(), which the action button (not the action itself) handles + REF(action.viewers[hud_used]), + )) + + return data #define MOB_FACE_DIRECTION_DELAY 1 @@ -938,23 +998,6 @@ 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) - mob_spell_list += S - S.action?.Grant(src) - -///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) - if(client) - client << output(null, "statbrowser:check_spells") - ///Return any anti magic atom on this mob that matches the magic type /mob/proc/anti_magic_check(magic = TRUE, holy = FALSE, tinfoil = FALSE, chargecost = 1, self = FALSE) return @@ -1275,6 +1318,13 @@ /mob/proc/set_nutrition(var/change) //Seriously fuck you oldcoders. nutrition = max(0, change) +/mob/proc/set_stat(new_stat) + if(new_stat == stat) + return + . = stat + stat = new_stat + SEND_SIGNAL(src, COMSIG_MOB_STATCHANGE, new_stat, .) + ///Set the movement type of the mob and update it's movespeed /mob/setMovetype(newval) . = ..() diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 56355ba108fc..c3c2ca83a065 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -21,6 +21,12 @@ var/lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE var/datum/mind/mind var/static/next_mob_id = 0 + /// The current client inhabiting this mob. Managed by login/logout + /// This exists so we can do cleanup in logout for occasions where a client was transfere rather then destroyed + /// We need to do this because the mob on logout never actually has a reference to client + /// We also need to clear this var/do other cleanup in client/Destroy, since that happens before logout + /// HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + var/client/canon_client /// List of movement speed modifiers applying to this mob var/list/movespeed_modification //Lazy list, see mob_movespeed.dm @@ -30,8 +36,9 @@ var/list/datum/action/actions = list() /// Actions that belong to this mob used in observers var/list/datum/action/originalactions = 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 @@ -85,13 +92,6 @@ /// Default body temperature var/bodytemperature = BODYTEMP_NORMAL //310.15K / 98.6F - /// Drowsyness level of the mob - var/drowsyness = 0//Carbon - /// Dizziness level of the mob - var/dizziness = 0//Carbon - /// Jitteryness level of the mob - var/jitteriness = 0//Carbon - /// Hunger level of the mob var/nutrition = NUTRITION_LEVEL_START_MIN // randomised in Initialize /// Satiation level of the mob var/satiety = 0//Carbon @@ -154,15 +154,6 @@ ///A weakref to the last mob/living/carbon to push/drag/grab this mob (exclusively used by slimes friend recognition) var/datum/weakref/LAssailant = null - /** - * 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_movement.dm b/code/modules/mob/mob_movement.dm index 1d095c73add7..88992cbd2a1d 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -140,13 +140,13 @@ else move_delay = world.time - if(L.confused) + if(L.has_status_effect(/datum/status_effect/confusion)) var/newdir = 0 - if(L.confused > 40) + if(L.get_timed_status_effect_duration(/datum/status_effect/confusion) > 40) newdir = pick(GLOB.alldirs) - else if(prob(L.confused * 1.5)) + else if(prob(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 1.5)) newdir = angle2dir(dir2angle(direct) + pick(90, -90)) - else if(prob(L.confused * 3)) + else if(prob(L.get_timed_status_effect_duration(/datum/status_effect/confusion) * 3)) newdir = angle2dir(dir2angle(direct) + pick(45, -45)) if(newdir) direct = newdir diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 852e2955c7e1..3ee05bf0c893 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -61,9 +61,15 @@ return whisper(message) -///whisper a message -/mob/proc/whisper(message, datum/language/language=null) - say(message, language) //only living mobs actually whisper, everything else just talks +/** + * 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 = language) /mob/verb/me_wrapper() set name = ".me" diff --git a/code/modules/mob/status_procs.dm b/code/modules/mob/status_procs.dm index 0f11b1c2b117..ad0730f3cf4d 100644 --- a/code/modules/mob/status_procs.dm +++ b/code/modules/mob/status_procs.dm @@ -3,24 +3,6 @@ //The effects include: stun, knockdown, unconscious, sleeping, resting, jitteriness, dizziness, ear damage, // eye damage, eye_blind, eye_blurry, druggy, TRAIT_BLIND trait, and TRAIT_NEARSIGHT trait. - - -///Set the jitter of a mob -/mob/proc/Jitter(amount) - jitteriness = max(jitteriness,amount,0) - -/** - * Set the dizzyness of a mob to a passed in amount - * - * Except if dizziness is already higher in which case it does nothing - */ -/mob/proc/Dizzy(amount) - dizziness = max(dizziness,amount,0) - -///FOrce set the dizzyness of a mob -/mob/proc/set_dizziness(amount) - dizziness = max(amount, 0) - ///Blind a mobs eyes by amount /mob/proc/blind_eyes(amount) if(amount>0) @@ -118,14 +100,6 @@ GW.backdrop(src) OT.backdrop(src) -///Adjust the drugginess of a mob -/mob/proc/adjust_drugginess(amount) - return - -///Set the drugginess of a mob -/mob/proc/set_drugginess(amount) - return - ///Adjust the disgust level of a mob /mob/proc/adjust_disgust(amount) return diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 3e13cf7a4590..0764f2aa2654 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -22,6 +22,8 @@ var/last_battery_percent = 0 var/last_world_time = "00:00" var/list/last_header_icons + ///A pAI currently loaded into the modular computer. + var/obj/item/paicard/inserted_pai /// Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too. var/base_active_power_usage = 50 @@ -113,7 +115,7 @@ if(active_program?.tap(A, user, params)) user.do_attack_animation(A) //Emulate this animation since we kill the attack in three lines playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) //Likewise for the tap sound - addtimer(CALLBACK(src, .proc/play_ping), 0.5 SECONDS, TIMER_UNIQUE) //Slightly delayed ping to indicate success + addtimer(CALLBACK(src, PROC_REF(play_ping)), 0.5 SECONDS, TIMER_UNIQUE) //Slightly delayed ping to indicate success return return ..() diff --git a/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm b/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm index c8c05bf93d6b..9b731f1261df 100644 --- a/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm +++ b/code/modules/ninja/suit/n_suit_verbs/ninja_adrenaline.dm @@ -10,7 +10,7 @@ H.SetImmobilized(0) H.SetParalyzed(0) H.adjustStaminaLoss(-75) - H.stuttering = 0 + H.remove_status_effect(/datum/status_effect/speech/stutter) H.lying = 0 H.update_mobility() H.reagents.add_reagent(/datum/reagent/medicine/stimulants, 5) diff --git a/code/modules/pai/actions.dm b/code/modules/pai/actions.dm new file mode 100644 index 000000000000..72ed1ecc9a49 --- /dev/null +++ b/code/modules/pai/actions.dm @@ -0,0 +1,62 @@ +/datum/action/innate/pai + name = "PAI Action" + button_icon = 'icons/mob/actions/actions_silicon.dmi' + var/mob/living/silicon/pai/pai_owner + +/datum/action/innate/pai/Trigger(trigger_flags) + if(!ispAI(owner)) + return FALSE + pai_owner = owner + +/datum/action/innate/pai/software + name = "Software Interface" + button_icon_state = "pai" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/software/Trigger(trigger_flags) + ..() + pai_owner.ui_act() + +/datum/action/innate/pai/shell + name = "Toggle Holoform" + button_icon_state = "pai_holoform" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/shell/Trigger(trigger_flags) + ..() + if(pai_owner.holoform) + pai_owner.fold_in(0) + else + pai_owner.fold_out() + +/datum/action/innate/pai/chassis + name = "Holochassis Appearance Composite" + button_icon_state = "pai_chassis" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/chassis/Trigger(trigger_flags) + ..() + pai_owner.choose_chassis() + +/datum/action/innate/pai/rest + name = "Rest" + button_icon_state = "pai_rest" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/rest/Trigger(trigger_flags) + ..() + var/mob/living/silicon/pai/pAI = usr + if(!pAI.resting) + pAI.set_resting(TRUE) + else + pAI.set_resting(FALSE) + +/datum/action/innate/pai/light + name = "Toggle Integrated Lights" + button_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "emp" + background_icon_state = "bg_tech" + +/datum/action/innate/pai/light/Trigger(trigger_flags) + ..() + pai_owner.toggle_integrated_light() diff --git a/code/modules/pai/camera.dm b/code/modules/pai/camera.dm new file mode 100644 index 000000000000..30b1918651e4 --- /dev/null +++ b/code/modules/pai/camera.dm @@ -0,0 +1,58 @@ +/mob/living/silicon/pai/ClickOn(atom/target, params) + ..() + if(!camera?.in_camera_mode) + return FALSE + //pAI picture taking + camera.toggle_camera_mode(sound = FALSE) + camera.captureimage(target, usr, camera.picture_size_x - 1, camera.picture_size_y - 1) + return TRUE + +/obj/item/camera/siliconcam/pai_camera + name = "pAI photo camera" + light_color = COLOR_PAI_GREEN + +/obj/item/camera/siliconcam/pai_camera/after_picture(mob/user, datum/picture/picture) + var/number = length(stored) + picture.picture_name = "Image [number] (taken by [loc.name])" + stored[picture] = TRUE + playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3) + balloon_alert(user, "image recorded") + +/** + * Handles selecting and printing stored images. + * + * @param {mob} user - The pAI. + * + * @returns {boolean} - TRUE if the pAI prints an image, + * FALSE otherwise. +*/ +/obj/item/camera/siliconcam/pai_camera/proc/pai_print(mob/user) + var/mob/living/silicon/pai/pai = loc + var/datum/picture/selection = selectpicture(user) + if(!istype(selection)) + balloon_alert(user, "invalid image") + return FALSE + printpicture(user, selection) + user.visible_message(span_notice("A picture appears on top of the chassis of [pai.name]!"), span_notice("You print a photograph.")) + return TRUE + +/** + * All inclusive camera proc. Zooms, snaps, prints. + * + * @param {mob} user - The pAI requesting the camera. + * + * @param {string} mode - The camera option to toggle. + * + * @returns {boolean} - TRUE if the camera worked. + */ +/mob/living/silicon/pai/proc/use_camera(mob/user, mode) + if(!camera || isnull(mode)) + return FALSE + switch(mode) + if(PAI_PHOTO_MODE_CAMERA) + camera.toggle_camera_mode(user) + if(PAI_PHOTO_MODE_PRINTER) + camera.pai_print(user) + if(PAI_PHOTO_MODE_ZOOM) + camera.adjust_zoom(user) + return TRUE diff --git a/code/modules/pai/candidate.dm b/code/modules/pai/candidate.dm new file mode 100644 index 000000000000..6455492e1379 --- /dev/null +++ b/code/modules/pai/candidate.dm @@ -0,0 +1,35 @@ +/** + * #pAI Candidate + * + * Created when a user opens the pAI submit interface. + * Stores the candidate in an associative list of ckey: candidate objects. + */ +/datum/pai_candidate + /// User inputted OOC comments + var/comments + /// User inputted behavior description + var/description + /// User's ckey + var/ckey + /// User's pAI name. If blank, ninja name. + var/name + /// If the user has hit "submit" + var/ready = FALSE + +/datum/pai_candidate/New(ckey) + src.ckey = ckey + +/** + * Checks if a candidate is ready so that they may be displayed or + * downloaded. Removes any invalid entries. + * + * @returns {boolean} - TRUE if the candidate is ready, FALSE if not + */ +/datum/pai_candidate/proc/check_ready() + var/mob/candidate_mob = get_mob_by_key(ckey) + if(!candidate_mob?.client || !isobserver(candidate_mob) || is_banned_from(ckey, ROLE_PAI)) + SSpai.candidates -= ckey + return FALSE + if(!ready) + return FALSE + return TRUE diff --git a/code/modules/pai/card.dm b/code/modules/pai/card.dm new file mode 100644 index 000000000000..312b8a26abc1 --- /dev/null +++ b/code/modules/pai/card.dm @@ -0,0 +1,259 @@ +/obj/item/pai_card + name = "personal AI device" + desc = "Downloads personal AI assistants to accompany its owner or others." + icon = 'icons/obj/aicards.dmi' + icon_state = "pai" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + cryo_preserve = TRUE + /// Spam alert prevention + var/alert_cooldown + /// The emotion icon displayed. + var/emotion_icon = "off" + /// Any pAI personalities inserted + var/mob/living/silicon/pai/pai + /// Prevents a crew member from hitting "request pAI" repeatedly + var/request_spam = FALSE + +/obj/item/pai_card/attackby(obj/item/used, mob/user, params) + if(pai && istype(used, /obj/item/encryptionkey)) + if(!pai.encrypt_mod) + to_chat(user, span_alert("Encryption Key ports not configured.")) + return + user.set_machine(src) + pai.radio.attackby(used, user, params) + to_chat(user, span_notice("You insert [used] into the [src].")) + return + return ..() + +/obj/item/pai_card/attack_self(mob/user) + if(!in_range(src, user)) + return + user.set_machine(src) + ui_interact(user) + +/obj/item/pai_card/Destroy() + //Will stop people throwing friend pAIs into the singularity so they can respawn + SSpai.pai_card_list.Remove(src) + if(!QDELETED(pai)) + QDEL_NULL(pai) + return ..() + +/obj/item/pai_card/emag_act(mob/user) + if(pai) + pai.handle_emag(user) + +/obj/item/pai_card/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + if(pai && !pai.holoform) + pai.emp_act(severity) + +/obj/item/pai_card/handle_atom_del(atom/thing) + if(thing == pai) //double check /mob/living/silicon/pai/Destroy() if you change these. + pai = null + emotion_icon = initial(emotion_icon) + update_icon() + return ..() + +/obj/item/pai_card/Initialize(mapload) + . = ..() + update_icon() + SSpai.pai_card_list += src + +/obj/item/pai_card/suicide_act(mob/living/user) + user.visible_message(span_suicide("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!")) + return OXYLOSS + +/obj/item/pai_card/proc/update_overlays() + . = ..() + . += "pai-[emotion_icon]" + if(pai?.hacking_cable) + . += "[initial(icon_state)]-connector" + +/obj/item/pai_card/vv_edit_var(vname, vval) + . = ..() + if(vname == NAMEOF(src, emotion_icon)) + update_icon() + +/obj/item/pai_card/ui_interact(mob/user, datum/tgui/ui) + . = ..() + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PaiCard") + ui.open() + +/obj/item/pai_card/ui_status(mob/user) + if(user in get_nested_locs(src)) + return UI_INTERACTIVE + return ..() + +/obj/item/pai_card/ui_data(mob/user) + . = ..() + var/list/data = list() + if(!pai) + data["candidates"] = pool_candidates() || list() + return data + data["pai"] = list( + can_holo = pai.can_holo, + dna = pai.master_dna, + emagged = pai.emagged, + laws = pai.laws.supplied, + master = pai.master_name, + name = pai.name, + transmit = pai.can_transmit, + receive = pai.can_receive, + ) + return data + +/obj/item/pai_card/ui_act(action, list/params, datum/tgui/ui) + . = ..() + if(.) + return TRUE + // Actions that don't require a pAI + if(action == "download") + download_candidate(usr, params["ckey"]) + return TRUE + if(action == "request") + find_pai(usr) + return TRUE + // pAI specific actions. + if(!pai) + return FALSE + switch(action) + if("fix_speech") + pai.fix_speech() + return TRUE + if("reset_software") + pai.reset_software() + return TRUE + if("set_dna") + pai.set_dna(usr) + return TRUE + if("set_laws") + pai.set_laws(usr) + return TRUE + if("toggle_holo") + pai.toggle_holo() + return TRUE + if("toggle_radio") + pai.toggle_radio(params["option"]) + return TRUE + if("wipe_pai") + pai.wipe_pai(usr) + ui.close() + return TRUE + return FALSE + +/** Flashes the pai card screen */ +/obj/item/pai_card/proc/add_alert() + if(pai) + return + add_overlay( + list(mutable_appearance(icon, "[initial(icon_state)]-alert"), + mutable_appearance(icon, "[initial(icon_state)]-alert", src, alpha = src.alpha))) + +/** Removes any overlays */ +/obj/item/pai_card/proc/remove_alert() + if(pai) + return + cut_overlays() + +/** Alerts pAI cards that someone has submitted candidacy */ +/obj/item/pai_card/proc/alert_update() + if(!COOLDOWN_FINISHED(src, alert_cooldown)) + return + COOLDOWN_START(src, alert_cooldown, 5 SECONDS) + add_alert() + addtimer(CALLBACK(src, PROC_REF(remove_alert)), 5 SECONDS) + playsound(src, 'sound/machines/ping.ogg', 30, TRUE) + visible_message(span_notice("[src] flashes a message across its screen: New personalities available for download!"), blind_message = span_notice("[src] vibrates with an alert.")) + +/** + * Downloads a candidate from the list and removes them from SSpai.candidates + * + * @param {string} ckey The ckey of the candidate to download + * + * @returns {boolean} - TRUE if the candidate was downloaded, FALSE if not + */ +/obj/item/pai_card/proc/download_candidate(mob/user, ckey) + if(pai) + return FALSE + var/datum/pai_candidate/candidate = SSpai.candidates[ckey] + if(!candidate?.check_ready()) + balloon_alert(user, "download interrupted") + return FALSE + var/mob/living/silicon/pai/new_pai = new(src) + new_pai.name = candidate.name || pick(GLOB.ninja_names) + new_pai.real_name = new_pai.name + new_pai.key = candidate.ckey + set_personality(new_pai) + SSpai.candidates -= ckey + return TRUE + +/** + * Pings ghosts to announce that someone is requesting a pAI + * + * @param {mob} user - The user who is requesting a pAI + * + * @returns {boolean} - TRUE if the pAI was requested, FALSE if not + */ +/obj/item/pai_card/proc/find_pai(mob/user) + if(pai) + return FALSE + if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS)) + balloon_alert(user, "unavailable: NT blacklisted") + return FALSE + if(request_spam) + balloon_alert(user, "request sent too recently") + return FALSE + request_spam = TRUE + playsound(src, 'sound/machines/ping.ogg', 20, TRUE) + balloon_alert(user, "pAI assistance requested") + var/mutable_appearance/alert_overlay = mutable_appearance('icons/obj/aicards.dmi', "pai") + notify_ghosts("[user] is requesting a pAI companion! Use the pAI button to submit yourself as one.", source = user, alert_overlay = alert_overlay, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "pAI Request!", ignore_key = POLL_IGNORE_PAI) + addtimer(VARSET_CALLBACK(src, request_spam, FALSE), PAI_SPAM_TIME, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_CLIENT_TIME | TIMER_DELETE_ME) + return TRUE + +/** + * Gathers a list of candidates to display in the download candidate + * window. If the candidate isn't marked ready, ie they have not + * pressed submit, they will be skipped over. + * + * @returns - An array of candidate objects. + */ +/obj/item/pai_card/proc/pool_candidates() + var/list/candidates = list() + if(pai || !length(SSpai?.candidates)) + return candidates + for(var/key in SSpai.candidates) + var/datum/pai_candidate/candidate = SSpai.candidates[key] + if(!candidate?.check_ready()) + continue + candidates += list(list( + ckey = candidate.ckey, + comments = candidate.comments, + description = candidate.description, + name = candidate.name, + )) + return candidates + +/** + * Sets the personality on the current pai_card + * + * @param {silicon/pai} downloaded - The new pAI to load into the card. + */ +/obj/item/pai_card/proc/set_personality(mob/living/silicon/pai/downloaded) + if(pai) + return FALSE + pai = downloaded + emotion_icon = "null" + update_icon() + playsound(src, 'sound/effects/pai_boot.ogg', 50, TRUE, -1) + audible_message("[src] plays a cheerful startup noise!") + return TRUE diff --git a/code/modules/pai/death.dm b/code/modules/pai/death.dm new file mode 100644 index 000000000000..62cd82fe009b --- /dev/null +++ b/code/modules/pai/death.dm @@ -0,0 +1,19 @@ +/mob/living/silicon/pai/death(gibbed) + if(stat == DEAD) + return + set_stat(DEAD) + update_sight() + clear_fullscreens() + /** + * New pAI's get a brand new mind to prevent meta stuff from their previous + * life. This new mind causes problems down the line if it's not deleted here. + */ + ghostize() + + if (!QDELETED(card) && loc != card) + card.forceMove(drop_location()) + card.pai = null + card.emotion_icon = initial(card.emotion_icon) + card.update_icon() + + qdel(src) diff --git a/code/modules/pai/debug.dm b/code/modules/pai/debug.dm new file mode 100644 index 000000000000..63d6a8eb5020 --- /dev/null +++ b/code/modules/pai/debug.dm @@ -0,0 +1,45 @@ +/client/proc/makepAI(turf/target in GLOB.mob_list) + set category = "Admin.Fun" + set name = "Make pAI" + set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI" + + var/list/available = list() + for(var/mob/player as anything in GLOB.player_list) + if(player.client && player.key) + available.Add(player) + var/mob/choice = tgui_input_list(usr, "Choose a player to play the pAI", "Spawn pAI", available) + if(isnull(choice)) + return + + var/chosen_name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text|null + if (isnull(chosen_name)) + return + + if(!isobserver(choice)) + var/confirm = tgui_alert(usr, "[choice.key] isn't ghosting right now. Are you sure you want to yank them out of their body and place them in this pAI?", "Spawn pAI Confirmation", list("Yes", "No")) + if(confirm != "Yes") + return + var/obj/item/pai_card/card = new(target) + var/mob/living/silicon/pai/pai = new(card) + + pai.name = chosen_name + pai.real_name = pai.name + pai.key = choice.key + card.set_personality(pai) + if(SSpai.candidates[key]) + SSpai.candidates -= key + SSblackbox.record_feedback("tally", "admin_verb", 1, "Make pAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + +/** + * Creates a new pAI. + * + * @param {boolean} delete_old - If TRUE, deletes the old pAI. + */ +/mob/proc/make_pai(delete_old) + var/obj/item/pai_card/card = new(src) + var/mob/living/silicon/pai/pai = new(card) + pai.key = key + pai.name = name + card.set_personality(pai) + if(delete_old) + qdel(src) diff --git a/code/modules/pai/defense.dm b/code/modules/pai/defense.dm new file mode 100644 index 000000000000..574aeb1c976e --- /dev/null +++ b/code/modules/pai/defense.dm @@ -0,0 +1,90 @@ +/mob/living/silicon/pai/blob_act(obj/structure/blob/B) + return FALSE + +/mob/living/silicon/pai/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + take_holo_damage(50 / severity) + Stun(40 SECONDS / severity) + if(holoform) + fold_in(force = TRUE) + //Need more effects that aren't instadeath or permanent law corruption. + //Ask and you shall receive + switch(rand(1, 3)) + if(1) + stuttering = 1 MINUTES / severity + to_chat(src, span_danger("Warning: Feedback loop detected in speech module.")) + if(2) + slurring = INFINITY + to_chat(src, span_danger("Warning: Audio synthesizer CPU stuck.")) + if(3) + derpspeech = INFINITY + to_chat(src, span_danger("Warning: Vocabulary databank corrupted.")) + if(prob(40)) + mind.language_holder.selected_language = get_random_spoken_language() + + +/mob/living/silicon/pai/ex_act(severity, target) + take_holo_damage(50 * severity) + switch(severity) + if(EXPLODE_DEVASTATE) //RIP + qdel(card) + qdel(src) + if(EXPLODE_HEAVY) + fold_in(force = 1) + Paralyze(400) + if(EXPLODE_LIGHT) + fold_in(force = 1) + Paralyze(200) + +/mob/living/silicon/pai/attack_hand(mob/living/carbon/human/user, list/modifiers) + if(user.a_intent == INTENT_HELP) + visible_message(span_notice("[user] gently pats [src] on the head, eliciting an off-putting buzzing from its holographic field.")) + return + user.do_attack_animation(src) + if(user.name != master_name) + visible_message(span_danger("[user] stomps on [src]!.")) + take_holo_damage(2) + return + visible_message(span_notice("Responding to its master's touch, [src] disengages its holochassis emitter, rapidly losing coherence.")) + if(!do_after(user, 1 SECONDS, src)) + return + fold_in() + if(user.put_in_hands(card)) + user.visible_message(span_notice("[user] promptly scoops up [user.p_their()] pAI's card.")) + +/mob/living/silicon/pai/bullet_act(obj/item/projectile/Proj) + if(Proj.stun) + fold_in(force = TRUE) + src.visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!")) + . = ..(Proj) + +/mob/living/silicon/pai/ignite_mob(silent) + return FALSE + +/mob/living/silicon/pai/proc/take_holo_damage(amount) + holochassis_health = clamp((holochassis_health - amount), -50, HOLOCHASSIS_MAX_HEALTH) + if(holochassis_health < 0) + fold_in(force = TRUE) + if(amount > 0) + to_chat(src, span_userdanger("The impact degrades your holochassis!")) + return amount + +/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + return take_holo_damage(amount) + +/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_bodytype) + return take_holo_damage(amount) + +/mob/living/silicon/pai/adjustStaminaLoss(amount, updating_stamina, forced = FALSE, required_biotype) + if(forced) + take_holo_damage(amount) + else + take_holo_damage(amount * 0.25) + +/mob/living/silicon/pai/getBruteLoss() + return HOLOCHASSIS_MAX_HEALTH - holochassis_health + +/mob/living/silicon/pai/getFireLoss() + return HOLOCHASSIS_MAX_HEALTH - holochassis_health diff --git a/code/modules/pai/door_jack.dm b/code/modules/pai/door_jack.dm new file mode 100644 index 000000000000..50013cf5862c --- /dev/null +++ b/code/modules/pai/door_jack.dm @@ -0,0 +1,129 @@ +#define CABLE_LENGTH 2 + +/** + * Switch that handles door jack operations. + * + * @param {string} mode - The requested operation of the door jack. + * + * @returns {boolean} - TRUE if the door jack state was switched, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/door_jack(mode) + if(isnull(mode)) + return FALSE + switch(mode) + if(PAI_DOOR_JACK_CABLE) + extend_cable() + return TRUE + if(PAI_DOOR_JACK_HACK) + hack_door() + return TRUE + if(PAI_DOOR_JACK_CANCEL) + QDEL_NULL(hacking_cable) + visible_message(span_notice("The cable retracts into the pAI.")) + return TRUE + return FALSE + +/** + * #Extend cable supporting proc + * + * When doorjack is installed, allows the pAI to drop + * a cable which is placed either on the floor or in + * someone's hands based (on distance). + * + * @returns {boolean} - TRUE if the cable was dropped, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/extend_cable() + QDEL_NULL(hacking_cable) //clear any old cables + hacking_cable = new + var/mob/living/carbon/hacker = get_holder() + if(iscarbon(hacker) && hacker.put_in_hands(hacking_cable)) //important to double check since get_holder can return non-null values that aren't carbons. + hacker.visible_message(span_notice("A port on [src] opens to reveal a cable, which [hacker] quickly grabs."), span_notice("A port on [src] opens to reveal a cable, which you quickly grab."), span_hear("You hear the soft click of a plastic component and manage to catch the falling cable.")) + track_pai() + track_thing(hacking_cable) + return TRUE + hacking_cable.forceMove(drop_location()) + hacking_cable.visible_message(message = span_notice("A port on [src] opens to reveal a cable, which promptly falls to the floor."), blind_message = span_hear("You hear the soft click of a plastic component fall to the ground.")) + track_pai() + track_thing(hacking_cable) + return TRUE + +/** Tracks the associated pai */ +/mob/living/silicon/pai/proc/track_pai() + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + RegisterSignal(card, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + +/** Untracks the associated pai */ +/mob/living/silicon/pai/proc/untrack_pai() + UnregisterSignal(src, COMSIG_MOVABLE_MOVED) + UnregisterSignal(card, COMSIG_MOVABLE_MOVED) + +/** Tracks the associated hacking_cable */ +/mob/living/silicon/pai/proc/track_thing(atom/movable/thing) + RegisterSignal(thing, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + var/list/locations = get_nested_locs(thing, include_turf = FALSE) + for(var/atom/movable/location in locations) + RegisterSignal(location, COMSIG_MOVABLE_MOVED, PROC_REF(handle_move)) + +/** Untracks the associated hacking */ +/mob/living/silicon/pai/proc/untrack_thing(atom/movable/thing) + UnregisterSignal(thing, COMSIG_MOVABLE_MOVED) + var/list/locations = get_nested_locs(thing, include_turf = FALSE) + for(var/atom/movable/location in locations) + UnregisterSignal(location, COMSIG_MOVABLE_MOVED) + +/** + * A periodic check to see if the source pAI is nearby. + * Deletes the extended cable if the source pAI is not nearby. + */ +/mob/living/silicon/pai/proc/handle_move(atom/movable/source, atom/movable/old_loc) + if(ismovable(old_loc)) + untrack_thing(old_loc) + if(hacking_cable && (!IN_GIVEN_RANGE(src, hacking_cable, CABLE_LENGTH))) + retract_cable() + return + if(ismovable(source.loc)) + track_thing(source.loc) + +/** + * Handles deleting the hacking cable and notifying the user. + */ +/mob/living/silicon/pai/proc/retract_cable() + balloon_alert(src, "cable retracted") + untrack_pai() + QDEL_NULL(hacking_cable) + return TRUE + +/** + * #Door jacking supporting proc + * + * After a 15 second timer, the door will crack open, + * provided they don't move out of the way. + * + * @returns {boolean} - TRUE if the door was jacked, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/hack_door() + if(!hacking_cable) + return FALSE + if(!hacking_cable.machine) + balloon_alert(src, "nothing connected") + return FALSE + playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE) + balloon_alert(src, "overriding...") + // Now begin hacking + if(!do_after(src, 15 SECONDS, hacking_cable.machine, timed_action_flags = NONE, progress = TRUE)) + balloon_alert(src, "failed! retracting...") + untrack_pai() + untrack_thing(hacking_cable) + QDEL_NULL(hacking_cable) + if(!QDELETED(card)) + card.update_icon() + return FALSE + var/obj/machinery/door/door = hacking_cable.machine + balloon_alert(src, "success") + door.open() + untrack_pai() + untrack_thing(hacking_cable) + QDEL_NULL(hacking_cable) + return TRUE + +#undef CABLE_LENGTH diff --git a/code/modules/pai/hud.dm b/code/modules/pai/hud.dm new file mode 100644 index 000000000000..52df720e507e --- /dev/null +++ b/code/modules/pai/hud.dm @@ -0,0 +1,267 @@ +#define PAI_MISSING_SOFTWARE_MESSAGE span_warning("You must download the required software to use this.") + +/atom/movable/screen/pai + icon = 'icons/mob/screen_pai.dmi' + var/required_software + +/atom/movable/screen/pai/Click() + if(isobserver(usr) || usr.incapacitated()) + return FALSE + var/mob/living/silicon/pai/user = usr + if(required_software && !user.installed_software.Find(required_software)) + to_chat(user, PAI_MISSING_SOFTWARE_MESSAGE) + return FALSE + return TRUE + +/atom/movable/screen/pai/software + name = "Software Interface" + icon_state = "pai" + +/atom/movable/screen/pai/software/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.ui_interact(pAI) + +/atom/movable/screen/pai/shell + name = "Toggle Holoform" + icon_state = "pai_holoform" + +/atom/movable/screen/pai/shell/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + if(pAI.holoform) + pAI.fold_in(0) + else + pAI.fold_out() + +/atom/movable/screen/pai/chassis + name = "Holochassis Appearance Composite" + icon_state = "pai_chassis" + +/atom/movable/screen/pai/chassis/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.choose_chassis() + +/atom/movable/screen/pai/rest + name = "Rest" + icon_state = "pai_rest" + +/atom/movable/screen/pai/rest/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + if(pAI.resting) + pAI.set_resting(FALSE) + else + pAI.set_resting(TRUE) + +/atom/movable/screen/pai/light + name = "Toggle Integrated Lights" + icon_state = "light" + +/atom/movable/screen/pai/light/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.toggle_integrated_light() + +/atom/movable/screen/pai/newscaster + name = "pAI Newscaster" + icon_state = "newscaster" + required_software = "Newscaster" + +/atom/movable/screen/pai/newscaster/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.newscaster.ui_interact(usr) + +/atom/movable/screen/pai/host_monitor + name = "Host Health Scan" + icon_state = "host_monitor" + required_software = "Host Scan" + +/atom/movable/screen/pai/host_monitor/Click(location, control, params) + . = ..() + if(!.) + return + var/mob/living/silicon/pai/pAI = usr + pAI.host_scan(PAI_SCAN_TARGET) + return TRUE + +/atom/movable/screen/pai/crew_manifest + name = "Crew Manifest" + icon_state = "manifest" + required_software = "Crew Manifest" + +/atom/movable/screen/pai/crew_manifest/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.ai_roster() + +/atom/movable/screen/pai/state_laws + name = "State Laws" + icon_state = "state_laws" + +/atom/movable/screen/pai/state_laws/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.checklaws() + +/atom/movable/screen/pai/modpc + name = "Messenger" + icon_state = "pda_send" + required_software = "Digital Messenger" + var/mob/living/silicon/pai/pAI + +/atom/movable/screen/pai/modpc/Click() + . = ..() + if(!.) // this works for some reason. + return + pAI.modularInterface?.interact(pAI) + +/atom/movable/screen/pai/internal_gps + name = "Internal GPS" + icon_state = "internal_gps" + required_software = "Internal GPS" + +/atom/movable/screen/pai/internal_gps/Click() + . = ..() + if(!.) + return + var/mob/living/silicon/pai/pAI = usr + if(!pAI.internal_gps) + pAI.internal_gps = new(pAI) + pAI.internal_gps.attack_self(pAI) + +/atom/movable/screen/pai/image_take + name = "Take Image" + icon_state = "take_picture" + required_software = "Photography Module" + +/atom/movable/screen/pai/image_take/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.camera.toggle_camera_mode(usr) + +/atom/movable/screen/pai/image_view + name = "View Images" + icon_state = "view_images" + required_software = "Photography Module" + +/atom/movable/screen/pai/image_view/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.camera.viewpictures(usr) + +/atom/movable/screen/pai/radio + name = "radio" + icon = 'icons/mob/screen_cyborg.dmi' + icon_state = "radio" + +/atom/movable/screen/pai/radio/Click() + if(!..()) + return + var/mob/living/silicon/pai/pAI = usr + pAI.radio.interact(usr) + +/datum/hud/pai/New(mob/living/silicon/pai/owner) + ..() + var/atom/movable/screen/using + var/mob/living/silicon/pai/mypai = mymob + +// Software menu + using = new /atom/movable/screen/pai/software + using.screen_loc = ui_pai_software + static_inventory += using + +// Holoform + using = new /atom/movable/screen/pai/shell + using.screen_loc = ui_pai_shell + static_inventory += using + +// Chassis Select Menu + using = new /atom/movable/screen/pai/chassis + using.screen_loc = ui_pai_chassis + static_inventory += using + +// Rest + using = new /atom/movable/screen/pai/rest + using.screen_loc = ui_pai_rest + static_inventory += using + +// Integrated Light + using = new /atom/movable/screen/pai/light + using.screen_loc = ui_pai_light + static_inventory += using + +// Newscaster + using = new /atom/movable/screen/pai/newscaster + using.screen_loc = ui_pai_newscaster + static_inventory += using + +// Language menu + using = new /atom/movable/screen/language_menu + using.screen_loc = ui_pai_language_menu + static_inventory += using + +// Host Monitor + using = new /atom/movable/screen/pai/host_monitor() + using.screen_loc = ui_pai_host_monitor + static_inventory += using + +// Crew Manifest + using = new /atom/movable/screen/pai/crew_manifest() + using.screen_loc = ui_pai_crew_manifest + static_inventory += using + +// Laws + using = new /atom/movable/screen/pai/state_laws() + using.screen_loc = ui_pai_state_laws + static_inventory += using + +// Modular Interface + using = new /atom/movable/screen/pai/modpc() + using.screen_loc = ui_pai_mod_int + static_inventory += using + mypai.pda_button = using + var/atom/movable/screen/pai/modpc/tablet_button = using + tablet_button.pAI = mypai + +// Internal GPS + using = new /atom/movable/screen/pai/internal_gps() + using.screen_loc = ui_pai_internal_gps + static_inventory += using + +// Take image + using = new /atom/movable/screen/pai/image_take() + using.screen_loc = ui_pai_take_picture + static_inventory += using + +// View images + using = new /atom/movable/screen/pai/image_view() + using.screen_loc = ui_pai_view_images + static_inventory += using + +// Radio + using = new /atom/movable/screen/pai/radio() + using.screen_loc = ui_pai_radio + static_inventory += using + + update_software_buttons() + +/datum/hud/pai/proc/update_software_buttons() + var/mob/living/silicon/pai/owner = mymob + for(var/atom/movable/screen/pai/button in static_inventory) + if(button.required_software) + button.color = owner.installed_software.Find(button.required_software) ? null : "#808080" + +#undef PAI_MISSING_SOFTWARE_MESSAGE diff --git a/code/modules/pai/login.dm b/code/modules/pai/login.dm new file mode 100644 index 000000000000..0f418c4bf32b --- /dev/null +++ b/code/modules/pai/login.dm @@ -0,0 +1,10 @@ +/mob/living/silicon/pai/Login() + . = ..() + if(!. || !client) + return FALSE + + client.perspective = EYE_PERSPECTIVE + if(holoform) + client.eye = src + else + client.eye = card diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm new file mode 100644 index 000000000000..71a1013a4c19 --- /dev/null +++ b/code/modules/pai/pai.dm @@ -0,0 +1,412 @@ +/mob/living/silicon/pai + can_be_held = TRUE + density = FALSE + desc = "A generic pAI hard-light holographics emitter." + health = 500 + held_lh = 'icons/mob/pai_item_lh.dmi' + held_rh = 'icons/mob/pai_item_rh.dmi' + held_icon = 'icons/mob/pai_item_head.dmi' + hud_type = /datum/hud/pai + icon = 'icons/mob/pai.dmi' + icon_state = "repairbot" + job = "Personal AI" + layer = BELOW_MOB_LAYER + light_color = COLOR_PAI_GREEN + light_flags = LIGHT_ATTACHED + light_on = FALSE + light_range = 3 + light_system = MOVABLE_LIGHT + maxHealth = 500 + mob_size = MOB_SIZE_TINY + mobility_flags = MOBILITY_FLAGS_DEFAULT + mouse_opacity = MOUSE_OPACITY_ICON + move_force = 0 + move_resist = 0 + name = "pAI" + pass_flags = PASSTABLE | PASSMOB + pull_force = 0 + radio = /obj/item/radio/headset/silicon/pai + worn_slot_flags = ITEM_SLOT_HEAD + + /// If someone has enabled/disabled the pAIs ability to holo + var/can_holo = TRUE + /// Whether this pAI can recieve radio messages + var/can_receive = TRUE + /// Whether this pAI can transmit radio messages + var/can_transmit = TRUE + /// The card we inhabit + var/obj/item/pai_card/card + /// The current chasis that will appear when in holoform + var/chassis = "repairbot" + /// Toggles whether the pAI can hold encryption keys or not + var/encrypt_mod = FALSE + /// The cable we produce when hacking a door + var/obj/item/pai_cable/hacking_cable + /// The current health of the holochassis + var/holochassis_health = 20 + /// Holochassis available to use + var/holochassis_ready = FALSE + /// Whether we are currently holoformed + var/holoform = FALSE + /// Installed software on the pAI + var/list/installed_software = list() + /// Toggles whether universal translator has been activated. Cannot be reversed + var/languages_granted = FALSE + /// Reference of the bound master + var/datum/weakref/master_ref + /// The master's name string + var/master_name + /// DNA string for owner verification + var/master_dna + /// Toggles whether the Medical HUD is active or not + var/medHUD = FALSE + /// Used as currency to purchase different abilities + var/ram = 100 + /// Toggles whether the Security HUD is active or not + var/secHUD = FALSE + + // Onboard Items + /// Atmospheric analyzer + var/obj/item/analyzer/atmos_analyzer + /// Health analyzer + var/obj/item/healthanalyzer/host_scan + /// GPS + var/obj/item/gps/internal/internal_gps + /// Music Synthesizer + var/obj/item/instrument/piano_synth/instrument + /// Newscaster + var/obj/machinery/newscaster/pai/newscaster + /// PDA + var/atom/movable/screen/ai/modpc/pda_button + /// Photography module + var/obj/item/camera/siliconcam/pai_camera/camera + /// Remote signaler + var/obj/item/assembly/signaler/internal/signaler + + // Static lists + /// List of all available downloads + var/static/list/available_software = list( + "Atmospheric Sensor" = 5, + "Crew Manifest" = 5, + "Digital Messenger" = 5, + "Photography Module" = 5, + "Encryption Slot" = 10, + "Music Synthesizer" = 10, + "Newscaster" = 10, + "Remote Signaler" = 10, + "Host Scan" = 20, + "Medical HUD" = 20, + "Security HUD" = 20, + "Crew Monitor" = 35, + "Door Jack" = 35, + "Internal GPS" = 35, + "Universal Translator" = 35, + ) + /// List of all possible chasises. TRUE means the pAI can be picked up in this chasis. + var/static/list/possible_chassis = list( + "cat" = TRUE, + "corgi" = FALSE, + "fox" = FALSE, + "monkey" = TRUE, + "mouse" = TRUE, + "rabbit" = TRUE, + ) + /// List of all available card overlays. + var/static/list/possible_overlays = list( + "null", + "angry", + "cat", + "extremely-happy", + "face", + "happy", + "laugh", + "off", + "sad", + "sunglasses", + "what" + ) + +/mob/living/silicon/pai/add_sensors() //pAIs have to buy their HUDs + return + +/mob/living/silicon/pai/can_interact_with(atom/target) + if(target == signaler) // Bypass for signaler + return TRUE + if(target == modularInterface) + return TRUE + return ..() + +/mob/living/silicon/pai/Destroy() + QDEL_NULL(atmos_analyzer) + QDEL_NULL(camera) + QDEL_NULL(hacking_cable) + QDEL_NULL(host_scan) + QDEL_NULL(instrument) + QDEL_NULL(internal_gps) + QDEL_NULL(newscaster) + QDEL_NULL(signaler) + card = null + GLOB.pai_list.Remove(src) + return ..() + +/mob/living/silicon/pai/emag_act(mob/user) + handle_emag(user) + +/mob/living/silicon/pai/examine(mob/user) + . = ..() + . += "Its master ID string seems to be [(!master_name || emagged) ? "empty" : master_name]." + +/mob/living/silicon/pai/get_status_tab_items() + . += ..() + if(!stat) + . += text("Emitter Integrity: [holochassis_health * (100 / HOLOCHASSIS_MAX_HEALTH)].") + else + . += text("Systems nonfunctional.") + +/mob/living/silicon/pai/handle_atom_del(atom/deleting_atom) + if(deleting_atom == hacking_cable) + untrack_pai() + untrack_thing(hacking_cable) + hacking_cable = null + SStgui.update_user_uis(src) + if(!QDELETED(card)) + card.update_icon() + if(deleting_atom == atmos_analyzer) + atmos_analyzer = null + if(deleting_atom == camera) + camera = null + if(deleting_atom == host_scan) + host_scan = null + if(deleting_atom == internal_gps) + internal_gps = null + if(deleting_atom == instrument) + instrument = null + if(deleting_atom == newscaster) + newscaster = null + if(deleting_atom == signaler) + signaler = null + return ..() + +/mob/living/silicon/pai/Initialize(mapload) + . = ..() + START_PROCESSING(SSfastprocess, src) + GLOB.pai_list += src + make_laws() + for(var/law in laws.inherent) + lawcheck += law + var/obj/item/pai_card/pai_card = loc + if(!istype(pai_card)) // when manually spawning a pai, we create a card to put it into. + var/newcardloc = pai_card + pai_card = new(newcardloc) + pai_card.set_personality(src) + forceMove(pai_card) + card = pai_card + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_INIT_TIME) + if(!holoform) + Immobilize(50 MINUTES) + desc = "A pAI hard-light holographics emitter. This one appears in the form of a [chassis]." + + RegisterSignal(src, COMSIG_LIVING_CULT_SACRIFICED, PROC_REF(on_cult_sacrificed)) + +/mob/living/silicon/pai/make_laws() + laws = new /datum/ai_laws/pai() + return TRUE + +/mob/living/silicon/pai/process(delta_time) + holochassis_health = clamp((holochassis_health + (HOLOCHASSIS_REGEN_PER_SECOND * delta_time)), -50, HOLOCHASSIS_MAX_HEALTH) + +/mob/living/silicon/pai/Process_Spacemove(movement_dir = 0, continuous_move = FALSE) + . = ..() + if(!.) + add_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK, update = TRUE, priority = 100, multiplicative_slowdown = 2) + return TRUE + remove_movespeed_modifier(MOVESPEED_ID_PAI_SPACEWALK, TRUE) + return TRUE + +/mob/living/silicon/pai/screwdriver_act(mob/living/user, obj/item/tool) + return radio.screwdriver_act(user, tool) + +/mob/living/silicon/pai/updatehealth() + if(status_flags & GODMODE) + return + set_health(maxHealth - getBruteLoss() - getFireLoss()) + update_stat() + SEND_SIGNAL(src, COMSIG_LIVING_HEALTH_UPDATE) + +/** + * Resolves the weakref of the pai's master. + * If the master has been deleted, calls reset_software(). + * + * @returns {mob/living || FALSE} - The master mob, or + * FALSE if the master is gone. + */ +/mob/living/silicon/pai/proc/find_master() + if(!master_ref) + return FALSE + var/mob/living/resolved_master = master_ref?.resolve() + if(!resolved_master) + reset_software() + return FALSE + return resolved_master + +/** + * Fixes weird speech issues with the pai. + * + * @returns {boolean} - TRUE if successful. + */ +/mob/living/silicon/pai/proc/fix_speech() + var/mob/living/silicon/pai = src + balloon_alert(pai, "speech modulation corrected") + stuttering = 0 + slurring = 0 + derpspeech = 0 + + return TRUE + +/** + * Gets the current holder of the pAI if its + * being carried in card or holoform. + * + * @returns {living/carbon || FALSE} - The holder of the pAI, + * or FALSE if the pAI is not being carried. + */ +/mob/living/silicon/pai/proc/get_holder() + var/mob/living/carbon/holder + if(!holoform && iscarbon(card.loc)) + holder = card.loc + if(holoform && ispickedupmob(loc) && iscarbon(loc.loc)) + holder = loc.loc + if(!holder || !iscarbon(holder)) + return FALSE + return holder + +/** + * Handles the pai card or the pai itself being hit with an emag. + * This replaces any current laws, masters, and DNA. + * + * @param {living/carbon} attacker - The user performing the action. + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/handle_emag(mob/living/carbon/attacker) + if(!isliving(attacker)) + return FALSE + balloon_alert(attacker, "directive override complete") + balloon_alert(src, "directive override detected") + log_game("[key_name(attacker)] emagged [key_name(src)], wiping their master DNA and supplemental directive.") + emagged = TRUE + master_ref = WEAKREF(attacker) + master_name = "The Syndicate" + master_dna = "Untraceable Signature" + // Sets supplemental directive to this + laws.supplied[1] = "Do not interfere with the operations of the Syndicate." + return TRUE + +/** + * Resets the pAI and any emagged status. + * + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/reset_software() + emagged = FALSE + if(!master_ref) + return FALSE + master_ref = null + master_name = null + master_dna = null + add_supplied_law(0, "None.") + balloon_alert(src, "software rebooted") + return TRUE + +/** + * Imprints your DNA onto the downloaded pAI + * + * @param {mob} user - The user performing the imprint. + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/set_dna(mob/user) + if(!iscarbon(user)) + balloon_alert(user, "incompatible DNA signature") + balloon_alert(src, "incompatible DNA signature") + return FALSE + if(emagged) + balloon_alert(user, "directive system malfunctional") + return FALSE + var/mob/living/carbon/master = user + master_ref = WEAKREF(master) + master_name = master.real_name + master_dna = master.dna.unique_enzymes + to_chat(src, span_boldannounce("You have been bound to a new master: [user.real_name]!")) + holochassis_ready = TRUE + return TRUE + +/** + * Opens a tgui alert that allows someone to enter laws. + * + * @param {mob} user - The user performing the law change. + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/set_laws(mob/user) + if(!master_ref) + balloon_alert(user, "access denied: no master") + return FALSE + var/new_laws = tgui_input_text(user, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", laws.supplied[1], 300) + if(!new_laws || !master_ref) + return FALSE + add_supplied_law(0, new_laws) + to_chat(src, span_notice(new_laws)) + return TRUE + +/** + * Toggles the ability of the pai to enter holoform + * + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/toggle_holo() + balloon_alert(src, "holomatrix [can_holo ? "disabled" : "enabled"]") + can_holo = !can_holo + return TRUE + +/** + * Toggles the radio settings on and off. + * + * @param {string} option - The option being toggled. + */ +/mob/living/silicon/pai/proc/toggle_radio(option) + // it can't be both so if we know it's not transmitting it must be receiving. + var/transmitting = option == "transmit" + var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX) + if(transmitting) + can_transmit = !can_transmit + else //receiving + can_receive = !can_receive + radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states + transmit_holder = (transmitting ? can_transmit : can_receive) //recycling can be fun! + balloon_alert(src, "[transmitting ? "outgoing" : "incoming"] radio [transmit_holder ? "enabled" : "disabled"]") + return TRUE + +/** + * Wipes the current pAI on the card. + * + * @param {mob} user - The user performing the action. + * + * @returns {boolean} - TRUE if successful, FALSE if not. + */ +/mob/living/silicon/pai/proc/wipe_pai(mob/user) + if(tgui_alert(user, "Are you certain you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No")) != "Yes") + return FALSE + to_chat(src, span_warning("You feel yourself slipping away from reality.")) + to_chat(src, span_danger("Byte by byte you lose your sense of self.")) + to_chat(src, span_userdanger("Your mental faculties leave you.")) + to_chat(src, span_rose("oblivion... ")) + balloon_alert(user, "personality wiped") + playsound(src, "sound/machines/buzz-two.ogg", 30, TRUE) + qdel(src) + return TRUE + +/// Signal proc for [COMSIG_LIVING_CULT_SACRIFICED] to give a funny message when a pai is attempted to be sac'd +/mob/living/silicon/pai/proc/on_cult_sacrificed(datum/source, list/invokers) + SIGNAL_HANDLER + + for(var/mob/living/cultist as anything in invokers) + to_chat(cultist, span_cultitalic("You don't think this is what Nar'Sie had in mind when She asked for blood sacrifices...")) + return STOP_SACRIFICE diff --git a/code/modules/pai/personality.dm b/code/modules/pai/personality.dm new file mode 100644 index 000000000000..92cf5747801f --- /dev/null +++ b/code/modules/pai/personality.dm @@ -0,0 +1,56 @@ +/** + * name + * key + * description + * role + * comments + * ready = TRUE + */ + +/datum/pai_candidate/proc/savefile_path(mob/user) + return "data/player_saves/[user.ckey[1]]/[user.ckey]/pai.sav" + +/datum/pai_candidate/proc/savefile_save(mob/user) + if(is_guest_key(user.key)) + to_chat(usr, span_warning("You cannot save pAI information as a guest.")) + return FALSE + var/savefile/F = new /savefile(src.savefile_path(user)) + WRITE_FILE(F["name"], name) + WRITE_FILE(F["description"], description) + WRITE_FILE(F["comments"], comments) + WRITE_FILE(F["version"], 1) + to_chat(usr, span_boldnotice("You have saved pAI information locally.")) + return TRUE + +// loads the savefile corresponding to the mob's ckey +// if silent=true, report incompatible savefiles +// returns TRUE if loaded (or file was incompatible) +// returns FALSE if savefile did not exist + +/datum/pai_candidate/proc/savefile_load(mob/user, silent = TRUE) + if (is_guest_key(user.key)) + return FALSE + + var/path = savefile_path(user) + + if (!fexists(path)) + return FALSE + + var/savefile/F = new /savefile(path) + + if(!F) + return //Not everyone has a pai savefile. + + var/version = null + F["version"] >> version + + if (isnull(version) || version != 1) + fdel(path) + if (!silent) + tgui_alert(user, "Your savefile was incompatible with this version and was deleted.") + return FALSE + + F["name"] >> src.name + F["description"] >> src.description + F["comments"] >> src.comments + return TRUE diff --git a/code/modules/pai/say.dm b/code/modules/pai/say.dm new file mode 100644 index 000000000000..b35abfe7f9d8 --- /dev/null +++ b/code/modules/pai/say.dm @@ -0,0 +1,2 @@ +/mob/living/silicon/pai/binarycheck() + return radio?.translate_binary diff --git a/code/modules/pai/shell.dm b/code/modules/pai/shell.dm new file mode 100644 index 000000000000..66e2e8bfb279 --- /dev/null +++ b/code/modules/pai/shell.dm @@ -0,0 +1,169 @@ +/mob/living/silicon/pai/mob_try_pickup(mob/living/user, instant=FALSE) + if(!possible_chassis[chassis]) + to_chat(user, span_warning("[src]'s current form isn't able to be carried!")) + return FALSE + return ..() + +/mob/living/silicon/pai/start_pulling(atom/movable/thing, state, force = move_force, supress_message = FALSE) + return FALSE + +/mob/living/silicon/pai/update_resting() + . = ..() + if(resting) + icon_state = "[chassis]_rest" + else + icon_state = "[chassis]" + if(loc != card) + visible_message(span_notice("[src] [resting? "lays down for a moment..." : "perks up from the ground."]")) + +/mob/living/silicon/pai/wabbajack_act(what_to_randomize, change_flags = WABBAJACK) + if(length(possible_chassis) < 2) + return FALSE + var/holochassis = pick(possible_chassis - chassis) + set_holochassis(holochassis) + balloon_alert(src, "[holochassis] composite engaged") + return TRUE + +/** + * Checks if we are allowed to interact with a radial menu + * + * @param {atom} anchor - The atom that is anchoring the menu. + * + * @returns {boolean} - TRUE if we are allowed to interact with the menu, + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/check_menu(atom/anchor) + if(incapacitated()) + return FALSE + if(get_turf(src) != get_turf(anchor)) + return FALSE + if(!isturf(loc) && loc != card) + balloon_alert(src, "can't do that here") + return FALSE + return TRUE + +/** + * Sets a new holochassis skin based on a pAI's choice. + * + * @returns {boolean} - True if the skin was successfully set. + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/choose_chassis() + var/list/skins = list() + for(var/holochassis_option in possible_chassis) + var/image/item_image = image(icon = src.icon, icon_state = holochassis_option) + skins += list("[holochassis_option]" = item_image) + var/atom/anchor = get_atom_on_turf(src) + var/choice = show_radial_menu(src, anchor, skins, custom_check = CALLBACK(src, PROC_REF(check_menu), anchor), radius = 40, require_near = TRUE) + if(!choice) + return FALSE + set_holochassis(choice) + balloon_alert(src, "[choice] composite engaged") + update_resting() + return TRUE + +/** + * Returns the pAI to card mode. + * + * @param {boolean} force - If TRUE, the pAI will be forced to card mode. + * + * @returns {boolean} - TRUE if the pAI was forced to card mode. + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/fold_in(force = FALSE) + holochassis_ready = FALSE + if(!force) + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN) + else + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_OVERLOAD_COOLDOWN) + icon_state = "[chassis]" + if(!holoform) + . = fold_out(force) + return FALSE + visible_message(span_notice("[src] deactivates its holochassis emitter and folds back into a compact card!")) + stop_pulling() + if(ispickedupmob(loc)) + var/obj/item/clothing/mob_holder/mob_head = loc + mob_head.release(display_messages = FALSE) + if(client) + client.perspective = EYE_PERSPECTIVE + var/turf/target = drop_location() + card.forceMove(target) + forceMove(card) + Immobilize(30 MINUTES) + set_density(FALSE) + set_light_on(FALSE) + holoform = FALSE + set_resting(resting) + return TRUE + +/** + * Engage holochassis form. + * + * @param {boolean} force - Force the form to engage. + * + * @returns {boolean} - TRUE if the form was successfully engaged. + * FALSE otherwise. + */ +/mob/living/silicon/pai/proc/fold_out(force = FALSE) + if(holochassis_health < 0) + balloon_alert(src, "emitter repair incomplete") + return FALSE + if(!can_holo && !force) + balloon_alert(src, "emitters are disabled") + return FALSE + if(holoform) + . = fold_in(force) + return + if(!holochassis_ready) + balloon_alert(src, "emitters recycling...") + return FALSE + holochassis_ready = FALSE + addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN) + SetImmobilized(0) + set_density(TRUE) + if(istype(card.loc, /obj/item/modular_computer)) + var/obj/item/modular_computer/pc = card.loc + pc.inserted_pai = null + pc.visible_message(span_notice("[src] ejects itself from [pc]!")) + if(isliving(card.loc)) + var/mob/living/living_holder = card.loc + if(!living_holder.temporarilyRemoveItemFromInventory(card)) + balloon_alert(src, "unable to expand") + return FALSE + forceMove(get_turf(card)) + card.forceMove(src) + if(client) + client.perspective = EYE_PERSPECTIVE + client.eye = src + set_light_on(FALSE) + icon_state = "[chassis]" + held_state = "[chassis]" + visible_message(span_boldnotice("[src] folds out its holochassis emitter and forms a holoshell around itself!")) + holoform = TRUE + return TRUE + +/** + * Sets the holochassis skin and updates the icons + * + * @param {string} choice - The skin that will be used for the pAI holoform + * + * @returns {boolean} - TRUE if the skin was successfully set. FALSE otherwise. + */ +/mob/living/silicon/pai/proc/set_holochassis(choice) + if(!choice) + return FALSE + chassis = choice + icon_state = "[chassis]" + held_state = "[chassis]" + desc = "A pAI mobile hard-light holographics emitter. This one appears in the form of a [chassis]." + return TRUE + +/** + * Toggles the onboard light + * + * @returns {boolean} - TRUE if the light was toggled. + */ +/mob/living/silicon/pai/proc/toggle_integrated_light() + set_light_on(!light_on) + return TRUE diff --git a/code/modules/pai/software.dm b/code/modules/pai/software.dm new file mode 100644 index 000000000000..1ff2e02d3585 --- /dev/null +++ b/code/modules/pai/software.dm @@ -0,0 +1,242 @@ +/mob/living/silicon/pai/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "PaiInterface", name) + ui.open() + ui.set_autoupdate(FALSE) + +/mob/living/silicon/pai/ui_data(mob/user) + var/list/data = list() + data["door_jack"] = hacking_cable + data["image"] = card.emotion_icon + data["installed"] = installed_software + data["ram"] = ram + return data + +/mob/living/silicon/pai/ui_static_data(mob/user) + var/list/data = list() + data["available"] = available_software + data["directives"] = laws.supplied + data["emagged"] = emagged + data["languages"] = languages_granted + data["master_name"] = master_name + data["master_dna"] = master_dna + return data + +/mob/living/silicon/pai/ui_act(action, list/params, datum/tgui/ui) + . = ..() + if(.) + return TRUE + if(action == "buy") + buy_software(params["selection"]) + return TRUE + if(action == "change image") + change_image() + return TRUE + if(action == "check dna") + check_dna() + return TRUE + // Software related ui actions + if(available_software[action] && !installed_software.Find(action)) + balloon_alert(usr, "software unavailable") + return FALSE + switch(action) + if("Atmospheric Sensor") + atmos_analyzer.attack_self(src) + return TRUE + if("Crew Manifest") + ai_roster() + return TRUE + if("Crew Monitor") + GLOB.crewmonitor.show(usr, src) + return TRUE + if("Digital Messenger") + modularInterface?.interact(usr) + return TRUE + if("Door Jack") + // Look to door_jack.dm for implementation + door_jack(params["mode"]) + return TRUE + if("Encryption Slot") + balloon_alert(usr, "radio frequencies [!encrypt_mod ? "enabled" : "disabled"]") + encrypt_mod = !encrypt_mod + radio.subspace_transmission = !radio.subspace_transmission + return TRUE + if("Host Scan") + host_scan(params["mode"]) + return TRUE + if("Internal GPS") + internal_gps.attack_self(src) + return TRUE + if("Music Synthesizer") + instrument.interact(src) + return TRUE + if("Medical HUD") + toggle_hud(PAI_TOGGLE_MEDICAL_HUD) + return TRUE + if("Newscaster") + newscaster.ui_interact(src) + return TRUE + if("Photography Module") + // Look to pai_camera.dm for implementation + use_camera(usr, params["mode"]) + return TRUE + if("Remote Signaler") + signaler.ui_interact(src) + return TRUE + if("Security HUD") + toggle_hud(PAI_TOGGLE_SECURITY_HUD) + return TRUE + if("Universal Translator") + grant_languages() + ui.send_full_update() + return TRUE + return FALSE + +/** + * Purchases the selected software from the list and deducts their + * available ram. + * + * @param {string} selection - The software to purchase. + * + * @returns {boolean} - TRUE if the software was purchased, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/buy_software(selection) + if(!available_software[selection] || installed_software.Find(selection)) + return FALSE + var/cost = available_software[selection] + if(ram < cost) + return FALSE + installed_software.Add(selection) + ram -= cost + var/datum/hud/pai/pAIhud = hud_used + pAIhud?.update_software_buttons() + switch(selection) + if("Atmospheric Sensor") + atmos_analyzer = new(src) + if("Digital Messenger") + create_modularInterface() + if("Host Scan") + host_scan = new(src) + if("Internal GPS") + internal_gps = new(src) + if("Music Synthesizer") + instrument = new(src) + if("Newscaster") + newscaster = new(src) + if("Photography Module") + camera = new(src) + if("Remote Signaler") + signaler = new(src) + return TRUE + +/** + * Changes the image displayed on the pAI. + * + * @param {mob} user - The user who is changing the image. + * + * @returns {boolean} - TRUE if the image was changed, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/change_image() + var/new_image = tgui_input_list(src, "Select your new display image", "Display Image", possible_overlays) + if(isnull(new_image)) + return FALSE + card.emotion_icon = new_image + card.update_icon() + return TRUE + +/** + * Supporting proc for the pAI to prick it's master's hand + * or... whatever. It must be held in order to work + * Gives the owner a popup if they want to get the jab. + * + * @returns {boolean} - TRUE if a sample was taken, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/check_dna() + if(emagged) // Their master DNA signature is scrambled anyway + to_chat(src, span_syndradio("You are not at liberty to do this! All agents are clandestine.")) + return FALSE + var/mob/living/carbon/holder = get_holder() + if(!iscarbon(holder)) + balloon_alert(src, "not being carried") + return FALSE + balloon_alert(src, "requesting dna sample") + if(tgui_alert(holder, "[src] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "Checking DNA", list("Yes", "No")) != "Yes") + balloon_alert(src, "dna sample refused!") + return FALSE + holder.visible_message(span_notice("[holder] presses [holder.p_their()] thumb against [src]."), span_notice("You press your thumb against [src]."), span_notice("[src] makes a sharp clicking sound as it extracts DNA material from [holder].")) + if(!holder.has_dna()) + balloon_alert(src, "no dna detected!") + return FALSE + to_chat(src, span_boldannounce(("[holder]'s UE string: [holder.dna.unique_enzymes]"))) + to_chat(src, span_notice("DNA [holder.dna.unique_enzymes == master_dna ? "matches" : "does not match"] our stored Master's DNA.")) + return TRUE + +/** + * Grant all languages to the current pAI. + * + * @returns {boolean} - TRUE if the languages were granted, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/grant_languages() + if(languages_granted) + return FALSE + grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE) + languages_granted = TRUE + return TRUE + +/** + * Host scan supporting proc + * + * Allows the pAI to scan its host's health vitals + * using an integrated health analyzer. + * + * @returns {boolean} - TRUE if the scan was successful, FALSE otherwise. + */ +/mob/living/silicon/pai/proc/host_scan(mode) + if(isnull(mode)) + return FALSE + if(mode == PAI_SCAN_TARGET) + var/mob/living/target = get_holder() + if(!target || !isliving(target)) + balloon_alert(src, "not being carried") + return FALSE + host_scan.attack(target, src) + return TRUE + if(mode == PAI_SCAN_MASTER) + if(!master_ref) + balloon_alert(src, "no master detected") + return FALSE + var/mob/living/resolved_master = find_master() + if(!resolved_master) + balloon_alert(src, "cannot locate master") + return FALSE + if(!is_valid_z_level(get_turf(src), get_turf(resolved_master))) + balloon_alert(src, "master out of range") + return FALSE + host_scan.attack(resolved_master, src) + return TRUE + return FALSE + +/** + * Proc that toggles any active huds based on the option. + * + * @param {string} mode - The hud to toggle. + */ +/mob/living/silicon/pai/proc/toggle_hud(mode) + if(isnull(mode)) + return FALSE + var/datum/atom_hud/hud + var/hud_on + if(mode == PAI_TOGGLE_MEDICAL_HUD) + hud = GLOB.huds[med_hud] + medHUD = !medHUD + hud_on = medHUD + if(mode == PAI_TOGGLE_SECURITY_HUD) + hud = GLOB.huds[sec_hud] + secHUD = !secHUD + hud_on = secHUD + if(hud_on) + hud.show_to(src) + else + hud.hide_from(src) + return TRUE diff --git a/code/modules/paperwork/contract.dm b/code/modules/paperwork/contract.dm index 7a724f643474..8f755ba29763 100644 --- a/code/modules/paperwork/contract.dm +++ b/code/modules/paperwork/contract.dm @@ -103,7 +103,7 @@ H.say("OH GREAT INFERNO! I DEMAND YOU COLLECT YOUR BOUNTY IMMEDIATELY!", forced = "infernal contract suicide") H.visible_message(span_suicide("[H] holds up a contract claiming [user.p_their()] soul, then immediately catches fire. It looks like [user.p_theyre()] trying to commit suicide!")) H.adjust_fire_stacks(20) - H.IgniteMob() + H.ignite_mob() return(FIRELOSS) return ..() diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index b388cb0275ae..cb6dafe51db2 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -322,7 +322,7 @@ span_userdanger("You miss the paper and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 5b1998dda7fa..97985c89f6ff 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -83,7 +83,7 @@ span_userdanger("You miss [src] and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm index 63fe8717a945..fcc8d03554f4 100644 --- a/code/modules/paperwork/ticketmachine.dm +++ b/code/modules/paperwork/ticketmachine.dm @@ -91,7 +91,7 @@ theirticket.fire_act() user.dropItemToGround(theirticket) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return /obj/machinery/ticket_machine/attackby(obj/item/O, mob/user, params) @@ -163,7 +163,7 @@ user.visible_message(span_warning("[user] accidentally ignites [user.p_them()]self!"), span_userdanger("You miss the ticket and accidentally light yourself on fire!")) user.dropItemToGround(P) user.adjust_fire_stacks(1) - user.IgniteMob() + user.ignite_mob() return if(!(in_range(user, src))) //to prevent issues as a result of telepathically lighting a paper diff --git a/code/modules/pool/components/swimming_felinid.dm b/code/modules/pool/components/swimming_felinid.dm index 31a7355bf9b5..62006b528f11 100644 --- a/code/modules/pool/components/swimming_felinid.dm +++ b/code/modules/pool/components/swimming_felinid.dm @@ -14,10 +14,9 @@ to_chat(parent, "You can't touch the bottom!") L.emote("scream") if(5 to 7) - if(L.confused < 5) - L.confused += 1 + L.adjust_confusion_up_to(1 SECONDS, 5 SECONDS) if(8 to 12) - L.Jitter(10) + L.adjust_jitter(10 SECONDS) if(13 to 14) shake_camera(L, 15, 1) L.emote("whimper") diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index a44284bf9cc4..8ad7505489ff 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -429,7 +429,7 @@ auto.Grant(M, src) /datum/action/innate/protoemitter - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS var/obj/machinery/power/emitter/prototype/PE var/mob/living/carbon/U @@ -454,7 +454,7 @@ for(var/obj/item/I in U.held_items) if(istype(I, /obj/item/turret_control)) qdel(I) - UpdateButtonIcon() + UpdateButtons() return else playsound(PE,'sound/mecha/mechmove01.ogg', 50, TRUE) @@ -471,7 +471,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/TC = new /obj/item/turret_control() U.put_in_hands(TC) - UpdateButtonIcon() + UpdateButtons() /obj/item/turret_control diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index a94ebfc5f424..6a09e85e9265 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -486,7 +486,7 @@ C.visible_message(span_warning("[C]'s skin bursts into flame!"), \ span_userdanger("You feel an inner fire as your skin bursts into flames!")) C.adjust_fire_stacks(5) - C.IgniteMob() + C.ignite_mob() return diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index 7fafa9db7829..49ae5034cda3 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -346,7 +346,7 @@ set_panels(azimuth_target) if(powernet && force_auto) search_for_connected() //are we actually connected to anything useful? - if(connected_tracker && !isemptylist(connected_panels)) + if(connected_tracker && length(connected_panels)) track = SOLAR_TRACK_AUTO connected_tracker.sun_update(SSsun, SSsun.azimuth) update_icon() diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 8b2380abd006..1a68e0ffbaa6 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -360,7 +360,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) if(istype(M) && T2 && T2.z == z) if(ishuman(M)) var/mob/living/carbon/human/H = M - H.hallucination += max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(M, src) + 1)) ) ) + H.adjust_hallucinations(max(50, min(300, DETONATION_HALLUCINATION * sqrt(1 / (get_dist(M, src) + 1)) ) ) ) var/rads = DETONATION_RADS * sqrt( 1 / (get_dist(M, src) + 1) ) M.rad_act(rads) @@ -601,9 +601,12 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) for(var/mob/living/carbon/human/l in view(src, HALLUCINATION_RANGE(power))) // If they can see it without mesons on. Bad on them. if((!HAS_TRAIT(l, TRAIT_MESONS)) || corruptor_attached) - var/D = sqrt(1 / max(1, get_dist(l, src))) - l.hallucination += power * config_hallucination_power * D - l.hallucination = clamp(l.hallucination, 0, 200) + visible_hallucination_pulse( + center = src, + radius = HALLUCINATION_RANGE(power), + hallucination_duration = power * 0.1, + hallucination_max_duration = 400 SECONDS, + ) power -= ((power/500)**3) * powerloss_inhibitor diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index 475427b80168..787b6cdc430c 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -22,6 +22,7 @@ connect_to_network() RegisterSignal(SSsun, COMSIG_SUN_MOVED, .proc/sun_update) + /obj/machinery/power/tracker/Destroy() unset_control() //remove from control computer return ..() diff --git a/code/modules/projectiles/attachments/laser_sight.dm b/code/modules/projectiles/attachments/laser_sight.dm index 35a7642c6e3a..13b629b01e74 100644 --- a/code/modules/projectiles/attachments/laser_sight.dm +++ b/code/modules/projectiles/attachments/laser_sight.dm @@ -137,3 +137,52 @@ STOP_PROCESSING(SSfastprocess, src) QDEL_LIST(attached_gun?.current_tracers) return ..() + +/datum/action/item_action/toggle_laser_sight + name = "Toggle Laser Sight" + icon_icon = 'icons/obj/guns/attachment.dmi' + button_icon_state = "laser_sight" + var/obj/item/attachment/laser_sight/att + +/datum/action/item_action/toggle_laser_sight/Trigger() + if(!att) + if(istype(target, /obj/item/gun)) + var/obj/item/gun/parent_gun = target + for(var/obj/item/attachment/A in parent_gun.current_attachments) + if(istype(A, /obj/item/attachment/laser_sight)) + att = A + break + att?.toggle_on() + UpdateButtons() + +/datum/action/item_action/toggle_laser_sight/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force) + var/obj/item/attachment/laser_sight/sight = target + if(istype(sight)) + button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]" + + return ..() + +/datum/action/item_action/change_laser_sight_color + name = "Change Laser Sight Color" + icon_icon = 'icons/obj/guns/attachment.dmi' + button_icon_state = "laser_sight" + var/obj/item/attachment/laser_sight/att + +/datum/action/item_action/change_laser_sight_color/Trigger() + if(!att) + if(istype(target, /obj/item/gun)) + var/obj/item/gun/H = target + for(var/obj/item/attachment/A in H.current_attachments) + if(istype(A, /obj/item/attachment/laser_sight)) + att = A + break + if(att && owner) + var/C = input(owner, "Select Laser Color", "Select laser color", att.laser_color) as null|color + if(!C || QDELETED(att)) + return + att.laser_color = C + UpdateButtons() + +/datum/action/item_action/change_laser_sight_color/UpdateButtons(status_only = FALSE, force) + button_icon_state = "laser_sight[att?.is_on ? "_on" : ""]" + ..() diff --git a/code/modules/projectiles/attachments/scopes.dm b/code/modules/projectiles/attachments/scopes.dm index 666405278701..1e766aa5efe4 100644 --- a/code/modules/projectiles/attachments/scopes.dm +++ b/code/modules/projectiles/attachments/scopes.dm @@ -66,3 +66,27 @@ if(user) REMOVE_TRAIT(user, TRAIT_INFRARED_VISION, ATTACHMENT_TRAIT) user.update_sight() + +/datum/action/item_action/toggle_infrared_sight + name = "Toggle Infrared" + icon_icon = 'icons/obj/guns/attachment.dmi' + button_icon_state = "ifr_sight" + var/obj/item/attachment/scope/infrared/att + +/datum/action/item_action/toggle_infrared_sight/Trigger() + if(!att) + if(istype(target, /obj/item/gun)) + var/obj/item/gun/parent_gun = target + for(var/obj/item/attachment/A in parent_gun.current_attachments) + if(istype(A, /obj/item/attachment/scope/infrared)) + att = A + break + att?.toggle_on() + UpdateButtons() + +/datum/action/item_action/toggle_infrared_sight/UpdateButtons(atom/movable/screen/movable/action_button/button, status_only = FALSE, force) + var/obj/item/attachment/scope/infrared/ifrsight = target + if(istype(ifrsight)) + button_icon_state = "ifr_sight[att?.is_on ? "_on" : ""]" + + return ..() \ No newline at end of file diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 23dda2cd9b0e..a9fb01a0f48d 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -632,7 +632,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/gun/proc/update_attachments() for(var/mutable_appearance/M in attachment_overlays) @@ -649,7 +649,7 @@ update_icon(TRUE) for(var/datum/action/A as anything in actions) - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/gun/pickup(mob/user) ..() @@ -732,7 +732,7 @@ /datum/action/toggle_scope_zoom name = "Toggle Scope" - check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_LYING + check_flags = AB_CHECK_CONSCIOUS|AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_LYING icon_icon = 'icons/mob/actions/actions_items.dmi' button_icon_state = "sniper_zoom" var/obj/item/gun/gun = null diff --git a/code/modules/projectiles/guns/ballistic/automatic.dm b/code/modules/projectiles/guns/ballistic/automatic.dm index ad9c83325812..0c285db114ed 100644 --- a/code/modules/projectiles/guns/ballistic/automatic.dm +++ b/code/modules/projectiles/guns/ballistic/automatic.dm @@ -59,7 +59,7 @@ update_icon() for(var/X in actions) var/datum/action/A = X - A.UpdateButtonIcon() + A.UpdateButtons() /obj/item/gun/ballistic/automatic/c20r name = "\improper C-20r SMG" diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index e9a11b716ffe..74b29c99dd91 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -200,9 +200,10 @@ /obj/item/gun/energy/plasmacutter/use_tool(atom/target, mob/living/user, delay, amount=1, volume=0, datum/callback/extra_checks, robo_check) if(amount) - target.add_overlay(GLOB.welding_sparks) + var/mutable_appearance/sparks = mutable_appearance('icons/effects/welding_effect.dmi', "welding_sparks", GASFIRE_LAYER, src, ABOVE_LIGHTING_PLANE) + target.add_overlay(sparks) . = ..() - target.cut_overlay(GLOB.welding_sparks) + target.cut_overlay(sparks) else . = ..(amount=1) diff --git a/code/modules/projectiles/guns/magic/wand.dm b/code/modules/projectiles/guns/magic/wand.dm index 2d7fb5451255..fdc15b4e7469 100644 --- a/code/modules/projectiles/guns/magic/wand.dm +++ b/code/modules/projectiles/guns/magic/wand.dm @@ -68,7 +68,7 @@ /obj/item/gun/magic/wand/death/zap_self(mob/living/user) ..() to_chat(user, "You irradiate yourself with pure energy! \ - [pick("Do not pass go. Do not collect 200 zorkmids.","You feel more confident in your spell casting skills.","You Die...","Do you want your possessions identified?")]\ + [pick("Do not pass go. Do not collect 200 zorkmids.","You feel more confident in your spell casting skills.","You die...","Do you want your possessions identified?")]\ ") user.adjustOxyLoss(500) charges-- diff --git a/code/modules/projectiles/guns/misc/beam_rifle.dm b/code/modules/projectiles/guns/misc/beam_rifle.dm index b6c59cfdb955..4ae672507b46 100644 --- a/code/modules/projectiles/guns/misc/beam_rifle.dm +++ b/code/modules/projectiles/guns/misc/beam_rifle.dm @@ -27,6 +27,7 @@ weapon_weight = WEAPON_HEAVY w_class = WEIGHT_CLASS_BULKY ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan) + actions_types = list(/datum/action/item_action/zoom_lock_action) cell_type = /obj/item/stock_parts/cell/beam_rifle canMouseDown = TRUE pin = null @@ -73,7 +74,6 @@ var/static/image/charged_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_charged") var/static/image/drained_overlay = image(icon = 'icons/obj/guns/energy.dmi', icon_state = "esniper_empty") - var/datum/action/item_action/zoom_lock_action/zoom_lock_action var/mob/listeningTo /obj/item/gun/energy/beam_rifle/debug @@ -96,7 +96,7 @@ return ..() /obj/item/gun/energy/beam_rifle/ui_action_click(mob/user, actiontype) - if(istype(actiontype, zoom_lock_action)) + if(istype(actiontype, /datum/action/item_action/zoom_lock_action)) zoom_lock++ if(zoom_lock > 3) zoom_lock = 0 @@ -110,8 +110,9 @@ if(ZOOM_LOCK_OFF) to_chat(user, span_boldnotice("You disable [src]'s zooming system.")) reset_zooming() - else - ..() + return + + return ..() /obj/item/gun/energy/beam_rifle/proc/set_autozoom_pixel_offsets_immediate(current_angle) if(zoom_lock == ZOOM_LOCK_CENTER_VIEW || zoom_lock == ZOOM_LOCK_OFF) @@ -170,7 +171,6 @@ . = ..() fire_delay = delay START_PROCESSING(SSfastprocess, src) - zoom_lock_action = new(src) /obj/item/gun/energy/beam_rifle/Destroy() STOP_PROCESSING(SSfastprocess, src) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 7b28397a4f69..02494392fe75 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -340,7 +340,7 @@ if(!can_hit_target(M, permutated, M == original, TRUE)) continue mobs += M - var/mob/M = safepick(mobs) + var/mob/M = pick(mobs) if(M) return M.lowest_buckled_mob() var/list/obj/possible_objs = typecache_filter_list(T, GLOB.typecache_machine_or_structure) @@ -349,7 +349,7 @@ if(!can_hit_target(O, permutated, O == original, TRUE)) continue objs += O - var/obj/O = safepick(objs) + var/obj/O = pick(objs) if(O) return O //Nothing else is here that we can hit, hit the turf if we haven't. @@ -586,7 +586,7 @@ var/mob/living/L = M if((target in L.hasparasites()) && target.loc == L.loc) return FALSE - if((target == firer) || ((target == firer.loc) && (ismecha(firer.loc) || isspacepod(firer.loc))) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))) //cannot shoot yourself or your mech // yogs - or your spacepod) + if((target == firer) || ((target == firer.loc) && (ismecha(firer.loc) || isspacepod(firer.loc))) || !ismovable(M) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))) //cannot shoot yourself or your mech // yogs - or your spacepod) return FALSE if(!ignore_loc && (loc != target.loc)) return FALSE diff --git a/code/modules/projectiles/projectile/beams.dm b/code/modules/projectiles/projectile/beams.dm index 783e14d9af63..e7f6a5b4d864 100644 --- a/code/modules/projectiles/projectile/beams.dm +++ b/code/modules/projectiles/projectile/beams.dm @@ -33,7 +33,7 @@ wound_bonus = 0 speed = 0.6 // higher power = faster, that's how light works right -/obj/projectile/beam/laser/hellfire/Initialize() +/obj/item/projectile/beam/laser/hellfire/Initialize() . = ..() transform *= 2 @@ -49,7 +49,7 @@ /obj/item/projectile/beam/laser/on_hit(atom/target, blocked = FALSE) if((blocked != 100) && iscarbon(target)) var/mob/living/carbon/M = target - M.IgniteMob() + M.ignite_mob() else if(isturf(target)) impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser/wall return ..() diff --git a/code/modules/projectiles/projectile/bullets/_incendiary.dm b/code/modules/projectiles/projectile/bullets/_incendiary.dm index b023e86fc288..04fa152412b3 100644 --- a/code/modules/projectiles/projectile/bullets/_incendiary.dm +++ b/code/modules/projectiles/projectile/bullets/_incendiary.dm @@ -7,7 +7,7 @@ if(iscarbon(target)) var/mob/living/carbon/M = target M.adjust_fire_stacks(fire_stacks) - M.IgniteMob() + M.ignite_mob() /obj/item/projectile/bullet/incendiary/Move() . = ..() diff --git a/code/modules/projectiles/projectile/bullets/revolver.dm b/code/modules/projectiles/projectile/bullets/revolver.dm index 1953e1977586..5a9e4b5602e8 100644 --- a/code/modules/projectiles/projectile/bullets/revolver.dm +++ b/code/modules/projectiles/projectile/bullets/revolver.dm @@ -32,7 +32,7 @@ if((blocked != 100) && iscarbon(target)) var/mob/living/carbon/M = target M.adjust_fire_stacks(2) - M.IgniteMob() + M.ignite_mob() return ..() /obj/item/projectile/bullet/c38/iceblox //see /obj/item/projectile/temp for the original code diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm index 8c10d168661d..8faa2e82db98 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/magic.dm @@ -30,6 +30,15 @@ var/mob/M = target M.death(0) +/obj/item/projectile/magic/spellcard + name = "enchanted card" + desc = "A piece of paper enchanted to give it extreme durability and stiffness, along with a very hot burn to anyone unfortunate enough to get hit by a charged one." + icon_state = "spellcard" + damage_type = BURN + damage = 2 + nodamage = FALSE + antimagic_charge_cost = 0 // since the cards gets spammed like a shotgun + /obj/item/projectile/magic/resurrection name = "bolt of resurrection" icon_state = "ion" @@ -517,7 +526,7 @@ . = ..() if(ismob(target)) var/mob/M = target - SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, src, /datum/mood_event/sapped) + SEND_SIGNAL(target, COMSIG_ADD_MOOD_EVENT, REF(src), /datum/mood_event/sapped) /obj/item/projectile/magic/necropotence name = "bolt of necropotence" @@ -525,21 +534,16 @@ /obj/item/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(span_warning("[src] vanishes on contact with [target]!")) - return BULLET_ACT_BLOCK - to_chat(L, span_danger("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, span_userdanger("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/cooldown/spell/tap/tap = new(src) + if(tap.is_valid_target(target)) + tap.cast(target) + + qdel(tap) /obj/item/projectile/magic/wipe name = "bolt of possession" @@ -585,19 +589,62 @@ to_chat(M, span_notice("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/item/projectile/magic/aoe - name = "Area Bolt" - desc = "What the fuck does this do?!" damage = 0 - var/proxdet = TRUE + + /// 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/item/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/item/projectile/magic/aoe/lightning name = "lightning bolt" @@ -608,25 +655,28 @@ speed = 0.3 flag = MAGIC + /// The power of the zap itself when it electrocutes someone var/tesla_power = 20000 + /// The range of the zap itself when it electrocutes someone var/tesla_range = 15 + /// The flags of the zap itself when it electrocutes someone var/tesla_flags = TESLA_MOB_DAMAGE | TESLA_MOB_STUN | TESLA_OBJ_DAMAGE - var/chain - var/mob/living/caster + /// A reference to the chain beam between the caster and the projectile + var/datum/beam/chain /obj/item/projectile/magic/aoe/lightning/fire(setAngle) - if(caster) - chain = caster.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY) - ..() + if(firer) + chain = firer.Beam(src, icon_state = "lightning[rand(1, 12)]") + return ..() /obj/item/projectile/magic/aoe/lightning/on_hit(target) . = ..() tesla_zap(src, tesla_range, tesla_power, tesla_flags) qdel(src) -/obj/item/projectile/magic/aoe/lightning/Destroy() - qdel(chain) - . = ..() +/obj/projectile/magic/aoe/lightning/Destroy() + QDEL_NULL(chain) + return ..() /obj/item/projectile/magic/aoe/fireball name = "bolt of fireball" @@ -635,19 +685,83 @@ 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 + /// Flash radius of the fireball + var/exp_flash = 3 -/obj/item/projectile/magic/aoe/fireball/on_hit(target) +/obj/projectile/magic/fireball/on_hit(atom/target, blocked = FALSE, pierce_hit) . = ..() - if(ismob(target)) - var/mob/living/M = target - 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) + 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, + explosion_cause = src, + ) + + +/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) + . = ..() + 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)) /obj/item/projectile/magic/aoe/fireball/infernal name = "infernal fireball" @@ -660,7 +774,7 @@ . = ..() var/turf/T = get_turf(target) for(var/i=0, i<50, i+=10) - addtimer(CALLBACK(GLOBAL_PROC, .proc/explosion, T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i) //still magic related, but a different path @@ -740,7 +854,7 @@ if(iscarbon(target)) var/mob/living/carbon/X = target X.adjust_fire_stacks(2) - X.IgniteMob() + X.ignite_mob() /obj/item/projectile/magic/runic_honk @@ -851,7 +965,7 @@ if(iscarbon(target)) var/mob/living/carbon/X = target X.adjust_fire_stacks(1) - X.IgniteMob() + X.ignite_mob() X.adjustBruteLoss(5) /obj/item/projectile/magic/runic_mutation diff --git a/code/modules/projectiles/projectile/reusable/arrow.dm b/code/modules/projectiles/projectile/reusable/arrow.dm index 12431d4251c2..a458a2617830 100644 --- a/code/modules/projectiles/projectile/reusable/arrow.dm +++ b/code/modules/projectiles/projectile/reusable/arrow.dm @@ -141,7 +141,7 @@ var/mob/living/carbon/M = target M.apply_damage(8, BURN) M.adjust_fire_stacks(1) - M.IgniteMob() + M.ignite_mob() return ..() /obj/item/projectile/energy/arrow //Hardlight projectile. Significantly more robust than a standard laser. Capable of hardening in target's flesh diff --git a/code/modules/projectiles/projectile/special/hallucination.dm b/code/modules/projectiles/projectile/special/hallucination.dm index 35bb0af84883..31d1b34f79e5 100644 --- a/code/modules/projectiles/projectile/special/hallucination.dm +++ b/code/modules/projectiles/projectile/special/hallucination.dm @@ -167,11 +167,11 @@ /obj/item/projectile/hallucination/taser/hal_apply_effect() hal_target.Paralyze(100) - hal_target.stuttering += 20 + hal_target.adjust_stutter(2 SECONDS) if(hal_target.dna && (hal_target.dna.check_mutation(HULK)|| hal_target.dna.check_mutation(ACTIVE_HULK))) hal_target.say(pick(";RAAAAAAAARGH!", ";HNNNNNNNNNGGGGGGH!", ";GWAAAAAAAARRRHHH!", "NNNNNNNNGGGGGGGGHH!", ";AAAAAAARRRGH!" ), forced = "hulk") else if((hal_target.status_flags & CANKNOCKDOWN) && !HAS_TRAIT(hal_target, TRAIT_STUNIMMUNE)) - addtimer(CALLBACK(hal_target, /mob/living/carbon.proc/do_jitter_animation, 20), 5) + addtimer(CALLBACK(hal_target, TYPE_PROC_REF(/mob/living/carbon, do_jitter_animation), 20), 0.5 SECONDS) /obj/item/projectile/hallucination/disabler name = "disabler beam" @@ -200,7 +200,7 @@ /obj/item/projectile/hallucination/ebow/hal_apply_effect() hal_target.Paralyze(100) - hal_target.stuttering += 5 + hal_target.adjust_stutter(5 SECONDS) hal_target.adjustStaminaLoss(8) /obj/item/projectile/hallucination/change diff --git a/code/modules/projectiles/projectile/special/mindflayer.dm b/code/modules/projectiles/projectile/special/mindflayer.dm index a50c2972e652..a43737ee8121 100644 --- a/code/modules/projectiles/projectile/special/mindflayer.dm +++ b/code/modules/projectiles/projectile/special/mindflayer.dm @@ -19,7 +19,7 @@ var/mob/living/carbon/human/M = target var/current_oxygen_damage = M.getOxyLoss() M.adjustOrganLoss(ORGAN_SLOT_BRAIN, current_oxygen_damage) //the more oxygen damage you have the more brain damage you get - M.hallucination = max(50, M.hallucination) //50 hallucination (5 seconds) when the target get hit but you can't stack it to make someone hallucinate for 5 hours because door shock hallucination exist + M.adjust_hallucinations_up_to(5 SECONDS, 5 SECONDS) //50 hallucination (5 seconds) when the target get hit but you can't stack it to make someone hallucinate for 5 hours because door shock hallucination exist /obj/item/projectile/beam/anoxia/bounce name = "bouncing anoxia ball" @@ -29,4 +29,4 @@ ricochet_chance = 100 /obj/item/projectile/beam/anoxia/bounce/check_ricochet_flag(atom/A) - return TRUE //whatever it is, we bounce on it \ No newline at end of file + return TRUE //whatever it is, we bounce on it diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm index b79376e81499..2c7ac151d63d 100644 --- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm @@ -33,17 +33,19 @@ All effects don't start immediately, but rather get worse over time; the rate is 91-100: Dangerously toxic - swift death */ -/datum/reagent/consumable/ethanol/on_mob_life(mob/living/carbon/C) - if(C.drunkenness < volume * boozepwr * ALCOHOL_THRESHOLD_MODIFIER) +/datum/reagent/consumable/ethanol/on_mob_life(mob/living/carbon/drinker) + if(drinker.get_drunk_amount() < volume * boozepwr * ALCOHOL_THRESHOLD_MODIFIER || boozepwr < 0) var/booze_power = boozepwr - if(HAS_TRAIT(C, TRAIT_ALCOHOL_TOLERANCE)) //we're an accomplished drinker + if(HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) //we're an accomplished drinker booze_power *= 0.7 - if(HAS_TRAIT(C, TRAIT_LIGHT_DRINKER)) + if(HAS_TRAIT(drinker, TRAIT_LIGHT_DRINKER)) booze_power *= 2 - C.drunkenness = max((C.drunkenness + (sqrt(volume) * booze_power * ALCOHOL_RATE)), 0) //Volume, power, and server alcohol rate effect how quickly one gets drunk - var/obj/item/organ/liver/L = C.getorganslot(ORGAN_SLOT_LIVER) - if (istype(L)) - L.applyOrganDamage(((max(sqrt(volume) * (boozepwr ** ALCOHOL_EXPONENT) * L.alcohol_tolerance, 0))/150)) + // Volume, power, and server alcohol rate effect how quickly one gets drunk + drinker.adjust_drunk_effect(sqrt(volume) * booze_power * ALCOHOL_RATE * REM) + if(boozepwr > 0) + var/obj/item/organ/liver/liver = drinker.getorganslot(ORGAN_SLOT_LIVER) + if (istype(liver)) + liver.applyOrganDamage(((max(sqrt(volume) * (boozepwr ** ALCOHOL_EXPONENT) * liver.alcohol_tolerance, 0))/150)) return ..() /datum/reagent/consumable/ethanol/reaction_obj(obj/O, reac_volume) @@ -159,11 +161,11 @@ All effects don't start immediately, but rather get worse over time; the rate is shot_glass_icon_state = "shotglasscream" /datum/reagent/consumable/ethanol/kahlua/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.AdjustSleeping(-40, FALSE) if(!HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -193,16 +195,16 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "This is a glass of Thirteen Loko, it appears to be of the highest quality. The drink, not the glass." /datum/reagent/consumable/ethanol/thirteenloko/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-7) + M.adjust_drowsiness(-7 SECONDS) M.AdjustSleeping(-40) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) if(!HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) return ..() /datum/reagent/consumable/ethanol/thirteenloko/overdose_start(mob/living/M) to_chat(M, span_userdanger("Your entire body violently jitters as you start to feel queasy. You really shouldn't have drank all of that [name]!")) - M.Jitter(20) + M.adjust_jitter(20 SECONDS) M.Stun(15) /datum/reagent/consumable/ethanol/thirteenloko/overdose_process(mob/living/M) @@ -211,7 +213,7 @@ All effects don't start immediately, but rather get worse over time; the rate is if(I) M.dropItemToGround(I) to_chat(M, "Your hands jitter and you drop what you were holding!") - M.Jitter(10) + M.adjust_jitter(10 SECONDS) if(prob(7)) to_chat(M, span_notice("[pick("You have a really bad headache.", "Your eyes hurt.", "You find it hard to stay still.", "You feel your heart practically beating out of your chest.")]")) @@ -233,7 +235,7 @@ All effects don't start immediately, but rather get worse over time; the rate is if(prob(3) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) M.Unconscious(100) - M.Jitter(350) + M.adjust_jitter(350) if(prob(1) && iscarbon(M)) var/datum/disease/D = new /datum/disease/heart_failure @@ -395,7 +397,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/absinthe/on_mob_life(mob/living/carbon/M) if(prob(10) && !HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.hallucination += 4 //Reference to the urban myth + M.adjust_hallucinations(4 SECONDS) //Reference to the urban myth ..() /datum/reagent/consumable/ethanol/hooch @@ -669,7 +671,7 @@ All effects don't start immediately, but rather get worse over time; the rate is ..() /datum/reagent/consumable/ethanol/beepsky_smash/on_mob_life(mob/living/carbon/M) - M.Jitter(2) + M.adjust_jitter(2 SECONDS) if(HAS_TRAIT(M.mind, TRAIT_LAW_ENFORCEMENT_METABOLISM)) M.adjustStaminaLoss(-10, 0) if(prob(20)) @@ -880,7 +882,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Snow White" glass_desc = "A cold refreshment." -/datum/reagent/consumable/ethanol/demonsblood //Prevents the imbiber from being dragged into a pool of blood by a slaughter demon. +/datum/reagent/consumable/ethanol/demonsblood name = "Demon's Blood" description = "AHHHH!!!!" color = "#820000" // rgb: 130, 0, 0 @@ -891,7 +893,34 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Demons Blood" 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/cooldown/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( + span_warning("Something prevents [source] from entering [blood]!"), + blind_message = span_notice("You hear a splash and a thud.") + ) + to_chat(jaunter, span_warning("A strange force is blocking [source] from entering!")) + + return COMPONENT_STOP_CONSUMPTION + +/datum/reagent/consumable/ethanol/devilskiss name = "Devil's Kiss" description = "Creepy time!" color = "#A68310" // rgb: 166, 131, 16 @@ -902,6 +931,41 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Devils Kiss" glass_desc = "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/cooldown/spell/jaunt/bloodcrawl/crawl, + mob/living/jaunter, +) + + SIGNAL_HANDLER + + . = COMPONENT_STOP_CONSUMPTION + + to_chat(jaunter, span_boldwarning("AAH! THEIR FLESH! IT BURNS!")) + jaunter.apply_damage(25, BRUTE, wound_bonus = CANT_WOUND) + + 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(span_warning("[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(span_warning("[source] appears from nowhere, covered in blood!")) + crawl.exit_blood_effect(source) + /datum/reagent/consumable/ethanol/vodkatonic name = "Vodka and Tonic" description = "For when a gin and tonic isn't Russian enough." @@ -1077,12 +1141,9 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Changeling Sting" glass_desc = "A stingy drink." -/datum/reagent/consumable/ethanol/changelingsting/on_mob_life(mob/living/carbon/M) - if(M.mind) //Changeling Sting assists in the recharging of changeling chemicals. - var/datum/antagonist/changeling/changeling = M.mind.has_antag_datum(/datum/antagonist/changeling) - if(changeling) - changeling.chem_charges += metabolization_rate - changeling.chem_charges = clamp(changeling.chem_charges, 0, changeling.chem_storage) +/datum/reagent/consumable/ethanol/changelingsting/on_mob_life(mob/living/carbon/target) + var/datum/antagonist/changeling/changeling = target.mind?.has_antag_datum(/datum/antagonist/changeling) + changeling?.adjust_chemicals(metabolization_rate * REM) return ..() /datum/reagent/consumable/ethanol/irishcarbomb @@ -1278,22 +1339,20 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Atomic Bomb" glass_desc = "Nanotrasen cannot take legal responsibility for your actions after imbibing." -/datum/reagent/consumable/ethanol/atomicbomb/on_mob_life(mob/living/carbon/M) - M.set_drugginess(50) - if(!HAS_TRAIT(M, TRAIT_ALCOHOL_TOLERANCE)) - M.confused = max(M.confused+2,0) - M.Dizzy(10) - if (!M.slurring) - M.slurring = 1 - M.slurring += 3 +/datum/reagent/consumable/ethanol/atomicbomb/on_mob_life(mob/living/carbon/drinker) + drinker.set_drugginess(100 SECONDS * REM) + if(!HAS_TRAIT(drinker, TRAIT_ALCOHOL_TOLERANCE)) + drinker.adjust_confusion(2 SECONDS * REM) + drinker.set_dizzy_if_lower(20 SECONDS * REM) + drinker.adjust_slurring(6 SECONDS * REM) switch(current_cycle) if(51 to 200) - M.Sleeping(100, FALSE) - . = 1 + drinker.Sleeping(10 SECONDS * REM) + . = TRUE if(201 to INFINITY) - M.AdjustSleeping(40, FALSE) - M.adjustToxLoss(2, 0) - . = 1 + drinker.AdjustSleeping(4 SECONDS* REM) + drinker.adjustToxLoss(2 * REM, FALSE) + . = TRUE ..() /datum/reagent/consumable/ethanol/gargle_blaster @@ -1307,21 +1366,20 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Pan-Galactic Gargle Blaster" glass_desc = "Like having your brain smashed out by a slice of lemon wrapped around a large gold brick." -/datum/reagent/consumable/ethanol/gargle_blaster/on_mob_life(mob/living/carbon/M) - M.dizziness +=1.5 +/datum/reagent/consumable/ethanol/gargle_blaster/on_mob_life(mob/living/carbon/drinker) + drinker.adjust_dizzy(3 SECONDS * REM) switch(current_cycle) if(15 to 45) - if(!M.slurring) - M.slurring = 1 - M.slurring += 3 + drinker.adjust_slurring(3 SECONDS * REM) + if(45 to 55) if(prob(50)) - M.confused = max(M.confused+3,0) + drinker.adjust_confusion(3 SECONDS * REM) if(55 to 200) - M.set_drugginess(55) + drinker.set_drugginess(110 SECONDS * REM) if(200 to INFINITY) - M.adjustToxLoss(2, 0) - . = 1 + drinker.adjustToxLoss(2 * REM, FALSE) + . = TRUE ..() /datum/reagent/consumable/ethanol/neurotoxin @@ -1341,7 +1399,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/neurotoxin/on_mob_life(mob/living/carbon/M) M.set_drugginess(50) - M.dizziness +=2 + M.adjust_dizzy(2 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1*REM, 150) if(prob(20)) M.adjustStaminaLoss(10) @@ -1384,29 +1442,28 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "A drink enjoyed by people during the 1960's." /datum/reagent/consumable/ethanol/hippies_delight/on_mob_life(mob/living/carbon/M) - if (!M.slurring) - M.slurring = 1 + M.set_slurring_if_lower(1 SECONDS * REM) switch(current_cycle) if(1 to 5) - M.Dizzy(10) + M.adjust_dizzy(10) M.set_drugginess(30) if(prob(10)) M.emote(pick("twitch","giggle")) if(5 to 10) - M.Jitter(20) - M.Dizzy(20) + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(20) M.set_drugginess(45) if(prob(20)) M.emote(pick("twitch","giggle")) if (10 to 200) - M.Jitter(40) - M.Dizzy(40) + M.adjust_jitter(40 SECONDS) + M.adjust_dizzy(40) M.set_drugginess(60) if(prob(30)) M.emote(pick("twitch","giggle")) if(200 to INFINITY) - M.Jitter(60) - M.Dizzy(60) + M.adjust_jitter(1 MINUTES) + M.adjust_dizzy(60) M.set_drugginess(75) if(prob(40)) M.emote(pick("twitch","giggle")) @@ -1439,11 +1496,11 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_name = "Nar'Sour" glass_desc = "A new hit cocktail inspired by THE ARM Breweries will have you shouting Fuu ma'jin in no time!" -/datum/reagent/consumable/ethanol/narsour/on_mob_life(mob/living/carbon/M) - M.cultslurring = min(M.cultslurring + 3, 3) - M.stuttering = min(M.stuttering + 3, 3) - if(iscultist(M)) - M.heal_overall_damage(0.5, 0.5) +/datum/reagent/consumable/ethanol/narsour/on_mob_life(mob/living/carbon/drinker) + drinker.adjust_timed_status_effect(6 SECONDS * REM, /datum/status_effect/speech/slurring/cult, max_duration = 6 SECONDS) + drinker.adjust_stutter_up_to(6 SECONDS * REM, 6 SECONDS) + if(iscultist(drinker)) + drinker.heal_overall_damage(0.5, 0.5) ..() /datum/reagent/consumable/ethanol/triple_sec @@ -2053,7 +2110,7 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/turbo/on_mob_life(mob/living/carbon/M) if(prob(4)) to_chat(M, span_notice("[pick("You feel disregard for the rule of law.", "You feel pumped!", "Your head is pounding.", "Your thoughts are racing..")]")) - M.adjustStaminaLoss(-M.drunkenness * 0.25) + M.adjustStaminaLoss(-M.get_drunk_amount() * 0.25) return ..() /datum/reagent/consumable/ethanol/old_timer @@ -2124,8 +2181,8 @@ All effects don't start immediately, but rather get worse over time; the rate is /datum/reagent/consumable/ethanol/trappist/on_mob_life(mob/living/carbon/M) if(M.mind.holy_role) M.adjustFireLoss(-2.5, 0) - M.jitteriness = max(0, M.jitteriness-1) - M.stuttering = max(0, M.stuttering-1) + M.adjust_jitter(-1 SECONDS) + M.adjust_stutter(-1 SECONDS) return ..() /datum/reagent/consumable/ethanol/blazaam @@ -2140,7 +2197,7 @@ All effects don't start immediately, but rather get worse over time; the rate is var/stored_teleports = 0 /datum/reagent/consumable/ethanol/blazaam/on_mob_life(mob/living/carbon/M) - if(M.drunkenness > 40) + if(M.get_drunk_amount() > 40) if(stored_teleports) do_teleport(M, get_turf(M), rand(1,3), channel = TELEPORT_CHANNEL_WORMHOLE) stored_teleports-- @@ -2281,7 +2338,7 @@ All effects don't start immediately, but rather get worse over time; the rate is glass_desc = "an amazing concoction of various different bar drinks and a secret ingredient" /datum/reagent/consumable/ethanol/flaming_moe/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-5 SECONDS) M.AdjustStun(-20, FALSE) M.AdjustKnockdown(-20, FALSE) M.AdjustUnconscious(-20, FALSE) @@ -2289,7 +2346,7 @@ All effects don't start immediately, but rather get worse over time; the rate is M.AdjustParalyzed(-20, FALSE) if(M.reagents.has_reagent(/datum/reagent/toxin/mindbreaker)) M.reagents.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) - M.hallucination = max(0, M.hallucination - 10) + M.adjust_hallucinations(-10 SECONDS) if(prob(30)) M.adjustToxLoss(1, 0) . = 1 diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm index 2955be815c50..438655e52f33 100644 --- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm @@ -50,7 +50,7 @@ /datum/reagent/medicine/c2/probital/overdose_process(mob/living/M) M.adjustStaminaLoss(3 * REM, 0) if(M.getStaminaLoss() >= 80) - M.drowsyness += 1 * REM + M.adjust_drowsiness(2 SECONDS * REM) if(M.getStaminaLoss() >= 100) to_chat(M,span_warning("You feel more tired than you usually do, perhaps if you rest your eyes for a bit...")) M.adjustStaminaLoss(-100, TRUE) @@ -157,7 +157,7 @@ M.adjustOxyLoss(-3 * REM) M.adjustStaminaLoss(2 * REM) if(drowsycd && COOLDOWN_FINISHED(src, drowsycd)) - M.drowsyness += 10 + M.adjust_drowsiness(20 SECONDS) COOLDOWN_START(src, drowsycd, 45 SECONDS) else if(!drowsycd) COOLDOWN_START(src, drowsycd, 15 SECONDS) diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm index 4bcd00a3889a..5fbbd564f22d 100644 --- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm @@ -351,12 +351,12 @@ glass_desc = "Don't drop it, or you'll send scalding liquid and glass shards everywhere." /datum/reagent/consumable/coffee/overdose_process(mob/living/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() /datum/reagent/consumable/coffee/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-10 SECONDS * REM) + M.adjust_drowsiness(-6 SECONDS * REM) M.AdjustSleeping(-40, FALSE) //310.15 is the normal bodytemp. M.adjust_bodytemperature(25 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL) @@ -376,9 +376,9 @@ glass_desc = "Drinking it from here would not seem right." /datum/reagent/consumable/tea/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-2) - M.drowsyness = max(0,M.drowsyness-1) - M.jitteriness = max(0,M.jitteriness-3) + M.adjust_dizzy(-2 SECONDS) + M.adjust_drowsiness(-1 SECONDS) + M.adjust_jitter(-3 SECONDS) M.AdjustSleeping(-20, FALSE) if(M.getToxLoss() && prob(20)) M.adjustToxLoss(-1, 0) @@ -440,11 +440,11 @@ glass_desc = "A drink to perk you up and refresh you!" /datum/reagent/consumable/icecoffee/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -459,8 +459,8 @@ glass_desc = "All natural, antioxidant-rich flavour sensation." /datum/reagent/consumable/icetea/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-2) - M.drowsyness = max(0,M.drowsyness-1) + M.adjust_dizzy(-2 SECONDS) + M.adjust_drowsiness(-1 SECONDS) M.AdjustSleeping(-40, FALSE) if(M.getToxLoss() && prob(20)) M.adjustToxLoss(-1, 0) @@ -478,7 +478,7 @@ glass_desc = "A glass of refreshing Space Cola." /datum/reagent/consumable/space_cola/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-5) + M.adjust_drowsiness(-5 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -492,7 +492,7 @@ glass_desc = "A glass of refreshing fizzing root beer." /datum/reagent/consumable/rootbeer/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-5) + M.adjust_drowsiness(-5 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -515,10 +515,10 @@ ..() /datum/reagent/consumable/nuka_cola/on_mob_life(mob/living/carbon/M) - M.Jitter(20) + M.adjust_jitter(20 SECONDS) M.set_drugginess(30) - M.dizziness +=1.5 - M.drowsyness = 0 + M.adjust_dizzy(1.5 SECONDS) + M.remove_status_effect(/datum/status_effect/drowsiness) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) M.apply_effect(5,EFFECT_IRRADIATE,0) @@ -544,9 +544,9 @@ ..() /datum/reagent/consumable/grey_bull/on_mob_life(mob/living/carbon/M) - M.Jitter(20) - M.dizziness +=1 - M.drowsyness = 0 + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(1 SECONDS) + M.remove_status_effect(/datum/status_effect/drowsiness) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -561,10 +561,10 @@ glass_desc = "Space Mountain Wind. As you know, there are no mountains in space, only wind." /datum/reagent/consumable/spacemountainwind/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-7) + M.adjust_drowsiness(-7 SECONDS) M.AdjustSleeping(-20, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -578,7 +578,7 @@ glass_desc = "Dr. Gibb. Not as dangerous as the glass_name might imply." /datum/reagent/consumable/dr_gibb/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(0,M.drowsyness-6) + M.adjust_drowsiness(-6 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -645,8 +645,8 @@ glass_desc = "Soda water. Why not make a scotch and soda?" /datum/reagent/consumable/sodawater/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -660,8 +660,8 @@ glass_desc = "Quinine tastes funny, but at least it'll keep that Space Malaria away." /datum/reagent/consumable/tonic/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.AdjustSleeping(-40, FALSE) M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() @@ -676,12 +676,12 @@ glass_name = "glass of Monkey Energy" glass_desc = "You can unleash the ape, but without the pop of the can?" -/datum/reagent/consumable/monkey_energy/on_mob_life(mob/living/carbon/M) - M.Jitter(20) - M.dizziness +=1 - M.drowsyness = 0 - M.AdjustSleeping(-40, FALSE) - M.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) +/datum/reagent/consumable/monkey_energy/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_jitter(20 SECONDS) + affected_mob.adjust_dizzy(2 SECONDS * REM) + affected_mob.remove_status_effect(/datum/status_effect/drowsiness) + affected_mob.AdjustSleeping(-40, FALSE) + affected_mob.adjust_bodytemperature(-5 * TEMPERATURE_DAMAGE_COEFFICIENT, BODYTEMP_NORMAL) ..() /datum/reagent/consumable/monkey_energy/on_mob_metabolize(mob/living/L) @@ -718,11 +718,11 @@ glass_desc = "A nice and refreshing beverage while you're reading." /datum/reagent/consumable/soy_latte/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-5 SECONDS) + M.adjust_drowsiness(-3 SECONDS) M.SetSleeping(0, FALSE) M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(M.getBruteLoss() && prob(20)) M.heal_bodypart_damage(1,0, 0) ..() @@ -739,11 +739,11 @@ glass_desc = "A nice, strong and refreshing beverage while you're reading." /datum/reagent/consumable/cafe_latte/on_mob_life(mob/living/carbon/M) - M.dizziness = max(0,M.dizziness-5) - M.drowsyness = max(0,M.drowsyness-3) + M.adjust_dizzy(-10 SECONDS * REM) + M.adjust_drowsiness(-12 SECONDS * REM) M.SetSleeping(0, FALSE) M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(M.getBruteLoss() && prob(20)) M.heal_bodypart_damage(1,0, 0) ..() diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index 27310251ee6b..565df2f6f5bc 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -28,9 +28,10 @@ to_chat(M, span_userdanger("You start tripping hard!")) SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "[type]_overdose", /datum/mood_event/overdose, name) -/datum/reagent/drug/space_drugs/overdose_process(mob/living/M) - if(M.hallucination < volume && prob(20)) - M.hallucination += 5 +/datum/reagent/drug/space_drugs/overdose_process(mob/living/affected_mob) + var/hallucination_duration_in_seconds = (affected_mob.get_timed_status_effect_duration(/datum/status_effect/hallucination) / 10) + if(hallucination_duration_in_seconds < volume && prob(20)) + affected_mob.adjust_hallucinations(10 SECONDS) ..() /datum/reagent/drug/nicotine @@ -67,24 +68,24 @@ /datum/reagent/drug/nicotine/addiction_act_stage1(mob/living/M) if(prob(5) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() /datum/reagent/drug/nicotine/addiction_act_stage2(mob/living/M) if(prob(20) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() . = 1 /datum/reagent/drug/nicotine/addiction_act_stage3(mob/living/M) if(prob(20) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() . = 1 /datum/reagent/drug/nicotine/addiction_act_stage4(mob/living/M) if(prob(40) && iscarbon(M)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) ..() . = 1 @@ -213,7 +214,7 @@ M.AdjustParalyzed(-40, FALSE) M.AdjustImmobilized(-40, FALSE) M.adjustStaminaLoss(-2, 0) - M.Jitter(2) + M.adjust_jitter(2 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, rand(1,4)) if(prob(5)) M.emote(pick("twitch", "shiver")) @@ -235,14 +236,14 @@ . = 1 /datum/reagent/drug/methamphetamine/addiction_act_stage1(mob/living/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(20)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/methamphetamine/addiction_act_stage2(mob/living/M) - M.Jitter(10) - M.Dizzy(10) + M.adjust_jitter(10 SECONDS) + M.adjust_dizzy(10) if(prob(30)) M.emote(pick("twitch","drool","moan")) ..() @@ -251,8 +252,8 @@ if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 4, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(15) - M.Dizzy(15) + M.adjust_jitter(15 SECONDS) + M.adjust_dizzy(15) if(prob(40)) M.emote(pick("twitch","drool","moan")) ..() @@ -261,8 +262,8 @@ if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 8, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(20) - M.Dizzy(20) + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(20) M.adjustToxLoss(5, 0) if(prob(50)) M.emote(pick("twitch","drool","moan")) @@ -307,7 +308,7 @@ to_chat(M, span_notice("[high_message]")) M.adjustStaminaLoss(-5, 0) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 4) - M.hallucination += 5 + M.adjust_hallucinations(5 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) step(M, pick(GLOB.cardinals)) step(M, pick(GLOB.cardinals)) @@ -315,7 +316,7 @@ . = 1 /datum/reagent/drug/bath_salts/overdose_process(mob/living/M) - M.hallucination += 5 + M.adjust_hallucinations(5 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i in 1 to 8) step(M, pick(GLOB.cardinals)) @@ -326,47 +327,47 @@ ..() /datum/reagent/drug/bath_salts/addiction_act_stage1(mob/living/M) - M.hallucination += 10 + M.adjust_hallucinations(5 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 8, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(20)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/bath_salts/addiction_act_stage2(mob/living/M) - M.hallucination += 20 + M.adjust_hallucinations(10 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 8, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(10) - M.Dizzy(10) + M.adjust_jitter(10 SECONDS) + M.adjust_dizzy(10 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(30)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/bath_salts/addiction_act_stage3(mob/living/M) - M.hallucination += 30 + M.adjust_hallucinations(30 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 12, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(15) - M.Dizzy(15) + M.adjust_jitter(15 SECONDS) + M.adjust_dizzy(15 SECONDS) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(40)) M.emote(pick("twitch","drool","moan")) ..() /datum/reagent/drug/bath_salts/addiction_act_stage4(mob/living/carbon/human/M) - M.hallucination += 30 + M.adjust_hallucinations(30 SECONDS) if((M.mobility_flags & MOBILITY_MOVE) && !ismovable(M.loc)) for(var/i = 0, i < 16, i++) step(M, pick(GLOB.cardinals)) - M.Jitter(50) - M.Dizzy(50) + M.adjust_jitter(50 SECONDS) + M.adjust_dizzy(50 SECONDS) M.adjustToxLoss(5, 0) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 10) if(prob(50)) @@ -410,11 +411,11 @@ SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "happiness_drug") ..() -/datum/reagent/drug/happiness/on_mob_life(mob/living/carbon/M) - M.jitteriness = 0 - M.confused = 0 - M.disgust = 0 - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2) +/datum/reagent/drug/happiness/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.remove_status_effect(/datum/status_effect/jitter) + affected_mob.remove_status_effect(/datum/status_effect/confusion) + affected_mob.disgust = 0 + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.2) ..() . = 1 @@ -427,7 +428,7 @@ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "happiness_drug", /datum/mood_event/happiness_drug_good_od) if(2) M.emote("sway") - M.Dizzy(25) + M.adjust_dizzy(25) if(3) M.emote("frown") SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "happiness_drug", /datum/mood_event/happiness_drug_bad_od) @@ -438,7 +439,7 @@ /datum/reagent/drug/happiness/addiction_act_stage1(mob/living/M)// all work and no play makes jack a dull boy var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(min(mood.sanity, SANITY_DISTURBED)) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(20)) M.emote(pick("twitch","laugh","frown")) ..() @@ -446,7 +447,7 @@ /datum/reagent/drug/happiness/addiction_act_stage2(mob/living/M) var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(min(mood.sanity, SANITY_UNSTABLE)) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) if(prob(30)) M.emote(pick("twitch","laugh","frown")) ..() @@ -454,7 +455,7 @@ /datum/reagent/drug/happiness/addiction_act_stage3(mob/living/M) var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(min(mood.sanity, SANITY_CRAZY)) - M.Jitter(15) + M.adjust_jitter(15 SECONDS) if(prob(40)) M.emote(pick("twitch","laugh","frown")) ..() @@ -462,7 +463,7 @@ /datum/reagent/drug/happiness/addiction_act_stage4(mob/living/carbon/human/M) var/datum/component/mood/mood = M.GetComponent(/datum/component/mood) mood.setSanity(SANITY_INSANE) - M.Jitter(20) + M.adjust_jitter(20 SECONDS) if(prob(50)) M.emote(pick("twitch","laugh","frown")) ..() @@ -493,17 +494,17 @@ if(10) to_chat(M, span_warning("You start to feel tired...") ) if(11 to 25) - M.drowsyness ++ + M.adjust_drowsiness(2 SECONDS) if(26 to INFINITY) M.Sleeping(60, 0) . = 1 //Providing a Mood Boost - M.confused -= 3 - M.jitteriness -= 5 + M.adjust_confusion(-3 SECONDS) + M.adjust_jitter(-5 SECONDS) M.disgust -= 3 //Ketamine is also a dissociative anasthetic which means Hallucinations! - M.hallucination += 5 + M.adjust_hallucinations(20 SECONDS) ..() /datum/reagent/drug/ketamine/overdose_process(mob/living/M) @@ -515,7 +516,7 @@ /datum/reagent/drug/ketamine/addiction_act_stage1(mob/living/M) if(prob(20)) M.drop_all_held_items() - M.Jitter(2) + M.adjust_jitter(2 SECONDS) ..() /datum/reagent/drug/ketamine/addiction_act_stage2(mob/living/M) @@ -523,8 +524,8 @@ M.drop_all_held_items() M.adjustToxLoss(2*REM, 0) . = 1 - M.Jitter(3) - M.Dizzy(3) + M.adjust_jitter(3 SECONDS) + M.adjust_dizzy(3) ..() /datum/reagent/drug/ketamine/addiction_act_stage3(mob/living/M) @@ -532,8 +533,8 @@ M.drop_all_held_items() M.adjustToxLoss(3*REM, 0) . = 1 - M.Jitter(4) - M.Dizzy(4) + M.adjust_jitter(4 SECONDS) + M.adjust_dizzy(4) ..() /datum/reagent/drug/pumpup @@ -557,7 +558,7 @@ ..() /datum/reagent/drug/pumpup/on_mob_life(mob/living/carbon/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(5)) to_chat(M, span_notice("[pick("Go! Go! GO!", "You feel ready...", "You feel invincible...")]")) @@ -571,7 +572,7 @@ to_chat(M, span_userdanger("You can't stop shaking, your heart beats faster and faster...")) /datum/reagent/drug/pumpup/overdose_process(mob/living/M) - M.Jitter(5) + M.adjust_jitter(5 SECONDS) if(prob(5)) M.drop_all_held_items() if(prob(15)) diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm index f01ae15c99d5..64d4e87175ab 100644 --- a/code/modules/reagents/chemistry/reagents/food_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm @@ -312,16 +312,16 @@ victim.emote("scream") victim.blur_eyes(14) victim.blind_eyes(10) - victim.confused = max(M.confused, 10) + victim.set_confusion_if_lower(10 SECONDS) victim.damageoverlaytemp = 75 - victim.Paralyze(100) + victim.Paralyze(10 SECONDS) M.adjustStaminaLoss(3) return else if ( eyes_covered ) // Eye cover is better than mouth cover if(prob(20)) victim.emote("cough") victim.blur_eyes(4) - victim.confused = max(M.confused, 6) + victim.set_confusion_if_lower(5 SECONDS) victim.damageoverlaytemp = 50 M.adjustStaminaLoss(3) return @@ -330,9 +330,9 @@ victim.emote("scream") victim.blur_eyes(14) victim.blind_eyes(10) - victim.confused = max(M.confused, 12) + victim.set_confusion_if_lower(12 SECONDS) victim.damageoverlaytemp = 100 - victim.Paralyze(140) + victim.Paralyze(14 SECONDS) M.adjustStaminaLoss(5) victim.update_damage_hud() @@ -402,23 +402,22 @@ taste_description = "mushroom" /datum/reagent/drug/mushroomhallucinogen/on_mob_life(mob/living/carbon/M) - if(!M.slurring) - M.slurring = 1 + M.set_slurring_if_lower(1 SECONDS) switch(current_cycle) if(1 to 5) - M.Dizzy(5) + M.adjust_dizzy(5 SECONDS) M.set_drugginess(30) if(prob(10)) M.emote(pick("twitch","giggle")) if(5 to 10) - M.Jitter(10) - M.Dizzy(10) + M.adjust_jitter(10 SECONDS) + M.adjust_dizzy(10 SECONDS) M.set_drugginess(35) if(prob(20)) M.emote(pick("twitch","giggle")) if (10 to INFINITY) - M.Jitter(20) - M.Dizzy(20) + M.adjust_jitter(20 SECONDS) + M.adjust_dizzy(20 SECONDS) M.set_drugginess(40) if(prob(30)) M.emote(pick("twitch","giggle")) @@ -436,7 +435,7 @@ if(prob(min(25,current_cycle))) to_chat(M, span_danger("You can't get the scent of garlic out of your nose! You can barely think...")) M.Paralyze(10) - M.Jitter(10) + M.adjust_jitter(10 SECONDS) else if(ishuman(M)) var/mob/living/carbon/human/H = M if(H.job == "Cook") diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index 55047b35368e..ebc502a87232 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -44,7 +44,7 @@ M.radiation = 0 M.heal_bodypart_damage(5,5) M.adjustToxLoss(-5, 0, TRUE) - M.hallucination = 0 + M.remove_status_effect(/datum/status_effect/hallucination) REMOVE_TRAITS_NOT_IN(M, list(SPECIES_TRAIT, ROUNDSTART_TRAIT, ORGAN_TRAIT)) M.set_blurriness(0) M.set_blindness(0) @@ -54,14 +54,14 @@ M.SetParalyzed(0, FALSE) M.SetImmobilized(0, FALSE) M.silent = FALSE - M.dizziness = 0 + M.remove_status_effect(/datum/status_effect/dizziness) M.disgust = 0 - M.drowsyness = 0 - M.stuttering = 0 - M.slurring = 0 - M.confused = 0 + M.remove_status_effect(/datum/status_effect/drowsiness) + M.remove_status_effect(/datum/status_effect/speech/stutter) + M.remove_status_effect(/datum/status_effect/speech/slurring) + M.remove_status_effect(/datum/status_effect/confusion) M.SetSleeping(0, 0) - M.jitteriness = 0 + M.remove_status_effect(/datum/status_effect/jitter) if(M.blood_volume < BLOOD_VOLUME_NORMAL(M)) M.blood_volume = BLOOD_VOLUME_NORMAL(M) @@ -87,18 +87,18 @@ description = "Increases resistance to stuns as well as reducing drowsiness and hallucinations." color = "#FF00FF" -/datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(M.drowsyness-5, 0) - M.AdjustStun(-20, FALSE) - M.AdjustKnockdown(-20, FALSE) - M.AdjustUnconscious(-20, FALSE) - M.AdjustImmobilized(-20, FALSE) - M.AdjustParalyzed(-20, FALSE) +/datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_drowsiness(-10 SECONDS * REM) + affected_mob.AdjustStun(-20, FALSE) + affected_mob.AdjustKnockdown(-20, FALSE) + affected_mob.AdjustUnconscious(-20, FALSE) + affected_mob.AdjustImmobilized(-20, FALSE) + affected_mob.AdjustParalyzed(-20, FALSE) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) - M.hallucination = max(0, M.hallucination - 10) + affected_mob.adjust_hallucinations(-20 SECONDS * REM) if(prob(30)) - M.adjustToxLoss(1, 0) + affected_mob.adjustToxLoss(1, 0) . = 1 ..() @@ -108,12 +108,12 @@ color = "#EC536D" // rgb: 236, 83, 109 /datum/reagent/medicine/synaphydramine/on_mob_life(mob/living/carbon/M) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-10 SECONDS * REM) if(holder.has_reagent(/datum/reagent/toxin/mindbreaker)) holder.remove_reagent(/datum/reagent/toxin/mindbreaker, 5) if(holder.has_reagent(/datum/reagent/toxin/histamine)) holder.remove_reagent(/datum/reagent/toxin/histamine, 5) - M.hallucination = max(0, M.hallucination - 10) + M.adjust_hallucinations(-20 SECONDS * REM) if(prob(30)) M.adjustToxLoss(1, 0) . = 1 @@ -220,8 +220,8 @@ /datum/reagent/medicine/rezadone/overdose_process(mob/living/M) M.adjustToxLoss(1, 0) - M.Dizzy(5) - M.Jitter(5) + M.adjust_dizzy(5) + M.adjust_jitter(5 SECONDS) ..() . = 1 @@ -632,7 +632,7 @@ var/obj/item/I = M.get_active_held_item() if(I && M.dropItemToGround(I)) to_chat(M, "Your hands spaz out and you drop what you were holding!") - M.Jitter(10) + M.adjust_jitter(10 SECONDS) M.AdjustAllImmobility(-20, FALSE) M.adjustStaminaLoss(-1*REM, FALSE) @@ -658,8 +658,8 @@ /datum/reagent/medicine/ephedrine/addiction_act_stage1(mob/living/M) if(prob(3) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) - M.Unconscious(100) - M.Jitter(350) + M.Unconscious(10 SECONDS) + M.adjust_jitter(350 SECONDS) if(prob(33)) M.adjustToxLoss(2*REM, 0) @@ -670,8 +670,8 @@ /datum/reagent/medicine/ephedrine/addiction_act_stage2(mob/living/M) if(prob(6) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) - M.Unconscious(100) - M.Jitter(350) + M.Unconscious(10 SECONDS) + M.adjust_jitter(350 SECONDS) if(prob(33)) M.adjustToxLoss(3*REM, 0) @@ -683,7 +683,7 @@ if(prob(12) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) M.Unconscious(100) - M.Jitter(350) + M.adjust_jitter(350) if(prob(33)) M.adjustToxLoss(4*REM, 0) @@ -695,7 +695,7 @@ if(prob(24) && iscarbon(M)) M.visible_message(span_danger("[M] starts having a seizure!"), span_userdanger("You have a seizure!")) M.Unconscious(100) - M.Jitter(350) + M.adjust_jitter(350) if(prob(33)) M.adjustToxLoss(5*REM, 0) @@ -713,14 +713,14 @@ /datum/reagent/medicine/diphenhydramine/on_mob_life(mob/living/carbon/M) if(prob(10)) - M.drowsyness += 1 - M.jitteriness -= 1 + M.adjust_drowsiness(1 SECONDS) + M.adjust_jitter(-1 SECONDS) M.reagents.remove_reagent(/datum/reagent/toxin/histamine,3) ..() /datum/reagent/medicine/diphenhydramine/overdose_process(mob/living/M) M.set_drugginess(15) - M.hallucination += 5*REM + M.adjust_hallucinations(20 SECONDS * REM) ..() /datum/reagent/medicine/morphine @@ -748,7 +748,7 @@ if(11) to_chat(M, span_warning("You start to feel tired...") ) if(12 to 24) - M.drowsyness += 1 + M.adjust_drowsiness(1 SECONDS) if(24 to INFINITY) M.Sleeping(40, 0) . = 1 @@ -757,14 +757,14 @@ /datum/reagent/medicine/morphine/overdose_process(mob/living/M) if(prob(33)) M.drop_all_held_items() - M.Dizzy(2) - M.Jitter(2) + M.adjust_dizzy(2) + M.adjust_jitter(2 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage1(mob/living/M) if(prob(33)) M.drop_all_held_items() - M.Jitter(2) + M.adjust_jitter(2 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage2(mob/living/M) @@ -772,8 +772,8 @@ M.drop_all_held_items() M.adjustToxLoss(1*REM, 0) . = 1 - M.Dizzy(3) - M.Jitter(3) + M.adjust_dizzy(3) + M.adjust_jitter(3 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage3(mob/living/M) @@ -781,8 +781,8 @@ M.drop_all_held_items() M.adjustToxLoss(2*REM, 0) . = 1 - M.Dizzy(4) - M.Jitter(4) + M.adjust_dizzy(4) + M.adjust_jitter(4 SECONDS) ..() /datum/reagent/medicine/morphine/addiction_act_stage4(mob/living/M) @@ -790,8 +790,8 @@ M.drop_all_held_items() M.adjustToxLoss(3*REM, 0) . = 1 - M.Dizzy(5) - M.Jitter(5) + M.adjust_dizzy(5) + M.adjust_jitter(5 SECONDS) ..() /datum/reagent/medicine/oculine @@ -840,15 +840,15 @@ . = 1 M.losebreath = 0 if(prob(20)) - M.Dizzy(5) - M.Jitter(5) + M.adjust_dizzy(5) + M.adjust_jitter(5 SECONDS) ..() /datum/reagent/medicine/atropine/overdose_process(mob/living/M) M.adjustToxLoss(0.5*REM, 0) . = 1 - M.Dizzy(1) - M.Jitter(1) + M.adjust_dizzy(1) + M.adjust_jitter(1 SECONDS) ..() /datum/reagent/medicine/epinephrine @@ -968,7 +968,7 @@ taste_description = "acid" /datum/reagent/medicine/mutadone/on_mob_life(mob/living/carbon/M) - M.jitteriness = 0 + M.remove_status_effect(/datum/status_effect/jitter) if(M.has_dna()) M.dna.remove_all_mutations(list(MUT_NORMAL, MUT_EXTRA), TRUE) if(!QDELETED(M)) //We were a monkey, now a human @@ -979,17 +979,21 @@ description = "Purges alcoholic substance from the patient's body and eliminates its side effects." color = "#00B4C8" taste_description = "raw egg" - -/datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/M) - M.dizziness = 0 - M.drowsyness = 0 - M.slurring = 0 - M.confused = 0 - M.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3*REM, 0, 1) - M.adjustToxLoss(-0.2*REM, 0) - if(ishuman(M)) - var/mob/living/carbon/human/H = M - H.drunkenness = max(H.drunkenness - 10, 0) + /// All status effects we remove on metabolize. + /// Does not include drunk (despite what you may think) as that's decresed gradually + var/static/list/status_effects_to_clear = list( + /datum/status_effect/confusion, + /datum/status_effect/dizziness, + /datum/status_effect/drowsiness, + /datum/status_effect/speech/slurring/drunk, + ) + +/datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/affected_mob) + for(var/effect in status_effects_to_clear) + affected_mob.remove_status_effect(effect) + affected_mob.reagents.remove_all_type(/datum/reagent/consumable/ethanol, 3*REM, 0, 1) + affected_mob.adjustToxLoss(-0.2*REM, 0) + affected_mob.adjust_drunk_effect(-10 * REM) ..() . = 1 @@ -1256,14 +1260,14 @@ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2 * REM, 150) //This does, after all, come from ambrosia, and the most powerful ambrosia in existence, at that! M.adjustCloneLoss(-1 * REM, 0) M.adjustStaminaLoss(-30 * REM, 0) - M.jitteriness = min(max(0, M.jitteriness + 3), 30) - M.druggy = min(max(0, M.druggy + 10), 15) //See above + M.adjust_jitter_up_to(6 SECONDS * REM, 1 MINUTES) + M.adjust_drugginess_up_to(20 SECONDS * REM, 30 SECONDS * REM) //See above ..() . = 1 -/datum/reagent/medicine/earthsblood/overdose_process(mob/living/M) - M.hallucination = min(max(0, M.hallucination + 5), 60) - M.adjustToxLoss(5 * REM, 0) +/datum/reagent/medicine/earthsblood/overdose_process(mob/living/affected_mob) + affected_mob.adjust_hallucinations_up_to(10 SECONDS * REM, 120 SECONDS) + affected_mob.adjustToxLoss(5 * REM, 0) ..() . = 1 @@ -1277,11 +1281,9 @@ /datum/reagent/medicine/haloperidol/on_mob_life(mob/living/carbon/M) for(var/datum/reagent/drug/R in M.reagents.reagent_list) M.reagents.remove_reagent(R.type,5) - M.drowsyness += 2 - if(M.jitteriness >= 3) - M.jitteriness -= 3 - if (M.hallucination >= 5) - M.hallucination -= 5 + M.adjust_drowsiness(2 SECONDS) + M.adjust_jitter(-3 SECONDS) + M.adjust_hallucinations(-5 SECONDS) if(prob(20)) M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1*REM, 50) M.adjustStaminaLoss(2.5*REM, 0) @@ -1404,7 +1406,7 @@ overdose_threshold = overdose_threshold + rand(-10,10)/10 // for extra fun M.AdjustAllImmobility(-5, FALSE) M.adjustStaminaLoss(-0.5*REM, 0) - M.Jitter(1) + M.adjust_jitter(1 SECONDS) metabolization_rate = 0.01 * REAGENTS_METABOLISM * rand(5,20) // randomizes metabolism between 0.02 and 0.08 per tick . = TRUE ..() @@ -1417,17 +1419,17 @@ overdose_progress++ switch(overdose_progress) if(1 to 40) - M.jitteriness = min(M.jitteriness+1, 10) - M.stuttering = min(M.stuttering+1, 10) - M.Dizzy(5) + M.adjust_jitter_up_to(1 SECONDS, 10 SECONDS) + M.adjust_stutter_up_to(1 SECONDS, 10 SECONDS) + M.adjust_dizzy(5) if(prob(50)) M.losebreath++ if(41 to 80) M.adjustOxyLoss(0.1*REM, 0) M.adjustStaminaLoss(0.1*REM, 0) - M.jitteriness = min(M.jitteriness+1, 20) - M.stuttering = min(M.stuttering+1, 20) - M.Dizzy(10) + M.adjust_jitter_up_to(1 SECONDS, 20 SECONDS) + M.adjust_stutter_up_to(1 SECONDS, 20 SECONDS) + M.adjust_dizzy(10) if(prob(50)) M.losebreath++ if(prob(20)) @@ -1461,20 +1463,20 @@ REMOVE_TRAIT(L, TRAIT_FEARLESS, type) ..() -/datum/reagent/medicine/psicodine/on_mob_life(mob/living/carbon/M) - M.jitteriness = max(0, M.jitteriness-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) +/datum/reagent/medicine/psicodine/on_mob_life(mob/living/carbon/affected_mob, delta_time, times_fired) + affected_mob.adjust_jitter(-12 SECONDS * REM) + affected_mob.adjust_dizzy(-12 SECONDS * REM) + affected_mob.adjust_confusion(-6 SECONDS * REM) + affected_mob.disgust = max(affected_mob.disgust - (6 * REM), 0) + var/datum/component/mood/mood = affected_mob.GetComponent(/datum/component/mood) if(mood && 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 ..() . = 1 -/datum/reagent/medicine/psicodine/overdose_process(mob/living/M) - M.hallucination = min(max(0, M.hallucination + 5), 60) - M.adjustToxLoss(1, 0) +/datum/reagent/medicine/psicodine/overdose_process(mob/living/affected_mob) + affected_mob.adjust_hallucinations_up_to(10 SECONDS * REM, 120 SECONDS) + affected_mob.adjustToxLoss(1, 0) ..() . = 1 @@ -1694,9 +1696,9 @@ M.emote("twitch") if(prob(90)) M.adjustToxLoss(tox_roll*REM, 0) - M.Jitter(jitter) + M.adjust_jitter(jitter) if(prob(1)) // Last set of non OD probability effects - M.Dizzy(jitter) + M.adjust_dizzy(jitter) M.apply_effect(slur, EFFECT_SLUR) // End of base probability effects if(heal_roll < 0) // Healing payload after calculations M.adjustFireLoss(heal_roll*REM, 0) @@ -1709,12 +1711,12 @@ M.adjustOrganLoss(ORGAN_SLOT_BRAIN, pick(0,1,2,2.5,-1,-2)) if(prob(15)) M.set_drugginess(rand(2,6)) - M.Jitter(jitter) + M.adjust_jitter(jitter) if(prob(40)) M.adjustToxLoss(pick(1,-1)*REM, 0) if(prob(1)) - M.Dizzy(jitter) // end last set of OD probability effects - M.Jitter(jitter) // OD slur and jitter after calculations + M.adjust_dizzy(jitter) // end last set of OD probability effects + M.adjust_jitter(jitter) // OD slur and jitter after calculations M.apply_effect(slur, EFFECT_SLUR) if(heal_roll < 0) // OD Healing payload after calculations M.adjustFireLoss(heal_roll*REM, FALSE, FALSE, BODYPART_ORGANIC) @@ -1726,7 +1728,7 @@ if(!overdosed) jitter += 5 slur += 25 - M.drowsyness += 0.5 + M.adjust_drowsiness(0.5 SECONDS) heal_roll -= 0.2 else if(overdosed) heal_roll -= 0.4 @@ -1770,7 +1772,7 @@ if(prob(30)) jitter = 50 if(prob(30)) - M.drowsyness += 0.5 + M.adjust_drowsiness(0.5 SECONDS) if(prob(2)) to_chat(M, "Your fingers spasm!") M.drop_all_held_items() // end IF both drugs present @@ -1869,7 +1871,7 @@ if(M.stat != DEAD) return M.revive(full_heal = TRUE) - M.Jitter(10 SECONDS) + M.adjust_jitter(10 SECONDS) M.emote("gasp") /datum/reagent/medicine/naniteremover diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 60e4089f7186..51aeda04049b 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -11,6 +11,11 @@ shot_glass_icon_state = "shotglassred" /datum/reagent/blood/reaction_mob(mob/living/L, method=TOUCH, reac_volume) + var/datum/antagonist/bloodsucker/bloodsuckerdatum = IS_BLOODSUCKER(L) //bloodsucker start + if(bloodsuckerdatum) + bloodsuckerdatum.bloodsucker_blood_volume = min(bloodsuckerdatum.bloodsucker_blood_volume + round(reac_volume, 0.1), BLOOD_VOLUME_MAXIMUM(L)) + return //bloodsucker end + if(data && data["viruses"]) for(var/thing in data["viruses"]) var/datum/disease/D = thing @@ -221,17 +226,15 @@ if(!data) data = list("misc" = 1) data["misc"]++ - M.jitteriness = min(M.jitteriness+4,10) + M.adjust_jitter_up_to(4 SECONDS, 20 SECONDS) if(iscultist(M)) for(var/datum/action/innate/cult/blood_magic/BM in M.actions) to_chat(M, span_cultlarge("Your blood rites falter as holy water scours your body!")) for(var/datum/action/innate/cult/blood_spell/BS in BM.spells) qdel(BS) if(data["misc"] >= 25) // 10 units, 45 seconds @ metabolism 0.4 units & tick rate 1.8 sec - if(!M.stuttering) - M.stuttering = 1 - M.stuttering = min(M.stuttering+4, 10) - M.Dizzy(5) + M.adjust_stutter_up_to(4 SECONDS, 20 SECONDS) + M.set_dizzy_if_lower(10 SECONDS) if(iscultist(M) && prob(20)) M.say(pick("Av'te Nar'Sie","Pa'lid Mors","INO INO ORA ANA","SAT ANA!","Daim'niodeis Arc'iai Le'eones","R'ge Na'sie","Diabo us Vo'iscum","Eld' Mon Nobis"), forced = "holy water") if(prob(10)) @@ -254,8 +257,8 @@ SSticker.mode.remove_cultist(M.mind, FALSE, TRUE) else if(is_servant_of_ratvar(M)) remove_servant_of_ratvar(M) - M.jitteriness = 0 - M.stuttering = 0 + M.remove_status_effect(/datum/status_effect/jitter) + M.remove_status_effect(/datum/status_effect/speech/stutter) holder.remove_reagent(type, volume) // maybe this is a little too perfect and a max() cap on the statuses would be better?? return if(ishuman(M) && is_vampire(M) && prob(80)) // Yogs Start @@ -271,7 +274,7 @@ if(13 to INFINITY) M.visible_message("[M] suddenly bursts into flames!", span_userdanger("You suddenly ignite in a holy fire!")) M.adjust_fire_stacks(3) - M.IgniteMob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire + M.ignite_mob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire M.adjustFireLoss(3) //Hence the other damages... ain't I a bastard? // Yogs End if(ishuman(M) && is_sinfuldemon(M) && prob(80)) switch(data) @@ -288,7 +291,7 @@ if(13 to INFINITY) M.visible_message("[M] suddenly ignites in a brilliant flash of white!", span_userdanger("You suddenly ignite in a holy fire!")) M.adjust_fire_stacks(3) - M.IgniteMob() + M.ignite_mob() M.adjustFireLoss(4) holder.remove_reagent(type, 0.4) //fixed consumption to prevent balancing going out of whack @@ -314,7 +317,7 @@ /datum/reagent/fuel/unholywater/on_mob_life(mob/living/carbon/M) if(iscultist(M)) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-5 SECONDS) M.AdjustAllImmobility(-40, FALSE) M.adjustStaminaLoss(-10, 0) M.adjustToxLoss(-2, 0) @@ -340,7 +343,7 @@ /datum/reagent/hellwater/on_mob_life(mob/living/carbon/M) M.fire_stacks = min(5,M.fire_stacks + 3) - M.IgniteMob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire + M.ignite_mob() //Only problem with igniting people is currently the commonly availible fire suits make you immune to being on fire M.adjustToxLoss(1, 0) M.adjustFireLoss(1, 0) //Hence the other damages... ain't I a bastard? M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5, 150) @@ -354,7 +357,7 @@ /datum/reagent/eldritch/on_mob_life(mob/living/carbon/M) if(IS_HERETIC(M) || IS_HERETIC_MONSTER(M)) - M.drowsyness = max(M.drowsyness-5, 0) + M.adjust_drowsiness(-5 SECONDS) M.AdjustAllImmobility(-40, FALSE) M.adjustStaminaLoss(-10, FALSE) M.adjustToxLoss(-2, FALSE) @@ -1030,7 +1033,7 @@ /datum/reagent/bluespace/on_mob_life(mob/living/carbon/M) if(current_cycle > 10 && prob(15)) to_chat(M, span_warning("You feel unstable...")) - M.Jitter(2) + M.adjust_jitter(2 SECONDS) current_cycle = 1 addtimer(CALLBACK(M, /mob/living/proc/bluespace_shuffle), 30) ..() @@ -1136,11 +1139,17 @@ metabolization_rate = 1.5 * REAGENTS_METABOLISM taste_description = "sourness" -/datum/reagent/cryptobiolin/on_mob_life(mob/living/carbon/M) - M.Dizzy(1) - if(!M.confused) - M.confused = 1 - M.confused = max(M.confused, 20) +/datum/reagent/cryptobiolin/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.set_dizzy_if_lower(2 SECONDS) + + // Cryptobiolin adjusts the mob's confusion down to 20 seconds if it's higher, + // or up to 1 second if it's lower, but will do nothing if it's in between + var/confusion_left = affected_mob.get_timed_status_effect_duration(/datum/status_effect/confusion) + if(confusion_left < 1 SECONDS) + affected_mob.set_confusion(1 SECONDS) + + else if(confusion_left > 20 SECONDS) + affected_mob.set_confusion(20 SECONDS) ..() /datum/reagent/impedrezene @@ -1149,14 +1158,14 @@ color = "#C8A5DC" // rgb: 200, 165, 220A taste_description = "numbness" -/datum/reagent/impedrezene/on_mob_life(mob/living/carbon/M) - M.jitteriness = max(M.jitteriness-5,0) +/datum/reagent/impedrezene/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_jitter(-5 SECONDS) if(prob(80)) - M.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2*REM) + affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2*REM) if(prob(50)) - M.drowsyness = max(M.drowsyness, 3) + affected_mob.adjust_drowsiness(6 SECONDS) if(prob(10)) - M.emote("drool") + affected_mob.emote("drool") ..() /datum/reagent/nanomachines @@ -1274,18 +1283,20 @@ var/temp = holder ? holder.chem_temp : T20C T.atmos_spawn_air("n2o=[reac_volume/5];TEMP=[temp]") -/datum/reagent/nitrous_oxide/reaction_mob(mob/living/M, method=TOUCH, reac_volume) +/datum/reagent/nitrous_oxide/reaction_mob(mob/living/exposed_mob, method=TOUCH, reac_volume) if(method == VAPOR) - M.drowsyness += max(round(reac_volume, 1), 2) + // apply 2 seconds of drowsiness per unit applied, with a min duration of 4 seconds + var/drowsiness_to_apply = max(round(reac_volume, 1) * 2 SECONDS, 4 SECONDS) + exposed_mob.adjust_drowsiness(drowsiness_to_apply) /datum/reagent/nitrous_oxide/on_mob_life(mob/living/carbon/M) - M.drowsyness += 2 + M.adjust_drowsiness(4 SECONDS * REM) if(ishuman(M)) var/mob/living/carbon/human/H = M H.blood_volume = max(H.blood_volume - 5, 0) if(prob(20)) M.losebreath += 2 - M.confused = min(M.confused + 2, 5) + M.adjust_confusion_up_to(2 SECONDS, 5 SECONDS) ..() /datum/reagent/stimulum @@ -1968,11 +1979,11 @@ if(L.mind && L.mind.has_antag_datum(/datum/antagonist/changeling)) //yogs to_chat(L, span_boldnotice("Our blood is pure, we can regenerate chemicals again.")) //yogs -/datum/reagent/bz_metabolites/on_mob_life(mob/living/L) - if(L.mind) - var/datum/antagonist/changeling/changeling = L.mind.has_antag_datum(/datum/antagonist/changeling) +/datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target) + if(target.mind) + var/datum/antagonist/changeling/changeling = target.mind.has_antag_datum(/datum/antagonist/changeling) if(changeling) - changeling.chem_charges = max(changeling.chem_charges-2, 0) + changeling.adjust_chemicals(-2 * REM) return ..() /datum/reagent/pax/peaceborg @@ -1986,13 +1997,11 @@ metabolization_rate = 1.5 * REAGENTS_METABOLISM taste_description = "dizziness" -/datum/reagent/peaceborg/confuse/on_mob_life(mob/living/carbon/M) - if(M.confused < 6) - M.confused = clamp(M.confused + 3, 0, 5) - if(M.dizziness < 6) - M.dizziness = clamp(M.dizziness + 3, 0, 5) +/datum/reagent/peaceborg/confuse/on_mob_life(mob/living/carbon/affected_mob) + affected_mob.adjust_confusion_up_to(3 SECONDS * REM , 5 SECONDS) + affected_mob.adjust_dizzy_up_to(6 SECONDS * REM, 12 SECONDS) if(prob(20)) - to_chat(M, "You feel confused and disorientated.") + to_chat(affected_mob, "You feel confused and disorientated.") ..() /datum/reagent/peaceborg/tire diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm index bf0b84919df5..0f8672772f2c 100644 --- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm @@ -71,7 +71,7 @@ if(istype(M)) if(method != INGEST && method != INJECT) M.adjust_fire_stacks(min(reac_volume/5, 10)) - M.IgniteMob() + M.ignite_mob() if(!locate(/obj/effect/hotspot) in M.loc) new /obj/effect/hotspot(M.loc) @@ -100,7 +100,7 @@ /datum/reagent/blackpowder/on_mob_life(mob/living/carbon/M) ..() if(isplasmaman(M)) - M.hallucination += 5 + M.adjust_hallucinations(20 SECONDS) /datum/reagent/blackpowder/on_ex_act() var/location = get_turf(holder.my_atom) @@ -143,7 +143,7 @@ M.adjust_fire_stacks(1) var/burndmg = max(0.3*M.fire_stacks, 0.3) M.adjustFireLoss(burndmg, 0) - M.IgniteMob() + M.ignite_mob() ..() /datum/reagent/phlogiston/on_mob_life(mob/living/carbon/M) diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm index b41ab24378dd..41df11cd773b 100644 --- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm @@ -198,9 +198,9 @@ return TRUE switch(current_cycle) if(1 to 5) - M.confused += 1 - M.drowsyness += 1 - M.slurring += 3 + M.adjust_confusion(1 SECONDS) + M.adjust_drowsiness(1 SECONDS) + M.adjust_slurring(3 SECONDS) if(5 to 8) M.adjustStaminaLoss(40, 0) if(9 to INFINITY) @@ -236,7 +236,7 @@ /datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/M) if(!M.has_trauma_type(/datum/brain_trauma/mild/reality_dissociation)) - M.hallucination += 5 + M.adjust_hallucinations(20 SECONDS) return ..() /datum/reagent/toxin/relaxant @@ -316,7 +316,7 @@ /datum/reagent/toxin/spore_burning/on_mob_life(mob/living/carbon/M) M.adjust_fire_stacks(2) - M.IgniteMob() + M.ignite_mob() return ..() /datum/reagent/toxin/chloralhydrate @@ -328,18 +328,18 @@ toxpwr = 0 metabolization_rate = 1.5 * REAGENTS_METABOLISM -/datum/reagent/toxin/chloralhydrate/on_mob_life(mob/living/carbon/M) +/datum/reagent/toxin/chloralhydrate/on_mob_life(mob/living/carbon/affected_mob) switch(current_cycle) if(1 to 10) - M.confused += 2 - M.drowsyness += 2 + affected_mob.adjust_confusion(2 SECONDS * REM) + affected_mob.adjust_drowsiness(4 SECONDS * REM) if(10 to 50) - M.Sleeping(40, 0) - . = 1 + affected_mob.Sleeping(4 SECONDS * REM) + . = TRUE if(51 to INFINITY) - M.Sleeping(40, 0) - M.adjustToxLoss((current_cycle - 50)*REM, 0) - . = 1 + affected_mob.Sleeping(4 SECONDS * REM ) + affected_mob.adjustToxLoss(1 * (current_cycle - 50) * REM, FALSE) + . = TRUE ..() /datum/reagent/toxin/fakebeer //disguised as normal beer for use by emagged brobots diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 37f7145870d3..be8c2fd06533 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -88,7 +88,7 @@ to_chat(C, span_userdanger("The divine explosion sears you!")) C.Paralyze(40) C.adjust_fire_stacks(5) - C.IgniteMob() + C.ignite_mob() /datum/chemical_reaction/blackpowder name = "Black Powder" diff --git a/code/modules/reagents/reagent_containers/blood_pack.dm b/code/modules/reagents/reagent_containers/blood_pack.dm index 53953588eb1e..c5aa97631cee 100644 --- a/code/modules/reagents/reagent_containers/blood_pack.dm +++ b/code/modules/reagents/reagent_containers/blood_pack.dm @@ -8,45 +8,46 @@ var/unique_blood = null var/labelled = 0 +#define BLOODBAG_GULP_SIZE 10 + /obj/item/reagent_containers/blood/attack(mob/target, mob/user, def_zone) - if(reagents.total_volume > 0) - if(target != user) - user.visible_message( - span_notice("[user] forces [target] to drink from the [src]."), - span_notice("You put the [src] up to [target]'s mouth."), - ) - if(!do_mob(user, target, 5 SECONDS)) - return - else - if(!do_mob(user, target, 1 SECONDS)) - return + if(!reagents.total_volume) + user.balloon_alert(user, "empty!") + return ..() + + if(target != user) + if(!do_after(user, 5 SECONDS, target)) + return + user.visible_message( + span_notice("[user] forces [target] to drink from the [src]."), + span_notice("You put the [src] up to [target]'s mouth."), + ) + else + while(do_after(user, 1 SECONDS, stayStill = FALSE)) user.visible_message( span_notice("[user] puts the [src] up to their mouth."), span_notice("You take a sip from the [src]."), ) - // Safety: In case you spam clicked the blood bag on yourself, and it is now empty (below will divide by zero) - if(reagents.total_volume <= 0) - return - var/gulp_size = 5 - if(is_vampire(target)) - gulp_size = 10 - var/datum/antagonist/vampire/V = is_vampire(target) - V.usable_blood += 5 - - if(IS_BLOODSUCKER(target)) - var/datum/antagonist/bloodsucker/bloodsuckerdatum = target.mind.has_antag_datum(/datum/antagonist/bloodsucker) - bloodsuckerdatum.AddBloodVolume(gulp_size) - var/mob/living/carbon/H = target - addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, target, gulp_size), 5) - reagents.reaction(target, INGEST, 100*gulp_size) - if(H.blood_volume >= bloodsuckerdatum.max_blood_volume) - to_chat(target, span_notice("You are full, and can't consume more blood")) - return - else - reagents.reaction(target, INGEST, gulp_size) - addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, target, gulp_size), 5) - playsound(user.loc, 'sound/items/drink.ogg', rand(10,50), 1) - return ..() + var/datum/antagonist/vampire/V = is_vampire(user) + V?.usable_blood += 5 + + reagents.reaction(user, INGEST, BLOODBAG_GULP_SIZE) + reagents.trans_to(user, BLOODBAG_GULP_SIZE, transfered_by = user) + playsound(user.loc, 'sound/items/drink.ogg', rand(10, 50), TRUE) + return TRUE + +#undef BLOODBAG_GULP_SIZE + +///Bloodbag of Bloodsucker blood (used by Vassals only) +/obj/item/reagent_containers/blood/o_minus/bloodsucker + name = "blood pack" + unique_blood = /datum/reagent/blood/bloodsucker + +/obj/item/reagent_containers/blood/o_minus/bloodsucker/examine(mob/user) + . = ..() + if(user.mind.has_antag_datum(/datum/antagonist/ex_vassal) || user.mind.has_antag_datum(/datum/antagonist/vassal/revenge)) + . += span_notice("Seems to be just about the same color as your Master's...") + /obj/item/reagent_containers/blood/Initialize() . = ..() diff --git a/code/modules/research/nanites/nanite_programs/healing.dm b/code/modules/research/nanites/nanite_programs/healing.dm index 66ce7363175c..f62157ba29ca 100644 --- a/code/modules/research/nanites/nanite_programs/healing.dm +++ b/code/modules/research/nanites/nanite_programs/healing.dm @@ -237,7 +237,7 @@ C.set_heartattack(FALSE) C.revive() C.emote("gasp") - C.Jitter(10 SECONDS) + C.adjust_jitter(10 SECONDS) SEND_SIGNAL(C, COMSIG_LIVING_MINOR_SHOCK) else playsound(C, 'sound/machines/defib_failed.ogg', 50, 0) diff --git a/code/modules/research/nanites/nanite_programs/rogue.dm b/code/modules/research/nanites/nanite_programs/rogue.dm index 008171043c0e..37acbe23bc47 100644 --- a/code/modules/research/nanites/nanite_programs/rogue.dm +++ b/code/modules/research/nanites/nanite_programs/rogue.dm @@ -63,7 +63,7 @@ /datum/nanite_program/brain_decay/active_effect() if(prob(4)) - host_mob.hallucination = min(15, host_mob.hallucination) + host_mob.adjust_hallucinations_up_to(15 SECONDS, 5 SECONDS) host_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1) //Generic brain-affecting programs can also decay into this @@ -79,13 +79,13 @@ if(prob(10)) switch(rand(1,4)) if(1) - host_mob.hallucination += 15 + host_mob.adjust_hallucinations(15 SECONDS) if(2) - host_mob.confused += 10 + host_mob.adjust_confusion(10 SECONDS) if(3) - host_mob.drowsyness += 10 + host_mob.adjust_drowsiness(10 SECONDS) if(4) - host_mob.slurring += 10 + host_mob.adjust_slurring(10 SECONDS) //Generic skin-affecting programs will decay into this /datum/nanite_program/skin_decay @@ -118,10 +118,10 @@ /datum/nanite_program/nerve_decay/active_effect() if(prob(5)) to_chat(host_mob, span_warning("You feel unbalanced!")) - host_mob.confused += 10 + host_mob.adjust_confusion(10 SECONDS) else if(prob(4)) to_chat(host_mob, span_warning("You can't feel your hands!")) host_mob.drop_all_held_items() else if(prob(4)) to_chat(host_mob, span_warning("You can't feel your legs!")) - host_mob.Knockdown(30) + host_mob.Knockdown(3 SECONDS) diff --git a/code/modules/research/nanites/nanite_programs/suppression.dm b/code/modules/research/nanites/nanite_programs/suppression.dm index 25a884854d70..a551fb88cff3 100644 --- a/code/modules/research/nanites/nanite_programs/suppression.dm +++ b/code/modules/research/nanites/nanite_programs/suppression.dm @@ -12,8 +12,8 @@ if(!..()) return to_chat(host_mob, span_warning("You start to feel very sleepy...")) - host_mob.drowsyness += 20 - addtimer(CALLBACK(host_mob, /mob/living.proc/Sleeping, 200), rand(60,200)) + host_mob.adjust_drowsiness(20 SECONDS) + addtimer(CALLBACK(host_mob, TYPE_PROC_REF(/mob/living, Sleeping), 200), rand(60, 200)) /datum/nanite_program/triggered/shocking name = "Electric Shock" @@ -216,7 +216,7 @@ return var/mob/living/carbon/C = host_mob if(!hal_type) - C.hallucination += 15 + C.adjust_hallucinations(15 SECONDS) else switch(hal_type) if("Message") diff --git a/code/modules/research/nanites/nanite_programs/utility.dm b/code/modules/research/nanites/nanite_programs/utility.dm index 211dae33555a..25667a52dacd 100644 --- a/code/modules/research/nanites/nanite_programs/utility.dm +++ b/code/modules/research/nanites/nanite_programs/utility.dm @@ -398,7 +398,7 @@ /datum/action/innate/nanite_button name = "Button" icon_icon = 'icons/mob/actions/actions_items.dmi' - check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUN|AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED| AB_CHECK_IMMOBILE|AB_CHECK_CONSCIOUS button_icon_state = "power_green" var/datum/nanite_program/dermal_button/program diff --git a/code/modules/research/nanites/nanite_programs/weapon.dm b/code/modules/research/nanites/nanite_programs/weapon.dm index 0464404e6c79..1c3508576ece 100644 --- a/code/modules/research/nanites/nanite_programs/weapon.dm +++ b/code/modules/research/nanites/nanite_programs/weapon.dm @@ -138,7 +138,7 @@ /datum/nanite_program/pyro/active_effect() host_mob.adjust_fire_stacks(1) - host_mob.IgniteMob() + host_mob.ignite_mob() /datum/nanite_program/pyro name = "Sub-Dermal Combustion" @@ -154,7 +154,7 @@ /datum/nanite_program/pyro/active_effect() host_mob.adjust_fire_stacks(1) - host_mob.IgniteMob() + host_mob.ignite_mob() /datum/nanite_program/cryo name = "Cryogenic Treatment" diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm index 192f22dd109c..d5fa6a53436d 100644 --- a/code/modules/research/xenobiology/crossbreeding/_misc.dm +++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm @@ -209,7 +209,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 66551d9768e9..14e4d56fb0bb 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/cooldown/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 - invocation_type = "none" - shapeshift_type = /mob/living/simple_animal/slime/transformedslime + button_icon_state = "transformslime" + cooldown_time = 0 SECONDS + + invocation_type = INVOCATION_NONE + 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/cooldown/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, span_warning("I can't reproduce...")) +// Just in case. +/mob/living/simple_animal/slime/transformed_slime/Reproduce() + to_chat(src, span_warning("I can't reproduce...")) // Mood return //Slime corgi - Chilling Pink diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index 6fb88510d445..0542c25a95a7 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -213,7 +213,7 @@ /datum/status_effect/bonechill/tick() if(prob(50)) owner.adjustFireLoss(1) - owner.Jitter(3) + owner.adjust_jitter(3 SECONDS) owner.adjust_bodytemperature(-10) /datum/status_effect/bonechill/on_remove() @@ -484,24 +484,33 @@ datum/status_effect/rebreathing/tick() /datum/status_effect/stabilized/purple id = "stabilizedpurple" colour = "purple" + /// Whether we healed from our last tick + var/healed_last_tick = FALSE /datum/status_effect/stabilized/purple/tick() - var/is_healing = FALSE if(owner.getBruteLoss() > 0) owner.adjustBruteLoss(-0.2) - is_healing = TRUE + healed_last_tick = TRUE + if(owner.getFireLoss() > 0) owner.adjustFireLoss(-0.2) - is_healing = TRUE + healed_last_tick = TRUE + if(owner.getToxLoss() > 0) owner.adjustToxLoss(-0.2, forced = TRUE) //Slimepeople should also get healed. - is_healing = TRUE - if(is_healing) - examine_text = span_warning("SUBJECTPRONOUN is regenerating slowly, purplish goo filling in small injuries!") + healed_last_tick = TRUE + + // Technically, "healed this tick" by now. + if(healed_last_tick) new /obj/effect/temp_visual/heal(get_turf(owner), "#FF0000") - else - examine_text = null - ..() + + return ..() + +/datum/status_effect/stabilized/purple/get_examine_text() + if(healed_last_tick) + return span_warning("[owner.p_they(TRUE)] [owner.p_are()] regenerating slowly, purplish goo filling in small injuries!") + + return null /datum/status_effect/stabilized/blue id = "stabilizedblue" @@ -680,7 +689,7 @@ datum/status_effect/stabilized/blue/on_remove() owner.apply_status_effect(/datum/status_effect/bluespacestabilization) to_chat(owner, span_warning("You feel sick after [linked_extract] dragged you through bluespace.")) owner.Stun(1 SECONDS) - owner.dizziness += 30 + owner.adjust_dizzy(30 SECONDS) healthcheck = owner.health return ..() diff --git a/code/modules/research/xenobiology/crossbreeding/_structures.dm b/code/modules/research/xenobiology/crossbreeding/_structures.dm index 109aea6883ca..426f8a0c9b9b 100644 --- a/code/modules/research/xenobiology/crossbreeding/_structures.dm +++ b/code/modules/research/xenobiology/crossbreeding/_structures.dm @@ -136,7 +136,7 @@ GLOBAL_LIST_EMPTY(bluespace_slime_crystals) return var/mob/living/carbon/carbon_mob = affected_mob carbon_mob.fire_stacks++ - carbon_mob.IgniteMob() + carbon_mob.ignite_mob() /obj/structure/slime_crystal/orange/process() . = ..() diff --git a/code/modules/research/xenobiology/crossbreeding/burning.dm b/code/modules/research/xenobiology/crossbreeding/burning.dm index 82cc1c8391c4..ad9b96f31870 100644 --- a/code/modules/research/xenobiology/crossbreeding/burning.dm +++ b/code/modules/research/xenobiology/crossbreeding/burning.dm @@ -275,15 +275,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(span_danger("[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) - ..() + var/datum/action/cooldown/spell/shapeshift/slime_form/transform = new(user.mind || user) + transform.remove_on_restore = TRUE + transform.Grant(user) + transform.cast(user) + return ..() /obj/item/slimecross/burning/lightpink colour = "light pink" diff --git a/code/modules/shuttle/arrivals.dm b/code/modules/shuttle/arrivals.dm index 44d7c3b0c22f..6c3102022a78 100644 --- a/code/modules/shuttle/arrivals.dm +++ b/code/modules/shuttle/arrivals.dm @@ -79,7 +79,7 @@ damaged = TRUE if(console) console.say("Alert, hull breach detected!") - var/obj/machinery/announcement_system/announcer = safepick(GLOB.announcement_systems) + var/obj/machinery/announcement_system/announcer = pick(GLOB.announcement_systems) if(!QDELETED(announcer)) announcer.announce("ARRIVALS_BROKEN", channels = list()) if(mode != SHUTTLE_CALL) diff --git a/code/modules/shuttle/assault_pod.dm b/code/modules/shuttle/assault_pod.dm index 8660af3757ea..68dfb9704089 100644 --- a/code/modules/shuttle/assault_pod.dm +++ b/code/modules/shuttle/assault_pod.dm @@ -42,7 +42,7 @@ if(!src || QDELETED(src)) return - var/turf/T = safepick(get_area_turfs(picked_area)) + var/turf/T = pick(get_area_turfs(picked_area)) if(!T) return var/obj/docking_port/stationary/landing_zone = new /obj/docking_port/stationary(T) diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index f3e1b6bfa50c..3c1dae16ad1d 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -17,8 +17,10 @@ . = ..() /obj/machinery/computer/emergency_shuttle/attack_alien(mob/living/carbon/alien/humanoid/user) - if(istype(user, /mob/living/carbon/alien/humanoid/royal/queen)) - SSshuttle.clearHostileEnvironment(user) + if(isalienqueen(user)) + var/mob/living/carbon/alien/humanoid/royal/queen/queenuser = user + queenuser.kill_shuttle_timer() + balloon_alert(user, "shuttle ready to launch!") /obj/machinery/computer/emergency_shuttle/ui_state(mob/user) return GLOB.human_adjacent_state diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm index c88e2af1ee8b..32d01eaafa6e 100644 --- a/code/modules/spells/spell.dm +++ b/code/modules/spells/spell.dm @@ -1,566 +1,444 @@ -#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/mob/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() - . = ..() - if(has_action) - action = new base_action(src) +/** + * # 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/cooldown/spell/can_cast_spell] checks if the OWNER + * of the spell is able to cast the spell. + * - [is_valid_target][/datum/action/cooldown/spell/is_valid_target] checks if the TARGET + * THE SPELL IS BEING CAST ON is a valid target for the spell. NOTE: The CAST TARGET is often THE SAME as THE OWNER OF THE SPELL, + * but is not always - depending on how [Pre Activate][/datum/action/cooldown/spell/PreActivate] is resolved. + * - [can_invoke][/datum/action/cooldown/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: + * - [before_cast][/datum/action/cooldown/spell/before_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/cooldown/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/cooldown/spell/cast] is where the brunt of the spell effects should be done + * and implemented. + * - [after_cast][/datum/action/cooldown/spell/after_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/cooldown/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/cooldown/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 before_cast and would like the cooldown restart, call this. + * + * ## Other procs of note: + * - [level_spell][/datum/action/cooldown/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/cooldown/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/cooldown/spell/update_spell_name] updates the prefix of the spell name based on its level. + */ +/datum/action/cooldown/spell + name = "Spell" + desc = "A wizard spell." + background_icon_state = "bg_spell" + icon_icon = 'icons/mob/actions/actions_spells.dmi' + button_icon_state = "spell_default" + check_flags = AB_CHECK_CONSCIOUS + panel = "Spells" -/obj/effect/proc_holder/proc/on_gain(mob/living/user) - return + /// 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 -/obj/effect/proc_holder/proc/on_lose(mob/living/user) - return +/datum/action/cooldown/spell/Grant(mob/grant_to) + // If our spell is mind-bound, we only wanna grant it to our mind + if(istype(target, /datum/mind)) + var/datum/mind/mind_target = target + if(mind_target.current != grant_to) + return -/obj/effect/proc_holder/proc/fire(mob/living/user) - return TRUE + . = ..() + if(!owner) + return + + // Register some signals so our button's icon stays up to date + if(spell_requirements & SPELL_REQUIRES_STATION) + RegisterSignal(owner, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(update_status_on_signal)) + if(spell_requirements & (SPELL_REQUIRES_NO_ANTIMAGIC|SPELL_REQUIRES_WIZARD_GARB)) + RegisterSignal(owner, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(update_status_on_signal)) + RegisterSignals(owner, list(COMSIG_MOB_ENTER_JAUNT, COMSIG_MOB_AFTER_EXIT_JAUNT), PROC_REF(update_status_on_signal)) + owner.client?.stat_panel.send_message("check_spells") -/obj/effect/proc_holder/proc/get_panel_text() - return "" +/datum/action/cooldown/spell/Remove(mob/living/remove_from) -GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell)) //needed for the badmin verb for now + 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, + )) -/obj/effect/proc_holder/Destroy() - if (action) - qdel(action) - if(ranged_ability_user) - remove_ranged_ability() return ..() -/obj/effect/proc_holder/singularity_act() - return +/datum/action/cooldown/spell/IsAvailable(feedback = FALSE) + return ..() && can_cast_spell(feedback) -/obj/effect/proc_holder/singularity_pull() - return +/datum/action/cooldown/spell/Trigger(trigger_flags, 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 IsAvailable() checks can_cast_spell as well. + if(!can_cast_spell()) + return FALSE -/obj/effect/proc_holder/Topic(href, href_list) - . = ..() - if(href_list["click"]) - // first of all make sure we valid - var/mob/living/as_living = usr - if(!(src in usr.mob_spell_list) && !(usr.mind && (src in usr.mind.spell_list)) && !(istype(as_living) && (src in as_living.abilities))) - message_admins("[key_name_admin(src)] clicked on an invalid proc_holder href! ([src])") - log_game("[key_name(src)] clicked on an invalid proc_holder href! ([src])") - return - Click() - -/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, span_warning("[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 + return ..() -/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, span_warning("[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 = 0 - - 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/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 decisecond if charge_type = "recharge" or -- each cast if charge_type = "charges" - var/still_recharging_msg = span_notice("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 = "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/overlay = 0 - var/overlay_icon = 'icons/obj/wizard.dmi' - var/overlay_icon_state = "spell" - var/overlay_lifespan = 0 - - var/sparks_spread = 0 - var/sparks_amt = 0 //cropped at 10 - /// The typepath of the smoke to create on cast. - var/smoke_spread = null - /// The amount of smoke to create on case. This is a range so a value of 5 will create enough smoke to cover everything within 5 steps. - var/smoke_amt = 0 +/datum/action/cooldown/spell/set_click_ability(mob/on_who) + if(SEND_SIGNAL(on_who, COMSIG_MOB_SPELL_ACTIVATED, src) & SPELL_CANCEL_CAST) + return FALSE - var/centcom_cancast = TRUE //Whether or not the spell should be allowed on z2 + return ..() - var/atom/movable/screen/cooldown_overlay/cooldown_overlay +// Where the cast chain starts +/datum/action/cooldown/spell/PreActivate(atom/target) + if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START) + return FALSE + if(!is_valid_target(target)) + return FALSE - action_icon = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "spell_default" - action_background_icon_state = "bg_spell" - base_action = /datum/action/spell_action/spell + return Activate(target) -/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(player_lock) - if(!user.mind || !(src in user.mind.spell_list) && !(src in user.mob_spell_list)) - to_chat(user, span_warning("You shouldn't have this spell! Something's wrong.")) - return FALSE - else - if(!(src in user.mob_spell_list)) - return FALSE +/// Checks if the owner of the spell can currently cast it. +/// Does not check anything involving potential targets. +/datum/action/cooldown/spell/proc/can_cast_spell(feedback = TRUE) + if(!owner) + CRASH("[type] - can_cast_spell called on a spell without an owner!") - 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, span_notice("You can't cast this spell here.")) + // Certain spells are not allowed on the centcom zlevel + var/turf/caster_turf = get_turf(owner) + // Spells which require being on the station + if((spell_requirements & SPELL_REQUIRES_STATION) && !is_station_level(caster_turf.z)) + if(feedback) + to_chat(owner, span_warning("You can't cast [src] here!")) return FALSE - if(!skipcharge) - if(!charge_check(user)) - return FALSE - - if(user.stat && !stat_allowed) - to_chat(user, span_notice("Not when you're incapacitated.")) + if((spell_requirements & SPELL_REQUIRES_MIND) && !owner.mind) + // No point in feedback here, as mindless mobs aren't players return FALSE - if(!antimagic_allowed) - var/antimagic = user.anti_magic_check(TRUE, FALSE, FALSE, 0, TRUE) - if(antimagic) - if(isitem(antimagic)) - to_chat(user, span_notice("[antimagic] is interfering with your magic.")) - else - to_chat(user, span_notice("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, span_notice("[name] cannot be cast unless you are completely manifested in the material plane.")) + 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, span_warning("You must dedicate yourself to silence first!")) return FALSE - if(ishuman(user)) + // 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, span_warning("Some form of antimagic is preventing you from casting [src]!")) + return FALSE - var/mob/living/carbon/human/H = user + if(!(spell_requirements & SPELL_CASTABLE_WHILE_PHASED) && HAS_TRAIT(owner, TRAIT_MAGICALLY_PHASED)) + if(feedback) + to_chat(owner, span_warning("[src] cannot be cast unless you are completely manifested in the material plane!")) + return FALSE - if((invocation_type == "whisper" || invocation_type == "shout") && !H.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return FALSE + if(!try_invoke(feedback = feedback)) + return FALSE - var/list/casting_clothes = typecacheof(list(/obj/item/clothing/suit/wizrobe, + var/list/casting_clothes = typecacheof(list( //HELLO. CHANGE THIS LATER.... + /obj/item/clothing/suit/wizrobe, /obj/item/clothing/suit/space/hardsuit/wizard, /obj/item/clothing/head/wizard, /obj/item/clothing/head/wizard/armor, /obj/item/clothing/suit/wizrobe/armor, /obj/item/clothing/head/helmet/space/hardsuit/wizard, - /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard)) - - if(clothes_req) //clothes check - if(!is_type_in_typecache(H.wear_suit, casting_clothes)) - to_chat(H, span_notice("I don't feel strong enough without my robe.")) - return FALSE - if(!is_type_in_typecache(H.head, casting_clothes)) - to_chat(H, span_notice("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/space/hardsuit/cult)) - to_chat(H, span_notice("I don't feel strong enough without my armor.")) + /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard + )) + + if(ishuman(owner)) + if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB) + var/mob/living/carbon/human/human_owner = owner + if(!is_type_in_typecache(human_owner.wear_suit, casting_clothes)) + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your robe!")) return FALSE - if(!istype(H.head, /obj/item/clothing/head/magus) && !istype(H.head, /obj/item/clothing/head/helmet/space/hardsuit/cult)) - to_chat(H, span_notice("I don't feel strong enough without my helmet.")) + if(!is_type_in_typecache(human_owner.head, casting_clothes)) + if(feedback) + to_chat(owner, span_warning("You don't feel strong enough without your hat!")) return FALSE + else - if(clothes_req || human_req) - to_chat(user, span_notice("This spell can only be cast by humans!")) + // 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, span_warning("[src] can only be cast by humans!")) return FALSE - if(nonabstract_req && (isbrain(user) || ispAI(user))) - to_chat(user, span_notice("This spell can only be cast by physical beings!")) + + if(!(spell_requirements & SPELL_CASTABLE_AS_BRAIN) && isbrain(owner)) + if(feedback) + to_chat(owner, span_warning("[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 - if(!skipcharge) - 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) - if(action) - action.UpdateButtonIcon() return TRUE -/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, span_notice("[name] has no charges left.")) - return FALSE +/** + * 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. + * For click_to_activate spells, the target being checked is the clicked atom. + * + * Return TRUE if cast_on is valid, FALSE otherwise + */ +/datum/action/cooldown/spell/proc/is_valid_target(atom/cast_on) 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 +// 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/cooldown/spell/Activate(atom/cast_on) + 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 = before_cast(cast_on) + if(precast_result & SPELL_CANCEL_CAST) + return FALSE -/obj/effect/proc_holder/spell/proc/playMagSound() - playsound(get_turf(usr), sound,50,1) + // 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() -/obj/effect/proc_holder/spell/Initialize() - . = ..() - START_PROCESSING(SSfastprocess, src) + // Actually cast the spell. Main effects go here + cast(cast_on) - still_recharging_msg = span_notice("[name] is still recharging.") - charge_counter = charge_max + if(!(precast_result & SPELL_NO_IMMEDIATE_COOLDOWN)) + // The entire spell is done, start the actual cooldown at its set duration + StartCooldown() -/obj/effect/proc_holder/spell/Destroy() - STOP_PROCESSING(SSfastprocess, src) - qdel(action) - return ..() + // And then proceed with the aftermath of the cast + // Final effects that happen after all the casting is done can go here + after_cast(cast_on) + UpdateButtons() -/obj/effect/proc_holder/spell/Click() - if(cast_check()) - choose_targets() - return 1 + return TRUE -/obj/effect/proc_holder/spell/proc/choose_targets(mob/user = usr) //depends on subtype - /targeted or /aoe_turf - return +/** + * 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 after_cast. + */ +/datum/action/cooldown/spell/proc/before_cast(atom/cast_on) + SHOULD_CALL_PARENT(TRUE) + + var/sig_return = SEND_SIGNAL(src, COMSIG_SPELL_BEFORE_CAST, cast_on) + if(owner) + sig_return |= SEND_SIGNAL(owner, COMSIG_MOB_BEFORE_SPELL_CAST, src, cast_on) + + return sig_return + +/** + * Actions done as the main effect of the spell. + * + * For spells without a click intercept, [cast_on] will be the owner. + * For click spells, [cast_on] is whatever the owner clicked on in casting the spell. + */ +/datum/action/cooldown/spell/proc/cast(atom/cast_on) + SHOULD_CALL_PARENT(TRUE) + + SEND_SIGNAL(src, COMSIG_SPELL_CAST, cast_on) + if(owner) + SEND_SIGNAL(owner, COMSIG_MOB_CAST_SPELL, src, cast_on) + if(owner.ckey) + owner.log_message("cast the spell [name][cast_on != owner ? " on / at [cast_on]":""].", 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/cooldown/spell/proc/after_cast(atom/cast_on) + SHOULD_CALL_PARENT(TRUE) + if(!owner) // Could have been destroyed by the effect of the spell + SEND_SIGNAL(src, COMSIG_SPELL_AFTER_CAST, cast_on) + return -/obj/effect/proc_holder/spell/proc/can_target(mob/living/target) - return TRUE + if(sparks_amt) + do_sparks(sparks_amt, FALSE, get_turf(owner)) + if(ispath(smoke_type, /datum/effect_system/fluid_spread/smoke)) + var/datum/effect_system/fluid_spread/smoke/smoke = new smoke_type() + smoke.set_up(smoke_amt, holder = owner, location = get_turf(owner)) + smoke.start() -/obj/effect/proc_holder/spell/proc/start_recharge() - if(cooldown_overlay) - QDEL_NULL(cooldown_overlay) - cooldown_overlay = start_cooldown(action.button, world.time + charge_max) - recharging = TRUE - -/obj/effect/proc_holder/spell/process(delta_time) - if(recharging && charge_type == "recharge" && (charge_counter < charge_max)) - charge_counter += delta_time * 10 - cooldown_overlay?.tick() - if(charge_counter >= charge_max) - action.UpdateButtonIcon() - charge_counter = charge_max - recharging = FALSE - QDEL_NULL(cooldown_overlay) - -/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = TRUE, mob/user = usr) //if recharge is started is important for the trigger spells - before_cast(targets) - invocation(user) - if(user && user.ckey) - user.log_message(span_danger("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.anchored = TRUE - spell.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, text("[message]")) - if(sparks_spread) - do_sparks(sparks_amt, FALSE, location) - if(ispath(smoke_spread, /datum/effect_system/fluid_spread/smoke)) // Dear god this code is :agony: - var/datum/effect_system/fluid_spread/smoke/smoke = new smoke_spread() - smoke.set_up(smoke_amt, location = 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) - QDEL_NULL(cooldown_overlay) - 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)) + // Send signals last in case they delete the spell + SEND_SIGNAL(owner, COMSIG_MOB_AFTER_SPELL_CAST, src, cast_on) + SEND_SIGNAL(src, COMSIG_SPELL_AFTER_CAST, cast_on) + +/// Provides feedback after a spell cast occurs, in the form of a cast sound and/or invocation +/datum/action/cooldown/spell/proc/spell_feedback() + if(!owner) 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 - 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 - - -/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/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 + + 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/cooldown/spell/proc/invocation() + switch(invocation_type) + if(INVOCATION_SHOUT) + if(prob(50)) + owner.say(invocation, forced = "spell ([src])") 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) - M = input("Choose the target for the spell.", "Targeting") as null|mob in possible_targets - 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=1,i<=max_targets,i++) - if(!possible_targets.len) - break - if(target_ignore_prev) - var/target = pick(possible_targets) - possible_targets -= target - targets += target - else - targets += pick(possible_targets) - - if(!include_user && (user in targets)) - targets -= user - - if(!targets.len) //doesn't waste the spell - revert_cast(user) - return + owner.say(replacetext(invocation," ","`"), forced = "spell ([src])") - perform(targets,user=user) + if(INVOCATION_WHISPER) + if(prob(50)) + owner.whisper(invocation, forced = "spell ([src])") + else + owner.whisper(replacetext(invocation," ","`"), forced = "spell ([src])") -/obj/effect/proc_holder/spell/aoe_turf/choose_targets(mob/user = usr) - var/list/targets = list() + if(INVOCATION_EMOTE) + owner.visible_message(invocation, invocation_self_message) - 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 +/// Checks if the current OWNER of the spell is in a valid state to say the spell's invocation +/datum/action/cooldown/spell/proc/try_invoke(feedback = TRUE) + if(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION) + return TRUE - if(!targets.len) //doesn't waste the spell - revert_cast() - return + if(invocation_type == INVOCATION_NONE) + return TRUE - 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 - for(var/turf/turf in getline(A,B)) - for(var/atom/movable/AM in turf) - if(!AM.CanPass(dummy,turf,1)) - qdel(dummy) - return 0 - qdel(dummy) - return 1 - -/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)) + // If you want a spell usable by ghosts for some reason, it must be INVOCATION_NONE + if(!isliving(owner)) + if(feedback) + to_chat(owner, span_warning("You need to be living to invoke [src]!")) return FALSE - if(!charge_check(user,TRUE)) + var/mob/living/living_owner = owner + if(invocation_type == INVOCATION_EMOTE && HAS_TRAIT(living_owner, TRAIT_EMOTEMUTE)) + if(feedback) + to_chat(owner, span_warning("You can't position your hands correctly to invoke [src]!")) return FALSE - if(user.stat && !stat_allowed) + if((invocation_type == INVOCATION_WHISPER || invocation_type == INVOCATION_SHOUT) && !living_owner.can_speak()) + if(feedback) + to_chat(owner, span_warning("You can't get the words out to invoke [src]!")) return FALSE - if(!antimagic_allowed && user.anti_magic_check(TRUE, FALSE, FALSE, 0, TRUE)) + 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/cooldown/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 + UpdateButtons() + +/** + * 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/cooldown/spell/proc/level_spell(bypass_cap = FALSE) + // Spell cannot be levelled + if(spell_max_level <= 1) return FALSE - if(!ishuman(user)) - if(clothes_req || human_req) - return FALSE - if(nonabstract_req && (isbrain(user) || ispAI(user))) - 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.25 SECONDS) // 0 second CD starts to break things. + UpdateButtons() return TRUE -/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 +/** + * Levels the spell down a single level, down to 1. + */ +/datum/action/cooldown/spell/proc/delevel_spell() + // Spell cannot be levelled + if(spell_max_level <= 1) + return FALSE -/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 = "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(span_warning("A wreath of gentle light passes over [user]!"), span_notice("You wreath yourself in healing light!")) - user.adjustBruteLoss(-10) - user.adjustFireLoss(-10) + if(spell_level <= 1) + return FALSE + + spell_level-- + if(cooldown_reduction_per_rank > 0 SECONDS) + cooldown_time = min(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time)) + else + cooldown_time = max(cooldown_time + cooldown_reduction_per_rank, initial(cooldown_time)) + + UpdateButtons() + return TRUE + +/datum/action/cooldown/spell/update_button_name(atom/movable/screen/movable/action_button/button, force) + name = "[get_spell_title()][initial(name)]" + return ..() + +/// Gets the title of the spell based on its level. +/datum/action/cooldown/spell/proc/get_spell_title() + switch(spell_level) + if(2) + return "Efficient " + if(3) + return "Quickened " + if(4) + return "Free " + if(5) + return "Instant " + if(6) + return "Ludicrous " + + return "" \ No newline at end of file diff --git a/code/modules/spells/spell_types/aimed.dm b/code/modules/spells/spell_types/aimed.dm deleted file mode 100644 index 7464f1078622..000000000000 --- a/code/modules/spells/spell_types/aimed.dm +++ /dev/null @@ -1,181 +0,0 @@ - -/obj/effect/proc_holder/spell/aimed - name = "aimed projectile spell" - var/projectile_type = /obj/item/projectile/magic/teleport - var/deactive_msg = "You discharge your projectile..." - var/active_msg = "You charge your projectile!" - var/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 = span_warning("You can no longer cast [name]!") - remove_ranged_ability(msg) - return - if(active) - msg = span_notice("[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 = span_notice("[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-- - for(var/i in 1 to projectiles_per_fire) - var/obj/item/projectile/P = new projectile_type(user.loc) - 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/item/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. Insulated gloves can protect people from the blast." - school = "evocation" - charge_max = 100 - clothes_req = FALSE - invocation = "UN'LTD P'WAH" - invocation_type = "shout" - cooldown_min = 30 - active_icon_state = "lightning" - base_icon_state = "lightning" - 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/item/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 = 60 - clothes_req = FALSE - invocation = "ONI SOMA" - invocation_type = "shout" - range = 20 - cooldown_min = 20 //10 deciseconds reduction per rank - projectile_type = /obj/item/projectile/magic/aoe/fireball - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - 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 = "Blazing hot rapid-fire homing cards. Send your foes to the shadow realm with their mystical power!" - school = "evocation" - charge_max = 50 - clothes_req = FALSE - invocation = "Sigi'lu M'Fan 'Tasia" - invocation_type = "shout" - range = 40 - cooldown_min = 10 - projectile_amount = 5 - projectiles_per_fire = 7 - projectile_type = /obj/item/projectile/spellcard - 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/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/item/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 000000000000..49626cafc1f7 --- /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/cooldown/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/cooldown/spell/aoe/cast(atom/cast_on) + . = ..() + // Get every atom around us to our aoe cast on + var/list/atom/things_to_cast_on = get_things_to_cast_on(cast_on) + // 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, cast_on) + + // 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, cast_on) + num_targets++ + +/** + * Gets a list of atoms around [center] + * that are within range and affected by our aoe. + */ +/datum/action/cooldown/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/cooldown/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.") \ No newline at end of file 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 000000000000..1383ee73589f --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/area_conversion.dm @@ -0,0 +1,25 @@ +/datum/action/cooldown/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/mob/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/cooldown/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/cooldown/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)) \ No newline at end of file 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 000000000000..ca90056dc489 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/knock.dm @@ -0,0 +1,20 @@ +/datum/action/cooldown/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/cooldown/spell/aoe/knock/get_things_to_cast_on(atom/center) + return RANGE_TURFS(aoe_radius, center) + +/datum/action/cooldown/spell/aoe/knock/cast_on_thing_in_aoe(turf/victim, atom/caster) + SEND_SIGNAL(victim, COMSIG_ATOM_MAGICALLY_UNLOCKED, src, caster) \ No newline at end of file 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 000000000000..b62c70f493dc --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/magic_missile.dm @@ -0,0 +1,47 @@ +/datum/action/cooldown/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/item/projectile/projectile_type = /obj/item/projectile/magic/aoe/magic_missile + +/datum/action/cooldown/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/cooldown/spell/aoe/magic_missile/cast_on_thing_in_aoe(mob/living/victim, atom/caster) + fire_projectile(victim, caster) + +/datum/action/cooldown/spell/aoe/magic_missile/proc/fire_projectile(atom/victim, mob/caster) + var/obj/item/projectile/to_fire = new projectile_type() + to_fire.preparePixelProjectile(victim, caster) + to_fire.fire() + +/datum/action/cooldown/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/item/projectile/magic/aoe/magic_missile/lesser \ No newline at end of file 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 000000000000..45834c691ac6 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/repulse.dm @@ -0,0 +1,87 @@ +/datum/action/cooldown/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/cooldown/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/cooldown/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, span_userdanger("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, span_userdanger("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/cooldown/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/cooldown/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/mob/actions/actions_xeno.dmi' + button_icon_state = "tailsweep" + panel = "Alien" + 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/cooldown/spell/aoe/repulse/xeno/cast(atom/cast_on) + if(iscarbon(cast_on)) + var/mob/living/carbon/carbon_caster = cast_on + playsound(get_turf(carbon_caster), 'sound/voice/hiss5.ogg', 80, TRUE, TRUE) + carbon_caster.spin(6, 1) + + return ..() \ No newline at end of file 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 000000000000..188dc1a21f98 --- /dev/null +++ b/code/modules/spells/spell_types/aoe_spell/sacred_flame.dm @@ -0,0 +1,39 @@ +/datum/action/cooldown/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/cooldown/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/cooldown/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, span_warning("You suddenly feel very flammable.")) + +/datum/action/cooldown/spell/aoe/sacred_flame/cast(mob/living/cast_on) + . = ..() + cast_on.ignite_mob() + to_chat(cast_on, span_danger("You feel a roaring flame build up inside you!")) \ No newline at end of file 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 9696e1a39ec1..000000000000 --- a/code/modules/spells/spell_types/area_teleport.dm +++ /dev/null @@ -1,92 +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/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 && 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) - if(get_turf(target) == attempt) - success = TRUE - break - else - tempL.Remove(attempt) - - if(!success) - do_teleport(target, L, forceMove = TRUE, channel = TELEPORT_CHANNEL_MAGIC) - 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") - if(user.gender==MALE) - playsound(user.loc, pick('sound/misc/null.ogg','sound/misc/null.ogg'), 100, 1) - else - 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 7ae66eeaccbf..000000000000 --- a/code/modules/spells/spell_types/barnyard.dm +++ /dev/null @@ -1,51 +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 - charge_counter = 0 - clothes_req = FALSE - stat_allowed = FALSE - invocation = "KN'A FTAGHU, PUCK 'BTHNK!" - invocation_type = "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 = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "barn" - -/obj/effect/proc_holder/spell/targeted/barnyardcurse/cast(list/targets, mob/user = usr) - if(!targets.len) - to_chat(user, span_notice("No target found in range.")) - return - - var/mob/living/carbon/target = targets[1] - - - if(!is_type_in_typecache(target, compatible_mobs_typecache)) - to_chat(user, span_notice("You are unable to curse [target]'s head!")) - return - - if(!(target in oview(range))) - to_chat(user, span_notice("[target.p_theyre(TRUE)] too far away!")) - return - - if(target.anti_magic_check()) - to_chat(user, span_warning("The spell had no effect!")) - target.visible_message(span_danger("[target]'s face bursts into flames, which instantly burst outward, leaving [target] unharmed!"), \ - span_danger("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(span_danger("[target]'s face bursts into flames, and a barnyard animal's head takes its place!"), \ - span_danger("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, SLOT_WEAR_MASK, 1, 1) - - target.flash_act() diff --git a/code/modules/spells/spell_types/bloodcrawl.dm b/code/modules/spells/spell_types/bloodcrawl.dm deleted file mode 100644 index 3519b1e45022..000000000000 --- a/code/modules/spells/spell_types/bloodcrawl.dm +++ /dev/null @@ -1,41 +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/mob/actions/actions_minor_antag.dmi' - action_icon_state = "bloodcrawl" - action_background_icon_state = "bg_demon" - var/phased = FALSE - -/obj/effect/proc_holder/spell/bloodcrawl/choose_targets(mob/user = usr) - for(var/obj/effect/decal/cleanable/target in range(range, get_turf(user))) - if(target.can_bloodcrawl_in()) - perform(target) - return - revert_cast() - to_chat(user, span_warning("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)) - if(istype(user, /mob/living/simple_animal/slaughter)) - var/mob/living/simple_animal/slaughter/slaught = user - slaught.current_hitstreak = 0 - slaught.wound_bonus = initial(slaught.wound_bonus) - slaught.bare_wound_bonus = initial(slaught.bare_wound_bonus) - if(phased) - if(user.phasein(target)) - phased = FALSE - else - if(user.phaseout(target)) - phased = TRUE - start_recharge() - return - revert_cast() - to_chat(user, span_warning("You are unable to blood crawl!")) diff --git a/code/modules/spells/spell_types/charge.dm b/code/modules/spells/spell_types/charge.dm deleted file mode 100644 index 26d51644616c..000000000000 --- 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 = "whisper" - range = -1 - cooldown_min = 400 //50 deciseconds reduction per rank - include_user = TRUE - - -/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?.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, span_notice("You feel raw magic flowing through you. It feels good!")) - else - to_chat(M, span_notice("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, span_danger("Glowing red letters appear on the front cover...")) - to_chat(L, span_warning("[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, span_notice("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(span_warning("[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, span_notice("You feel magical power surging through your hands, but the feeling rapidly fades...")) - else if(burnt_out) - to_chat(L, span_caution("[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, span_notice("[charged_item] suddenly feels very warm!")) 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 000000000000..45ba9d456bbe --- /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/cooldown/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/cooldown/spell/cone/cast(atom/cast_on) + . = ..() + var/list/cone_turfs = get_cone_turfs(get_turf(cast_on), cast_on.dir, cone_levels) + SEND_SIGNAL(src, COMSIG_SPELL_CONE_ON_CAST, cone_turfs, cast_on) + make_cone(cone_turfs, cast_on) + +/datum/action/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/spell/cone/staggered + + /// The delay between each cone level triggering. + var/delay_between_level = 0.2 SECONDS + +/datum/action/cooldown/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) \ No newline at end of file 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 26dd15798614..000000000000 --- 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 = "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(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 - -/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/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 d466f1713713..000000000000 --- a/code/modules/spells/spell_types/conjure.dm +++ /dev/null @@ -1,100 +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=0,i 0) + QDEL_IN(summoned_object, summon_lifespan) + + post_summon(summoned_object, cast_on) + +/// Called on atoms summoned after they are created, allows extra variable editing and such of created objects +/datum/action/cooldown/spell/conjure/proc/post_summon(atom/summoned_object, atom/cast_on) + return \ No newline at end of file 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 000000000000..24850cc32af7 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/bees.dm @@ -0,0 +1,18 @@ +/datum/action/cooldown/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/bee/toxin) + summon_amount = 9 \ No newline at end of file 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 000000000000..339b507a6971 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/carp.dm @@ -0,0 +1,13 @@ +/datum/action/cooldown/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) \ No newline at end of file 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 000000000000..c56ce27a6c7a --- /dev/null +++ b/code/modules/spells/spell_types/conjure/constructs.dm @@ -0,0 +1,20 @@ +/datum/action/cooldown/spell/conjure/construct + name = "Summon Construct Shell" + desc = "This spell conjures a construct which may be controlled by Shades." + icon_icon = 'icons/mob/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/cooldown/spell/conjure/construct/lesser // Used by artificers. + name = "Create Construct Shell" + background_icon_state = "bg_demon" + cooldown_time = 3 MINUTES \ No newline at end of file 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 000000000000..876bb662293b --- /dev/null +++ b/code/modules/spells/spell_types/conjure/creatures.dm @@ -0,0 +1,15 @@ +/datum/action/cooldown/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 \ No newline at end of file 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 000000000000..e2ee4f5ead76 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/cult_turfs.dm @@ -0,0 +1,29 @@ +/datum/action/cooldown/spell/conjure/cult_floor + name = "Summon Cult Floor" + desc = "This spell constructs a cult floor." + background_icon_state = "bg_cult" + icon_icon = 'icons/mob/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/cooldown/spell/conjure/cult_wall + name = "Summon Cult Wall" + desc = "This spell constructs a cult wall." + background_icon_state = "bg_cult" + icon_icon = 'icons/mob/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. \ No newline at end of file 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 000000000000..6ccd29f9ab23 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/ed_swarm.dm @@ -0,0 +1,21 @@ +// test purposes - Also a lot of fun +/datum/action/cooldown/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/cooldown/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.declare_arrests = FALSE + summoned_bot.emag_act(owner) + + summoned_bot.projectile = /obj/item/projectile/beam/laser + summoned_bot.shoot_sound = 'sound/weapons/laser.ogg' \ No newline at end of file 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 000000000000..9f4eaad1e48a --- /dev/null +++ b/code/modules/spells/spell_types/conjure/invisible_chair.dm @@ -0,0 +1,34 @@ +/datum/action/cooldown/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/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_chair" + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = span_notice("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/cooldown/spell/conjure/invisible_chair/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] pulls out an invisible chair and sits down.") + +/datum/action/cooldown/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) \ No newline at end of file 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 000000000000..31b50aa6d86c --- /dev/null +++ b/code/modules/spells/spell_types/conjure/invisible_wall.dm @@ -0,0 +1,26 @@ +/datum/action/cooldown/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/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_wall" + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = span_notice("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/cooldown/spell/conjure/invisible_wall/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] looks as if a wall is in front of [cast_on.p_them()].") \ No newline at end of file diff --git a/code/modules/spells/spell_types/conjure/link_worlds.dm b/code/modules/spells/spell_types/conjure/link_worlds.dm new file mode 100644 index 000000000000..c04b59353105 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/link_worlds.dm @@ -0,0 +1,15 @@ +/datum/action/cooldown/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 \ No newline at end of file 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 000000000000..aab3d497011a --- /dev/null +++ b/code/modules/spells/spell_types/conjure/presents.dm @@ -0,0 +1,14 @@ +/datum/action/cooldown/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 \ No newline at end of file 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 000000000000..b56b36abd6a1 --- /dev/null +++ b/code/modules/spells/spell_types/conjure/soulstone.dm @@ -0,0 +1,26 @@ +/datum/action/cooldown/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/mob/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/cooldown/spell/conjure/soulstone/cult + name = "Create Nar'sian Soulstone" + cooldown_time = 6 MINUTES + +/datum/action/cooldown/spell/conjure/soulstone/noncult + name = "Create Soulstone" + summon_type = list(/obj/item/soulstone/anybody) + +/datum/action/cooldown/spell/conjure/soulstone/purified + name = "Create Purified Soulstone" + summon_type = list(/obj/item/soulstone/anybody/purified) 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 000000000000..b8650a542e5b --- /dev/null +++ b/code/modules/spells/spell_types/conjure/the_traps.dm @@ -0,0 +1,35 @@ +/datum/action/cooldown/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/cooldown/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 \ No newline at end of file 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 000000000000..f12626b248e8 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/_conjure_item.dm @@ -0,0 +1,46 @@ +/datum/action/cooldown/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/cooldown/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/cooldown/spell/conjure_item/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/datum/action/cooldown/spell/conjure_item/cast(mob/living/carbon/cast_on) + if(delete_old && LAZYLEN(item_refs)) + QDEL_LAZYLIST(item_refs) + + var/obj/item/existing_item = cast_on.get_active_held_item() + if(existing_item) + cast_on.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].") + + cast_on.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/cooldown/spell/conjure_item/proc/make_item() + var/obj/item/made_item = new item_type() + LAZYADD(item_refs, WEAKREF(made_item)) + return made_item \ No newline at end of file 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 000000000000..48f9a9bea35e --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/infinite_guns.dm @@ -0,0 +1,41 @@ +/datum/action/cooldown/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/cooldown/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/cooldown/spell/conjure_item/infinite_guns/make_item() + return new item_type() + +/datum/action/cooldown/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 + +/datum/action/cooldown/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 \ No newline at end of file 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 000000000000..6dd5e9090be6 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/invisible_box.dm @@ -0,0 +1,42 @@ +/datum/action/cooldown/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/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_box" + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + invocation = "Someone does a weird gesture." // Overriden in before cast + invocation_self_message = span_notice("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/cooldown/spell/conjure_item/invisible_box/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] moves [cast_on.p_their()] hands in the shape of a cube, pressing a box out of the air.") + +/datum/action/cooldown/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/cooldown/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) \ No newline at end of file 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 000000000000..83d4ef7b84a5 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/lightning_packet.dm @@ -0,0 +1,38 @@ +/datum/action/cooldown/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/cooldown/spell/conjure_item/spellpacket/cast(mob/living/carbon/cast_on) + . = ..() + cast_on.throw_mode_on() + +/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") \ No newline at end of file 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 000000000000..ae479e5f4894 --- /dev/null +++ b/code/modules/spells/spell_types/conjure_item/snowball.dm @@ -0,0 +1,8 @@ +/datum/action/cooldown/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 \ No newline at end of file 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 8a933b15e30a..000000000000 --- a/code/modules/spells/spell_types/construct_spells.dm +++ /dev/null @@ -1,336 +0,0 @@ -//////////////////////////////Construct Spells///////////////////////// - -/obj/effect/proc_holder/spell/aoe_turf/conjure/construct/lesser - charge_max = 1800 - action_icon = 'icons/mob/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 = 2500 - - -/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 = 50 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - range = 2 - action_icon = 'icons/mob/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 = 20 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - range = 0 - summon_type = list(/turf/open/floor/engine/cult) - action_icon = 'icons/mob/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 = 100 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - range = 0 - action_icon = 'icons/mob/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/wall/reinforced - name = "Greater Construction" - desc = "This spell constructs a reinforced metal wall." - - school = "conjuration" - charge_max = 300 - clothes_req = FALSE - invocation = "none" - invocation_type = "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 = 2400 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - range = 0 - action_icon = 'icons/mob/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 = 3600 - -/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/noncult/purified - summon_type = list(/obj/item/soulstone/anybody/purified) - -/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 = 400 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - wall_type = /obj/effect/forcefield/cult - action_icon = 'icons/mob/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 = 250 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - range = -1 - include_user = TRUE - jaunt_duration = 50 //in deciseconds - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "phaseshift" - action_background_icon_state = "bg_demon" - jaunt_in_time = 12 - 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/projectile/magic_missile/lesser - name = "Lesser Magic Missile" - desc = "This spell fires several, slow moving, magic projectiles at nearby targets." - - school = "evocation" - charge_max = 400 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - max_targets = 6 - action_icon_state = "magicm" - action_background_icon_state = "bg_demon" - proj_type = /obj/item/projectile/magic/spell/magic_missile/lesser - -/obj/item/projectile/magic/spell/magic_missile/lesser - name = "lesser magic missile" - color = "red" //Looks more culty this way - range = 10 - -/obj/item/projectile/magic/spell/magic_missile/lesser/on_hit(target) - if(ismob(target)) - var/mob/M = target - if(iscultist(target))//cultists can't be harmed by their own constructs' spells! - to_chat(target, span_danger("[src] harmlessly dissipates into crimson particles upon contacting your body!")) - return BULLET_ACT_BLOCK - if(M.anti_magic_check()) - M.visible_message(span_warning("[src] vanishes on contact with [target]!")) - return BULLET_ACT_BLOCK - . = ..() - -/obj/effect/proc_holder/spell/targeted/smoke/disable - name = "Paralysing Smoke" - desc = "This spell spawns a cloud of paralysing smoke." - - school = "conjuration" - charge_max = 200 - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - range = -1 - include_user = TRUE - cooldown_min = 20 //25 deciseconds reduction per rank - - smoke_spread = /datum/effect_system/fluid_spread/smoke/sleeping - 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 = 750 - range = 5 - include_user = FALSE - selection_type = "range" - stat_allowed = FALSE - - school = "evocation" - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - action_icon = 'icons/mob/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, span_notice("No target found in range.")) - revert_cast() - return - - var/mob/living/carbon/target = targets[1] - - if(!(target in oview(range))) - to_chat(user, span_notice("[target] is too far away!")) - revert_cast() - return - - if(target.anti_magic_check(TRUE, TRUE)) - to_chat(target, span_warning("You feel a freezing darkness closing in on you, but it rapidly dissipates.")) - return - - to_chat(target, span_userdanger("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(ABYSSAL_GAZE_BLIND) - addtimer(CALLBACK(src, .proc/cure_blindness, target), 40) - target.adjust_bodytemperature(-200) - -/obj/effect/proc_holder/spell/targeted/abyssal_gaze/proc/cure_blindness(mob/target) - if(isliving(target)) - var/mob/living/L = target - L.cure_blind(ABYSSAL_GAZE_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 = 600 - range = 7 - include_user = FALSE - selection_type = "range" - stat_allowed = FALSE - - school = "evocation" - clothes_req = FALSE - invocation = "none" - invocation_type = "none" - action_icon = 'icons/mob/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, span_notice("No target found in range.")) - revert_cast() - return - - var/mob/living/simple_animal/S = targets[1] - - if(S.ckey) - to_chat(user, span_warning("[S] is too intelligent to dominate!")) - revert_cast() - return - - if(S.stat) - to_chat(user, span_warning("[S] is dead!")) - revert_cast() - return - - if(S.sentience_type != SENTIENCE_ORGANIC) - to_chat(user, span_warning("[S] cannot be dominated!")) - revert_cast() - return - - if(!(S in oview(range))) - to_chat(user, span_notice("[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 = 800 - 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/item/projectile/magic/spell/juggernaut - charge_max = 350 - clothes_req = FALSE - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "cultfist" - action_background_icon_state = "bg_demon" - sound = 'sound/weapons/resonator_blast.ogg' - -/obj/item/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/item/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(src,1)) - 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/devil.dm b/code/modules/spells/spell_types/devil.dm index aebd488225ae..a5e6e3c46bf2 100644 --- a/code/modules/spells/spell_types/devil.dm +++ b/code/modules/spells/spell_types/devil.dm @@ -71,7 +71,7 @@ C.put_in_hands(contract) else to_chat(user, span_notice("[C] seems to not be sentient. You cannot summon a contract for [C.p_them()].")) - action.UpdateButtonIcon() + action.UpdateButtons() charge_counter = charge_max recharging = FALSE diff --git a/code/modules/spells/spell_types/emplosion.dm b/code/modules/spells/spell_types/emplosion.dm deleted file mode 100644 index a5ba0a39142c..000000000000 --- a/code/modules/spells/spell_types/emplosion.dm +++ /dev/null @@ -1,18 +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()) - continue - empulse(target.loc, emp_heavy, emp_light) - - return \ No newline at end of file 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 4433c3d5fbf9..000000000000 --- a/code/modules/spells/spell_types/ethereal_jaunt.dm +++ /dev/null @@ -1,84 +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 = 300 - clothes_req = TRUE - invocation = "none" - invocation_type = "none" - range = -1 - cooldown_min = 10 SECONDS //5 seconds reduction per rank - include_user = TRUE - nonabstract_req = TRUE - var/jaunt_duration = 5 SECONDS //in seconds - var/jaunt_in_time = 0.5 SECONDS - var/jaunt_in_type = /obj/effect/temp_visual/wizard - var/jaunt_out_type = /obj/effect/temp_visual/wizard/out - action_icon_state = "jaunt" - -/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/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. - jaunt_steam(mobloc) - - sleep(jaunt_duration) - - if(target.loc != holder) //mob warped out of the warp - qdel(holder) - return - mobloc = get_turf(target.loc) - jaunt_steam(mobloc) - target.mobility_flags &= ~MOBILITY_MOVE - holder.reappearing = 1 - play_sound("exit",target) - sleep(2.5 SECONDS - 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 - target.mobility_flags |= MOBILITY_MOVE - -/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 - movespeed = 2 //quite slow. - 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, .)) - to_chat(user, "Holy energies block your path!") - return null diff --git a/code/modules/spells/spell_types/explosion.dm b/code/modules/spells/spell_types/explosion.dm deleted file mode 100644 index 0ef21ab7869f..000000000000 --- 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) - - return \ No newline at end of file diff --git a/code/modules/spells/spell_types/forcewall.dm b/code/modules/spells/spell_types/forcewall.dm deleted file mode 100644 index 2329790525d8..000000000000 --- a/code/modules/spells/spell_types/forcewall.dm +++ /dev/null @@ -1,41 +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 = "shout" - sound = 'sound/magic/forcewall.ogg' - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - 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),user) - if(user.dir == SOUTH || user.dir == NORTH) - new wall_type(get_step(user, EAST),user) - new wall_type(get_step(user, WEST),user) - else - new wall_type(get_step(user, NORTH),user) - new wall_type(get_step(user, SOUTH),user) - - -/obj/effect/forcefield/wizard - var/mob/wizard - -/obj/effect/forcefield/wizard/Initialize(mapload, mob/summoner) - . = ..() - wizard = summoner - -/obj/effect/forcefield/wizard/CanAllowThrough(atom/movable/mover, turf/target) - . = ..() - if(mover == wizard) - return TRUE - if(ismob(mover)) - var/mob/M = mover - if(M.anti_magic_check(chargecost = 0)) - 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 c3bdc74eb72c..000000000000 --- a/code/modules/spells/spell_types/genetic.dm +++ /dev/null @@ -1,44 +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.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 - addtimer(CALLBACK(src, .proc/remove, target), duration) - -/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)) - for(var/A in mutations) - target.dna.remove_mutation(A) - for(var/A in traits) - REMOVE_TRAIT(target, A, GENETICS_SPELL) \ No newline at end of file diff --git a/code/modules/spells/spell_types/godhand.dm b/code/modules/spells/spell_types/godhand.dm deleted file mode 100644 index cc659303c17c..000000000000 --- a/code/modules/spells/spell_types/godhand.dm +++ /dev/null @@ -1,203 +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/wizard.dmi' - lefthand_file = 'icons/mob/inhands/misc/touchspell_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/touchspell_righthand.dmi' - icon_state = "disintegrate" - item_state = null - item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL - w_class = WEIGHT_CLASS_HUGE - force = 0 - throwforce = 0 - throw_range = 0 - throw_speed = 0 - var/charges = 1 - -/obj/item/melee/touch_attack/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - -/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, span_warning("You can't reach out!")) - return - ..() - -/obj/item/melee/touch_attack/afterattack(atom/target, mob/user, proximity) - . = ..() - user.say(catchphrase, forced = "spell") - playsound(get_turf(user), on_use_sound,50,1) - charges-- - if(charges <= 0) - 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, span_notice("You can't get the words out!")) - return - var/mob/M = target - do_sparks(4, FALSE, M.loc) - for(var/mob/living/L in view(src, 7)) - if(L != user) - L.flash_act(affect_silicon = FALSE) - var/atom/A = M.anti_magic_check() - if(A) - if(isitem(A)) - target.visible_message(span_warning("[target]'s [A] glows brightly as it wards off the spell!")) - user.visible_message(span_warning("The feedback blows [user]'s arm off!"),span_userdanger("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(SLOT_WEAR_SUIT) - if(istype(suit)) - M.visible_message(span_danger("[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, span_warning("You can't reach out!")) - return - if(!user.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return - var/mob/living/M = target - if(M.anti_magic_check()) - to_chat(user, span_warning("The spell can't seem to affect [M]!")) - to_chat(M, span_warning("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/flagellate - name = "\improper flagellating touch" - desc = "I'd like to see them greytide me now." - catchphrase = "RETRIBUTION!!" - on_use_sound = 'sound/magic/wandodeath.ogg' - icon_state = "flagellation" - item_state = "hivehand" - -/obj/item/melee/touch_attack/flagellate/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user)) //flagellating your own mind painfully wouldn't be THAT bad but still bad - return - if(!(user.mobility_flags & MOBILITY_USE)) - to_chat(user, span_warning("You can't reach out!")) - return - if(!user.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return - var/mob/living/M = target - if(M.anti_magic_check()) - to_chat(user, span_warning("The spell can't seem to affect [M]!")) - to_chat(M, span_warning("You feel faint energies trying to get into your head, before they suddenly vanish!")) - ..() - return - M.adjustBruteLoss(18) //same as nullrod, but with a large cooldown, so it should be fine - M.blur_eyes(10) - M.confused = max(M.confused, 6) - M.visible_message(span_danger("[M] cringes in pain as they hold their head for a second!")) - M.emote("scream") - to_chat(M, span_warning("You feel an explosion of pain erupt in your mind!")) - return ..() - -/obj/item/melee/touch_attack/pacifism - name = "\improper pacifism touch" - desc = "Yes" - catchphrase = "PAC'FY" - on_use_sound = 'sound/magic/wandodeath.ogg' - icon_state = "flagellation" - item_state = "hivehand" - color = "#FF0000" - -/obj/item/melee/touch_attack/pacifism/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user)) - return - var/mob/living/carbon/human/H = target - if(!H) - return - if(H.anti_magic_check()) - return - H.reagents.add_reagent(/datum/reagent/pax, 5) - H.reagents.add_reagent(/datum/reagent/toxin/mutetoxin, 5) - H.ForceContractDisease(new /datum/disease/transformation/gondola(), FALSE, TRUE) - to_chat(H, span_notice("You feel calm...")) - return ..() - -/obj/item/melee/touch_attack/touchofdeath //yogs start - name = "\improper necrotic touch" - desc = "What has a beginning but no end?" - catchphrase = "DIM MAK!!" - on_use_sound = 'sound/magic/wandodeath.ogg' - icon_state = "touchofdeath" - item_state = "touchofdeath" - -/obj/item/melee/touch_attack/touchofdeath/afterattack(atom/target, mob/living/carbon/user, proximity) - if(!proximity || target == user || !isliving(target) || !iscarbon(user) || !(user.mobility_flags & MOBILITY_USE)) - return - if(!user.can_speak_vocal()) - to_chat(user, span_notice("You can't get the words out!")) - return - var/mob/living/M = target - do_sparks(4, FALSE, M.loc) - for(var/mob/living/L in view(src, 7)) - if(L != user) - L.flash_act(affect_silicon = FALSE) - var/atom/A = M.anti_magic_check() - if(A) - if(isitem(A)) - target.visible_message(span_warning("[target]'s [A] glows brightly as it wards off the spell!")) - user.visible_message(span_warning("[user]'s arm becomes a pale shade of grey and falls off!"),span_userdanger("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(SLOT_WEAR_SUIT) - if(istype(suit)) - M.visible_message(span_danger("[M]'s [suit] rots away into a pile of goo!")) - M.dropItemToGround(suit) - qdel(suit) - new /obj/effect/decal/cleanable/molten_object(M.loc) - return ..() - M.adjustBruteLoss(max(200-(M.getOxyLoss() + M.getToxLoss() + M.getBruteLoss() + M.getFireLoss()),0)) - M.death(FALSE) - return ..() //yogs end diff --git a/code/modules/spells/spell_types/hivemind.dm b/code/modules/spells/spell_types/hivemind.dm index 91a11e273278..f25df1d7bd82 100644 --- a/code/modules/spells/spell_types/hivemind.dm +++ b/code/modules/spells/spell_types/hivemind.dm @@ -210,9 +210,9 @@ target.blind_eyes(4*power) target.blur_eyes(30*power) target.minimumDeafTicks(15*power) //equivalent to 30s deafness max - target.Jitter(10*power) + target.adjust_jitter(power SECONDS) target.silent += 10*power - target.stuttering += 30*power + target.adjust_stutter(3*power SECONDS) target.Knockdown(1*power) target.stop_pulling() to_chat(target, span_userdanger("You feel your mind start to burn!")) @@ -390,7 +390,7 @@ else backseat.mind.transfer_to(vessel,1) vessel.visible_message(span_userdanger("[src] suddenly wakes up, as though he was under foreign control!")) - vessel.Jitter(3) + vessel.adjust_jitter(3 SECONDS) message_admins("[ADMIN_LOOKUPFLW(vessel)] is no longer being controlled by [ADMIN_LOOKUPFLW(original_body)] (Hivemind Host).") log_game("[key_name(vessel)] was released from Mind Control by [key_name(original_body)].") @@ -540,7 +540,7 @@ for(var/mob/living/carbon/human/target in targets) if(target.stat == DEAD) continue - target.Jitter(14) + target.adjust_jitter(14 SECONDS) target.apply_damage(35 + rand(0,15), STAMINA, target.get_bodypart(BODY_ZONE_HEAD)) if(target.is_real_hivehost() || target.anti_magic_check(FALSE, FALSE, TRUE)) continue @@ -555,20 +555,20 @@ if(2) to_chat(target, span_userdanger("You panic and flail around!")) target.click_random_mob() - addtimer(CALLBACK(target, "click_random_mob"), 5) - addtimer(CALLBACK(target, "click_random_mob"), 10) - addtimer(CALLBACK(target, "click_random_mob"), 15) - addtimer(CALLBACK(target, "click_random_mob"), 20) - addtimer(CALLBACK(target, "Stun", 30), 25) - target.confused += 10 + addtimer(CALLBACK(target, "click_random_mob"), 0.5 SECONDS) + addtimer(CALLBACK(target, "click_random_mob"), 1 SECONDS) + addtimer(CALLBACK(target, "click_random_mob"), 1.5 SECONDS) + addtimer(CALLBACK(target, "click_random_mob"), 2 SECONDS) + addtimer(CALLBACK(target, "Stun", 3 SECONDS), 2.5 SECONDS) + target.adjust_confusion(10 SECONDS) if(3) to_chat(target, span_userdanger("You freeze up in fear!")) - target.Stun(70) + target.Stun(7 SECONDS) if(4) to_chat(target, span_userdanger("You feel nauseous as dread washes over you!")) - target.Dizzy(15) + target.adjust_dizzy(15 SECONDS) target.apply_damage(30, STAMINA, target.get_bodypart(BODY_ZONE_HEAD)) - target.hallucination += 45 + target.adjust_hallucinations(45 SECONDS) for(var/mob/living/silicon/target in targets) target.Unconscious(50) @@ -959,7 +959,7 @@ continue if(C.stat == DEAD) continue - C.Jitter(15) + C.adjust_jitter(15 SECONDS) C.Unconscious(150) C.anti_magic_check(FALSE, FALSE, TRUE, 6) to_chat(C, span_boldwarning("Something's wrong...")) 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 ddf292366854..000000000000 --- a/code/modules/spells/spell_types/infinite_guns.dm +++ /dev/null @@ -1,29 +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 = "none" - include_user = TRUE - range = -1 - - school = "conjuration" - charge_max = 750 - clothes_req = TRUE - cooldown_min = 10 //Gun wizard - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - 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 = 'icons/mob/actions/actions_spells.dmi' - action_icon_state = "arcane_barrage" - summon_path = /obj/item/gun/ballistic/rifle/boltaction/enchanted/arcane_barrage diff --git a/code/modules/spells/spell_types/inflict_handler.dm b/code/modules/spells/spell_types/inflict_handler.dm deleted file mode 100644 index f590d3b72a07..000000000000 --- a/code/modules/spells/spell_types/inflict_handler.dm +++ /dev/null @@ -1,67 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/inflict_handler - name = "Inflict Handler" - desc = "This spell blinds and/or destroys/damages/heals/ignites and/or knockdowns/stuns the target." - - var/amt_paralyze = 0 - var/amt_unconscious = 0 - var/amt_stun = 0 - - var/inflict_status - var/list/status_params = list() - - //set to negatives for healing - var/amt_dam_fire = 0 - var/amt_dam_brute = 0 - var/amt_dam_oxy = 0 - var/amt_dam_tox = 0 - - var/amt_eye_blind = 0 - var/amt_eye_blurry = 0 - - var/amt_firestacks = 0 - var/ignites = FALSE - - var/destroys = "none" //can be "none", "gib" or "disintegrate" - - var/summon_type = null //this will put an obj at the target's location - - var/check_anti_magic = TRUE - var/check_holy = FALSE - -/obj/effect/proc_holder/spell/targeted/inflict_handler/cast(list/targets,mob/user = usr) - for(var/mob/living/target in targets) - playsound(target,sound, 50,1) - if(target.anti_magic_check(check_anti_magic, check_holy)) - return - switch(destroys) - if("gib") - target.gib() - if("disintegrate") - target.dust() - - if(!target) - continue - //damage/healing - target.adjustBruteLoss(amt_dam_brute) - target.adjustFireLoss(amt_dam_fire) - target.adjustToxLoss(amt_dam_tox) - target.adjustOxyLoss(amt_dam_oxy) - //disabling - target.Paralyze(amt_paralyze) - target.Unconscious(amt_unconscious) - target.Stun(amt_stun) - - target.blind_eyes(amt_eye_blind) - target.blur_eyes(amt_eye_blurry) - target.adjust_fire_stacks(amt_firestacks) - //summoning - if(summon_type) - new summon_type(target.loc, target) - - if(inflict_status) - var/list/stat_args = status_params.Copy() - stat_args.Insert(1,inflict_status) - target.apply_status_effect(arglist(stat_args)) - - if(ignites) - target.IgniteMob() 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 000000000000..2613d7809532 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/_jaunt.dm @@ -0,0 +1,83 @@ +/** + * ## 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/cooldown/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/cooldown/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.area_flags & NOTELEPORT || owner_area.noteleport) // ihate this but i'm kinda tired so you deal with it + if(feedback) + to_chat(owner, span_danger("Some dull, universal force is stopping 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/cooldown/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/cooldown/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 + +/datum/action/cooldown/spell/jaunt/Remove(mob/living/remove_from) + exit_jaunt(remove_from) + return ..() \ No newline at end of file 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 000000000000..e477b53dc4fb --- /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/cooldown/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/mob/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/cooldown/spell/jaunt/bloodcrawl/cast(mob/living/cast_on) + . = ..() + for(var/obj/effect/decal/cleanable/blood_nearby in range(blood_radius, get_turf(cast_on))) + if(blood_nearby.can_bloodcrawl_in()) + return do_bloodcrawl(blood_nearby, cast_on) + + reset_spell_cooldown() + to_chat(cast_on, span_warning("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/cooldown/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, span_warning("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/cooldown/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(span_warning("[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(span_warning("[jaunter] sinks into [blood]!")) + playsound(jaunt_turf, 'sound/magic/enter_blood.ogg', 50, TRUE, -1) + jaunter.extinguish_mob() + + 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/cooldown/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, span_warning("You cannot exit yet!!")) + return FALSE + + if(exit_blood_time > 0 SECONDS) + blood.visible_message(span_warning("[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(span_boldwarning("[jaunter] rises out of [blood]!")) + return TRUE + +/datum/action/cooldown/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/cooldown/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/cooldown/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/cooldown/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( + span_warning("[victim] kicks free of [blood] just before entering it!"), + blind_message = span_notice("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( + span_boldwarning("[jaunter] drags [victim] into [blood]!"), + blind_message = span_notice("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/cooldown/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, span_danger("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, wound_bonus = CANT_WOUND) + victim.death() + on_victim_consumed(victim, jaunter) + +/** + * Called when a victim starts to be consumed. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_start_consume(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_danger("You begin to feast on [victim]... You can not move while you are doing this.")) + +/** + * Called when a victim is successfully consumed. + */ +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/proc/on_victim_consumed(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_danger("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/cooldown/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/spookoween/scary_horn.ogg' + + // Keep the people we hug! + var/list/mob/living/consumed_mobs = list() + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/Destroy() + consumed_mobs.Cut() + return ..() + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/Grant(mob/grant_to) + . = ..() + if(owner) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_death)) + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/Remove(mob/living/remove_from) + UnregisterSignal(remove_from, COMSIG_LIVING_DEATH) + return ..() + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/on_victim_start_consume(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_clown("You invite [victim] to your party! You can not move while you are doing this.")) + +/datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/funny/on_victim_consumed(mob/living/victim, mob/living/jaunter) + to_chat(jaunter, span_clown("[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/cooldown/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, span_clown("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/cooldown/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(span_warning("[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/cooldown/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) \ No newline at end of file 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 000000000000..8cb3f2807e42 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/ethereal_jaunt.dm @@ -0,0 +1,256 @@ +/datum/action/cooldown/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/cooldown/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.extinguish_mob() + do_steam_effects(cast_turf) + +/datum/action/cooldown/spell/jaunt/ethereal_jaunt/cast(mob/living/cast_on) + . = ..() + do_jaunt(cast_on) + +/** + * 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/cooldown/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/cooldown/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/cooldown/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), target) + 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/cooldown/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, span_danger("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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/mob/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/cooldown/spell/jaunt/ethereal_jaunt/shift/do_steam_effects(mobloc) + return + +/datum/action/cooldown/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/cooldown/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/cooldown/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, span_warning("Holy energies block your path!")) + return null \ No newline at end of file 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 000000000000..10b937df70c8 --- /dev/null +++ b/code/modules/spells/spell_types/jaunt/shadow_walk.dm @@ -0,0 +1,82 @@ +/datum/action/cooldown/spell/jaunt/shadow_walk + name = "Shadow Walk" + desc = "Grants unlimited movement in darkness." + background_icon_state = "bg_alien" + icon_icon = 'icons/mob/actions/actions_minor_antag.dmi' + button_icon_state = "ninja_cloak" + + spell_requirements = NONE + jaunt_type = /obj/effect/dummy/phased_mob/shadow + +/datum/action/cooldown/spell/jaunt/shadow_walk/cast(mob/living/cast_on) + . = ..() + if(is_jaunting(cast_on)) + exit_jaunt(cast_on) + return + + var/turf/cast_turf = get_turf(cast_on) + if(cast_turf.get_lumcount() >= SHADOW_SPECIES_LIGHT_THRESHOLD) + to_chat(cast_on, span_warning("It isn't dark enough here!")) + return + + playsound(cast_turf, 'sound/magic/ethereal_enter.ogg', 50, TRUE, -1) + cast_on.visible_message(span_boldwarning("[cast_on] melts into the shadows!")) + cast_on.SetAllImmobility(0) + cast_on.setStaminaLoss(0, FALSE) + enter_jaunt(cast_on) + +/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, span_warning("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(span_boldwarning("[jaunter] is revealed by the light!")) + else + reveal_turf.visible_message(span_boldwarning("[jaunter] emerges from the darkness!")) + playsound(reveal_turf, 'sound/magic/ethereal_exit.ogg', 50, TRUE, -1) + + return ..() \ No newline at end of file diff --git a/code/modules/spells/spell_types/lichdom.dm b/code/modules/spells/spell_types/lichdom.dm deleted file mode 100644 index 7c135ac2eb20..000000000000 --- a/code/modules/spells/spell_types/lichdom.dm +++ /dev/null @@ -1,160 +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 = "shout" - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 10 - include_user = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.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, span_caution("You must hold an item you wish to make your phylactery...")) - return - if(!M.mind.hasSoul) - to_chat(user, span_caution("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, span_warning("You begin to focus your very being into [item]...")) - break - - if(!marked_item) - to_chat(M, span_warning("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, marked_item, FALSE)) - to_chat(M, span_warning("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, span_userdanger("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), SLOT_WEAR_SUIT) - H.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(H), SLOT_HEAD) - H.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(H), SLOT_W_UNIFORM) - - // 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/resurrections = 0 - var/datum/mind/mind - var/respawn_time = 1800 - - var/static/active_phylacteries = 0 - -/obj/item/phylactery/Initialize(mapload, datum/mind/newmind) - . = ..() - mind = newmind - name = "phylactery of [mind.name]" - - active_phylacteries++ - GLOB.poi_list |= src - 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-- - GLOB.poi_list -= src - 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/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), SLOT_SHOES) - lich.equip_to_slot_or_del(new /obj/item/clothing/under/color/black(lich), SLOT_W_UNIFORM) - lich.equip_to_slot_or_del(new /obj/item/clothing/suit/wizrobe/black(lich), SLOT_WEAR_SUIT) - lich.equip_to_slot_or_del(new /obj/item/clothing/head/wizard/black(lich), SLOT_HEAD) - - lich.real_name = mind.name - mind.transfer_to(lich) - mind.grab_ghost(force=TRUE) - lich.hardset_dna(null,null,null,lich.real_name,null, new /datum/species/skeleton) - to_chat(lich, span_warning("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 && 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(span_warning("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,maxdistance=INFINITY) - 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 f8f4ec4ec0de..000000000000 --- 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 if they do not have shock protection." - charge_type = "recharge" - charge_max = 200 - clothes_req = TRUE - invocation = "UN'LTD P'WAH!" - invocation_type = "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, span_notice("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_mob(user,user,100,1)) - 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, span_notice("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, span_notice("[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(span_warning("[current] absorbs the spell, remaining unharmed!"), span_userdanger("You absorb the spell, remaining unharmed!")) - else if(bounces < 1) - current.electrocute_act(bolt_energy,"Lightning Bolt",safety=1) - playsound(get_turf(current), 'sound/magic/lightningshock.ogg', 50, 1, -1) - else - current.electrocute_act(bolt_energy,"Lightning Bolt",safety=1) - 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_target/_list_target.dm b/code/modules/spells/spell_types/list_target/_list_target.dm new file mode 100644 index 000000000000..fcbcffab1e4b --- /dev/null +++ b/code/modules/spells/spell_types/list_target/_list_target.dm @@ -0,0 +1,41 @@ +/** + * ## List Target spells + * + * These spells will prompt the user with a tgui list + * of all nearby targets that they select on to cast. + * + * To add effects on cast, override "cast(atom/cast_on)". + * The cast_on atom is the atom that was selected by the list. + */ +/datum/action/cooldown/spell/list_target + /// The message displayed as the title of the tgui target input list. + var/choose_target_message = "Choose a target." + /// Radius around the caster that living targets are picked to choose from + var/target_radius = 7 + +/datum/action/cooldown/spell/list_target/PreActivate(atom/caster) + var/list/list_targets = get_list_targets(caster, target_radius) + if(!length(list_targets)) + caster.balloon_alert(caster, "no targets nearby!") + return FALSE + + var/atom/chosen = tgui_input_list(caster, choose_target_message, name, sortUsernames(list_targets)) + if(QDELETED(src) || QDELETED(caster) || QDELETED(chosen) || !can_cast_spell()) + return FALSE + + if(get_dist(chosen, caster) > target_radius) + caster.balloon_alert(caster, "they're too far!") + return FALSE + + return Activate(chosen) + +/// Get a list of living targets in radius of the center to put in the target list. +/datum/action/cooldown/spell/list_target/proc/get_list_targets(atom/center, target_radius = 7) + var/list/things = list() + for(var/mob/living/nearby_living in view(target_radius, center)) + if(nearby_living == owner || nearby_living == center) + continue + + things += nearby_living + + return things \ No newline at end of file diff --git a/code/modules/spells/spell_types/list_target/telepathy.dm b/code/modules/spells/spell_types/list_target/telepathy.dm new file mode 100644 index 000000000000..93ac61b538a0 --- /dev/null +++ b/code/modules/spells/spell_types/list_target/telepathy.dm @@ -0,0 +1,51 @@ +/datum/action/cooldown/spell/list_target/telepathy + name = "Telepathy" + desc = "Telepathically transmits a message to the target." + icon_icon = 'icons/mob/actions/actions_revenant.dmi' + button_icon_state = "r_transmit" + + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + antimagic_flags = MAGIC_RESISTANCE_MIND + + choose_target_message = "Choose a target to whisper to." + + /// 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/cooldown/spell/list_target/telepathy/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + message = tgui_input_text(owner, "What do you wish to whisper to [cast_on]?", "[src]") + if(QDELETED(src) || QDELETED(owner) || QDELETED(cast_on) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + + if(!message) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/list_target/telepathy/cast(mob/living/cast_on) + . = ..() + log_directed_talk(owner, cast_on, message, LOG_SAY, name) + + var/formatted_message = "[message]" + + to_chat(owner, "You transmit to [cast_on]: [formatted_message]") + if(!cast_on.can_block_magic(antimagic_flags, charge_cost = 0)) //hear no evil + to_chat(cast_on, "You hear something behind you talking... [formatted_message]") + + 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, cast_on) + var/to_mob_name = span_name("[cast_on]") + + to_chat(ghost, "[from_link] [from_mob_name] [formatted_message] [to_link] [to_mob_name]") \ No newline at end of file diff --git a/code/modules/spells/spell_types/curse.dm b/code/modules/spells/spell_types/madness_curse.dm similarity index 100% rename from code/modules/spells/spell_types/curse.dm rename to code/modules/spells/spell_types/madness_curse.dm diff --git a/code/modules/spells/spell_types/mime.dm b/code/modules/spells/spell_types/mime.dm deleted file mode 100644 index 668052b766b0..000000000000 --- a/code/modules/spells/spell_types/mime.dm +++ /dev/null @@ -1,249 +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 = "Mime" - summon_type = list(/obj/effect/forcefield/mime) - invocation_type = "emote" - invocation_emote_self = span_notice("You form a wall in front of yourself.") - summon_lifespan = 300 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - -/obj/effect/proc_holder/spell/aoe_turf/conjure/mime_wall/Click() - var/mob/living/carbon/C = usr - if(usr && usr.mind) - if(C.handcuffed) - to_chat(usr, span_notice("You cannot cast this while handcuffed!")) - return - if(!usr.mind.miming) - to_chat(usr, span_notice("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 = "Mime" - summon_type = list(/obj/structure/chair/mime) - invocation_type = "emote" - invocation_emote_self = span_notice("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/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - 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, span_notice("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 = "Mime" - summon_type = list(/obj/item/storage/box/mime) - invocation_type = "emote" - invocation_emote_self = span_notice("You conjure up an invisible box, large enough to store a few things.") - summon_lifespan = 250 - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - range = 0 - cast_sound = null - human_req = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - 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) - if(user.get_active_held_item() == B) - B.alpha = 255 - addtimer(CALLBACK(B, /obj/item/storage/box/mime/.proc/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, span_notice("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 = "Mime" - clothes_req = FALSE - human_req = TRUE - antimagic_allowed = TRUE - charge_max = 3000 - range = -1 - include_user = TRUE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - 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 = span_warning("You can't break your vow of silence that fast!") - else - still_recharging_msg = span_warning("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, span_notice("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, span_notice("You break your vow of silence.")) - -// 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 = "Mime" - wall_type = /obj/effect/forcefield/mime/advanced - invocation_type = "emote" - invocation_emote_self = span_notice("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/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - 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, span_notice("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/aimed/finger_guns - name = "Finger Guns" - desc = "Shoot a mimed bullet from your fingers that silences and does some damage." - school = "mime" - panel = "Mime" - charge_max = 300 - clothes_req = FALSE - antimagic_allowed = TRUE - invocation_type = "emote" - invocation_emote_self = span_dangers("You fire your finger gun!") - range = 20 - projectile_type = /obj/item/projectile/bullet/mime - projectile_amount = 3 - sound = null - active_msg = "You draw your fingers!" - deactive_msg = "You put your fingers at ease. Another time." - active = FALSE - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - action_icon_state = "mime" - action_background_icon_state = "bg_mime" - base_icon_state = "mime" - - -/obj/effect/proc_holder/spell/aimed/finger_guns/Click() - var/mob/living/carbon/human/owner = usr - if(owner.incapacitated()) - to_chat(owner, span_warning("You can't properly point your fingers while incapacitated.")) - return - if(usr && usr.mind) - if(!usr.mind.miming) - to_chat(usr, span_notice("You must dedicate yourself to silence first.")) - return - invocation = "[usr.real_name] fires [usr.p_their()] finger gun!" - else - invocation_type ="none" - ..() - - -/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/aimed/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 7d605c8748ad..000000000000 --- a/code/modules/spells/spell_types/mind_transfer.dm +++ /dev/null @@ -1,99 +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 = "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 = 'icons/mob/actions/humble/actions_humble.dmi' - 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, span_warning("No mind found!")) - return - - if(targets.len > 1) - if(!silent) - to_chat(user, span_warning("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, span_warning("[t_He] [t_is] too far away!")) - return - - if(ismegafauna(target) || (target.status_flags & GODMODE)) - if(!silent) - to_chat(user, span_warning("This creature is too powerful to control!")) - return - - if(target.stat == DEAD) - if(!silent) - to_chat(user, span_warning("You don't particularly want to be dead!")) - return - - if(!target.key || !target.mind) - if(!silent) - to_chat(user, span_warning("[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, span_warning("You're killing yourself! You can't concentrate enough to do this!")) - return - - var/datum/mind/TM = target.mind - if((target.anti_magic_check(TRUE, FALSE) || TM.has_antag_datum(/datum/antagonist/wizard) || TM.has_antag_datum(/datum/antagonist/cult) || TM.has_antag_datum(/datum/antagonist/clockcult) || TM.has_antag_datum(/datum/antagonist/changeling) || TM.has_antag_datum(/datum/antagonist/rev) || TM.has_antag_datum(/datum/antagonist/darkspawn)) || target.key[1] == "@") - if(!silent) - to_chat(user, span_warning("[target.p_their(TRUE)] mind is resisting your spell!")) - return - - if(istype(target, /mob/living/simple_animal/hostile/guardian)) - var/mob/living/simple_animal/hostile/guardian/stand = target - if(stand.summoner) - if(stand.summoner == user) - if(!silent) - to_chat(user, span_warning("Swapping minds with your own guardian would just put you back into your own head!")) - return - else - target = stand.summoner - - 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(0) - 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 f358f2eb9e7f..000000000000 --- a/code/modules/spells/spell_types/personality_commune.dm +++ /dev/null @@ -1,32 +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 - 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/_pointed.dm b/code/modules/spells/spell_types/pointed/_pointed.dm new file mode 100644 index 000000000000..832903726222 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/_pointed.dm @@ -0,0 +1,181 @@ +/** + * ## 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/cooldown/spell/pointed + click_to_activate = 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/cooldown/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/cooldown/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/cooldown/spell/pointed/unset_click_ability(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(!.) + return + + on_deactivation(on_who, refund_cooldown = refund_cooldown) + +/datum/action/cooldown/spell/pointed/before_cast(atom/cast_on) + . = ..() + 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/cooldown/spell/pointed/proc/on_activation(mob/on_who) + SHOULD_CALL_PARENT(TRUE) + + to_chat(on_who, span_notice("[active_msg] Left-click to cast the spell on a target!")) + if(base_icon_state) + button_icon_state = "[base_icon_state]1" + UpdateButtons() + return TRUE + +/// Called when the spell is deactivated / the click ability is unset from our spell +/datum/action/cooldown/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, span_notice("[deactive_msg]")) + if(base_icon_state) + button_icon_state = "[base_icon_state]0" + UpdateButtons() + return TRUE + +/datum/action/cooldown/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/cooldown/spell/pointed/is_valid_target(atom/cast_on) + if(cast_on == owner) + to_chat(owner, span_warning("You cannot cast [src] on yourself!")) + return FALSE + + if(get_dist(owner, cast_on) > cast_range) + to_chat(owner, span_warning("[cast_on.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/cooldown/spell/pointed/projectile + /// What projectile we create when we shoot our spell. + var/obj/item/projectile/magic/projectile_type = /obj/item/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/cooldown/spell/pointed/projectile/New(Target) + . = ..() + if(projectile_amount > 1) + unset_after_click = FALSE + +/datum/action/cooldown/spell/pointed/projectile/is_valid_target(atom/cast_on) + return TRUE + +/datum/action/cooldown/spell/pointed/projectile/on_activation(mob/on_who) + . = ..() + if(!.) + return + + current_amount = projectile_amount + +/datum/action/cooldown/spell/pointed/projectile/on_deactivation(mob/on_who, refund_cooldown = TRUE) + . = ..() + if(projectile_amount > 1 && current_amount) + StartCooldown(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/cooldown/spell/pointed/projectile/cast(atom/cast_on) + . = ..() + 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(cast_on) + 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/cooldown/spell/pointed/projectile/after_cast(atom/cast_on) + . = ..() + if(current_amount > 0) + // We still have projectiles to cast! + // Reset our cooldown and let them fire away + reset_spell_cooldown() + +/datum/action/cooldown/spell/pointed/projectile/proc/fire_projectile(atom/target) + current_amount-- + for(var/i in 1 to projectiles_per_fire) + var/obj/item/projectile/to_fire = new projectile_type() + ready_projectile(to_fire, target, owner, i) + to_fire.fire() + return TRUE + +/datum/action/cooldown/spell/pointed/projectile/proc/ready_projectile(obj/item/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/on_cast_hit) + + if(istype(to_fire, /obj/item/projectile/magic)) + var/obj/item/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/cooldown/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) \ No newline at end of file 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 000000000000..d93100ec9b1b --- /dev/null +++ b/code/modules/spells/spell_types/pointed/abyssal_gaze.dm @@ -0,0 +1,53 @@ +/datum/action/cooldown/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" + icon_icon = 'icons/mob/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/cooldown/spell/pointed/abyssal_gaze/is_valid_target(atom/cast_on) + return iscarbon(target) + +/datum/action/cooldown/spell/pointed/abyssal_gaze/cast(mob/living/carbon/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(owner, span_warning("The spell had no effect!")) + to_chat(cast_on, span_warning("You feel a freezing darkness closing in on you, but it rapidly dissipates.")) + return FALSE + + to_chat(cast_on, span_userdanger("A freezing darkness surrounds you...")) + cast_on.playsound_local(get_turf(cast_on), 'sound/hallucinations/i_see_you1.ogg', 50, 1) + owner.playsound_local(get_turf(owner), 'sound/effects/ghost2.ogg', 50, 1) + cast_on.become_blind(ABYSSAL_GAZE_BLIND) + addtimer(CALLBACK(src, PROC_REF(cure_blindness), cast_on), blind_duration) + if(ishuman(cast_on)) + var/mob/living/carbon/human/human_cast_on = cast_on + human_cast_on.adjust_coretemperature(-amount_to_cool) + cast_on.adjust_bodytemperature(-amount_to_cool) + +/** + * cure_blidness: Cures Abyssal Gaze blindness from the target + * + * Arguments: + * * target The mob that is being cured of the blindness. + */ +/datum/action/cooldown/spell/pointed/abyssal_gaze/proc/cure_blindness(mob/living/carbon/cast_on) + if(QDELETED(cast_on) || !istype(cast_on)) + return + + cast_on.cure_blind(ABYSSAL_GAZE_BLIND) \ No newline at end of file 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 000000000000..b6fce6521555 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/barnyard.dm @@ -0,0 +1,55 @@ +/datum/action/cooldown/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/cooldown/spell/pointed/barnyardcurse/is_valid_target(atom/cast_on) + . = ..() + if(!.) + return FALSE + if(!ishuman(cast_on)) + return FALSE + + var/mob/living/carbon/human/human_target = cast_on + if(!human_target.wear_mask) + return TRUE + + return !(human_target.wear_mask.type in GLOB.cursed_animal_masks) + +/datum/action/cooldown/spell/pointed/barnyardcurse/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + cast_on.visible_message( + span_danger("[cast_on]'s face bursts into flames, which instantly burst outward, leaving [cast_on.p_them()] unharmed!"), + span_danger("Your face starts burning up, but the flames are repulsed by your anti-magic protection!"), + ) + to_chat(owner, span_warning("The spell had no effect!")) + return FALSE + + var/chosen_type = pick(GLOB.cursed_animal_masks) + var/obj/item/clothing/mask/animal/cursed_mask = new chosen_type(get_turf(target)) + + cast_on.visible_message( + span_danger("[target]'s face bursts into flames, and a barnyard animal's head takes its place!"), + span_userdanger("Your face burns up, and shortly after the fire you realise you have the face of a [cursed_mask.animal_type]!"), + ) + + // Can't drop? Nuke it + if(!cast_on.dropItemToGround(cast_on.wear_mask)) + qdel(cast_on.wear_mask) + + cast_on.equip_to_slot_if_possible(cursed_mask, ITEM_SLOT_MASK, TRUE, TRUE) + cast_on.flash_act() + return TRUE diff --git a/code/modules/spells/spell_types/pointed/blind.dm b/code/modules/spells/spell_types/pointed/blind.dm index 4968b47b3eb4..db5edf24b443 100644 --- a/code/modules/spells/spell_types/pointed/blind.dm +++ b/code/modules/spells/spell_types/pointed/blind.dm @@ -1,34 +1,43 @@ -/obj/effect/proc_holder/spell/pointed/trigger/blind +/datum/action/cooldown/spell/pointed/blind name = "Blind" desc = "This spell temporarily blinds a single target." - school = "transmutation" - charge_max = 300 - clothes_req = FALSE - invocation = "STI KALY" - invocation_type = "whisper" - message = span_notice("Your eyes cry out in pain!") - cooldown_min = 50 //12 deciseconds reduction per rank + button_icon_state = "blind" ranged_mousepointer = 'icons/effects/mouse_pointers/blind_target.dmi' - action_icon_state = "blind" - active_msg = "You prepare to blind a target..." -/obj/effect/proc_holder/spell/targeted/inflict_handler/blind - amt_eye_blind = 10 - amt_eye_blurry = 20 sound = 'sound/magic/blind.ogg' + school = SCHOOL_TRANSMUTATION + cooldown_time = 30 SECONDS + cooldown_reduction_per_rank = 6.25 SECONDS -/obj/effect/proc_holder/spell/targeted/genetic/blind - mutations = list(BLINDMUT) - duration = 300 - charge_max = 400 // needs to be higher than the duration or it'll be permanent - sound = 'sound/magic/blind.ogg' + invocation = "STI KALY" + invocation_type = INVOCATION_WHISPER + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + active_msg = "You prepare to blind a target..." -/obj/effect/proc_holder/spell/pointed/trigger/blind/can_target(atom/target, mob/user, silent) + /// The amount of blind to apply + var/eye_blind_duration = 20 SECONDS + /// The amount of blurriness to apply + var/eye_blur_duration = 40 SECONDS + +/datum/action/cooldown/spell/pointed/blind/is_valid_target(atom/cast_on) . = ..() if(!.) return FALSE - if(!isliving(target)) - if(!silent) - to_chat(user, span_warning("You can only blind living beings!")) + if(!ishuman(cast_on)) + return FALSE + + var/mob/living/carbon/human/human_target = cast_on + return !human_target.is_blind() + +/datum/action/cooldown/spell/pointed/blind/cast(mob/living/carbon/human/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_notice("Your eye itches, but it passes momentarily.")) + to_chat(owner, span_warning("The spell had no effect!")) return FALSE - return TRUE \ No newline at end of file + + to_chat(cast_on, span_warning("Your eyes cry out in pain!")) + cast_on.adjust_blindness(eye_blind_duration) + cast_on.blur_eyes(eye_blur_duration) + 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 000000000000..1bd76dc53b06 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/dominate.dm @@ -0,0 +1,49 @@ +/datum/action/cooldown/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/mob/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/cooldown/spell/pointed/dominate/is_valid_target(atom/cast_on) + if(!isanimal(cast_on)) + return FALSE + + var/mob/living/simple_animal/animal = cast_on + 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/cooldown/spell/pointed/dominate/cast(mob/living/simple_animal/cast_on) + . = ..() + if(cast_on.can_block_magic(antimagic_flags)) + to_chat(cast_on, span_warning("Your feel someone attempting to subject your mind to terrible machinations!")) + to_chat(owner, span_warning("[cast_on] resists your domination!")) + return FALSE + + var/turf/cast_turf = get_turf(cast_on) + cast_on.add_atom_colour("#990000", FIXED_COLOUR_PRIORITY) + cast_on.faction |= "cult" + playsound(cast_turf, 'sound/effects/ghost.ogg', 100, TRUE) + new /obj/effect/temp_visual/cult/sac(cast_turf) \ No newline at end of file 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 000000000000..20cf7e58e593 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/finger_guns.dm @@ -0,0 +1,48 @@ +/datum/action/cooldown/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/mob/actions/actions_mime.dmi' + button_icon_state = "finger_guns0" + panel = "Mime" + sound = null + + school = SCHOOL_MIME + cooldown_time = 30 SECONDS + + invocation = "" + invocation_type = INVOCATION_EMOTE + invocation_self_message = span_danger("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/item/projectile/bullet/mime + projectile_amount = 3 + +/datum/action/cooldown/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, span_warning("You can't properly point your fingers while incapacitated.")) + return FALSE + if(human_owner.get_active_held_item()) + if(feedback) + to_chat(owner, span_warning("You can't properly fire your finger guns with something in your hand.")) + return FALSE + + return ..() + +/datum/action/cooldown/spell/pointed/projectile/finger_guns/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] fires [cast_on.p_their()] finger gun!") \ No newline at end of file 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 000000000000..d7f06052c121 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/fireball.dm @@ -0,0 +1,23 @@ +/datum/action/cooldown/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/item/projectile/magic/fireball + +/datum/action/cooldown/spell/pointed/projectile/fireball/ready_projectile(obj/item/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + to_fire.range = (6 + 2 * spell_level) \ No newline at end of file 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 000000000000..4bb137d7cd1e --- /dev/null +++ b/code/modules/spells/spell_types/pointed/lightning_bolt.dm @@ -0,0 +1,43 @@ +/datum/action/cooldown/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/item/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/cooldown/spell/pointed/projectile/lightningbolt/Grant(mob/grant_to) + . = ..() + ADD_TRAIT(owner, TRAIT_SHOCKIMMUNE, type) //HELL YEAHHH, LIGHTNING BOLT! + +/datum/action/cooldown/spell/pointed/projectile/lightningbolt/Remove(mob/living/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_SHOCKIMMUNE, type) //FUCK + return ..() + +/datum/action/cooldown/spell/pointed/projectile/lightningbolt/ready_projectile(obj/item/projectile/to_fire, atom/target, mob/user, iteration) + . = ..() + if(!istype(to_fire, /obj/item/projectile/magic/aoe/lightning)) + return + + var/obj/item/projectile/magic/aoe/lightning/bolt = to_fire + bolt.zap_range = bolt_range + bolt.zap_power = bolt_power + bolt.zap_flags = bolt_flags \ No newline at end of file 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 000000000000..2580dc9910a8 --- /dev/null +++ b/code/modules/spells/spell_types/pointed/mind_transfer.dm @@ -0,0 +1,133 @@ +/datum/action/cooldown/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 + /// If TRUE, we cannot mindswap into people without a mind. + /// You may be wondering "What's the point of mindswap if the target has no mind"? + /// Primarily for debugging - targets hit with this set to FALSE will init a mind, then do the swap. + var/target_requires_mind = 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/slaughter, + /mob/living/simple_animal/hostile/megafauna, + )) + +/datum/action/cooldown/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, span_warning("You're killing yourself! You can't concentrate enough to do this!")) + return FALSE + return TRUE + +/datum/action/cooldown/spell/pointed/mind_transfer/is_valid_target(atom/cast_on) + . = ..() + if(!.) + return FALSE + + if(!isliving(cast_on)) + to_chat(owner, span_warning("You can only swap minds with living beings!")) + return FALSE + if(is_type_in_typecache(cast_on, blacklisted_mobs)) + to_chat(owner, span_warning("This creature is too [pick("powerful", "strange", "arcane", "obscene")] to control!")) + return FALSE + if(isguardian(cast_on)) + var/mob/living/simple_animal/hostile/guardian/stand = cast_on + if(stand.summoner && stand.summoner == owner) + to_chat(owner, span_warning("Swapping minds with your own guardian would just put you back into your own head!")) + return FALSE + + var/mob/living/living_target = cast_on + if(living_target.stat == DEAD) + to_chat(owner, span_warning("You don't particularly want to be dead!")) + return FALSE + if(!living_target.mind && target_requires_mind) + to_chat(owner, span_warning("[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, span_warning("[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/cooldown/spell/pointed/mind_transfer/cast(mob/living/cast_on) + . = ..() + swap_minds(owner, cast_on) + +/datum/action/cooldown/spell/pointed/mind_transfer/proc/swap_minds(mob/living/caster, mob/living/cast_on) + + var/mob/living/to_swap = cast_on + if(isguardian(cast_on)) + var/mob/living/simple_animal/hostile/guardian/stand = cast_on + if(stand.summoner) + to_swap = stand.summoner + + // Gives the target a mind if we don't require one and they don't have one + if(!to_swap.mind && !target_requires_mind) + to_swap.mind_initialize() + + 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, span_warning("[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/pointed.dm b/code/modules/spells/spell_types/pointed/pointed.dm deleted file mode 100644 index 3e65690541fd..000000000000 --- a/code/modules/spells/spell_types/pointed/pointed.dm +++ /dev/null @@ -1,105 +0,0 @@ -/obj/effect/proc_holder/spell/pointed - name = "pointed spell" - ranged_mousepointer = 'icons/effects/mouse_pointers/throw_target.dmi' - action_icon_state = "projectile" - /// 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..." - /// Variable dictating if the user is allowed to cast a spell on himself. - var/self_castable = FALSE - /// Variable dictating if the spell will use turf based aim assist - var/aim_assist = TRUE - -/obj/effect/proc_holder/spell/pointed/Click() - var/mob/living/user = usr - if(!istype(user)) - return - var/msg - if(!can_cast(user)) - msg = span_warning("You can no longer cast [name]!") - remove_ranged_ability(msg) - return - if(active) - msg = span_notice("[deactive_msg]") - remove_ranged_ability(msg) - else - msg = span_notice("[active_msg] Left-click to activate spell on a target!") - add_ranged_ability(user, msg, TRUE) - -/obj/effect/proc_holder/spell/pointed/on_lose(mob/living/user) - remove_ranged_ability() - -/obj/effect/proc_holder/spell/pointed/remove_ranged_ability(msg) - . = ..() - on_deactivation(ranged_ability_user) - -/obj/effect/proc_holder/spell/pointed/add_ranged_ability(mob/living/user, msg, forced) - . = ..() - on_activation(user) - -/** - * on_activation: What happens upon pointed spell activation. - * - * Arguments: - * * user The mob interacting owning the spell. - */ -/obj/effect/proc_holder/spell/pointed/proc/on_activation(mob/user) - return - -/** - * on_activation: What happens upon pointed spell deactivation. - * - * Arguments: - * * user 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 - if(active) - action.button_icon_state = "[action_icon_state]1" - else - action.button_icon_state = "[action_icon_state]" - action.UpdateButtonIcon() - -/obj/effect/proc_holder/spell/pointed/InterceptClickOn(mob/living/caller, params, atom/target) - if(..()) - return TRUE - if(aim_assist && isturf(target)) - var/list/possible_targets = list() - for(var/A in target) - if(intercept_check(caller, A, TRUE)) - possible_targets += A - if(possible_targets.len == 1) - target = possible_targets[1] - if(!intercept_check(caller, target)) - return TRUE - if(!cast_check(FALSE, caller)) - return TRUE - perform(list(target), user = caller) - remove_ranged_ability() - return TRUE // Do not do any underlying actions after the spell cast - -/** - * intercept_check: Specific spell checks for InterceptClickOn() targets. - * - * Arguments: - * * user The mob using the ranged spell via intercept. - * * target The atom that is being targeted by the spell via intercept. - * * silent If the checks should produce not any feedback messages for the user. - */ -/obj/effect/proc_holder/spell/pointed/proc/intercept_check(mob/user, atom/target, silent = FALSE) - if(!self_castable && target == user) - if(!silent) - to_chat(user, span_warning("You cannot cast the spell on yourself!")) - return FALSE - if(!(target in view_or_range(range, user, selection_type))) - if(!silent) - to_chat(user, span_warning("[target.p_theyre(TRUE)] too far away!")) - return FALSE - if(!can_target(target, user, silent)) - return FALSE - 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 000000000000..64ba78315bed --- /dev/null +++ b/code/modules/spells/spell_types/pointed/spell_cards.dm @@ -0,0 +1,82 @@ +/datum/action/cooldown/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/item/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/cooldown/spell/pointed/projectile/spell_cards/Destroy() + QDEL_NULL(lockon_component) + return ..() + +/datum/action/cooldown/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/on_lockon_component)) + +/datum/action/cooldown/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/cooldown/spell/pointed/projectile/spell_cards/on_deactivation(mob/on_who, refund_cooldown = TRUE) + . = ..() + QDEL_NULL(lockon_component) + +/datum/action/cooldown/spell/pointed/projectile/spell_cards/ready_projectile(obj/item/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) \ No newline at end of file diff --git a/code/modules/spells/spell_types/projectile.dm b/code/modules/spells/spell_types/projectile.dm deleted file mode 100644 index 3860173e4916..000000000000 --- a/code/modules/spells/spell_types/projectile.dm +++ /dev/null @@ -1,134 +0,0 @@ -/obj/item/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/item/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/item/projectile/magic/spell/Moved(atom/OldLoc, Dir) - . = ..() - if(trail) - create_trail() - -/obj/item/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.density = FALSE - trail.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - QDEL_IN(trail, trail_lifespan) - -/obj/item/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)) - var/mob/M = target - if(M.anti_magic_check(check_antimagic, check_holy)) - return FALSE - if(ignored_factions && ignored_factions.len && faction_check(M.faction,ignored_factions)) - return FALSE - -/obj/item/projectile/magic/spell/on_hit(mob/living/carbon/target) - .=..() - if(target.anti_magic_check()) - target.visible_message(span_warning("[src] vanishes on contact with [target]!")) - return BULLET_ACT_BLOCK - - -//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/item/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/item/projectile/magic/spell/projectile = new proj_type() - - 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 |= UNSTOPPABLE - 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 = 1; i < range; i++) - 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 000000000000..77a473532821 --- /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/cooldown/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/item/projectile/projectile_type = /obj/item/projectile/magic/aoe/magic_missile + +/datum/action/cooldown/spell/basic_projectile/cast(atom/cast_on) + . = ..() + var/turf/target_turf = get_turf(cast_on) + for(var/i in 1 to projectile_range - 1) + var/turf/next_turf = get_step(target_turf, cast_on.dir) + if(next_turf.density) + break + target_turf = next_turf + + fire_projectile(target_turf, cast_on) + +/datum/action/cooldown/spell/basic_projectile/proc/fire_projectile(atom/target, atom/caster) + var/obj/item/projectile/to_fire = new projectile_type() + to_fire.preparePixelProjectile(target, caster) + to_fire.fire() \ No newline at end of file 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 000000000000..e66ac12d9308 --- /dev/null +++ b/code/modules/spells/spell_types/projectile/juggernaut.dm @@ -0,0 +1,12 @@ +/datum/action/cooldown/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/mob/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/item/projectile/magic/aoe/juggernaut \ No newline at end of file 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 9829deee73cc..000000000000 --- a/code/modules/spells/spell_types/rod_form.dm +++ /dev/null @@ -1,52 +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 = 300 - cooldown_min = 100 - range = -1 - include_user = TRUE - invocation = "CLANG!" - invocation_type = "shout" - action_icon_state = "immrod" - -/obj/effect/proc_holder/spell/targeted/rod_form/cast(list/targets,mob/user = usr) - 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/Move() - if(get_dist(start_turf, get_turf(src)) >= max_distance) - 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(span_danger("[src] hits [L], but it bounces back, then vanishes!") , span_userdanger("[src] hits you... but it bounces back, then vanishes!") , "You hear a weak, sad, CLANG.") - qdel(src) - return - L.visible_message(span_danger("[L] is penetrated by an immovable rod!") , span_userdanger("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 e807f863ba92..000000000000 --- 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 = "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 000000000000..acef65e1638b --- /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/cooldown/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/cooldown/spell/basic_heal/cast(mob/living/cast_on) + . = ..() + cast_on.visible_message( + span_warning("A wreath of gentle light passes over [cast_on]!"), + span_notice("You wreath yourself in healing light!"), + ) + cast_on.adjustBruteLoss(-brute_to_heal, FALSE) + cast_on.adjustFireLoss(-burn_to_heal) \ No newline at end of file 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 000000000000..46e9c3768cc3 --- /dev/null +++ b/code/modules/spells/spell_types/self/charge.dm @@ -0,0 +1,58 @@ +/datum/action/cooldown/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/cooldown/spell/charge/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/charge/cast(mob/living/cast_on) + . = ..() + + // Charge people we're pulling first and foremost + if(isliving(cast_on.pulling)) + var/mob/living/pulled_living = cast_on.pulling + var/pulled_has_spells = FALSE + + for(var/datum/action/cooldown/spell/spell in pulled_living.actions) + spell.reset_spell_cooldown() + pulled_has_spells = TRUE + + if(pulled_has_spells) + to_chat(pulled_living, span_notice("You feel raw magic flowing through you. It feels good!")) + to_chat(cast_on, span_notice("[pulled_living] suddenly feels very warm!")) + return + + to_chat(pulled_living, span_notice("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 = cast_on.get_active_held_item() || cast_on.get_inactive_held_item() + if(!to_charge) + to_chat(cast_on, span_notice("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, cast_on) + + if(QDELETED(to_charge)) + to_chat(cast_on, span_warning("[src] seems to react adversely with [to_charge]!")) + return + + if(charge_return & COMPONENT_ITEM_BURNT_OUT) + to_chat(cast_on, span_warning("[to_charge] seems to react negatively to [src], becoming uncomfortably warm!")) + + else if(charge_return & COMPONENT_ITEM_CHARGED) + to_chat(cast_on, span_notice("[to_charge] suddenly feels very warm!")) + + else + to_chat(cast_on, span_notice("[to_charge] doesn't seem to be react to [src].")) \ No newline at end of file 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 000000000000..558e4e6943bf --- /dev/null +++ b/code/modules/spells/spell_types/self/disable_tech.dm @@ -0,0 +1,30 @@ +/datum/action/cooldown/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/cooldown/spell/emp/cast(atom/cast_on) + . = ..() + empulse(get_turf(cast_on), emp_heavy, emp_light) + +/datum/action/cooldown/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 \ No newline at end of file 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 000000000000..825a79298f7b --- /dev/null +++ b/code/modules/spells/spell_types/self/forcewall.dm @@ -0,0 +1,66 @@ +/datum/action/cooldown/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/cooldown/spell/forcewall/cast(atom/cast_on) + . = ..() + 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/cooldown/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/mob/actions/actions_cult.dmi' + button_icon_state = "cultforcewall" + + cooldown_time = 40 SECONDS + invocation_type = INVOCATION_NONE + + wall_type = /obj/effect/forcefield/cult + +/datum/action/cooldown/spell/forcewall/mime + name = "Invisible Blockade" + desc = "Form an invisible three tile wide blockade." + background_icon_state = "bg_mime" + icon_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "invisible_blockade" + panel = "Mime" + 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 = span_notice("You form a blockade in front of yourself.") + spell_max_level = 1 + + wall_type = /obj/effect/forcefield/mime/advanced + +/datum/action/cooldown/spell/forcewall/mime/before_cast(atom/cast_on) + . = ..() + invocation = span_notice("[cast_on] looks as if a blockade is in front of [cast_on.p_them()].") \ No newline at end of file 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 000000000000..d9d664ddbd24 --- /dev/null +++ b/code/modules/spells/spell_types/self/lichdom.dm @@ -0,0 +1,83 @@ +/datum/action/cooldown/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/mob/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_STATION|SPELL_REQUIRES_MIND + spell_max_level = 1 + +/datum/action/cooldown/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_target(owner)) + if(feedback) + to_chat(owner, span_warning("You don't have a soul to bind!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/lichdom/is_valid_target(atom/cast_on) + return isliving(cast_on) && !HAS_TRAIT(owner, TRAIT_NO_SOUL) + +/datum/action/cooldown/spell/lichdom/cast(mob/living/cast_on) + var/obj/item/marked_item = cast_on.get_active_held_item() + if(!marked_item || marked_item.item_flags & ABSTRACT) + return + if(HAS_TRAIT(marked_item, TRAIT_NODROP)) + to_chat(cast_on, span_warning("[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, cast_on) & COMPONENT_BLOCK_IMBUE) + to_chat(cast_on, span_warning("[marked_item] is not suitable for emplacement of your fragile soul.")) + return + + . = ..() + playsound(cast_on, 'sound/effects/pope_entry.ogg', 100) + + to_chat(cast_on, span_green("You begin to focus your very being into [marked_item]...")) + if(!do_after(cast_on, 5 SECONDS, target = marked_item, needhand = FALSE)) + to_chat(cast_on, span_warning("Your soul snaps back to your body as you stop ensouling [marked_item]!")) + return + + marked_item.AddComponent(/datum/component/phylactery, cast_on.mind) + + cast_on.set_species(/datum/species/skeleton) + to_chat(cast_on, span_userdanger("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(cast_on)) + var/mob/living/carbon/carbon_cast_on = cast_on + var/obj/item/organ/internal/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(cast_on)) + var/mob/living/carbon/human/human_cast_on = cast_on + 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(cast_on, TRAIT_NO_SOUL, LICH_TRAIT) + // You only get one phylactery. + qdel(src) \ No newline at end of file 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 000000000000..af2974fbb541 --- /dev/null +++ b/code/modules/spells/spell_types/self/lightning.dm @@ -0,0 +1,128 @@ +/datum/action/cooldown/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/cooldown/spell/tesla/Remove(mob/living/remove_from) + reset_tesla(remove_from) + return ..() + +/datum/action/cooldown/spell/tesla/set_statpanel_format() + . = ..() + if(!islist(.)) + return + + if(currently_channeling) + .[PANEL_DISPLAY_STATUS] = "CHANNELING" + +/datum/action/cooldown/spell/tesla/can_cast_spell(feedback = TRUE) + . = ..() + if(!.) + return FALSE + if(currently_channeling) + if(feedback) + to_chat(owner, span_warning("You're already channeling [src]!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/tesla/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + to_chat(cast_on, span_notice("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) + cast_on.add_overlay(halo) + playsound(get_turf(cast_on), charge_sound, 50, FALSE) + + currently_channeling = TRUE + if(!do_after(cast_on, channel_time, stayStill = FALSE, needhand = FALSE)) + reset_tesla(cast_on) + return . | SPELL_CANCEL_CAST + + return TRUE + +/datum/action/cooldown/spell/tesla/reset_spell_cooldown() + reset_tesla(owner) + return ..() + +/// Resets the tesla effect. +/datum/action/cooldown/spell/tesla/proc/reset_tesla(atom/to_reset) + to_reset.cut_overlay(halo) + currently_channeling = FALSE + +/datum/action/cooldown/spell/tesla/cast(atom/cast_on) + . = ..() + + // 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(cast_on), charge_sound, 50, FALSE) + + var/mob/living/carbon/to_zap_first = get_target(cast_on) + if(QDELETED(to_zap_first)) + cast_on.balloon_alert(cast_on, "no targets nearby!") + reset_spell_cooldown() + return FALSE + + playsound(get_turf(cast_on), 'sound/magic/lightningbolt.ogg', 50, TRUE) + zap_target(cast_on, to_zap_first) + reset_tesla(cast_on) + return TRUE + +/// Zaps a target, the bolt originating from origin. +/datum/action/cooldown/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( + span_warning("[to_zap] absorbs the spell, remaining unharmed!"), + span_userdanger("You absorb the spell, remaining unharmed!"), + ) + + else + to_zap.electrocute_act(bolt_energy, "Lightning Bolt") + + 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/cooldown/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) \ No newline at end of file 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 000000000000..92962f130b59 --- /dev/null +++ b/code/modules/spells/spell_types/self/mime_vow.dm @@ -0,0 +1,24 @@ +/datum/action/cooldown/spell/vow_of_silence + name = "Speech" + desc = "Make (or break) a vow of silence." + background_icon_state = "bg_mime" + icon_icon = 'icons/mob/actions/actions_mime.dmi' + button_icon_state = "mime_speech" + panel = "Mime" + + school = SCHOOL_MIME + cooldown_time = 5 MINUTES + + spell_requirements = SPELL_REQUIRES_HUMAN|SPELL_REQUIRES_MIND + spell_max_level = 1 + +/datum/action/cooldown/spell/vow_of_silence/cast(mob/living/carbon/human/cast_on) + . = ..() + cast_on.mind.miming = !cast_on.mind.miming + if(cast_on.mind.miming) + to_chat(cast_on, span_notice("You make a vow of silence.")) + SEND_SIGNAL(cast_on, COMSIG_CLEAR_MOOD_EVENT, "vow") + else + to_chat(cast_on, span_notice("You break your vow of silence.")) + SEND_SIGNAL(cast_on, COMSIG_ADD_MOOD_EVENT, "vow", /datum/mood_event/broken_vow) + cast_on.update_action_buttons_icon() \ No newline at end of file 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 000000000000..d74582c09f01 --- /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/cooldown/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/cooldown/spell/apply_mutations/New(Target) + . = ..() + spell_requirements |= SPELL_REQUIRES_HUMAN // The spell involves mutations, so it always require human / dna + +/datum/action/cooldown/spell/apply_mutations/Remove(mob/living/remove_from) + remove_mutations(remove_from) + return ..() + +/datum/action/cooldown/spell/apply_mutations/is_valid_target(atom/cast_on) + var/mob/living/carbon/human/human_caster = cast_on // Requires human anyways + return !!human_caster.dna + +/datum/action/cooldown/spell/apply_mutations/cast(mob/living/carbon/human/cast_on) + . = ..() + for(var/mutation in mutations_to_add) + cast_on.dna.add_mutation(mutation) + addtimer(CALLBACK(src, .proc/remove_mutations, cast_on), mutation_duration, TIMER_DELETE_ME) + +/// Removes the mutations we added from casting our spell +/datum/action/cooldown/spell/apply_mutations/proc/remove_mutations(mob/living/carbon/human/cast_on) + if(QDELETED(cast_on) || !is_valid_target(cast_on)) + return + + for(var/mutation in mutations_to_add) + cast_on.dna.remove_mutation(mutation) + +/datum/action/cooldown/spell/apply_mutations/mutate + name = "Mutate" + desc = "This spell causes you to turn into a hulk and gain laser vision 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/human/laser_eyes, /datum/mutation/human/hulk) + mutation_duration = 30 SECONDS \ No newline at end of file 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 000000000000..c3b3fc9977fa --- /dev/null +++ b/code/modules/spells/spell_types/self/night_vision.dm @@ -0,0 +1,39 @@ +//Toggle Night Vision +/datum/action/cooldown/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/cooldown/spell/night_vision/New(Target) + . = ..() + name = "[name] \[ON\]" + +/datum/action/cooldown/spell/night_vision/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/night_vision/cast(mob/living/cast_on) + . = ..() + to_chat(cast_on, "You toggle your night vision.") + + var/next_mode_text = "" + switch(cast_on.lighting_alpha) + if (LIGHTING_PLANE_ALPHA_VISIBLE) + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + next_mode_text = "More" + if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + next_mode_text = "Full" + if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + next_mode_text = "OFF" + else + cast_on.lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE + next_mode_text = "ON" + + cast_on.update_sight() + name = "[initial(name)] \[[next_mode_text]\]" \ No newline at end of file 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 000000000000..5c57dbc8a996 --- /dev/null +++ b/code/modules/spells/spell_types/self/personality_commune.dm @@ -0,0 +1,54 @@ +// This can probably be changed to use mind linker at some point +/datum/action/cooldown/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 = span_boldnotice("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/cooldown/spell/personality_commune/New(Target) + . = ..() + if(!istype(target, /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/cooldown/spell/personality_commune/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/personality_commune/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/datum/brain_trauma/severe/split_personality/trauma = target + if(!istype(trauma)) // hypothetically impossible but you never know + return . | SPELL_CANCEL_CAST + + to_send = tgui_input_text(cast_on, "What would you like to tell your other self?", "Commune") + if(QDELETED(src) || QDELETED(trauma)|| QDELETED(cast_on) || QDELETED(trauma.owner) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!to_send) + reset_cooldown() + return . | SPELL_CANCEL_CAST + + return TRUE + +// Pillaged and adapted from telepathy code +/datum/action/cooldown/spell/personality_commune/cast(mob/living/cast_on) + . = ..() + var/datum/brain_trauma/severe/split_personality/trauma = target + + var/user_message = span_boldnotice("You concentrate and send thoughts to your other self:") + var/user_message_body = span_notice("[to_send]") + to_chat(cast_on, "[user_message] [user_message_body]") + to_chat(trauma.owner, "[fluff_text] [user_message_body]") + log_directed_talk(cast_on, 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, cast_on)] [span_boldnotice("[cast_on] [name]:")] [span_notice("\"[to_send]\" to")] [span_name("[trauma]")]") \ No newline at end of file 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 000000000000..fd9a52be412f --- /dev/null +++ b/code/modules/spells/spell_types/self/rod_form.dm @@ -0,0 +1,160 @@ +/// The base distance a wizard rod will go without upgrades. +#define BASE_WIZ_ROD_RANGE 13 + +/datum/action/cooldown/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_STATION + + /// 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/cooldown/spell/rod_form/cast(atom/cast_on) + . = ..() + // 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(cast_on), cast_on.dir, (rod_max_distance + 2)) + + new /obj/effect/immovablerod/wizard( + get_turf(cast_on), + distant_turf, + null, + FALSE, + cast_on, + rod_max_distance, + rod_damage_bonus, + ) + +/datum/action/cooldown/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/cooldown/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 + loopy_rod = TRUE + dnd_style_level_up = 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( + span_danger("[src] hits [penetrated], but it bounces back, then vanishes!"), + span_userdanger("[src] hits you... but it bounces back, then vanishes!"), + span_danger("You hear a weak, sad, CLANG.") + ) + stop_travel() + return + + penetrated.visible_message( + span_danger("[penetrated] is penetrated by an immovable rod!"), + span_userdanger("The [src] penetrates you!"), + span_danger("You hear a CLANG!"), + ) + penetrated.adjustBruteLoss(70 + damage_bonus) + +/obj/effect/immovablerod/wizard/suplex_rod(mob/living/strongman) + var/mob/living/wizard = our_wizard?.resolve() + if(QDELETED(wizard)) + return ..() // There's no wizard in this rod? It's pretty much a normal rod at this point + + strongman.visible_message( + span_boldwarning("[src] transforms into [wizard] as [strongman] suplexes them!"), + span_warning("As you grab [src], it suddenly turns into [wizard] as you suplex them!") + ) + to_chat(wizard, span_boldwarning("You're suddenly jolted out of rod-form as [strongman] somehow manages to grab you, slamming you into the ground!")) + stop_travel() + wizard.Stun(6 SECONDS) + wizard.apply_damage(25, BRUTE) + return TRUE + +/** + * 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 000000000000..f5a6e4c43ed5 --- /dev/null +++ b/code/modules/spells/spell_types/self/smoke.dm @@ -0,0 +1,37 @@ +/// Basic smoke spell. +/datum/action/cooldown/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 = /datum/effect_system/fluid_spread/smoke/bad + smoke_amt = 4 + +/// Chaplain smoke. +/datum/action/cooldown/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 = /datum/effect_system/fluid_spread/smoke + smoke_amt = 2 + +/// Unused smoke that makes people sleep. Used to be for cult? +/datum/action/cooldown/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 = /datum/effect_system/fluid_spread/smoke/sleeping \ No newline at end of file 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 000000000000..db1be25ef57e --- /dev/null +++ b/code/modules/spells/spell_types/self/soultap.dm @@ -0,0 +1,62 @@ +/** + * 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/cooldown/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/cooldown/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_target(owner)) + if(feedback) + to_chat(owner, span_warning("You have no soul to tap into!")) + return FALSE + + return TRUE + +/datum/action/cooldown/spell/tap/is_valid_target(atom/cast_on) + return isliving(cast_on) && !HAS_TRAIT(owner, TRAIT_NO_SOUL) + +/datum/action/cooldown/spell/tap/cast(mob/living/cast_on) + . = ..() + cast_on.maxHealth -= tap_health_taken + cast_on.health = min(cast_on.health, cast_on.maxHealth) + + for(var/datum/action/cooldown/spell/spell in cast_on.actions) + spell.reset_spell_cooldown() + + // If the tap took all of our life, we die and lose our soul! + if(cast_on.maxHealth <= 0) + to_chat(cast_on, span_userdanger("Your weakened soul is completely consumed by the tap!")) + ADD_TRAIT(cast_on, TRAIT_NO_SOUL, MAGIC_TRAIT) + + cast_on.visible_message(span_danger("[cast_on] suddenly dies!"), ignored_mobs = cast_on) + cast_on.death() + + // If the next tap will kill us, give us a heads-up + else if(cast_on.maxHealth - tap_health_taken <= 0) + to_chat(cast_on, span_bolddanger("Your body feels incredibly drained, and the burning is hard to ignore!")) + + // Otherwise just give them some feedback + else + to_chat(cast_on, span_danger("Your body feels drained and there is a burning pain in your chest.")) \ No newline at end of file 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 000000000000..ecf4f3f443d4 --- /dev/null +++ b/code/modules/spells/spell_types/self/spacetime_distortion.dm @@ -0,0 +1,168 @@ +// This could probably be an aoe spell but it's a little cursed, so I'm not touching it +/datum/action/cooldown/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_STATION + 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/cooldown/spell/spacetime_dist/Destroy() + QDEL_LAZYLIST(effects) + return ..() + +/datum/action/cooldown/spell/spacetime_dist/can_cast_spell(feedback = TRUE) + return ..() && ready + +/datum/action/cooldown/spell/spacetime_dist/set_statpanel_format() + . = ..() + if(!islist(.)) + return + + if(!ready) + .[PANEL_DISPLAY_STATUS] = "NOT READY" + +/datum/action/cooldown/spell/spacetime_dist/cast(atom/cast_on) + . = ..() + var/list/turf/to_switcharoo = get_targets_to_scramble(cast_on) + if(!length(to_switcharoo)) + to_chat(cast_on, span_warning("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/cooldown/spell/spacetime_dist/after_cast() + . = ..() + addtimer(CALLBACK(src, PROC_REF(clean_turfs)), duration) + +/// Callback which cleans up our effects list after the duration expires. +/datum/action/cooldown/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/cooldown/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, charge_cost = 0)) + 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 ..() \ No newline at end of file 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 000000000000..70105aada39c --- /dev/null +++ b/code/modules/spells/spell_types/self/stop_time.dm @@ -0,0 +1,30 @@ +/datum/action/cooldown/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/cooldown/spell/timestop/Grant(mob/grant_to) + . = ..() + if(owner) + ADD_TRAIT(owner, TRAIT_TIME_STOP_IMMUNE, REF(src)) + +/datum/action/cooldown/spell/timestop/Remove(mob/remove_from) + REMOVE_TRAIT(remove_from, TRAIT_TIME_STOP_IMMUNE, REF(src)) + return ..() + +/datum/action/cooldown/spell/timestop/cast(atom/cast_on) + . = ..() + new /obj/effect/timestop/magic(get_turf(cast_on), timestop_range, timestop_duration, list(cast_on)) \ No newline at end of file diff --git a/code/modules/spells/spell_types/self/summonitem.dm b/code/modules/spells/spell_types/self/summonitem.dm new file mode 100644 index 000000000000..a055763e9520 --- /dev/null +++ b/code/modules/spells/spell_types/self/summonitem.dm @@ -0,0 +1,154 @@ +/datum/action/cooldown/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/cooldown/spell/summonitem/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/// Set the passed object as our marked item +/datum/action/cooldown/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/cooldown/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/cooldown/spell/summonitem/proc/on_marked_item_deleted(datum/source) + SIGNAL_HANDLER + + if(owner) + to_chat(owner, span_boldwarning("You sense your marked item has been destroyed!")) + unmark_item() + +/datum/action/cooldown/spell/summonitem/cast(mob/living/cast_on) + . = ..() + if(QDELETED(marked_item)) + try_link_item(cast_on) + return + + if(marked_item == cast_on.get_active_held_item()) + try_unlink_item(cast_on) + return + + try_recall_item(cast_on) + +/// If we don't have a marked item, attempts to mark the caster's held item. +/datum/action/cooldown/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, span_warning("You must hold the desired item in your hands to mark it for recall!")) + else + to_chat(caster, span_warning("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, span_notice(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/cooldown/spell/summonitem/proc/try_unlink_item(mob/living/caster) + to_chat(caster, span_notice("You begin removing the mark on [marked_item]...")) + if(!do_after(caster, 5 SECONDS, marked_item)) + to_chat(caster, span_notice("You decide to keep [marked_item] marked.")) + return FALSE + + to_chat(caster, span_notice("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/cooldown/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(span_warning("[holding_mark] suddenly disappears!")) + holding_mark.forceMove(caster.loc) + holding_mark.loc.visible_message(span_warning("[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", addition = "COMBAT MODE: [uppertext(caster.combat_mode)]") + organ.Remove(organ.owner) + + if(!item_to_retrieve) + return + + item_to_retrieve.loc?.visible_message(span_warning("[item_to_retrieve] suddenly disappears!")) + + if(isitem(item_to_retrieve) && caster.put_in_hands(item_to_retrieve)) + item_to_retrieve.loc.visible_message(span_warning("[item_to_retrieve] suddenly appears in [caster]'s hand!")) + else + item_to_retrieve.forceMove(caster.drop_location()) + item_to_retrieve.loc.visible_message(span_warning("[item_to_retrieve] suddenly appears!")) + playsound(get_turf(item_to_retrieve), 'sound/magic/summonitems_generic.ogg', 50, TRUE) \ No newline at end of file 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 000000000000..3b617792bddf --- /dev/null +++ b/code/modules/spells/spell_types/self/voice_of_god.dm @@ -0,0 +1,50 @@ +/datum/action/cooldown/spell/voice_of_god + name = "Voice of God" + desc = "Speak with an incredibly compelling voice, forcing listeners to obey your commands." + icon_icon = 'icons/mob/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/cooldown/spell/voice_of_god/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + command = tgui_input_text(cast_on, "Speak with the Voice of God", "Command") + if(QDELETED(src) || QDELETED(cast_on) || !can_cast_spell()) + return . | SPELL_CANCEL_CAST + if(!command) + reset_spell_cooldown() + return . | SPELL_CANCEL_CAST + +/datum/action/cooldown/spell/voice_of_god/cast(atom/cast_on) + . = ..() + var/command_cooldown = voice_of_god(uppertext(command), cast_on, spans, base_multiplier = power_mod) + cooldown_time = (command_cooldown * cooldown_mod) + +// "Invocation" is done by the actual voice of god proc +/datum/action/cooldown/spell/voice_of_god/invocation() + return + +/datum/action/cooldown/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/spookoween/scary_horn.ogg' + cooldown_mod = 0.5 + power_mod = 0.1 + spans = list("clown") \ No newline at end of file diff --git a/code/modules/spells/spell_types/shadow_walk.dm b/code/modules/spells/spell_types/shadow_walk.dm index 49b9b2151c88..25584bc5d960 100644 --- a/code/modules/spells/spell_types/shadow_walk.dm +++ b/code/modules/spells/spell_types/shadow_walk.dm @@ -56,7 +56,7 @@ var/light_amount = T.get_lumcount() if(!jaunter || jaunter.loc != src) qdel(src) - if (light_amount < 0.2 && (!QDELETED(jaunter))) //heal in the dark + if (light_amount < LIGHTING_TILE_IS_DARK && (!QDELETED(jaunter))) //heal in the dark jaunter.heal_overall_damage((SHADOW_REGEN_RATE * delta_time), (SHADOW_REGEN_RATE * delta_time), 0, BODYPART_ORGANIC) check_light_level() @@ -75,7 +75,7 @@ /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 + if(light_amount > LIGHTING_TILE_IS_DARK) // jaunt ends end_jaunt(TRUE) /obj/effect/dummy/phased_mob/shadow/proc/end_jaunt(forced = FALSE) 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 000000000000..500aa10dea1a --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/_shapeshift.dm @@ -0,0 +1,244 @@ +/datum/action/cooldown/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/cooldown/spell/shapeshift/is_valid_target(atom/cast_on) + return isliving(cast_on) + +/datum/action/cooldown/spell/shapeshift/proc/is_shifted(mob/living/cast_on) + return locate(/obj/shapeshift_holder) in cast_on + +/datum/action/cooldown/spell/shapeshift/before_cast(atom/cast_on) + . = ..() + 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( + cast_on, + cast_on, + shape_names_to_image, + custom_check = CALLBACK(src, .proc/check_menu, cast_on), + 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/cooldown/spell/shapeshift/cast(mob/living/cast_on) + . = ..() + cast_on.buckled?.unbuckle_mob(cast_on, force = TRUE) + + var/currently_ventcrawling = (cast_on.movement_type & VENTCRAWLING) + + // Do the shift back or forth + if(is_shifted(cast_on)) + restore_form(cast_on) + else + do_shapeshift(cast_on) + + // 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(cast_on.ventcrawler = VENTCRAWLER_ALWAYS || cast_on.ventcrawler = VENTCRAWLER_NUDE) + return + + // Uh oh. You've shapeshifted into something that can't fit into a vent, while ventcrawling. + eject_from_vents(cast_on) + +/// 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/cooldown/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.return_pipenets() + if(islist(pipenets)) + our_pipeline = pipenets[1] + else + our_pipeline = pipenets + + to_chat(cast_on, span_userdanger("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/cooldown/spell/shapeshift/proc/check_menu(mob/living/caster) + if(QDELETED(src)) + return FALSE + if(QDELETED(caster)) + return FALSE + + return !caster.incapacitated() + +/datum/action/cooldown/spell/shapeshift/proc/do_shapeshift(mob/living/caster) + if(is_shifted(caster)) + to_chat(caster, span_warning("You're already shapeshifted!")) + CRASH("[type] called do_shapeshift while shapeshifted.") + + var/mob/living/new_shape = new shapeshift_type(caster.loc) + 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/cooldown/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() + 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/cooldown/spell/shapeshift/source + +/obj/shapeshift_holder/Initialize(mapload, datum/action/cooldown/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, wound_bonus = CANT_WOUND); + shape.blood_volume = stored.blood_volume; + + RegisterSignal(shape, list(COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH), PROC_REF(shape_death)) + RegisterSignal(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, wound_bonus=CANT_WOUND) + 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 \ No newline at end of file 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 000000000000..9cb1f50fb282 --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/dragon.dm @@ -0,0 +1,7 @@ +/datum/action/cooldown/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) \ No newline at end of file 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 000000000000..5fe06dc009fb --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/polar_bear.dm @@ -0,0 +1,7 @@ +/datum/action/cooldown/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/asteroid/polarbear/lesser) \ No newline at end of file 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 000000000000..0e7357c180fd --- /dev/null +++ b/code/modules/spells/spell_types/shapeshift/shapechange.dm @@ -0,0 +1,22 @@ +/datum/action/cooldown/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/giant_spider/poison/viper/wizard, + /mob/living/simple_animal/hostile/construct/armored, + ) \ No newline at end of file diff --git a/code/modules/spells/spell_types/soultap.dm b/code/modules/spells/spell_types/soultap.dm deleted file mode 100644 index be22aac90fb9..000000000000 --- a/code/modules/spells/spell_types/soultap.dm +++ /dev/null @@ -1,33 +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 = "shout" - level_max = 0 - cooldown_min = 10 - - action_icon = 'icons/mob/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, span_warning("You do not possess a soul to tap into!")) - return - to_chat(user, span_danger("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, span_userdanger("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() \ No newline at end of file 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 568849d3b9aa..000000000000 --- a/code/modules/spells/spell_types/spacetime_distortion.dm +++ /dev/null @@ -1,124 +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 = 300 - var/duration = 150 - range = 7 - var/list/effects - var/ready = TRUE - centcom_cancast = FALSE - sound = 'sound/effects/magic.ogg' - cooldown_min = 300 - level_max = 0 - -/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/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)) - -/obj/effect/cross_action/spacetime_dist/proc/walk_link(atom/movable/AM) - if(ismob(AM)) - var/mob/M = AM - if(M.anti_magic_check(chargecost = 0)) - 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/Crossed(atom/movable/AM) - . = ..() - 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 0382ccc81738..000000000000 --- a/code/modules/spells/spell_types/summonitem.dm +++ /dev/null @@ -1,118 +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 = "whisper" - range = -1 - level_max = 0 //cannot be improved - cooldown_min = 100 - include_user = TRUE - - var/obj/marked_item - - action_icon = 'icons/mob/actions/humble/actions_humble.dmi' - 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) //linking item to the spell - message = "" - for(var/obj/item/item in hand_items) - if(item.item_flags & ABSTRACT) - 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 = span_caution("You aren't holding anything that can be marked for recall.") - else - message = span_notice("You must hold the desired item in your hands to mark it for recall.") - - else if(marked_item && (marked_item in hand_items)) //unlinking item to the spell - message = span_notice("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 = span_warning("You sense your marked item has been destroyed!") - name = "Instant Summons" - marked_item = null - - 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(span_warning("[M] suddenly disappears!")) - M.forceMove(L.loc) - M.loc.visible_message(span_caution("[M] suddenly appears!")) - item_to_retrieve = null - break - M.dropItemToGround(item_to_retrieve) - - if(iscarbon(M)) //Edge case housekeeping - var/mob/living/carbon/C = M - //yogs start -- Yogs Vorecode - if(C.stomach_contents && (item_to_retrieve in C.stomach_contents)) - C.stomach_contents -= item_to_retrieve - //Yogs end - for(var/X in C.bodyparts) - var/obj/item/bodypart/part = X - if(item_to_retrieve in part.embedded_objects) - C.remove_embedded_object(item_to_retrieve, silent = TRUE, forced = TRUE) - to_chat(C, span_warning("The [item_to_retrieve] that was embedded in your [L] has mysteriously vanished. How fortunate!")) - break - - 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(span_warning("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(span_caution("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(span_caution("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/telepathy.dm b/code/modules/spells/spell_types/telepathy.dm deleted file mode 100644 index 51c613aa9855..000000000000 --- a/code/modules/spells/spell_types/telepathy.dm +++ /dev/null @@ -1,32 +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/mob/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 - var/tinfoil_check = TRUE - -/obj/effect/proc_holder/spell/targeted/telepathy/cast(list/targets, mob/living/simple_animal/revenant/user = usr) - for(var/mob/living/M in targets) - var/msg = stripped_input(usr, "What do you wish to tell [M]?", null, "") - if(!msg) - charge_counter = charge_max - return - 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, tinfoil_check, 0)) //hear no evil - to_chat(M, "You hear something behind you talking... [msg]") - 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] [span_name("[M]")]") \ No newline at end of file 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 000000000000..5d3ff695ae30 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/_teleport.dm @@ -0,0 +1,145 @@ + +/** + * ## Teleport Spell + * + * Teleports the caster to a turf selected by get_destinations(). + */ +/datum/action/cooldown/spell/teleport + sound = 'sound/weapons/zapbang.ogg' + + school = SCHOOL_TRANSLOCATION + + /// What channel the teleport is done under. + var/teleport_channel = TELEPORT_CHANNEL_MAGIC + /// Whether we force the teleport to happen (ie, it cannot be blocked by noteleport 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' + +/datum/action/cooldown/spell/teleport/cast(atom/cast_on) + . = ..() + var/list/turf/destinations = get_destinations(cast_on) + if(!length(destinations)) + CRASH("[type] failed to find a teleport destination.") + + do_teleport(cast_on, pick(destinations), asoundout = post_teleport_sound, channel = teleport_channel, forced = force_teleport) + +/// Gets a list of destinations that are valid +/datum/action/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/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/cooldown/spell/teleport/area_teleport + force_teleport = TRUE // Forced, as the Wizard Den is noteleport 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/cooldown/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/cooldown/spell/teleport/area_teleport/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/area/target_area + if(randomise_selection) + target_area = pick(GLOB.teleportlocs) + else + target_area = tgui_input_list(cast_on, "Chose an area to teleport to.", "Teleport", GLOB.teleportlocs) + + if(QDELETED(src) || QDELETED(cast_on) || !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/cooldown/spell/teleport/area_teleport/cast(atom/cast_on) + if(isliving(cast_on)) + var/mob/living/living_cast_on = cast_on + living_cast_on.buckled?.unbuckle_mob(cast_on, force = TRUE) + return ..() + +/datum/action/cooldown/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])") \ No newline at end of file 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 000000000000..90c6d5b43903 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/blink.dm @@ -0,0 +1,19 @@ +/datum/action/cooldown/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_TRANSLOCATION + cooldown_time = 2 SECONDS + cooldown_reduction_per_rank = 0.4 SECONDS + + invocation_type = INVOCATION_NONE + + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 0 + + inner_tele_radius = 0 + outer_tele_radius = 6 + + post_teleport_sound = 'sound/magic/blink.ogg' \ No newline at end of file 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 000000000000..e9125b534349 --- /dev/null +++ b/code/modules/spells/spell_types/teleport/teleport.dm @@ -0,0 +1,51 @@ +/// The wizard's teleport SPELL +/datum/action/cooldown/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 + + invocation = "SCYAR NILA" + invocation_type = INVOCATION_SHOUT + + smoke_type = /datum/effect_system/fluid_spread/smoke + smoke_amt = 2 + + post_teleport_sound = 'sound/magic/teleport_app.ogg' + +// Santa's teleport, themed as such +/datum/action/cooldown/spell/teleport/area_teleport/wizard/santa + name = "Santa Teleport" + + invocation = "HO HO HO!" + spell_requirements = NONE + antimagic_flags = NONE + + invocation_says_area = FALSE // Santa moves in mysterious ways + +/// Used by the wizard's teleport scroll +/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll + name = "Teleport (scroll)" + cooldown_time = 0 SECONDS + + invocation = null + invocation_type = INVOCATION_NONE + spell_requirements = NONE + + invocation_says_area = FALSE + +/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/IsAvailable() + return ..() && owner.is_holding(target) + +/datum/action/cooldown/spell/teleport/area_teleport/wizard/scroll/before_cast(atom/cast_on) + . = ..() + if(. & SPELL_CANCEL_CAST) + return + + var/mob/living/carbon/caster = cast_on + if(caster.incapacitated() || !caster.is_holding(target)) + return . | SPELL_CANCEL_CAST \ No newline at end of file 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 cc270764fa5d..000000000000 --- 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 = 50 - - clothes_req = TRUE - invocation = "CAVERE INSIDIAS" - invocation_type = "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 000000000000..86330741b84a --- /dev/null +++ b/code/modules/spells/spell_types/touch/_touch.dm @@ -0,0 +1,271 @@ +/datum/action/cooldown/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 = span_notice("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 = span_notice("You draw the power out of your hand.") + +/datum/action/cooldown/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/cooldown/spell/touch/Remove(mob/living/remove_from) + remove_hand(remove_from) + return ..() + +/datum/action/cooldown/spell/touch/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE) + . = ..() + if(!button) + return + if(attached_hand) + button.color = COLOR_GREEN + +/datum/action/cooldown/spell/touch/set_statpanel_format() + . = ..() + if(!islist(.)) + return + + if(attached_hand) + .[PANEL_DISPLAY_STATUS] = "ACTIVE" + +/datum/action/cooldown/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/cooldown/spell/touch/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/** + * 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/cooldown/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, span_warning("You dont have any usable hands!")) + else + to_chat(cast_on, span_warning("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_ITEM_AFTERATTACK_SECONDARY, PROC_REF(on_secondary_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/cooldown/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_ITEM_AFTERATTACK_SECONDARY, 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 + StartCooldown() + +// Touch spells don't go on cooldown OR give off an invocation until the hand is used itself. +/datum/action/cooldown/spell/touch/before_cast(atom/cast_on) + return ..() | SPELL_NO_FEEDBACK | SPELL_NO_IMMEDIATE_COOLDOWN + +/datum/action/cooldown/spell/touch/cast(mob/living/carbon/cast_on) + if(!QDELETED(attached_hand) && (attached_hand in cast_on.held_items)) + remove_hand(cast_on, reset_cooldown_after = TRUE) + return + + create_hand(cast_on) + 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/cooldown/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) + +/** + * Signal proc for [COMSIG_ITEM_AFTERATTACK_SECONDARY] from our attached hand. + * + * Same as on_hand_hit, but for if right-click was used on hit. + */ +/*/datum/action/cooldown/spell/touch/proc/on_secondary_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_secondary_hand_hit), source, victim, caster)*/ + + //FUCK COMBAT MODE!!! + +/** + * Calls cast_on_hand_hit() from the caster onto the victim. + */ +/datum/action/cooldown/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) + +/** + * Calls do_secondary_hand_hit() from the caster onto the victim. + */ +/*/datum/action/cooldown/spell/touch/proc/do_secondary_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + var/secondary_result = cast_on_secondary_hand_hit(hand, victim, caster) + switch(secondary_result) + // Continue will remove the hand here and stop + if(SECONDARY_ATTACK_CONTINUE_CHAIN) + log_combat(caster, victim, "cast the touch spell [name] on", hand, "(secondary / alt cast)") + spell_feedback() + remove_hand(caster) + + // Call normal will call the normal cast proc + if(SECONDARY_ATTACK_CALL_NORMAL) + do_hand_hit(hand, victim, caster) + + // Cancel chain will do nothing, + if(SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) + return */ + + //FUCK COMBAT MODE!!! + +/** + * 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/cooldown/spell/touch/proc/cast_on_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return FALSE + +/** + * For any special casting effects done if the user right-clicks + * on touch spell instead of left-clicking + * + * Return SECONDARY_ATTACK_CALL_NORMAL to call the normal cast_on_hand_hit + * Return SECONDARY_ATTACK_CONTINUE_CHAIN to prevent the normal cast_on_hand_hit from calling, but still use up the hand + * Return SECONDARY_ATTACK_CANCEL_CHAIN to prevent the spell from being used + */ +/*/datum/action/cooldown/spell/touch/proc/cast_on_secondary_hand_hit(obj/item/melee/touch_attack/hand, atom/victim, mob/living/carbon/caster) + return SECONDARY_ATTACK_CALL_NORMAL*/ + + //FUCK COMBAT MODE!!! + +/** + * 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/cooldown/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/cooldown/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" + inhand_icon_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/cooldown/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, span_warning("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/mansus_fist/proc/remove_hand_with_no_refund(mob/holder) + var/datum/action/cooldown/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) \ No newline at end of file diff --git a/code/modules/spells/spell_types/touch/duffelbag_curse.dm b/code/modules/spells/spell_types/touch/duffelbag_curse.dm new file mode 100644 index 000000000000..c2ffbacc4d89 --- /dev/null +++ b/code/modules/spells/spell_types/touch/duffelbag_curse.dm @@ -0,0 +1,85 @@ +/datum/action/cooldown/spell/touch/duffelbag + name = "Bestow Cursed Duffel Bag" + desc = "A spell that summons a duffel bag demon on the target, slowing them down and slowly eating them." + button_icon_state = "duffelbag_curse" + sound = 'sound/magic/mm_hit.ogg' + + school = SCHOOL_CONJURATION + cooldown_time = 6 SECONDS + cooldown_reduction_per_rank = 1 SECONDS + + invocation = "HU'SWCH H'ANS!!" + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + + hand_path = /obj/item/melee/touch_attack/duffelbag + + /// Some meme "elaborate backstories" to use. + var/static/list/elaborate_backstory = list( + "spacewar origin story", + "military background", + "corporate connections", + "life in the colonies", + "anti-government activities", + "upbringing on the space farm", + "fond memories with your buddy Keith", + ) + +/datum/action/cooldown/spell/touch/duffelbag/is_valid_target(atom/cast_on) + return iscarbon(cast_on) + +/datum/action/cooldown/spell/touch/duffelbag/on_antimagic_triggered(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster) + to_chat(caster, span_warning("The spell can't seem to affect [victim]!")) + to_chat(victim, span_warning("You really don't feel like talking about your [pick(elaborate_backstory)] with complete strangers today.")) + +/datum/action/cooldown/spell/touch/duffelbag/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/carbon/victim, mob/living/carbon/caster) + + // To get it started, stun and knockdown the person being hit + victim.flash_act() + victim.Immobilize(5 SECONDS) + victim.apply_damage(80, STAMINA) + victim.Knockdown(5 SECONDS) + + // If someone's already cursed, don't try to give them another + if(istype(victim.back, /obj/item/storage/backpack/duffelbag/cursed)) + to_chat(caster, span_warning("The burden of [victim]'s duffel bag becomes too much, shoving them to the floor!")) + to_chat(victim, span_warning("The weight of this bag becomes overburdening!")) + return TRUE + + // However if they're uncursed, they're fresh for getting a cursed bag + var/obj/item/storage/backpack/duffelbag/cursed/conjured_duffel = new get_turf(victim) + victim.visible_message( + span_danger("A growling duffel bag appears on [victim]!"), + span_danger("You feel something attaching itself to you, and a strong desire to discuss your [pick(elaborate_backstory)] at length!"), + ) + + conjured_duffel.pickup(victim) + conjured_duffel.forceMove(victim) + + // Put it on their back first + if(victim.dropItemToGround(victim.back)) + victim.equip_to_slot_if_possible(conjured_duffel, ITEM_SLOT_BACK, TRUE, TRUE) + return TRUE + + // If the back equip failed, put it in their hands first + if(victim.put_in_hands(conjured_duffel)) + return TRUE + + // If they had no empty hands, try to put it in their inactive hand first + victim.dropItemToGround(victim.get_inactive_held_item()) + if(victim.put_in_hands(conjured_duffel)) + return TRUE + + // If their inactive hand couldn't be emptied or found, put it in their active hand + victim.dropItemToGround(victim.get_active_held_item()) + if(victim.put_in_hands(conjured_duffel)) + return TRUE + + // Well, we failed to give them the duffel bag, + // but technically we still stunned them so that's something + return TRUE + +/obj/item/melee/touch_attack/duffelbag + name = "\improper burdening touch" + desc = "Where is the bar from here?" + icon_state = "duffelcurse" + inhand_icon_state = "duffelcurse" \ No newline at end of file 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 000000000000..81af743a685e --- /dev/null +++ b/code/modules/spells/spell_types/touch/flesh_to_stone.dm @@ -0,0 +1,33 @@ +/datum/action/cooldown/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/cooldown/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, span_warning("The spell can't seem to affect [victim]!")) + to_chat(victim, span_warning("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" + inhand_icon_state = "fleshtostone" \ No newline at end of file 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 000000000000..3013a38c8a6a --- /dev/null +++ b/code/modules/spells/spell_types/touch/smite.dm @@ -0,0 +1,55 @@ +/datum/action/cooldown/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/cooldown/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( + span_warning("The feedback blows [caster]'s arm off!"), + span_userdanger("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(span_danger("[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" + inhand_icon_state = "disintegrate" \ No newline at end of file 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 e0b84061920d..000000000000 --- a/code/modules/spells/spell_types/touch_attacks.dm +++ /dev/null @@ -1,116 +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 = "none" //you scream on connecting, not summoning - include_user = TRUE - range = -1 - -/obj/effect/proc_holder/spell/targeted/touch/proc/remove_hand(recharge = FALSE) - QDEL_NULL(attached_hand) - if(recharge) - 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(TRUE) - to_chat(user, span_notice("[dropmessage]")) - return - - for(var/mob/living/carbon/C in targets) - if(!attached_hand) - if(ChargeHand(C)) - 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 - else - return ..() - -/obj/effect/proc_holder/spell/targeted/touch/proc/ChargeHand(mob/living/carbon/user) - attached_hand = new hand_path(src) - attached_hand.attached_spell = src - if(!user.put_in_hands(attached_hand)) - remove_hand(TRUE) - if (user.get_num_arms() <= 0) - to_chat(user, span_warning("You dont have any usable hands!")) - else - to_chat(user, span_warning("Your hands are full!")) - return FALSE - to_chat(user, span_notice("[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/flagellate //doesn't do much, mostly cosmetic punishment tool for chaplains - name = "Flagellate" - desc = "This spell charges your hand with the power of the old gods, allowing you to flagellate heathens." - hand_path = /obj/item/melee/touch_attack/flagellate - clothes_req = FALSE - - charge_max = 300 //its very weak so it doesn't need a super long cooldown. 30 second cooldown - - action_icon = 'icons/mob/actions/actions_cult.dmi' - action_icon_state = "horror" - action_background_icon_state = "bg_ecult" - sound = 'sound/magic/fleshtostone.ogg' - -/obj/effect/proc_holder/spell/targeted/touch/pacify - name = "Pacify" - desc = "This spell charges your hand with pure pacifism, allowing you to temporarily mute and pacify your targets, as well as turn them into gondolas." - hand_path = /obj/item/melee/touch_attack/pacifism - - school = "evocation" - charge_max = 1 MINUTES - clothes_req = FALSE - cooldown_min = 20 SECONDS - action_icon ='icons/mob/gondolas.dmi' - - action_icon_state = "gondola" - -/obj/effect/proc_holder/spell/targeted/touch/touch_of_death //yogs start - name = "Touch of Death" - desc = "This spell charges your hand with necrotic energy that can kill both organic and inorganic beings instantly." - hand_path = /obj/item/melee/touch_attack/touchofdeath - - school = "evocation" - charge_max = 400 - clothes_req = TRUE - cooldown_min = 200 //50 deciseconds reduction per rank - - action_icon_state = "touchofdeath" //yogs end diff --git a/code/modules/spells/spell_types/trigger.dm b/code/modules/spells/spell_types/trigger.dm deleted file mode 100644 index 39cff63d98f6..000000000000 --- a/code/modules/spells/spell_types/trigger.dm +++ /dev/null @@ -1,30 +0,0 @@ -/obj/effect/proc_holder/spell/targeted/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/targeted/trigger/Initialize() - . = ..() - - 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/targeted/trigger/Destroy() - for(var/spell in contents) - qdel(spell) - linked_spells = null - starting_spells = null - return ..() - -/obj/effect/proc_holder/spell/targeted/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) - - return \ No newline at end of file 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 14240de49aa6..000000000000 --- a/code/modules/spells/spell_types/turf_teleport.dm +++ /dev/null @@ -1,44 +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 in range(target,outer_tele_radius)) - if(T in range(target,inner_tele_radius)) - continue - 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.yYour robotic ears are ringing, uselessly.") if(2) - owner.Jitter(15) - owner.Dizzy(15) + owner.adjust_jitter(15 SECONDS) + owner.adjust_dizzy(15) owner.Knockdown(10 SECONDS) to_chat(owner, "Your robotic ears buzz.") diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 699f3b64635c..a5d6cee0357c 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -88,7 +88,7 @@ H.stop_sound_channel(CHANNEL_HEARTBEAT) beat = BEAT_NONE - if(H.jitteriness) + if(owner.has_status_effect(/datum/status_effect/jitter)) if(H.health > HEALTH_THRESHOLD_FULLCRIT && (!beat || beat == BEAT_SLOW)) H.playsound_local(get_turf(H),fastbeat,40,0, channel = CHANNEL_HEARTBEAT) beat = BEAT_FAST @@ -204,7 +204,7 @@ fakingit = FALSE return ..() -/obj/item/organ/heart/vampheart/proc/FakeStart() +/obj/item/organ/heart/vampheart/proc/fake_start_heart() fakingit = TRUE // We're pretending to beat, to fool people. /// Bloodsuckers don't have a heartbeat at all when stopped (default is "an unstable") diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 19ca3f2e10be..5ca69e13e192 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -255,13 +255,13 @@ var/bz_pp = breath.get_breath_partial_pressure(breath.get_moles(/datum/gas/bz)) if(bz_pp > BZ_trip_balls_min) - H.hallucination += 10 + H.adjust_hallucinations(10 SECONDS) H.reagents.add_reagent(/datum/reagent/bz_metabolites,5) if(prob(33)) H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) else if(bz_pp > 0.01) - H.hallucination += 5 + H.adjust_hallucinations(5 SECONDS) H.reagents.add_reagent(/datum/reagent/bz_metabolites,1) @@ -347,7 +347,7 @@ // Hexane gas_breathed = breath.get_moles(/datum/gas/hexane) if(gas_breathed > gas_stimulation_min) - H.hallucination += 50 + H.adjust_hallucinations(50 SECONDS) H.reagents.add_reagent(/datum/reagent/hexane,5) if(prob(33)) H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm index d77be9139526..0c5b03e466f6 100644 --- a/code/modules/surgery/organs/stomach.dm +++ b/code/modules/surgery/organs/stomach.dm @@ -50,17 +50,17 @@ var/pukeprob = 5 + 0.05 * H.disgust if(H.disgust >= DISGUST_LEVEL_GROSS) if(prob(10)) - H.stuttering += 1 - H.confused += 2 + H.adjust_stutter(1 SECONDS) + H.adjust_confusion(2 SECONDS) if(prob(10) && !H.stat) to_chat(H, span_warning("You feel kind of iffy...")) - H.jitteriness = max(H.jitteriness - 3, 0) + H.adjust_jitter(-6 SECONDS) if(H.disgust >= DISGUST_LEVEL_VERYGROSS) if(prob(pukeprob)) //iT hAndLeS mOrE ThaN PukInG - H.confused += 2.5 - H.stuttering += 1 + H.adjust_confusion(2.5 SECONDS) + H.adjust_stutter(1 SECONDS) H.vomit(10, 0, 1, 0, 1, 0) - H.Dizzy(5) + H.adjust_dizzy(5 SECONDS) if(H.disgust >= DISGUST_LEVEL_DISGUSTED) if(prob(25)) H.blur_eyes(3) //We need to add more shit down here diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm index f9746d572723..c8046f5bc97f 100644 --- a/code/modules/surgery/organs/vocal_cords.dm +++ b/code/modules/surgery/organs/vocal_cords.dm @@ -334,7 +334,7 @@ for(var/V in listeners) var/mob/living/L = V L.adjust_fire_stacks(1 * power_multiplier) - L.IgniteMob() + L.ignite_mob() //HOT else if((findtext(message, hot_words))) diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm index 30e8d06942a9..853a814c217a 100644 --- a/code/modules/surgery/surgery_step.dm +++ b/code/modules/surgery/surgery_step.dm @@ -269,7 +269,7 @@ ouchie_mod *= ouchie_modifying_chems[R] if(target.stat == UNCONSCIOUS) ouchie_mod *= 0.8 - ouchie_mod *= clamp(1 - target.drunkenness / 100, 0, 1) + ouchie_mod *= clamp(1 - target.get_drunk_amount() / 100, 0, 1) if(!success) ouchie_mod *= 2 var/final_ouchie_chance = SURGERY_FUCKUP_CHANCE * ouchie_mod diff --git a/code/modules/swarmers/swarmer.dm b/code/modules/swarmers/swarmer.dm index 62f9160effb1..176a9dda918e 100644 --- a/code/modules/swarmers/swarmer.dm +++ b/code/modules/swarmers/swarmer.dm @@ -78,7 +78,7 @@ . = ..() remove_verb(src, /mob/living/verb/pulled) for(var/datum/atom_hud/data/diagnostic/diag_hud in GLOB.huds) - diag_hud.add_to_hud(src) + diag_hud.add_atom_to_hud(src) /mob/living/simple_animal/hostile/swarmer/med_hud_set_health() var/image/holder = hud_list[DIAG_HUD] diff --git a/code/modules/tgui/tgui_alert.dm b/code/modules/tgui_input/alert.dm similarity index 100% rename from code/modules/tgui/tgui_alert.dm rename to code/modules/tgui_input/alert.dm diff --git a/code/modules/tgui/tgui_input_list.dm b/code/modules/tgui_input/list.dm similarity index 100% rename from code/modules/tgui/tgui_input_list.dm rename to code/modules/tgui_input/list.dm diff --git a/code/modules/tgui_input/number.dm b/code/modules/tgui_input/number.dm new file mode 100644 index 000000000000..5b654ddaf3f4 --- /dev/null +++ b/code/modules/tgui_input/number.dm @@ -0,0 +1,149 @@ +/** + * Creates a TGUI window with a number input. Returns the user's response as num | null. + * + * This proc should be used to create windows for number entry that the caller will wait for a response from. + * If tgui fancy chat is turned off: Will return a normal input. If a max or min value is specified, will + * validate the input inside the UI and ui_act. + * + * Arguments: + * * user - The user to show the number input to. + * * message - The content of the number input, shown in the body of the TGUI window. + * * title - The title of the number input modal, shown on the top of the TGUI window. + * * default - The default (or current) value, shown as a placeholder. Users can press refresh with this. + * * max_value - Specifies a maximum value. If none is set, any number can be entered. Pressing "max" defaults to 1000. + * * min_value - Specifies a minimum value. Often 0. + * * timeout - The timeout of the number input, after which the modal will close and qdel itself. Set to zero for no timeout. + * * round_value - whether the inputted number is rounded down into an integer. + */ +/proc/tgui_input_number(mob/user, message, title = "Number Input", default = 0, max_value = 10000, min_value = 0, timeout = 0, round_value = TRUE) + if (!user) + user = usr + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + // Client does NOT have tgui_input on: Returns regular input + var/datum/tgui_input_number/number_input = new(user, message, title, default, max_value, min_value, timeout, round_value) + number_input.ui_interact(user) + number_input.wait() + if (number_input) + . = number_input.entry + qdel(number_input) + +/** + * # tgui_input_number + * + * Datum used for instantiating and using a TGUI-controlled number input that prompts the user with + * a message and has an input for number entry. + */ +/datum/tgui_input_number + /// Boolean field describing if the tgui_input_number was closed by the user. + var/closed + /// The default (or current) value, shown as a default. Users can press reset with this. + var/default + /// The entry that the user has return_typed in. + var/entry + /// The maximum value that can be entered. + var/max_value + /// The prompt's body, if any, of the TGUI window. + var/message + /// The minimum value that can be entered. + var/min_value + /// Whether the submitted number is rounded down into an integer. + var/round_value + /// The time at which the number input was created, for displaying timeout progress. + var/start_time + /// The lifespan of the number input, after which the window will close and delete itself. + var/timeout + /// The title of the TGUI window + var/title + +/datum/tgui_input_number/New(mob/user, message, title, default, max_value, min_value, timeout, round_value) + src.default = default + src.max_value = max_value + src.message = message + src.min_value = min_value + src.title = title + src.round_value = round_value + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + /// Checks for empty numbers - bank accounts, etc. + if(max_value == 0) + src.min_value = 0 + if(default) + src.default = 0 + /// Sanity check + if(default < min_value) + src.default = min_value + if(default > max_value) + CRASH("Default value is greater than max value.") + +/datum/tgui_input_number/Destroy(force, ...) + SStgui.close_uis(src) + return ..() + +/** + * Waits for a user's response to the tgui_input_number's prompt before returning. Returns early if + * the window was closed by the user. + */ +/datum/tgui_input_number/proc/wait() + while (!entry && !closed && !QDELETED(src)) + stoplag(1) + +/datum/tgui_input_number/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "NumberInputModal") + ui.open() + +/datum/tgui_input_number/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_input_number/ui_state(mob/user) + return GLOB.always_state + +/datum/tgui_input_number/ui_static_data(mob/user) + var/list/data = list() + data["init_value"] = default // Default is a reserved keyword + data["max_value"] = max_value + data["message"] = message + data["min_value"] = min_value + data["title"] = title + data["round_value"] = round_value + return data + +/datum/tgui_input_number/ui_data(mob/user) + var/list/data = list() + if(timeout) + data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS)) + return data + +/datum/tgui_input_number/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("submit") + if(!isnum(params["entry"])) + CRASH("A non number was input into tgui input number by [usr]") + var/choice = round_value ? round(params["entry"]) : params["entry"] + if(choice > max_value) + CRASH("A number greater than the max value was input into tgui input number by [usr]") + if(choice < min_value) + CRASH("A number less than the min value was input into tgui input number by [usr]") + set_entry(choice) + closed = TRUE + SStgui.close_uis(src) + return TRUE + if("cancel") + closed = TRUE + SStgui.close_uis(src) + return TRUE + +/datum/tgui_input_number/proc/set_entry(entry) + src.entry = entry diff --git a/code/modules/tgui_input/text.dm b/code/modules/tgui_input/text.dm new file mode 100644 index 000000000000..55d50a015b8c --- /dev/null +++ b/code/modules/tgui_input/text.dm @@ -0,0 +1,144 @@ +/** + * Creates a TGUI window with a text input. Returns the user's response. + * + * This proc should be used to create windows for text entry that the caller will wait for a response from. + * If tgui fancy chat is turned off: Will return a normal input. If max_length is specified, will return + * stripped_multiline_input. + * + * Arguments: + * * user - The user to show the text input to. + * * message - The content of the text input, shown in the body of the TGUI window. + * * title - The title of the text input modal, shown on the top of the TGUI window. + * * default - The default (or current) value, shown as a placeholder. + * * max_length - Specifies a max length for input. MAX_MESSAGE_LEN is default (1024) + * * multiline - Bool that determines if the input box is much larger. Good for large messages, laws, etc. + * * encode - Toggling this determines if input is filtered via html_encode. Setting this to FALSE gives raw input. + * * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout. + */ +/proc/tgui_input_text(mob/user, message = "", title = "Text Input", default, max_length = MAX_MESSAGE_LEN, multiline = FALSE, encode = TRUE, timeout = 0) + if (!user) + user = usr + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return + + var/datum/tgui_input_text/text_input = new(user, message, title, default, max_length, multiline, encode, timeout) + text_input.ui_interact(user) + text_input.wait() + if (text_input) + . = text_input.entry + qdel(text_input) + +/** + * tgui_input_text + * + * Datum used for instantiating and using a TGUI-controlled text input that prompts the user with + * a message and has an input for text entry. + */ +/datum/tgui_input_text + /// Boolean field describing if the tgui_input_text was closed by the user. + var/closed + /// The default (or current) value, shown as a default. + var/default + /// Whether the input should be stripped using html_encode + var/encode + /// The entry that the user has return_typed in. + var/entry + /// The maximum length for text entry + var/max_length + /// The prompt's body, if any, of the TGUI window. + var/message + /// Multiline input for larger input boxes. + var/multiline + /// The time at which the text input was created, for displaying timeout progress. + var/start_time + /// The lifespan of the text input, after which the window will close and delete itself. + var/timeout + /// The title of the TGUI window + var/title + +/datum/tgui_input_text/New(mob/user, message, title, default, max_length, multiline, encode, timeout) + src.default = default + src.encode = encode + src.max_length = max_length + src.message = message + src.multiline = multiline + src.title = title + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + +/datum/tgui_input_text/Destroy(force, ...) + SStgui.close_uis(src) + return ..() + +/** + * Waits for a user's response to the tgui_input_text's prompt before returning. Returns early if + * the window was closed by the user. + */ +/datum/tgui_input_text/proc/wait() + while (!entry && !closed && !QDELETED(src)) + stoplag(1) + +/datum/tgui_input_text/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TextInputModal") + ui.open() + +/datum/tgui_input_text/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_input_text/ui_state(mob/user) + return GLOB.always_state + +/datum/tgui_input_text/ui_static_data(mob/user) + var/list/data = list() + data["max_length"] = max_length + data["message"] = message + data["multiline"] = multiline + data["placeholder"] = default // Default is a reserved keyword + data["title"] = title + return data + +/datum/tgui_input_text/ui_data(mob/user) + var/list/data = list() + if(timeout) + data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS)) + return data + +/datum/tgui_input_text/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("submit") + if(max_length) + if(length(params["entry"]) > max_length) + CRASH("[usr] typed a text string longer than the max length") + if(encode && (length(html_encode(params["entry"])) > max_length)) + to_chat(usr, span_notice("Your message was clipped due to special character usage.")) + set_entry(params["entry"]) + closed = TRUE + SStgui.close_uis(src) + return TRUE + if("cancel") + closed = TRUE + SStgui.close_uis(src) + return TRUE + +/** + * Sets the return value for the tgui text proc. + * If html encoding is enabled, the text will be encoded. + * This can sometimes result in a string that is longer than the max length. + * If the string is longer than the max length, it will be clipped. + */ +/datum/tgui_input_text/proc/set_entry(entry) + if(!isnull(entry)) + var/converted_entry = encode ? html_encode(entry) : entry + src.entry = trim(converted_entry, max_length) diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm index 33470352402f..a333bc17d4bd 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -92,7 +92,7 @@ //ACTION DATUMS /datum/action/vehicle - check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUN | AB_CHECK_CONSCIOUS + check_flags = AB_CHECK_HANDS_BLOCKED | AB_CHECK_IMMOBILE | AB_CHECK_CONSCIOUS icon_icon = 'icons/mob/actions/actions_vehicle.dmi' button_icon_state = "vehicle_eject" var/obj/vehicle/vehicle_target diff --git a/icons/effects/mouse_pointers/barn_target.dmi b/icons/effects/mouse_pointers/barn_target.dmi new file mode 100644 index 0000000000000000000000000000000000000000..b5ac050955bc83fe964a45593c0aaa00bc756878 GIT binary patch literal 575 zcmV-F0>J%=P)4Z*5%%ejWev3J+Wq11Q)(Ziznii*m)hS}HG@_!iD%Bey?K-|;7yM#g7%%;`E zp4P>n*2SCF#i2YrJgmC9;q~=q006+iz{!GQ)&Kwi0d!JMQvg8b*k%9#0DF2=Sad{X zb7OL8aCB*JZU6vyoKseCa&`CgQ*iP1RZ&@20eMs#8q?M(faN&8^=!4*>D>(b7QT=zQ&EoOdKp% zjhSKFx%qtI9HyCRvU0pZyY$dXuhU}Nhd1On)H>gJ-}k-mI`7&krnw56M3Mo5!)1AL0LfuU3gNnD=P+0cuz~@@vjaH(j)20y2oAvQZl9JC9FAaOk_lxv6c0yS z@fD_d`2ZEiaV*K_lf^GV)oX2TZl|(;`Iy&Qra>{X9l0GT`%>RHb zE4X1t;DvDcrIXKgUz|uRdnhEdT|~28L#k9dok=aPSwDd;ET&O5y=6_nz`)y({b~RJ z00DGTPE!Ct=GbNc004V>R9JLGWpiV4X>fFDZ*Bkpc$`yKaB_9`^iy#0_2eo`Eh^5; z&r`5fFwryM;w;ZhDainGjE%TBGg33tGfE(w;*!LYR3K9+F(*ffi!&v&s2IpIEWsQDQm~it-C^DNYDx5&+VWK+DHx2pIqX0ZvIoK~z|U z?U(CLgfJ9@D`$&aP}i=j?RsH-|ECmCszvZL%&FNZ^N)NJra&?olv;Hzx+q%Z<3&Lm z(lqNqd3mWfTTP~_XwrdlRccdfLlqe^R~fjvzR{*V0ByO7h>knv?>FNHaeCLNg+q7bZU zsP5j`ZLGR^g~SQtCy#uzz0UZyZg`l*p>=}&mRCTaS74HrVAm(^0?nYXg0IDl)j|xH zCODivp3Wf&a-FA(V~9uR+o^_phYWaJ>jU@&R>~-_$TPIcDEJ1aO)Go$Z+XJ8i5qM; zEZFmw_ha}ncAmNpbE}vQJHOw2l6UarqYn?w#G4PNx{DpRJpY9E-L;TOm#v+;bj9{2 zcV2gyzNpM+O7Q9>Wos<&u5{hH_|h~v_h9Q+Vz=93-_GbOvsqaFVC&&0%?BSaZ)BGd z-&bv}@MXbn{jUq8CsbX~^{{>5Vf%t@iCn=E6jZ{$M6RS>p!$HM?T`9M56C74hRQml( zaKXo~Pqe&swa%S6AG{&d;G*$^M>^+yG*2=V_4MwrFb*=lZ0w~l=h37ilR^}NH4W9> zJG+flH?NR5Vf^HgkG9tt-_{KevpBR)u;20u2=od}vJ&k2#9g2n6jt!Hn6X-j;nD<$ z)5p^}B!SMT^mK6y@#uUz^&oG90*`CA@B-nGu!7c2jBoaL`Pz62-;((H<^Ee)I~AD< z7X}}XXwB+A>!r>o3zxe_O_lks&fNOg&s#K8rrdVn_urkx`{NoP`(=h&yklVaV4BB& z=D_QN9l!VPHz=q|V2k5_!|TWIc1LaoV->@{=PVnebn5M|_(=gB!QkoY=d#Wzp$P!S CW`>ji literal 0 HcmV?d00001 diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi index df688bd2a4005d466f621b5e946e209caada41dd..3418b3009d34ad762c0b63a8ccccece81ffe48e4 100644 GIT binary patch literal 11426 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 diff --git a/icons/mob/actions/actions_bloodsucker.dmi b/icons/mob/actions/actions_bloodsucker.dmi index 265548073f2733a616e258d41a3dc47b55ed9c5a..e8dcf8a42f7ab6f79e58c358e8626a9cfab13dcc 100644 GIT binary patch literal 22094 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�nSxFF*iED9XG9e6Z?oNmq;q-y=s0G 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 YGi)3JW&^~zM6-bT*-NISXHYT!0q?)4IRF3v diff --git a/icons/mob/actions/actions_gangrel_bloodsucker.dmi b/icons/mob/actions/actions_gangrel_bloodsucker.dmi index 30b9532502d87966466b05d281fcc309a689d76c..58b75beafd147ed12cbb03617278a2f9bc9547e0 100644 GIT binary patch literal 13202 zcmbWecT`is_b+-v3rH8m&_RTVNE7KL2uc?e6{HtY=^d#dp$I5dP(eUK2bC%yD82XI zK{^N_K)t=^z3;vC);h_Y%sDf&XV0F!KYQ=VyGJ@|w3k>f0RTX&p?+Tv z03hHkFh@xNey`uFd<0hT_&$B^asQ?J3pZOA4_jv^0Ps%v6xZ-RSd>Aj@1J1k!7V-0 zmxl{4OKx*cn#Vqdv$8(a_{nyFKa73*{;3sJ2-!^amyRV$%u3P8rIGAch^(kb;vZw- zaAui-DMn52uIG2-V5&Hg)(pOz^Ys!s!(}$~x7XfEK2q+yUg&xqW%fZwgY!N#1^V#k zZ$Y&`j|t1+GO)RiOCm=J-p^+;kPS6yO?LFZY;JG7{PY*5(~yyR<6M~vufdoLUIDuA z%Uj<0h+fWJ$%zRQkZZxZ6+9ruPa4X@@3M`o{79L2(_3|tT2+Hz{z!*<6%*aL;duXr z;0gDXfWTu!F+oN(?;*q5jX$D%wv;M0ux8=@Lp$4vCjB3J^Nw2HA-m;4YkH-KY##%L zP~WHG0Kg4s+`sqKJ7wc5(*MeXR3akYdPnq&1~(7A%4HHN?n@!2E#is$SKdh6BC&t^ zcdC8I_+{oMr*MVh27S!gZ`_BfmVGao-*OXqE7W4SB-b-mud^=rPBfE`@$qNO3-s|& z(P;2%-|+==HyTiYqtaF;t6L5 z#KC(iS!Dy)dPQG+n5ct6;b*f2`}<_!A8-0N^b zUKq(bxww$w&K_=uUuWrJRJ;88z0Q5x%6uB6-xYS5H>dagoDpt!YU>!`bq;6&KJwDt zmC7ucJ9kM=61gU`3}twFW-*397t0atexa0(j}88t3^*@qhlW`31Z0X5I#o;xq{R%(H@1jf zOaH>++uZhm>L6;IV|C4yHd)p*ro{ENA`R7J(Po@QI4K21+rW%?U?kqT`FAhfb0OyY zY}!GUp&juFUp_^pE3yF=HjVpBOriNNL{;TtScYh%$0i^^aTMjQgk@>t$GD*Ge22aD zSvuY2nCQ{+!&EL$838wS4C9odSi`dSEd)Ak4O(>$3E4Xy`*TK`+E+Jv@)(#?dRo=9 zsl8-^ze!QB2rG+T1ZiFBd9N~)FCDW0FmPRa`-SBRJhZvW#WhkF55A_lPB*@;^YHSh zB*-~Jv&d{yNKYponnIi9RSc~ORuE0v^Ok*)8*x6wB4>FQd+f3hkFYGgzTI^Q00R{9 zX^*{c5yO%4aL+%S8ac2Bm|?7Mlrz@4ENu7zGtV}W_? z^sRyW$m%c6`xKk390nQ$mOeYGJm8Jiw!?`oG<}^!4YpQqx^I! zCvddEgM)eO7wv2d3)QN`w*GsF^UTW66?bZgf5}IXfOR}^#`$a53ltoilcX!h$ z;3ZzYS{)b>Z-r~$;VFMu_@LcoNjsTCTzt>}Z?&N-tW0OWcyqkOl84_xUwb?syi1=O zFSU<^{fwqk>?;)OjK;r;DePWO+5MZ7|nO7=&yi~@EsjoqYxiSk~e@xcv#wz z&Akh%RkQ}4OUl^Y%!x+kd8H1M57!#sSKUuH!~diQ;@Ii-fV^EdjeDIa6juDG=(|v5 z-5$$dlr`6k-=ZyHH*|J>X%p45cm!1+A-T|`^iVU8fR~R4y zIiSDhH%4^lHc0FH=pCBU{eIOS>^N^p2k?DEopsd(@(9zl_@y>X{lF-Rq2=I1mCRd4 z0fVV*(K@ETzB&hYy=f@_5^G0vTkr|WN-#za9@7F3=PGD(QyjPN58T!``@0q|+T6l3 z=5(!DCEoqY(aP0ble<39SnK!q4EhrDjdl2lM}8cxBsUgkA~DUUhf#;_;HEar1SVau z8&TO(*Z^eOXJd)u)3K2^4K?Oo+e`golA+^yH=lpJAJ)yIt$Rk8MWMw64 zBT9LI62EhuB{d?(i|SAUE!ceOD7hEoSLCi2093x*C?cD(b#;X$CMv0oND?OZuB@IQ z%*ts?9zQOg+7RBh%FIB&qk6CmMVlkGZs&aIc`_yb3ZR2VLe1)n9>!4aMM2G!k|${H zKS(!Y?{iRPn=2t?oe}rU=4|j zwQ+%msVZ=59!Ckd9IR0T_g#A-SCE5FY+sY4f8spCM{)Cs)uTCTB${e~_~JtU%ZlCo zdS~5DXQU6?404VNP{MUhxjZoW`}BD|pIO3mVI{$n4*L1QgL#!2PRS~c*74`HgtM#0 zTbukVH#5_$sP6z*upUQ`irV^DZ_>EBR=#wI)XnS-+o{Uza=Ny60FW(vT~iy`MaW1D zBYe)WJ6c~+Udx9QkPs3(Rz;p+RbbOMAjs5;OeyU@G-VN(PlwmVY7|7p0;4&MAppo~ z_2~C*Y@=2crzYns3LjCEsT}C0K*>@TDhg6Xw8t@h!{JWV3kx^J5N_c*E$^sa1pn9f zxjUqrU0kDdO4Q1>WOLtE;8R}O2S64U$-qYKmV`~MPhdo=@%woKKiAgWZr5E_E+=$D z!YQ*E(FS<>xwtS!ATT1vpaz62K)o=hiK9RBI2E7f1q3AFQnNmM@WC}Ky&_U6i$HQ z0MLU+>|L~9SnEJ84ohJn+Lq&_B>4oR1dI&8I6q0+{za%Z#ZO5;V7BvOXqR<}4W9nK z8GU;v-tYJeDFdUELcRIYtGPQH5X0k_jjF&7oUMZh#vhT8cJV?QUE=70H?`5eEv#fCs)$)YdWR-H}?TD zb3c+DcKNjP(k)HPW%`~SaVYookG=rOG!yWoCJ?r;4;eco3wc4WZm~R8_@9}zaH6>@ z;Asj7E~!XAB@)59lmF@rl!Ki&+<&CfKKL>*@PLy7A2CV%7e_ZC)jS6$cN0|TS84SF z4!t=bt1m?9sKVx)u3rAFUSriA%kcKz4;S;iRgw2GZLqd&h5DAe%kZFACrd%hrodkO zm`&gknPQhA@{+jV;=zs6(bKR3ci6WHn4b^6?M=>bZsV+g=#a-NogS{bzX zCMML{ygHHjhK@raGl*<3?!{dA;P#J*_6J;HEN0LH?UG$aL zmhBy6PTyW|CVprk&276(I^EHm7&TH!07p|Ag|cXNX$PcpMPgBvI< zm(EsV8!Svdi$d!y?u&LqL}83H?f{J{Sra=D%-Hb1RcDSvmI%|b=NcJ5KeYs$J&9?c zG+6z`MI-k}%e;it28B$BW)?Cw>=sk2uQb^n{#8?sEZ9({o!Z4-PIGuEmcYms6*+Fx zEzxFGDDydr0@zFV%ef`HD)Rd)FGk6p>C9r4;yskBvM<{)q2TsE^(wEi^Dcd6cX~w^ z%dO&(tm3L?P8hbc33 zykzS7R7tmL7{l32)Aq6Q_rEK4sEF>{D$Z6!kfu1I3S~x@kG?sr@QGq?kF1@Qk0zYy z&n;li^bU_}P5HN+JkPGovHltjOW$uK z)Be`DCvKHFATHYw_UxI73bMir;&$%Dp=M4O&h*PHk-?u5A$UsuI$Q$nAE19g9sP## z1K=2Gbp0j4NNEu6`{8}cLDAc7`)~MBNLgL2*4X)pFo!EyGs{y>`7GYlP*r)VL`5XU zU~V6SGzlL|@-IA+1M8E~t17s&YUhJO?P1e@=&9pZ9-`)hY~xys9vok5%kF%0*Y2>k zL6)f3?ns=^!0!q+G<#9pqURj^FoQsz(A?mcHQ@g3fxYZ2k{aeX{BLL(U8TY8m4`fQ z+v7E!<Cwe}VWwkJ?kU+@*iT!7O0(pOVY7ulw+M=9fJ09jH_DG``?WYuav!-T2>39M9 zz`;89SP#IidqEr)+27UziN$$?p3|hsb;FCaU?NQGT31&R6 zlm>h$5T@5jzI4A3h0VuwxdYY|h=$nk=KJ$u{DYjEmZHLHCaS>F0msDoHZQ_j1xc}Z z=WPLPJE$Vov(x_X+TnjGjlAxt9%BzFN#}P-grhU|hs&ukR~0;RZ&a^)k=%iOXwBMI zwfvsx@hB`4=x==(sRQL^MZ4aqK5P9al%V*97^$n!;FB2&@ zqtfk^R(XotbTwVhh}MrxbNo&BAGT_Q?BegXK_UT|!7a1jD=#+vUqfG6zob4e^)e0A z8t+rI2OPInHR1RM*VL6M6OD-h4Z4l9J2nyZ9_ zUa_RqAb2=+)Y97_PdASf?avo(>J9b~oPqYvjt6REz`wq(g(gYN`h9(!!Y-Ti}gYT6UVQKT83P+-#%>;dO-M;9r4HUU9@LcctQe1SEa%YhDUcsj#SM)AftY=4O z0TqD0_-S;Y>9$|qPxmXJcny;;;Wf6=1px~1>&lx+p4Hos`qlv+&@929V^X8pLQky8 z7(Ayl9JIRSZ#brs#d9LDNew-fbv7JB=0Scr*sr-KA1(*ZnIt?HIDsI}vO1X69Vi zI1U1gAIvdccK7f~&|9xdOH;5GWF$u`K>*JgZ);npcGgLNf7T31r&ffiDALVigw z<`{9>28nS+?LH6z=J`mFV@gp(^1LWDP^anH#N1jT>*qA4hR^9B9)bOTTr;n}Tb>A_ zi;iMDM#?h{(x}j(qt`&=$uJM}>}TY4^&5-Zm|uw?X_7zf+GkG&iyNqA#0O@P*>LlG4N4|!yFHif!3*6yz|1| z5726TXC#?p<3e7)r8Ro|<)Oh}Ec8;rzDoJIAZRE)fgq-tu5WA~tMbSk%57wDfMs-s z6cG-dJwePQvmP+PT^1sWT!=9!^w?o76kyUMGD=&P-l7s_K}=fgz*G-R(e|37*g^rr z+#eti853WdJZh)np8xpTfOBTAe3156K;AGy^ci#y9;;<-pO1jl_dc81as+jNu>!e+ z|2DPB+Aqx+W-@f0sgHu$>?voenW7w9FOj^xF9g#nBE^B>B>U6Nru1mP!%?G-8$A#GS@*P!X@Ofr}11~QTJ()zoU%l0t-=IK9rw->7QV_QLjjPiFkUF zVn7;?JyCG|H;h1a^keRv8v?zguw(0UaXRdkAZlY4buzKR+|JD><(3@}y+EXvGC8R^ z2yUaid$TP4pO9;=@@F&Df)F$e#fj!TvLt}vq+_}lhdEbFj%Mn3_A@byf63YQ4r|-P zG7qC_j+x|t+J_*1`3(8S?Dw40u2M+&*-H#rkuk6Rtltz`A3#XLVLFeH=j!zKh_dYDV>10P=BVffax2kkk zBAEI!a$ohTE41@L0Jan_8eoMjPy&NJY-v|$5Qe}aDMP2pEkv)wKwAmk%K#L-L6&sr zhH#4uEBxQps-H>w-H>iMeU#TOWhBeUU@TUiP?>R{5xDr%%Q0AS! zHaKLuK$i~KV_n7|M*%>wKzH+pqTxYaDRS)$7dT9R|Cl8`P=rFkeib(M%7k@iWo9ED zy?3%y2C-gVy6*Str`Az)AtX>N6~B(ou3+1(Lkjtek?5WPWIn5_ZncFnk4^$_B6vNx z=`4(XRv-Ev&`Xa2R8Z*L{$(XQxf78Z?UAtewpw?;7-g}VK7->PGI_IyOkGZ1s|PJW(o>RHPe za(hQSVru1TXA5X<)d}eGA&rH_&?!0iYn=KXZ7lAW3p;aW$7r<}yst2Nj0#|&(i70b zy{|K|YxzwrDgTrDD9@jVNO#9=J@A%+$dV({?f1#v-zJnWvX!s8YS9jhkUVSn#)T}X zCcQDcuz0@aXLY<*WvT8_8~x|l8vePc*4PEAEEocRdXpKnwFa?BNUO4UDCFQ5ycy6GD@%kRuP z{fl_D?C)L*cS@DV()Fvl@@?EJl}0DVX8x=q2}UK$Ex8bBHKo3yh!u{x{{XG>FvMn2 zEj*fiJYqVzE^Jbl7!H=XYyHWa`>$v@7Hc<07Psb`Onh2&qNTZg%T)eZawea1<3lw27}ynej&CU8s4iiE6sy!F~Utj zdl4{+o`!Pjz`B%jpgqUeV#NedSh1|K2J2p14c~=QyNvSHNRnsnf$6iq1r=st|KcZv zcZItQW_$3)wI@yqm0Fh~`lqIXFVbs>{X0T39R>H|(-8`+lv1D!kXa$!WVT!2n1d5= z^K>lQtudLf&((-1qwo=;EzOkCza#J>Q$Oq~5$jWLyUEFe&c0Az+6qHfJ>@u08g$zZ zURe`8UBh#a`Z8zc$W;19zbi712ypnrL#TQaK24c(EM&dDkmZ}!AQeIN0gNEH6SX-6v zV=HsNA<7g*OB-b71@5VV@&5Wl!VL$t6(2?O=JcBapv;@K<%*%qm_J`(wgf3-Wn=B1 z8Vug2cWgNc`;}3V4Fww$T<^$8`yJedJ@l=^EuqeLt~!sOkp^=jhT`HGUTU}f-tC13 zE5!hLF*c>O?VUun{>dk*C|4XHFJWf2 zZtZwy;ffyOL}p>u_xT&y)Y$iGktWp-+qEUyHQNl3^@gh7z@%GRsG=n())h9cip)a2 zZC{2sD$A`oSn29+Q4YBrJ^O3)lkT;S7dqtGuA7CKUC^fPh??ta`${!ayH0ufT&wfN zhevLtm5rFri&+GpB2&{-B|IW|Upm>^Hi6w$pW~(~cIPjc@v4yJ%kNjUZBX=Bh4adO zhbc^t>s&s0R(*N^%LTi?h^4C?gHHjm$dNl9WfcGrDewjEKSF)QtnKGglD*?w^2!$+ zZ*(^k*0!-LBuj<5!fGgR(MR=T_p)+W)W~MR&GK{vn!=k_ew8_989L{Iu2OwEmR;Uji?|a`s2%&U>HsuD;L)hOp5bUqDJ=ls7uFX8Cqu zrCngxcH%m@Qrj4~jRUi{b%A;|R2#VgRD1BctzUAi-)c7h-%=-`^R2MHXuk+50JzM3 zaTXvp0TZ<;-spSxMjMZ<(Irs7UrHry@0Q4%kFUGbXmo1yA;CPPRYbSRW^ge*A*tAB zNY8Lzsq`j-Eh<;8-}_JSEJcV;`nCtusn(I!nJ^(!dKE`TYIv=*?B0N?VoUhcmK|fK zE^}c)@!YZJHlbn?6q^gPH+V!vE=6pYxg%X{;HGaPAXB=MmYp3%tO0eCsm}vJFBgNW z`h|kzvbFzWT8#Rx^ZP-PqMy#Ue)6(-HgJ8W-PmX{R@{&7_R1e?tgyiC##Ga)lB{A% zaQU|qXsyn=O~et1*e~|27IowgX)%}fq@0b4c-`T(@?R%jnS4(M7d;cdEamI{E3>MK zTHu>)vB@f_2jKBXm$=fs73{@(YBJyi?>U~?1i$n0w->za zZ9Nt~N2;{-5RtI)L$L>x9g@t?f5^CRnA4M0jgE0|svdfOd8yg;K=Tg^)a%*r zX1U5Amjtk{%F0r%t;20^8bDwhkPZaI@vG z==tm?qTbl#(->apK?af-pZBM4-}MQQ_eX?r;}p~Ufz)pRdsb1?2ZihgLmSddiFf6E=NyXSmw(|;(W*Jkk?LB5NNM_XIZ6>U4ul$m)|*M61?nEdde&99WnL3(od?H3<+QE_Lw zXh}~Vxdw`zanY~La~>ohGa8Y}7rUZ=q8v!T%ND`dxYd-cG85K&)9H35RWAHawa2gO%CHHh>N1fR7P-9W zncnQj&w?@}Tc2}8!kq;MiASe8W{r3eV=z1?D$LdR@393%aJu6EOS;1{C`AG&Gl6rD zpQLB8Xq%Q>(s66sfRRAp@CWeW>_*CD>@!p~*1kqI_G6Cz%Y%>#FoQ>SKCMrKc@Cn4 z^NqTN2TJW$JU3F9y-y>g#0W}fj>*FAntx=tlkN&&{|Ueu>K_;mhbmCPR4+h(!R-{> zuXG=|%_2#_d+3l^^&#gJL4@6~PV7-qNbr7cW$%IhU6kw7A+@4%&@T(Uy(D{0=qrN? z@=6cHrUYbSdj`D`?k3;U-E3yJs3aDYce?lGM;rScNWPDeLLYY;EpFc1=0gt~EuH7< zZcb+v^T@I~$@aG~Z8PnK#$ol9I^vj5cgP20nIPE0SqYoLT)kwe3~ zG(83=m}cizgoKsK8nQZ!U9!e^x35(<@#y3pnK?QkeS&NTkGXwbh&GbfZIZ<9cZ_sJ zyVYFy!tD!ehxx`oI^#NKc8XsoxO)NuZP!+1Eky}|(-hXC%83^kQN&%#ZLvfxkR_5q z4QtBJH~$56IF&zMJd$x1t`m}?13&@qeE**)a|WnWg3JvyHeUi%uPY{wuBOU2T%@cT zD~-AC6?k7PEl;$7kih^S8vK4U`JW0+Q_u2$+yCh{Q9M`fBB}@?!uYG_QECYdlVi91 z-r8Jvy$o+*r6TQ~tm{!qG^*i)2+{VwwL^OLMT@5hI={=YpesDi_kYZe{jd1<|Ce0a z|DU%*vPmEPF>&LjY>^j(&1|g!7jV0%`tgrE*SZ~5q1;dRVm(wk=9I;BX+qiMWzqj( zp1bg$zOQ;8Z4Q4w5q;fhjQM-5JqeqB|K`7U4O^CLIboOG#$LP@Ri=ue19c4l z`c}{QdNB{EZFF60=5a|_qs#AotVFAM{K(R*k#4i4yj))PIbga)N4LXwoksQM`~c*6 zuP5YD08*W|tf6=596osrUS(0#Zn7`)un77+j*{eT)J%^DEUsL1u$5|pDH7@SV zIm`c{L3kHN&pIV;rKX9vepJfk(qstBdgEhzF}aq*nW%;U>bRWjITL#+5N;9hD$bkB z38A$n`w$Dm68E3wn~x+uUoApA*3+rDv9HlRv;VHU!#$d{OR7{VZ}XVQsRmdVZ1*IO z;!dCxFZour*!jG*%&8yh+S;3%DT5BoW~!&X*tm0`V|_F@5^ARd&tf={)DuW%jA@m$ zfC3k20_>Joe2iuaZR&moGWBm{zQZ`h`UQLdN8dE8(2s=+m6zwCV(8Mq!!_{of;ay? z%5PsIJ5nVzypKiqZ%4M`#r|YJOSa-BF)@-UG{-J;&zPR#>IqrK*nl4=;8Ix5MxkP& zw6`LZL=UP{&^|(>aUsKjqZiWXj8{&pkrizl1$bCT&ZsTCX z0L#&jl}*FlOW`UIR#`oPkDV+{jh(!vpdp}q=)>K)2`G~9)cL6mbo|5P2U$5yb0kwJ?F z(j0^Nnqs5dK`--PhdEed6zxCo1YeJ6hS6vg-U0ayr0>)@rbv+|rM8%AfVZEK?M`hJt|uOr;NZDa$w1p zDkS!zE%a^DnpS$YB4|fJqSn7mNy7e$f=R|*|2!O%eu2@| z9qPK9J-mK&XdY^-r}*mK=&3N1r4T9uB#BtD*DTUtm&kFmVa&e-To#wbhcTT8>za+X zT1@fZpB3RW+NR%dQ)YuF#C$AyLB_-O;Szm5ds}Wz0{1#ASItJJT!r>((pzrM^~&Lw zfckkC@B%2$)xoIk)2CN5GE&Ztr=07A?XIfrfIZyXQDgtao=8f`tN6`FXQl8p2870f=K+CQeRXBkG~@$4ic-DL ze(1YuXlSUbtJnAUGeoNLhK7bJvHFAkso{0i)s^)0;_OtcjWGUkad9S!VEB%9fa&sO z0QmOpn<$J%T~o7hZjPG*3MF+jEVdiD6}`&G$JbyX$}J)BELMsSl&sd+8HKZB&FPY! zPyab6`0Vk;8OSg*yB%XV4-cfNsYzW+ixij{AImpvqNSzXo*x_>6qT1>f1JVu&}hZw=3Zkzvk#%%$ffpQE9U2^Tc_Y#$M}9I64soldxbiR|_;V)k zhck=4XI4I?Fek-9-AsL2Fc92Yp)duOg(1N^1Brufpd-=_htGZUx+>-EDpJ6#$yUys z2?&(Iu-=P(B75JHp#K16m@bp|((|Tfe-6=d&23#XPvLgWqUR|Ic>iFUGiEkt@3k{3 zBogT#{E;>KWQdvx={o7gJzt(eP04H*a|c|o$6$>4clPp2;nObAAS_7LGQgF%b+|Je+xNVdIYFM425jT>7IV49fbm2}~ZmwEdym7{vMP_bp*eoX}Co|!I{Fks2rLyh< zr1w+U#pp|usj!PZ$nF@lq~Ln7c7N9NZyV2>lz*0np!9oLI5{c4Y{P4CAlssAYZU_y z%>7Mh2`mtsWwQ2{jK4Xgq@~Mm-u~?{K;qW**kMo43%&%NsN$h8Bnz$$h34=dwBI+s z=OHd7B=gRT;;wc1!k;y}h5(crsjaOojFnZ{+?=riF-CcsZ_0aJW-@$)%$Fhq*hUWdgyKA$(BdJD&d&qOI}x3 zUn}U&0U&D-KIPOoWhMc4xoZ!usW8Xf;DtztvPR{7|L!(0vZhh_h!YaKhdqh?&Q{KC z8;>6x)Qj$o$l(RP?RO;C$#Z_(pQj(iEJUZ+tSG-k9`V8_e}jPa=~y;=ySSd4k*UI0u7!$!PJmf;cQbP0o&BQL#_Y;G3?LxR25iwSMf+R zAV^w7bCc$#tejE4u5g07A(xr_V*>-zjfDB3n;O9|r((Z33mjylrT>SGR|qa<@wx;rcqPyh|hjg8|n-n=AYS z6SvkR=g*)$?g&i!RA`(AzR;#x=$J{k@@!cF#&H-&QZ6*WFPbeGi(qde%Q1 z_Zh8WLL+&;ex_XHza|_AG-Y*6=*N#A2hc8_jah?}|Wz}cyeHj(a$V?FV9(?Tpddr;r;x9KE4|MJqDq95o E56tkcbN~PV literal 12771 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 diff --git a/icons/mob/actions/actions_lasombra_bloodsucker.dmi b/icons/mob/actions/actions_lasombra_bloodsucker.dmi index bc6c89bb0dfe6b61b09deb3a0aaeeb89c6c42deb..105363a1266a29c64b9384ffcc4720f8d119d991 100644 GIT binary patch literal 5255 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 N002ovPDHLkV1myAEeiku literal 3643 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 literal 0 HcmV?d00001 diff --git a/icons/mob/actions/actions_tzimisce_bloodsucker.dmi b/icons/mob/actions/actions_tzimisce_bloodsucker.dmi index e7f504f9baad644eba97ad1775a994cf5566e8a0..c6a8697c45c6edc4d7c1da04905501a3ba6c51e4 100644 GIT binary patch delta 934 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 T#}5F{mSOO8^>bP0l+XkK0)T6r literal 0 HcmV?d00001 diff --git a/icons/mob/bloodsucker_mobs.dmi b/icons/mob/bloodsucker_mobs.dmi index 20276e91d0cbe2873429ac39452bac3f46acc97e..2d23e0154a1903f994e0b7f8469e23af68683bd6 100644 GIT binary patch delta 7247 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 diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index 22c6ca201f4def649d6b8a6355798bd418664a71..eb386af007705af9375cbd86e05d7e93df9ebd25 100644 GIT binary patch literal 217418 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 z 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�-ZvJr_$^3vvzbL)-99upaDwp)Xkh^x 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 WRNhwUc55qE;Nb5^_ve~8g#H(K%ATnJ literal 93805 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

MpmL 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

%42wP8Z5CC#CL4=JeS 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 diff --git a/icons/obj/hydroponics/equipment.dmi b/icons/obj/hydroponics/equipment.dmi index 73dbc6d487f466d19290c503db0e8b23f779456b..4401299d8eed929cd2663813a0d8084983cb477a 100644 GIT binary patch literal 33686 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 literal 21508 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

89Gsbps&+D}^n*yfu^l)j5gZ0}VCl1ZUD zBJMY;6lb)Co>0Eej#2~L&Y`hqk}|+avutAW)X^-z7wPW1TY{wSV+lh?Ii^S=h+}5j z@IN==Z8i=aW(Agf@$I^UA*e!r!NanhaEpIgi}=asF!tdt2w397e@HcJCuT!JmS?Ri zLv0#V_ar*b2%oo`4CPK2JznE;(JKBAp2dQK-+Qe7GlG%)YGL(rG6cBdma-9B$T?^)smdZ~b{A8|xZSXgPoJ9K{h;;%u*HD*B>;i-gqNd82l7-Fk?T0PaM)L3yJx% z|778N93asIC3q507|f`O-|E#sIyuIs&ilpCa>b7yH{noab@fhU$cHqwT(Q{d(pCpX z@tt<+^fUwk1#=?6v*3|5&(Y<)2%Ma>bZ3e?dFnh2r8`vO&_3=n=`keK(E7y_cd1YmvA7y5GaB*|r^)+*QE~}x#cjQra21qfYh=^pd%InHoy@a6B zIE1|J;^Lxrb2WXY7ir34>yG5XSY!dg5Hb$A9o5JORFISl@+L)kNBqz5<~sW69mktqo*EFtbjk+Ig|-g9UVv{DqkZ zo~Z|oeO_X$5<#HN&rfU7Ojta7rD+Gqythv70uW>lg9@luTRxIxB%Yos%7ZB{BM(89p({uN}uG9 z*(-6VR{*(bPYE41zJ7fLGdgi946p}IqxC(my8RT& z;)W}kxR0Qr%}Q`E!f)+Pv@mZ?0guZ;j!pHK7d zODLc;F!yqpi$!F`xm&Na8gT$g3V5GtlkF_NJ0j4w2~L^cUM!wV>sBj{a<&zd4zqcp zB_6!-#Rh#*I~s}YfBgFn7m4)h=`X=t!LYbg%1v(4o2cZ zhYJb{n5kHx7E6s(PtXVY=B`ITWenXc!mYO`xp|36fOyksLw9nBi+1GiJdlx*(R1~a zR_X7WI*#3nwL6nPfYR1J`r^flq8GP1VjFI;K+F^uaP@$F>P=g*|Bv|9WrvDtFB<@- z?Z@^`YPtM(%vpFYYFvQC$srqQlq{SxfEJj ze>r3F4x?89eE_N9Ywr52d(WWmO#f=ux8oU5(Hx|%yvmM^k6NRmB!dp{XQyqS8JTQ( zj0}7UdkVS`PQ^U@@+jiQ;lJv{Zo0cyDE8?*_J8v`qKS=~)UD+{^cEWh!l^$#KF%ub zAj@q>&6%73CoHa?XIpCHm}axjmPwvc12IgU5gyJ8g(D6-)*$7mvcUo`=QQQ0=McRS z#ERw^jn>`SX@C3I9k>eeyEqq|D%j1>{w0D8I0yRJwlK|dM5I-7Q43zX#U*zZ^BHZ) zOR1zP)CNq@=D1t7f_nD`UOw}^USB5HPQULhf{}=f(xJugIIQY>b9AuMq=zTl?U2n| z7b>oO1Ey-c*z~p*GXyu>c;%F%O{L*ompmhUMXz)v@VS|(?sgRlUUc+XQ&VN#(?Yjj zf$724Tr;`gfM6xWO|Px~N# z@kS+!^|*Mm%7E89CR$ZIRZB}G^Q9~eRyl3kKJ-_0@*9F1Cpae07ow?~r?AM4Mg$=e zJtaRe!7j(->dh_u1*m9J1pH^6@%n>hBhj89%8mr^H};V%6Zn-ZtkQ~pFrp467h;@* z!>VrqZ`W=&w_gRU1-?$Q@0WwJa*BESJ_@DSxExFkAkj_aEW-hRKJa{OV2t$l>={+0 z?L|JVk4>Baw&noFW$Xya_-I(QusC&7bPt#vwsT+_8Wz#oU~*?B=EBPueSLkJ_fWjx zOI7Txume)SLV4qu+jn7ChNVxA5Xl(%;Wy#F#DiNmY^N}qCVRIwNmiF((ibu*=(Ub(hk@q<4*(*_xpv(ZQ%?yHY#Cw05^T z*L|t3$^rfZpzt$5>$AJ@fQjS7$sd#c0Sw2$>Ix2tGFmq3RBIhO{-BZI6=&VqHN*&g zwSvyTroJ8b#V|sr`d&O{fq;eRo?tvNMadkEPCb^eTvG8gl=E4u6VVQdirITk@YpNcrdZ&H8dYs#@?`&t}fLa+0U z3Xu9US9c>1A@mp4zLCcePUC%e1A4= zlVg_o1pdw&^%+w>3bvodAv)J|aJ=}a8i~Do>{#}Pwr6uU0A<aV_xY*Jc_` zG9kCboReezaiXzsh%YWb@5^`_FT{&om08^Idz_sEy34b1Q9ZC`lMT{qbzP1pw9YRB zVY2IH$c?BkYR~OHnB7DBB+pEJ4!2w=S9FcIrSFTAV$MWi2XLkS#-`a1>T63D>sP&1 zI*jf-&ID3NOaK9 z?S0;lVL?@V{Q{!!e zFaf{KUs?d;?sotLD*j5}b%)pXjcjTVCJ&!j-V#*k{{_k)UfbQPTNGPUYd(B71Jdb% zb2h^ujOS{=jaPUn5Tq6nnTRy9IC+OiBo?tS1#vvtVIbbk5%VdhMoKbHsO_tuT!-GYPNcP~h%p&j%) z6eIeu$}B@>Dcv95R}$Sx5$$&nm-Lz(%ae$Q+^^2zyLPUU^XEhMl9jl76eW0gK1m26 zG*livXi3M1xJn)$>}Ix2pI6T&LI~PEe$0t7tn5gcnW9~1Z#a7B%8~6aE2?mo`DoPu zS3_;*l`@7GZQu<(Ejyw;8*D`AEXI>1sd3(0;WHc!%`)|+TR?ir7Z&Ov;f2y^vgOR= z*X`$bUOWBMDwu~S#8A)Ev*gnir)helpBb1wQ#5PFUPjz^s=e)#ZSZtY$i(m*uEfA8 G{J#K!*Ial2 diff --git a/strings/steve.json b/strings/steve.json new file mode 100644 index 000000000000..06181e85eb89 --- /dev/null +++ b/strings/steve.json @@ -0,0 +1,21 @@ +{ + "ballmer_good_msg": [ + "Hear me out here. What if, and this is just a theory, we made R&D controllable from our PDAs?", + "Hey guys, what if we rolled out a bluespace wiring system so mice can't destroy the powergrid anymore?", + "I dunno about you guys, but IDs and PDAs being separate is clunky as fuck. Maybe we should merge them into a chip in our arms? That way they can't be stolen easily.", + "I'm thinking we should roll out a git repository for our research under the AGPLv3 license so that we can share it among the other stations freely.", + "Why the fuck aren't we just making every pair of shoes into galoshes? We have the technology." + ], + + "ballmer_windows_me_msg": [ + "Who keeps commiting to master?", + "Best idea ever: Disposal pipes instead of hallways.", + "Do you know who ate all the donuts?", + "Dude, radical idea: H.O.N.K mechs but with no bananium required.", + "So like, you know how we separate our codebase from the master copy that runs on our consumer boxes? What if we merged the two and undid the separation between codebase and server?", + "We should store bank records in a webscale datastore, like /dev/null.", + "What if we use a language that was written on a napkin and created over 1 weekend for all of our servers?", + "Yo man, what if, we like, uh, put a webserver that's automatically turned on with default admin passwords into every PDA?", + "You ever wonder if /dev/null supports sharding?" + ] +} diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js index d96fc98552c6..2495cc479212 100644 --- a/tgui/packages/tgui/index.js +++ b/tgui/packages/tgui/index.js @@ -9,6 +9,7 @@ import './styles/main.scss'; import './styles/themes/abductor.scss'; import './styles/themes/clockwork.scss'; import './styles/themes/cardtable.scss'; +import './styles/themes/darkspawn.scss'; import './styles/themes/hackerman.scss'; import './styles/themes/malfunction.scss'; import './styles/themes/ntos.scss'; diff --git a/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx b/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx new file mode 100644 index 000000000000..e8efba04ac52 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoBlob.tsx @@ -0,0 +1,203 @@ +import { BooleanLike } from 'common/react'; +import { useBackend } from '../backend'; +import { Box, Collapsible, Divider, LabeledList, Section, Stack } from '../components'; + +import { Window } from '../layouts'; + +type Data = { + color: string; + description: string; + effects: string; + name: string; + objectives: Objectives[]; +}; + +type Objectives = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +const BLOB_COLOR = '#556b2f'; + +export const AntagInfoBlob = (props, context) => { + return ( + + +

+ + + + + + +
+ + + ); +}; + +const Overview = (props, context) => { + const { data } = useBackend(context); + const { color, description, effects, name } = data; + + if (!name) { + return ( + + + You haven't revealed your true form yet! + + + You must be succumb to the infection. Find somewhere safe and pop! + + + ); + } + + return ( + + + You are the Blob! + + As the overmind, you can control the blob. + + Your blob reagent is:{' '} + + {name} + + + + The{' '} + + {name} + {' '} + reagent {description} + + {effects && ( + + The{' '} + + {name} + {' '} + reagent {effects} + + )} + + ); +}; + +const Basics = (props, context) => { + return ( + + + + You can expand, which will attack people, damage objects, or place a + Normal Blob if the tile is clear. + + + You will be able to manually place your blob core by pressing the + Place Blob Core button in the bottom right corner of the screen. + + + In addition to the buttons on your HUD, there are a few click + shortcuts to speed up expansion and defense. + + + Click = Expand Blob | Middle Mouse Click = Rally Spores | Ctrl Click = + Create Shield Blob | Alt Click = Remove Blob + + + Attempting to talk will send a message to all other overminds, + allowing you to coordinate with them. + + + + ); +}; + +const Minions = (props, context) => { + return ( + + + + Defenders that can be produced from factories for a cost, and are hard + to kill, powerful, and moderately smart. The factory used to create + one will become fragile and briefly unable to produce spores. + + + Produced automatically from factories, these are weak, but can be + rallied to attack enemies. They will also attack enemies near the + factory and attempt to zombify corpses. + + + + ); +}; + +const Structures = (props, context) => { + return ( + + + Normal Blobs will expand your reach and can be upgraded into special + blobs that perform certain functions. + +
+ You can upgrade normal blobs into the following types of blob: + + + + Strong and expensive blobs which take more damage. In additon, they + are fireproof and can block air, use these to protect yourself from + station fires. Upgrading them again will result in a reflective blob, + capable of reflecting most projectiles at the cost of the strong + blob's extra health. + + + Blobs which produce more resources for you, build as many of these as + possible to consume the station. This type of blob must be placed near + node blobs or your core to work. + + + Blobs that spawn blob spores which will attack nearby enemies. This + type of blob must be placed near node blobs or your core to work. + + + Blobs which grow, like the core. Like the core it can activate + resource and factory blobs. + + +
+ ); +}; + +const ObjectiveDisplay = (props, context) => { + const { data } = useBackend(context); + const { color, objectives } = data; + + return ( + + + {objectives.map(({ explanation }, index) => ( + + {explanation} + + ))} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx b/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx new file mode 100644 index 000000000000..68450abf7c9b --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoBloodsucker.tsx @@ -0,0 +1,266 @@ +import { resolveAsset } from '../assets'; +import { BooleanLike } from "../../common/react"; +import { useBackend, useLocalState } from "../../tgui/backend"; +import { Box, Button, Divider, Dropdown, Section, Stack, Tabs } from "../../tgui/components"; +import { Window } from '../../tgui/layouts'; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +} + +type BloodsuckerInformation = { + clan: ClanInfo[]; + in_clan: BooleanLike; + power: PowerInfo[]; +}; + +type ClanInfo = { + clan_name: string; + clan_description: string; + clan_icon: string; +}; + +type PowerInfo = { + power_name: string; + power_explanation: string; + power_icon: string; +}; + +type Info = { + objectives: Objective[]; +}; + +const ObjectivePrintout = (props: any, context: any) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your current objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +export const AntagInfoBloodsucker = (props: any, context: any) => { + const [tab, setTab] = useLocalState(context, 'tab', 1); + return ( + + + + setTab(1)}> + Introduction + + setTab(2)}> + Clan & Powers + + + {tab === 1 && } + {tab === 2 && } + + + ); +}; + +const BloodsuckerIntro = () => { + return ( + + +
+ + + You are a Bloodsucker, an undead blood-seeking monster + living aboard Space Station 13 + + + + + +
+
+ +
+ + + + You regenerate your health slowly, you're weak to fire, and + you depend on blood to survive. Don't allow your blood to + run too low, or you'll enter a + + Frenzy!
+ + Beware of your Humanity level! The more Humanity you lose, the + easier it is to fall into a{' '} + Frenzy! + +
+ + Avoid using your Feed ability while near others, or else you + will risk breaking the Masquerade! + +
+
+
+
+ +
+ + + Rest in a Coffin to claim it, and that area, as your lair. +
+ Examine your new structures to see how they function! +
+ Medical and Genetic Analyzers can sell you out, your Masquerade + ability will hide your identity to prevent this. +
+
+ +
+ Other Bloodsuckers are not necessarily your friends, but your + survival may depend on cooperation. Betray them at your own + discretion and peril. +
+
+
+
+
+
+ ); +}; + +const BloodsuckerClan = (props: any, context: any) => { + const { act, data } = useBackend(context); + const { clan, in_clan } = data; + + if (!in_clan) { + return ( +
+ + You are not in a Clan. + + +
+ ); + } + + return ( + + +
+ + + {clan.map((ClanInfo) => ( + <> + + + You are part of the {ClanInfo.clan_name} + + + {ClanInfo.clan_description} + + + ))} + + +
+ +
+
+ ); +}; + +const PowerSection = (props: any, context: any) => { + const { act, data } = useBackend(context); + const { power } = data; + if (!power) { + return
; + } + + const [selectedPower, setSelectedPower] = useLocalState( + context, + 'power', + power[0] + ); + + return ( +
+ }> + + + powers.power_name)} + onSelected={(powerName: string) => + setSelectedPower( + power.find((p) => p.power_name === powerName) || power[0] + ) + } + /> + {selectedPower && ( + + )} + + + + + {selectedPower && selectedPower.power_explanation} + + +
+ ); +}; + diff --git a/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx new file mode 100644 index 000000000000..0977d863eae0 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoChangeling.tsx @@ -0,0 +1,210 @@ +import { useBackend } from '../backend'; +import { Dimmer, Section, Stack, NoticeBox } from '../components'; +import { Window } from '../layouts'; + +const hivestyle = { + fontWeight: 'bold', + color: 'yellow', +}; + +const absorbstyle = { + color: 'red', + fontWeight: 'bold', +}; + +const revivestyle = { + color: 'lightblue', + fontWeight: 'bold', +}; + +const transformstyle = { + color: 'orange', + fontWeight: 'bold', +}; + +const storestyle = { + color: 'lightgreen', + fontWeight: 'bold', +}; + +const hivemindstyle = { + color: 'violet', + fontWeight: 'bold', +}; + +const fallenstyle = { + color: 'black', + fontWeight: 'bold', +}; + +type Objective = { + count: number; + name: string; + explanation: string; +}; + +type Info = { + true_name: string; + stolen_antag_info: string; + objectives: Objective[]; +}; + +export const AntagInfoChangeling = (props, context) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const ObjectivePrintout = (props, context) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your current objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +const HivemindSection = (props, context) => { + const { act, data } = useBackend(context); + const { true_name } = data; + return ( +
+ + + All Changelings, regardless of origin, are linked together by the{' '} + hivemind. You may communicate to + other Changelings under your mental alias,{' '} + {true_name}, by starting a message + with :g. Work together, and you + will bring the station to new heights of terror. + + + + Other Changelings are strong allies, but some Changelings may betray + you. Changelings grow in power greatly by absorbing their kind, and + getting absorbed by another Changeling will leave you as a{' '} + Fallen Changeling. There is no + greater humiliation. + + + +
+ ); +}; + +const IntroductionSection = (props, context) => { + const { act, data } = useBackend(context); + const { true_name, objectives } = data; + return ( +
4}> + + + You are {true_name}, a + {"Changeling"}. + + + + + +
+ ); +}; + +const AbilitiesSection = (props, context) => { + const { data } = useBackend(context); + return ( +
+ + + + + Your +  Absorb DNA ability allows + you to steal the DNA and memories of a victim, granting you their + memories or speech patterns and possibly even greater things. + + + + Your +  Reviving Stasis ability + allows you to revive. It means nothing short of a complete body + destruction can stop you! Obviously, this is loud and so should + not be done in front of people you are not planning on silencing. + + + + + + + + Your +  Transform ability allows + you to change into the form of those you have collected DNA from, + lethally and nonlethally. It will also mimic (NOT REAL CLOTHING) + the clothing they were wearing for every slot you have open. + + + + The +  Cellular Emporium is where + you purchase more abilities beyond your starting kit. You have 10 + genetic points to spend on abilities and you are able to readapt + after absorbing a body, refunding your points for different kits. + + + + +
+ ); +}; + +const VictimPatternsSection = (props, context) => { + const { data } = useBackend(context); + const { stolen_antag_info } = data; + return ( +
+ {(!!stolen_antag_info && stolen_antag_info) || ( + Absorb a victim first! + )} +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx b/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx new file mode 100644 index 000000000000..2759fa5e9cb9 --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoDemon.tsx @@ -0,0 +1,126 @@ +import { useBackend } from '../backend'; +import { Box, Section, Stack } from '../components'; +import { BooleanLike } from 'common/react'; +import { Window } from '../layouts'; + +const jauntstyle = { + color: 'lightblue', +}; + +const injurestyle = { + color: 'yellow', +}; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +type Info = { + fluff: string; + explain_attack: BooleanLike; + objectives: Objective[]; +}; + +export const AntagInfoDemon = (props, context) => { + const { data } = useBackend(context); + const { fluff, objectives, explain_attack } = data; + return ( + + + + + + + + + +
2}> + + + {fluff} + + + + + +
+
+ {!!explain_attack && ( + +
+ + + Blood Jaunt: You can + dive in and out of blood to travel anywhere you need to + be. You will gain a speed boost upon leaving the jaunt + for surprise attacks. You can drag victims you have + disabled through the blood, consuming them and restoring + health. + + + + Monstrous strike: You + can launch a devastating slam attack by right-clicking, + capable of smashing bones in one strike. Great for + preventing the escape of your victims, as their wounds + will slow them. + + +
+
+ )} +
+
+ + + +
+
+
+ ); +}; + +const ObjectivePrintout = (props, context) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + + It is in your nature to accomplish these goals: + + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +const DemonRunes = (props, context) => { + return ( +
+ {/* + shoutout to my boy Yuktopus from Crash Bandicoot: Crash of the Titans. + Damn, that was such a good game. + */} + + Y
U
K
T
O
P
U
S
+ Y
U
K
T
O
P
U
S
+ Y
U
K
T
O
P
U
S
+ Y
U
K
T
O
P
U
S +
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx b/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx new file mode 100644 index 000000000000..a4941f8aa04a --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoGeneric.tsx @@ -0,0 +1,57 @@ +import { useBackend } from '../backend'; +import { Section, Stack } from '../components'; +import { BooleanLike } from 'common/react'; +import { Window } from '../layouts'; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +type Info = { + antag_name: string; + objectives: Objective[]; +}; + +export const AntagInfoGeneric = (props, context) => { + const { data } = useBackend(context); + const { antag_name } = data; + return ( + + +
+ + + You are the {antag_name}! + + + + + +
+
+
+ ); +}; + +const ObjectivePrintout = (props, context) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx b/tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx new file mode 100644 index 000000000000..451cb46ec6ca --- /dev/null +++ b/tgui/packages/tgui/interfaces/AntagInfoRevengeVassal.tsx @@ -0,0 +1,150 @@ +import { resolveAsset } from '../assets'; +import { BooleanLike } from '../../common/react'; +import { useBackend, useLocalState } from '../../tgui/backend'; +import { Box, Button, Divider, Dropdown, Section, Stack } from '../../tgui/components'; +import { Window } from '../../tgui/layouts'; + +type Objective = { + count: number; + name: string; + explanation: string; + complete: BooleanLike; + was_uncompleted: BooleanLike; + reward: number; +}; + +type BloodsuckerInformation = { + power: PowerInfo[]; +}; + +type PowerInfo = { + power_name: string; + power_explanation: string; + power_icon: string; +}; + +type Info = { + objectives: Objective[]; +}; + +const ObjectivePrintout = (props: any, context: any) => { + const { data } = useBackend(context); + const { objectives } = data; + return ( + + Your current objectives: + + {(!objectives && 'None!') || + objectives.map((objective) => ( + + #{objective.count}: {objective.explanation} + + ))} + + + ); +}; + +export const AntagInfoRevengeVassal = (props: any, context: any) => { + return ( + + + + + + ); +}; + +const VassalInfo = () => { + return ( + + +
+ + + You are a Vassal tasked with taking revenge for the death of your + Master! + + + + + +
+
+ +
+ + + + You have gained your Master's old Powers, and a brand new + power. You will have to survive and maintain your old + Master's integrity. Bring their old Vassals back into the + fold using your new Ability. + + + +
+
+ + + +
+ ); +}; + +const PowerSection = (props: any, context: any) => { + const { act, data } = useBackend(context); + const { power } = data; + if (!power) { + return
; + } + + const [selectedPower, setSelectedPower] = useLocalState( + context, + 'power', + power[0] + ); + + return ( +
+ }> + + + powers.power_name)} + onSelected={(powerName: string) => + setSelectedPower( + power.find((p) => p.power_name === powerName) || power[0] + ) + } + /> + {selectedPower && ( + + )} + + + + + {selectedPower && selectedPower.power_explanation} + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/KindredArchives.js b/tgui/packages/tgui/interfaces/KindredArchives.js deleted file mode 100644 index 5035e2014baa..000000000000 --- a/tgui/packages/tgui/interfaces/KindredArchives.js +++ /dev/null @@ -1,65 +0,0 @@ -import { useBackend } from '../backend'; -import { Button, LabeledList, Section } from '../components'; -import { Window } from '../layouts'; - -export const KindredArchives = (props, context) => { - const { act, data } = useBackend(context); - return ( - - -
- {data.name} - - -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/KindredBook.tsx b/tgui/packages/tgui/interfaces/KindredBook.tsx new file mode 100644 index 000000000000..13eb797c581a --- /dev/null +++ b/tgui/packages/tgui/interfaces/KindredBook.tsx @@ -0,0 +1,42 @@ +import { useBackend } from '../../tgui/backend'; +import { Collapsible, Table, Section } from '../../tgui/components'; +import { Window } from '../../tgui/layouts'; + +type Data = { + clans: ClanInfo[]; +}; + +type ClanInfo = { + clan_name: string; + clan_desc: string; +}; + +export const KindredBook = (props, context) => { + const { data } = useBackend(context); + const { clans } = data; + return ( + + +
+ + + Written by generations of Curators, this holds all information we + the Curators know about the undead threat that looms the + station... + + So, what Clan are you interested in? +
+ + + {clans.map((clan) => ( + + {clan.clan_desc} + + ))} + +
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/NumberInputModal.tsx b/tgui/packages/tgui/interfaces/NumberInputModal.tsx new file mode 100644 index 000000000000..76f1306d159d --- /dev/null +++ b/tgui/packages/tgui/interfaces/NumberInputModal.tsx @@ -0,0 +1,118 @@ +import { Loader } from './common/Loader'; +import { InputButtons } from './common/InputButtons'; +import { KEY_ENTER, KEY_ESCAPE } from '../../common/keycodes'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, RestrictedInput, Section, Stack } from '../components'; +import { Window } from '../layouts'; + +type NumberInputData = { + init_value: number; + large_buttons: boolean; + max_value: number | null; + message: string; + min_value: number | null; + timeout: number; + title: string; + round_value: boolean; +}; + +export const NumberInputModal = (props, context) => { + const { act, data } = useBackend(context); + const { init_value, large_buttons, message = '', timeout, title } = data; + const [input, setInput] = useLocalState(context, 'input', init_value); + const onChange = (value: number) => { + if (value === input) { + return; + } + setInput(value); + }; + const onClick = (value: number) => { + if (value === input) { + return; + } + setInput(value); + }; + // Dynamically changes the window height based on the message. + const windowHeight = + 140 + + (message.length > 30 ? Math.ceil(message.length / 3) : 0) + + (message.length && large_buttons ? 5 : 0); + + return ( + + {timeout && } + { + const keyCode = window.event ? event.which : event.keyCode; + if (keyCode === KEY_ENTER) { + act('submit', { entry: input }); + } + if (keyCode === KEY_ESCAPE) { + act('cancel'); + } + }}> +
+ + + {message} + + + + + + + + +
+
+
+ ); +}; + +/** Gets the user input and invalidates if there's a constraint. */ +const InputArea = (props, context) => { + const { act, data } = useBackend(context); + const { min_value, max_value, init_value, round_value } = data; + const { input, onClick, onChange } = props; + return ( + + +
); @@ -242,9 +252,7 @@ const Loadouts = (props, context) => { const { points } = data; return ( - {points < 10 && ( - - )} + {points < 10 && } { the 2550's. Comes with Fireball, Magic Missile, Ei Nath, and Ethereal Jaunt. The key here is that every part of this kit is very easy to pick up and use. - `} /> + `} + /> { author="Jegudiel Worldshaker" blurb={multiline` The power of the mighty Mjolnir! Best not to lose it. - This loadout has Summon Item, Mutate, Blink, and - Force Wall. Mutate is your utility in this case: + This loadout has Summon Item, Mutate, Blink, Force Wall, + Tesla Blast, and Mjolnir. Mutate is your utility in this case: Use it for limited ranged fire and getting out of bad blinks. - `} /> + `} + /> @@ -285,7 +295,8 @@ const Loadouts = (props, context) => { Why kill when others will gladly do it for you? Embrace chaos with your kit: Soulshards, Staff of Change, Necro Stone, Teleport, and Jaunt! Remember, no offense spells! - `} /> + `} + /> { You can recharge very long recharge spells like Ei Nath by jumping into new bodies with Mind Swap and starting Soul Tap anew. - `} /> + `} + /> - ); }; @@ -309,14 +320,13 @@ const lineHeightRandomize = 6; const Randomize = (props, context) => { const { act, data } = useBackend(context); - const { points } = data; + const { points, semi_random_bonus, full_random_bonus } = data; return ( - {points < 10 && ( - - )} - + {points < 10 && } + Semi-Randomize will ensure you at least get some mobility and lethality. + Guaranteed to have {semi_random_bonus} points worth of spells. { fluid icon="dice-three" content="Semi-Randomize!" - onClick={() => act("semirandomize")} /> + onClick={() => act('semirandomize')} + /> Full Random will give you anything. There's no going back, either! + Guaranteed to have {full_random_bonus} points worth of spells. @@ -342,42 +354,37 @@ const Randomize = (props, context) => { color="black" icon="dice" content="Full Random!" - onClick={() => act("randomize")} /> + onClick={() => act('randomize')} + /> ); }; -const widthSection = "466px"; -const heightSection = "456px"; +const widthSection = '466px'; +const heightSection = '456px'; export const Spellbook = (props, context) => { const { act, data } = useBackend(context); - const { - entries, - points, - } = data; - const [ - tabIndex, - setTabIndex, - ] = useLocalState(context, 'tab-index', 1); - const ScrollableCheck = TAB2NAME[tabIndex-1].noScrollable ? false : true; - const ScrollableNextCheck = TAB2NAME[tabIndex-1].noScrollable !== 2; - const TabComponent = TAB2NAME[tabIndex-1].component - ? TAB2NAME[tabIndex-1].component() : null; + const { entries, points } = data; + const [tabIndex, setTabIndex] = useLocalState(context, 'tab-index', 1); + const ScrollableCheck = TAB2NAME[tabIndex - 1].noScrollable ? false : true; + const ScrollableNextCheck = TAB2NAME[tabIndex - 1].noScrollable !== 2; + const TabComponent = TAB2NAME[tabIndex - 1].component + ? TAB2NAME[tabIndex - 1].component() + : null; const TabNextComponent = TAB2NAME[tabIndex].component - ? TAB2NAME[tabIndex].component() : null; - const TabSpells = entries ? entries.filter( - entry => entry.cat === TAB2NAME[tabIndex-1].title) : null; - const TabNextSpells = entries ? entries.filter( - entry => entry.cat === TAB2NAME[tabIndex].title) : null; + ? TAB2NAME[tabIndex].component() + : null; + const TabSpells = entries + ? entries.filter((entry) => entry.cat === TAB2NAME[tabIndex - 1].title) + : null; + const TabNextSpells = entries + ? entries.filter((entry) => entry.cat === TAB2NAME[tabIndex].title) + : null; return ( - + @@ -389,7 +396,7 @@ export const Spellbook = (props, context) => { width={widthSection} height={heightSection} fill - title={TAB2NAME[tabIndex-1].title} + title={TAB2NAME[tabIndex - 1].title} buttons={ <>