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

-`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 += "
"
- 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 += "
"
+ 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 += ""
- 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 += ""
+ 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]