From 2533caefd93e41e9c30297b62bf8828c9854400f Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:34:27 -0400 Subject: [PATCH 1/5] combat code overhaul + projectiles v8: impact handling (#6579) --- .github/workflows/size_labeling.yml | 2 +- citadel.dme | 133 +- code/__DEFINES/_core.dm | 3 + code/__DEFINES/_flags/atom_flags.dm | 8 +- code/__DEFINES/_planes+layers.dm | 1 + code/__DEFINES/callbacks.dm | 6 +- code/__DEFINES/combat/armor.dm | 64 +- code/__DEFINES/combat/attack_types.dm | 7 + code/__DEFINES/combat/explosions.dm | 4 +- code/__DEFINES/combat/shieldcall.dm | 206 ++- code/__DEFINES/datums/event_args.dm | 5 + code/__DEFINES/dcs/flags.dm | 6 +- ...m_buckling.dm => signals_atom-buckling.dm} | 36 +- ...stem.dm => signals_atom-context_system.dm} | 2 +- .../signals_atom/signals_atom-defense.dm | 37 + ...m_throwing.dm => signals_atom-throwing.dm} | 11 +- ..._system.dm => signals_atom-tool_system.dm} | 2 +- .../signals_atom/signals_atom_defense.dm | 5 - .../signals_item/signals_item_inventory.dm | 8 +- ..._inventory.dm => signals_mob-inventory.dm} | 3 + .../signals_mob/signals_mob-perspective.dm | 7 + .../signals/signals_mob/signals_mob_legacy.dm | 13 + .../signals_mob/signals_mob_perspectiive.dm | 4 - code/__DEFINES/event_args.dm | 7 - code/__DEFINES/math.dm | 40 +- code/__DEFINES/misc.dm | 10 - code/__DEFINES/procs/clickcode.dm | 15 +- code/__DEFINES/projectiles/projectile.dm | 119 ++ code/__HELPERS/game/combat/arc.dm | 58 + code/__HELPERS/lists/string.dm | 4 +- code/__HELPERS/mobs.dm | 2 +- code/__HELPERS/type2type/type2type.dm | 10 +- code/__HELPERS/unsorted.dm | 2 +- code/__global_init.dm | 12 + code/controllers/subsystem/ai_movement.dm | 15 +- .../subsystem/processing/fastprocess.dm | 6 - .../subsystem/processing/process_20fps.dm | 7 + .../subsystem/processing/process_5fps.dm | 7 + code/controllers/subsystem/throwing.dm | 3 + .../datastructs => datums/armor}/armor.dm | 10 + code/datums/callback.dm | 16 + code/datums/components/atoms/fishing_spot.dm | 4 +- code/datums/components/gps_signal.dm | 2 +- code/datums/components/horror_aura.dm | 2 +- code/datums/components/items/active_parry.dm | 10 + code/datums/components/items/passive_parry.dm | 348 +++++ code/datums/components/items/shield_block.dm | 11 + code/datums/components/items/wielding.dm | 10 +- code/datums/components/mobs/block_frame.dm | 75 + code/datums/components/mobs/parry_frame.dm | 561 +++++++ .../datums/components/movable/spatial_grid.dm | 2 +- .../datums/components/riding/riding_filter.dm | 8 +- .../components/riding/riding_handler.dm | 4 +- code/datums/datumvars.dm | 7 +- code/datums/elements/conflict_checking.dm | 2 + code/datums/event_args/clickchain.dm | 11 + code/datums/outfits/ghostrole.dm | 6 +- code/datums/outfits/horror_killers.dm | 2 +- code/datums/outfits/outfit.dm | 8 +- code/datums/outfits/pirates.dm | 4 +- code/datums/position_point_vector.dm | 3 + code/datums/shieldcall.dm | 153 +- code/datums/soundbytes/effects/combat.dm | 47 + code/datums/status_effects/status_effect.dm | 7 +- code/datums/unarmed_attack.dm | 3 + code/datums/uplink/visible_weapons.dm | 12 +- code/game/antagonist/outsider/deathsquad.dm | 2 +- code/game/atoms/atom-construction.dm | 60 + code/game/atoms/atom-damage.dm | 333 +++++ code/game/atoms/atom-defense.dm | 532 +++++++ code/game/atoms/atom.dm | 1 + code/game/atoms/buckling.dm | 38 +- code/game/atoms/defense.dm | 547 ------- code/game/atoms/defense_old.dm | 4 - .../movable/{throwing.dm => movable-throw.dm} | 2 +- code/game/atoms/movable/movement.dm | 16 +- code/game/atoms/vv.dm | 2 +- code/game/click/context.dm | 6 +- code/game/click/items.dm | 18 +- code/game/click/mobs.dm | 5 +- .../nanotrasen-supply/contraband.dm | 2 +- .../nanotrasen/nanotrasen-supply/munitions.dm | 2 +- .../nanotrasen/nanotrasen-supply/security.dm | 2 +- .../gamemodes/changeling/powers/armblade.dm | 69 +- code/game/gamemodes/cult/cult_structures.dm | 7 +- code/game/gamemodes/events/black_hole.dm | 2 +- code/game/gamemodes/meteor/meteors.dm | 2 +- code/game/gamemodes/sandbox/h_sandbox.dm | 2 +- .../technomancer/devices/shield_armor.dm | 23 +- .../technomancer/devices/tesla_armor.dm | 24 +- .../technomancer/spells/energy_siphon.dm | 26 +- .../spells/projectile/chain_lightning.dm | 16 +- .../spells/projectile/lightning.dm | 8 +- .../gamemodes/technomancer/spells/reflect.dm | 109 +- .../gamemodes/technomancer/spells/shield.dm | 33 +- code/game/machinery/_machinery.dm | 22 +- code/game/machinery/doors/airlock/airlock.dm | 2 +- code/game/machinery/doors/defense.dm | 2 +- code/game/machinery/doors/unpowered.dm | 2 +- code/game/machinery/doors/windowdoor.dm | 2 +- code/game/machinery/fire_alarm.dm | 5 +- code/game/machinery/iv_drip.dm | 2 +- code/game/machinery/turnstile.dm | 5 - code/game/machinery/turrets/subtypes/misc.dm | 2 +- .../machinery/turrets/turret-ai_holder.dm | 3 +- code/game/machinery/turrets/turret.dm | 12 +- code/game/objects/defense.dm | 82 - code/game/objects/effects/_effect.dm | 1 + code/game/objects/effects/effect_system.dm | 4 +- .../game/objects/effects/item_pickup_ghost.dm | 10 +- code/game/objects/effects/mines.dm | 9 +- code/game/objects/effects/misc.dm | 20 - code/game/objects/effects/spiders.dm | 8 +- code/game/objects/effects/traps.dm | 4 +- code/game/objects/items-carry_weight.dm | 74 + code/game/objects/items-defense.dm | 12 + code/game/objects/items-interaction.dm | 159 ++ code/game/objects/items.dm | 300 +--- code/game/objects/items/contraband.dm | 10 +- .../objects/items/devices/chameleonproj.dm | 4 +- code/game/objects/items/devices/gps.dm | 4 +- code/game/objects/items/devices/spy_bug.dm | 7 +- .../objects/items/id_cards/station_ids.dm | 1 - code/game/objects/items/latexballoon.dm | 12 +- code/game/objects/items/melee/melee.dm | 22 + .../{weapons/melee => melee/types}/misc.dm | 22 +- .../items/melee/types/ninja_energy_blade.dm | 66 + .../objects/items/melee/types/transforming.dm | 177 +++ .../items/melee/types/transforming/energy.dm | 176 +++ .../melee/types/transforming/energy/axe.dm | 49 + .../types/transforming/energy/ionic_rapier.dm | 56 + .../melee/types/transforming/energy/saber.dm | 141 ++ .../melee/types/transforming/hfmachete.dm | 50 + code/game/objects/items/shield/shield.dm | 17 + .../items/shield/types/shields_legacy.dm | 204 +++ .../items/shield/types/shields_legacy_vr.dm | 14 + .../items/shield/types/transforming.dm | 110 ++ .../items/shield/types/transforming/energy.dm | 111 ++ .../shield/types/transforming/telescopic.dm | 37 + .../items/shield_projector/shield_matrix.dm | 79 + .../shield_projector/shield_projector.dm} | 78 - code/game/objects/items/shooting_range.dm | 23 +- code/game/objects/items/storage/belt.dm | 4 +- code/game/objects/items/storage/lockbox.dm | 2 +- code/game/objects/items/storage/secure.dm | 2 +- .../game/objects/items/storage/uplink_kits.dm | 2 +- code/game/objects/items/tools/switchtool.dm | 11 +- .../objects/items/weapons/cigs_lighters.dm | 6 +- .../items/weapons/grenades/concussion.dm | 2 +- .../items/weapons/grenades/explosive.dm | 30 +- .../items/weapons/grenades/flashbang.dm | 2 +- .../items/weapons/grenades/projectile.dm | 33 +- .../objects/items/weapons/material/knives.dm | 10 +- .../items/weapons/material/material.dm | 1 + .../objects/items/weapons/material/swords.dm | 42 +- .../items/weapons/material/twohanded.dm | 12 +- .../objects/items/weapons/melee/deflect.dm | 31 - .../objects/items/weapons/melee/energy.dm | 729 --------- .../game/objects/items/weapons/melee/melee.dm | 7 - code/game/objects/items/weapons/shields.dm | 494 ------ .../objects/items/weapons/swords_axes_etc.dm | 9 +- code/game/objects/items/weapons/tanks/tank.dm | 30 +- code/game/objects/obj-construction.dm | 9 + code/game/objects/obj-defense.dm | 118 ++ code/game/objects/{objs.dm => obj.dm} | 0 code/game/objects/random/mapping.dm | 10 +- code/game/objects/random/misc.dm | 4 +- code/game/objects/structures/catwalk.dm | 2 +- code/game/objects/structures/cliff.dm | 7 +- .../structures/crates_lockers/__closet.dm | 2 +- .../crates_lockers/closets/coffin.dm | 4 +- .../crates_lockers/closets/gimmick.dm | 4 +- .../crates_lockers/closets/secure/personal.dm | 2 +- .../crates_lockers/closets/secure/security.dm | 6 +- .../crates_lockers/closets/syndicate.dm | 2 +- .../structures/crates_lockers/crates.dm | 19 +- code/game/objects/structures/curtains.dm | 11 +- code/game/objects/structures/decorations.dm | 13 +- code/game/objects/structures/door_assembly.dm | 6 +- code/game/objects/structures/flora/rocks.dm | 9 +- code/game/objects/structures/flora/trees.dm | 4 +- code/game/objects/structures/grille.dm | 44 +- code/game/objects/structures/janicart.dm | 12 +- code/game/objects/structures/mirror.dm | 7 +- .../objects/structures/props/beam_prism.dm | 33 +- .../structures/props/projectile_lock.dm | 15 +- .../objects/structures/props/puzzledoor.dm | 7 +- .../objects/structures/tables/interactions.dm | 8 +- code/game/objects/structures/target_stake.dm | 7 +- code/game/objects/structures/window.dm | 2 +- code/game/turfs/defense.dm | 2 - code/game/turfs/simulated/wall/defense.dm | 48 - .../turfs/simulated/wall/wall-construction.dm | 7 + code/game/turfs/simulated/wall/wall-damage.dm | 14 + .../game/turfs/simulated/wall/wall-defense.dm | 97 ++ code/game/turfs/simulated/wall/wall.dm | 6 +- .../game/turfs/simulated/wall/wall_attacks.dm | 6 +- code/game/turfs/turf-construction.dm | 7 + code/modules/admin/topic.dm | 2 +- code/modules/admin/verbs/SDQL2/SDQL_2.dm | 4 +- code/modules/admin/verbs/smite.dm | 7 - .../admin/view_variables/view_variables.dm | 2 +- code/modules/assembly/infrared.dm | 4 +- .../atmospherics/environmental/zas/debug.dm | 16 +- code/modules/awaymissions/loot_vr.dm | 4 +- code/modules/blob2/blobs/base_blob.dm | 17 +- code/modules/clothing/clothing_accessories.dm | 10 - code/modules/clothing/gloves/arm_guards.dm | 8 - code/modules/clothing/sets/armor/ablative.dm | 64 + code/modules/clothing/shoes/leg_guards.dm | 8 - code/modules/clothing/spacesuits/alien.dm | 2 +- code/modules/clothing/spacesuits/syndi.dm | 2 +- code/modules/clothing/spacesuits/void/merc.dm | 18 +- .../clothing/spacesuits/void/xeno/tajara.dm | 4 +- code/modules/clothing/suits/armor.dm | 67 +- .../clothing/under/accessories/armor.dm | 32 +- .../clothing/under/accessories/holster.dm | 2 +- code/modules/examine/descriptions/weapons.dm | 2 +- code/modules/flufftext/Hallucination.dm | 2 +- code/modules/food/drinks/bottle.dm | 2 +- .../gamemaster/actions/electrified_door.dm | 2 +- code/modules/ghostroles/spawnpoint.dm | 2 +- code/modules/hardsuits/_rig.dm | 5 - code/modules/hardsuits/modules/combat.dm | 6 +- code/modules/hardsuits/suits/merc.dm | 2 +- code/modules/holodeck/HolodeckObjects.dm | 11 +- code/modules/holomap/holomap_datum.dm | 4 +- code/modules/hydroponics/trays/tray.dm | 16 +- .../integrated_electronics/subtypes/time.dm | 2 +- code/modules/loot/packs/weapons.dm | 4 +- .../mapping/map_helpers/engine_loader.dm | 2 +- code/modules/mining/kinetic_crusher.dm | 8 +- code/modules/mining/machine_processing.dm | 4 +- code/modules/mining/mine_outcrops.dm | 9 +- code/modules/mining/mine_turfs.dm | 10 +- code/modules/mob/defense.dm | 83 -- code/modules/mob/grab.dm | 26 +- code/modules/mob/inventory/items.dm | 15 +- code/modules/mob/living/bot/secbot.dm | 4 +- .../mob/living/carbon/carbon-defense.dm | 14 +- .../mob/living/carbon/human/defense.dm | 52 +- ...human_damage.dm => human-damage-legacy.dm} | 3 - .../mob/living/carbon/human/human-damage.dm | 4 + ...man_defense.dm => human-defense-legacy.dm} | 80 +- .../mob/living/carbon/human/human-defense.dm | 28 + .../living/carbon/human/human_attackhand.dm | 13 +- .../living/carbon/human/traits/weaver_objs.dm | 6 +- code/modules/mob/living/inventory.dm | 34 - .../{damage_procs.dm => living-damage.dm} | 14 + .../{defense.dm => living-defense-legacy.dm} | 112 +- code/modules/mob/living/living-defense.dm | 114 +- .../modules/mob/living/silicon/robot/robot.dm | 10 +- .../robot/robot_modules/station/misc.dm | 4 +- .../silicon/robot/robot_modules/swarm.dm | 2 +- .../silicon/robot/robot_modules/syndicate.dm | 4 +- .../mob/living/silicon/silicon-damage.dm | 14 + .../living/silicon/silicon-defense-legacy.dm | 57 + code/modules/mob/living/silicon/silicon.dm | 74 - .../simple_animal/constructs/constructs.dm | 4 +- code/modules/mob/living/simple_mob/combat.dm | 10 +- .../subtypes/animal/giant_spider/hunter.dm | 7 +- .../subtypes/animal/giant_spider/lurker.dm | 2 +- .../simple_mob/subtypes/animal/roach/roach.dm | 2 +- .../subtypes/animal/space/mouse_army.dm | 4 +- .../living/simple_mob/subtypes/horror/Eddy.dm | 4 +- .../simple_mob/subtypes/horror/Master.dm | 4 +- .../simple_mob/subtypes/horror/Rickey.dm | 4 +- .../simple_mob/subtypes/horror/Smiley.dm | 4 +- .../simple_mob/subtypes/horror/Steve.dm | 4 +- .../simple_mob/subtypes/horror/Willy.dm | 4 +- .../simple_mob/subtypes/horror/bradley.dm | 4 +- .../simple_mob/subtypes/horror/sally.dm | 4 +- .../simple_mob/subtypes/horror/shittytim.dm | 4 +- .../simple_mob/subtypes/horror/timling.dm | 4 +- .../simple_mob/subtypes/humanoid/cultist.dm | 14 +- .../subtypes/humanoid/mercs/mercs.dm | 47 +- .../simple_mob/subtypes/humanoid/pirates.dm | 26 +- .../simple_mob/subtypes/illusion/illusion.dm | 8 +- .../mechanical/cyber_horror/cyber_horror.dm | 20 +- .../mechanical/hivebot/ranged_damage.dm | 26 +- .../mechanical/mecha/adv_dark_gygax.dm | 13 +- .../subtypes/mechanical/mecha/mecha.dm | 8 +- .../subtypes/mechanical/mecha/odysseus.dm | 7 +- .../subtypes/occult/constructs/juggernaut.dm | 74 +- .../simple_mob/subtypes/slime/feral/feral.dm | 8 +- .../subtypes/slime/xenobio/subtypes.dm | 37 +- .../subtypes/vore/corrupt_hounds.dm | 15 +- code/modules/mob/mob-damage.dm | 7 + code/modules/mob/mob-defense.dm | 121 ++ code/modules/mob/mob.dm | 3 - code/modules/mob/movement.dm | 10 +- .../computers/modular_computer/damage.dm | 10 +- code/modules/multiz/atoms.dm | 2 +- code/modules/multiz/movement.dm | 6 +- code/modules/multiz/turf.dm | 3 - code/modules/organs/external/external.dm | 2 +- .../organs/internal/augment/armmounted.dm | 2 +- .../overmap/legacy/ships/computers/sensors.dm | 6 +- code/modules/power/antimatter/control.dm | 11 +- code/modules/power/antimatter/shielding.dm | 9 +- code/modules/power/fusion/core/_core.dm | 5 +- code/modules/power/fusion/core/core_field.dm | 128 +- .../power/fusion/fusion_particle_catcher.dm | 43 - code/modules/power/port_gen.dm | 6 +- .../power/singularity/field_generator.dm | 15 +- .../particle_accelerator/particle.dm | 92 +- .../particle_accelerator/particle_smasher.dm | 10 +- code/modules/power/singularity/singularity.dm | 4 +- code/modules/power/supermatter/supermatter.dm | 52 +- code/modules/projectiles/README.md | 8 + .../projectiles/ammunition/ammo_caliber.dm | 2 +- .../ammunition/calibers/special/dart.dm | 9 +- code/modules/projectiles/gun.dm | 105 +- code/modules/projectiles/gun_item_renderer.dm | 2 +- code/modules/projectiles/gun_mob_renderer.dm | 2 +- .../ballistic/microbattery/medigun_cells.dm | 49 +- .../ballistic/microbattery/revolver_cells.dm | 22 +- .../guns/energy/kinetic_accelerator.dm | 15 +- .../projectiles/guns/energy/particle.dm | 8 +- .../projectiles/guns/energy/sizegun_vr.dm | 11 +- .../projectiles/guns/energy/special.dm | 2 +- code/modules/projectiles/guns/launcher.dm | 5 - .../guns/legacy_vr_guns/crestrose.dm | 14 +- .../guns/legacy_vr_guns/pummeler.dm | 9 +- .../guns/legacy_vr_guns/sickshot.dm | 8 +- code/modules/projectiles/guns/magic/staff.dm | 21 +- .../projectiles/guns/projectile/automatic.dm | 13 - .../projectiles/guns/projectile/boltaction.dm | 6 +- .../projectiles/guns/projectile/pistol.dm | 5 - .../projectiles/guns/projectile/shotgun.dm | 7 +- code/modules/projectiles/guns/vox.dm | 9 +- .../modules/projectiles/projectile-tracing.dm | 51 - code/modules/projectiles/projectile.dm | 1320 ----------------- code/modules/projectiles/projectile/README.md | 6 + code/modules/projectiles/projectile/arc.dm | 82 +- .../modules/projectiles/projectile/helpers.dm | 68 + .../modules/projectiles/projectile/pellets.dm | 118 -- .../projectile/projectile-hitscan_visuals.dm | 195 +++ .../projectile/projectile-physics.dm | 434 ++++++ .../projectile/projectile-tracing.dm | 68 + .../projectiles/projectile/projectile.dm | 1180 +++++++++++++++ .../projectile/projectile_effect.dm | 51 + .../projectile/{ => subtypes}/animate.dm | 0 .../projectiles/projectile/subtypes/arc.dm | 82 + .../projectile/{ => subtypes}/beam/beams.dm | 45 +- .../{ => subtypes}/beam/beams_vr.dm | 18 +- .../projectile/{ => subtypes}/beam/blaster.dm | 0 .../projectile/{ => subtypes}/blob.dm | 7 +- .../projectile/{ => subtypes}/bullets.dm | 145 +- .../projectile/{ => subtypes}/bullets_vr.dm | 0 .../projectile/{ => subtypes}/change.dm | 7 +- .../projectile/{ => subtypes}/energy.dm | 21 +- .../projectile/{ => subtypes}/energy_vr.dm | 0 .../projectile/{ => subtypes}/explosive.dm | 18 +- .../projectile/{ => subtypes}/force.dm | 9 +- .../projectile/{ => subtypes}/hook.dm | 15 +- .../projectile/{ => subtypes}/magic.dm | 4 +- .../projectile/{ => subtypes}/magnetic.dm | 71 +- .../projectiles/projectile/subtypes/pellet.dm | 153 ++ .../projectile/{ => subtypes}/reusable.dm | 53 +- .../projectile/{ => subtypes}/scatter.dm | 0 .../projectile/{ => subtypes}/special.dm | 111 +- .../projectile/{ => subtypes}/trace.dm | 4 +- code/modules/projectiles/unsorted/magic.dm | 143 +- code/modules/random_map/drop/drop_types.dm | 8 +- code/modules/reagents/chemistry/holder.dm | 2 +- .../machinery/reagent_dispenser/fuel.dm | 13 +- .../machinery/reagent_dispenser/oil.dm | 5 +- .../reagents/reagent_containers/syringes.dm | 6 +- code/modules/research/designs/weapons.dm | 4 +- code/modules/shieldgen/energy_field.dm | 2 +- code/modules/shieldgen/energy_shield.dm | 7 +- code/modules/shieldgen/sheldwallgen.dm | 17 +- code/modules/shuttles/shuttle_console.dm | 10 - .../species/promethean/promethean_blob.dm | 7 +- .../species/xenomorphs/alien_facehugger.dm | 6 +- code/modules/spells/spell_projectile.dm | 7 +- .../modules/spells/targeted/ethereal_jaunt.dm | 2 - code/modules/surgery/generic.dm | 2 +- code/modules/vehicles/sealed.dm | 2 +- .../mecha/equipment/weapons/energy/pulse.dm | 8 - .../sealed/mecha/equipment/weapons/weapons.dm | 4 +- code/modules/vehicles/sealed/mecha/mecha.dm | 67 +- .../vehicles/sealed/mecha/mecha_actions.dm | 18 +- .../vehicles/sealed/mecha/mecha_wreckage.dm | 10 - .../sealed/mecha/subtypes/combat/gorilla.dm | 6 +- .../sealed/mecha/subtypes/working/ripley.dm | 7 - .../modules/vehicles_legacy/Securitrain_vr.dm | 9 +- code/modules/vehicles_legacy/bike.dm | 7 +- code/modules/vehicles_legacy/cargo_train.dm | 2 +- code/modules/vehicles_legacy/rover_vr.dm | 9 +- code/modules/vehicles_legacy/train.dm | 9 +- code/modules/vehicles_legacy/vehicle.dm | 8 +- code/modules/vore/fluffstuff/custom_items.dm | 137 +- .../xenoarcheaology/artifacts/artifact.dm | 14 +- .../xenoarcheaology/tools/coolant_tank.dm | 7 +- code/modules/xenobio/items/weapons.dm | 8 +- code/modules/xenobio2/mob/xeno procs.dm | 2 +- code/modules/xenobio2/mob/xeno.dm | 2 +- config/entries/game.txt | 12 +- icons/effects/defensive/README.md | 3 + icons/effects/defensive/main_parry.dmi | Bin 0 -> 37674 bytes icons/items/melee/basic.dmi | Bin 0 -> 2957 bytes icons/items/melee/transforming.dmi | Bin 0 -> 30606 bytes icons/items/shields/basic.dmi | Bin 0 -> 22474 bytes icons/items/shields/transforming.dmi | Bin 0 -> 7584 bytes icons/mob/items/lefthand_melee.dmi | Bin 79062 -> 49066 bytes icons/mob/items/righthand_melee.dmi | Bin 75923 -> 46536 bytes icons/obj/weapons.dmi | Bin 81769 -> 69684 bytes icons/obj/weapons_vr.dmi | Bin 5045 -> 2977 bytes maps/away_missions/140x140/zoo.dmm | 2 +- maps/away_missions/archive/spacebattle.dmm | 6 +- .../archive/stationCollision.dmm | 2 +- maps/rift/levels/rift-11-orbital.dmm | 8 +- .../piratebase_192/levels/piratebase_192.dmm | 6 +- .../tradeport_140/levels/tradeport_140.dmm | 2 +- .../tradeport_192/levels/tradeport_192.dmm | 2 +- maps/templates/admin/dhael_centcom.dmm | 48 +- maps/templates/admin/ert_base.dmm | 20 +- maps/templates/admin/kk_mercship.dmm | 24 +- maps/templates/admin/mercbase.dmm | 24 +- maps/templates/admin/skipjack.dmm | 2 +- maps/templates/admin/thunderdome.dmm | 8 +- .../shuttles/overmaps/generic/cruiser.dmm | 20 +- .../shuttles/overmaps/generic/shelter_6.dmm | 16 +- maps/triumph/levels/flagship.dmm | 48 +- .../effects/combat/block-metal-on-metal-1.ogg | Bin 0 -> 10382 bytes .../effects/combat/block-metal-on-metal-2.ogg | Bin 0 -> 12604 bytes .../effects/combat/block-metal-on-wood-1.ogg | Bin 0 -> 8236 bytes .../effects/combat/block-metal-on-wood-2.ogg | Bin 0 -> 6631 bytes .../effects/combat/block-wood-on-wood-1.ogg | Bin 0 -> 12163 bytes .../effects/combat/block-wood-on-wood-2.ogg | Bin 0 -> 12764 bytes .../soundbytes/effects/combat/parry-cycle.ogg | Bin 0 -> 22373 bytes .../effects/combat/parry-metal1.ogg | Bin 0 -> 10612 bytes .../effects/combat/parry-metal2.ogg | Bin 0 -> 9412 bytes .../soundbytes/effects/combat/parry-wood1.ogg | Bin 0 -> 10321 bytes .../soundbytes/effects/combat/parry-wood2.ogg | Bin 0 -> 11514 bytes 437 files changed, 9340 insertions(+), 6546 deletions(-) create mode 100644 code/__DEFINES/datums/event_args.dm rename code/__DEFINES/dcs/signals/signals_atom/{signals_atom_buckling.dm => signals_atom-buckling.dm} (60%) rename code/__DEFINES/dcs/signals/signals_atom/{signals_atom_context_system.dm => signals_atom-context_system.dm} (95%) create mode 100644 code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm rename code/__DEFINES/dcs/signals/signals_atom/{signals_atom_throwing.dm => signals_atom-throwing.dm} (79%) rename code/__DEFINES/dcs/signals/signals_atom/{signals_atom_tool_system.dm => signals_atom-tool_system.dm} (91%) delete mode 100644 code/__DEFINES/dcs/signals/signals_atom/signals_atom_defense.dm rename code/__DEFINES/dcs/signals/signals_mob/{signals_mob_inventory.dm => signals_mob-inventory.dm} (85%) create mode 100644 code/__DEFINES/dcs/signals/signals_mob/signals_mob-perspective.dm create mode 100644 code/__DEFINES/dcs/signals/signals_mob/signals_mob_legacy.dm delete mode 100644 code/__DEFINES/dcs/signals/signals_mob/signals_mob_perspectiive.dm delete mode 100644 code/__DEFINES/event_args.dm create mode 100644 code/__DEFINES/projectiles/projectile.dm create mode 100644 code/__HELPERS/game/combat/arc.dm delete mode 100644 code/controllers/subsystem/processing/fastprocess.dm create mode 100644 code/controllers/subsystem/processing/process_20fps.dm create mode 100644 code/controllers/subsystem/processing/process_5fps.dm rename code/{__HELPERS/datastructs => datums/armor}/armor.dm (96%) create mode 100644 code/datums/components/items/active_parry.dm create mode 100644 code/datums/components/items/passive_parry.dm create mode 100644 code/datums/components/items/shield_block.dm create mode 100644 code/datums/components/mobs/block_frame.dm create mode 100644 code/datums/components/mobs/parry_frame.dm create mode 100644 code/datums/soundbytes/effects/combat.dm create mode 100644 code/game/atoms/atom-construction.dm create mode 100644 code/game/atoms/atom-damage.dm create mode 100644 code/game/atoms/atom-defense.dm delete mode 100644 code/game/atoms/defense.dm rename code/game/atoms/movable/{throwing.dm => movable-throw.dm} (99%) delete mode 100644 code/game/objects/defense.dm create mode 100644 code/game/objects/items-carry_weight.dm create mode 100644 code/game/objects/items-defense.dm create mode 100644 code/game/objects/items/melee/melee.dm rename code/game/objects/items/{weapons/melee => melee/types}/misc.dm (94%) create mode 100644 code/game/objects/items/melee/types/ninja_energy_blade.dm create mode 100644 code/game/objects/items/melee/types/transforming.dm create mode 100644 code/game/objects/items/melee/types/transforming/energy.dm create mode 100644 code/game/objects/items/melee/types/transforming/energy/axe.dm create mode 100644 code/game/objects/items/melee/types/transforming/energy/ionic_rapier.dm create mode 100644 code/game/objects/items/melee/types/transforming/energy/saber.dm create mode 100644 code/game/objects/items/melee/types/transforming/hfmachete.dm create mode 100644 code/game/objects/items/shield/shield.dm create mode 100644 code/game/objects/items/shield/types/shields_legacy.dm create mode 100644 code/game/objects/items/shield/types/shields_legacy_vr.dm create mode 100644 code/game/objects/items/shield/types/transforming.dm create mode 100644 code/game/objects/items/shield/types/transforming/energy.dm create mode 100644 code/game/objects/items/shield/types/transforming/telescopic.dm create mode 100644 code/game/objects/items/shield_projector/shield_matrix.dm rename code/{modules/shieldgen/directional_shield.dm => game/objects/items/shield_projector/shield_projector.dm} (81%) delete mode 100644 code/game/objects/items/weapons/melee/deflect.dm delete mode 100644 code/game/objects/items/weapons/melee/energy.dm delete mode 100644 code/game/objects/items/weapons/melee/melee.dm delete mode 100644 code/game/objects/items/weapons/shields.dm create mode 100644 code/game/objects/obj-construction.dm create mode 100644 code/game/objects/obj-defense.dm rename code/game/objects/{objs.dm => obj.dm} (100%) delete mode 100644 code/game/turfs/defense.dm delete mode 100644 code/game/turfs/simulated/wall/defense.dm create mode 100644 code/game/turfs/simulated/wall/wall-construction.dm create mode 100644 code/game/turfs/simulated/wall/wall-damage.dm create mode 100644 code/game/turfs/simulated/wall/wall-defense.dm create mode 100644 code/game/turfs/turf-construction.dm create mode 100644 code/modules/clothing/sets/armor/ablative.dm delete mode 100644 code/modules/mob/defense.dm rename code/modules/mob/living/carbon/human/{human_damage.dm => human-damage-legacy.dm} (98%) create mode 100644 code/modules/mob/living/carbon/human/human-damage.dm rename code/modules/mob/living/carbon/human/{human_defense.dm => human-defense-legacy.dm} (90%) rename code/modules/mob/living/{damage_procs.dm => living-damage.dm} (93%) rename code/modules/mob/living/{defense.dm => living-defense-legacy.dm} (86%) create mode 100644 code/modules/mob/living/silicon/silicon-damage.dm create mode 100644 code/modules/mob/living/silicon/silicon-defense-legacy.dm create mode 100644 code/modules/mob/mob-damage.dm create mode 100644 code/modules/mob/mob-defense.dm delete mode 100644 code/modules/power/fusion/fusion_particle_catcher.dm create mode 100644 code/modules/projectiles/README.md delete mode 100644 code/modules/projectiles/projectile-tracing.dm delete mode 100644 code/modules/projectiles/projectile.dm create mode 100644 code/modules/projectiles/projectile/README.md create mode 100644 code/modules/projectiles/projectile/helpers.dm delete mode 100644 code/modules/projectiles/projectile/pellets.dm create mode 100644 code/modules/projectiles/projectile/projectile-hitscan_visuals.dm create mode 100644 code/modules/projectiles/projectile/projectile-physics.dm create mode 100644 code/modules/projectiles/projectile/projectile-tracing.dm create mode 100644 code/modules/projectiles/projectile/projectile.dm create mode 100644 code/modules/projectiles/projectile/projectile_effect.dm rename code/modules/projectiles/projectile/{ => subtypes}/animate.dm (100%) create mode 100644 code/modules/projectiles/projectile/subtypes/arc.dm rename code/modules/projectiles/projectile/{ => subtypes}/beam/beams.dm (89%) rename code/modules/projectiles/projectile/{ => subtypes}/beam/beams_vr.dm (84%) rename code/modules/projectiles/projectile/{ => subtypes}/beam/blaster.dm (100%) rename code/modules/projectiles/projectile/{ => subtypes}/blob.dm (92%) rename code/modules/projectiles/projectile/{ => subtypes}/bullets.dm (84%) rename code/modules/projectiles/projectile/{ => subtypes}/bullets_vr.dm (100%) rename code/modules/projectiles/projectile/{ => subtypes}/change.dm (93%) rename code/modules/projectiles/projectile/{ => subtypes}/energy.dm (93%) rename code/modules/projectiles/projectile/{ => subtypes}/energy_vr.dm (100%) rename code/modules/projectiles/projectile/{ => subtypes}/explosive.dm (66%) rename code/modules/projectiles/projectile/{ => subtypes}/force.dm (70%) rename code/modules/projectiles/projectile/{ => subtypes}/hook.dm (95%) rename code/modules/projectiles/projectile/{ => subtypes}/magic.dm (94%) rename code/modules/projectiles/projectile/{ => subtypes}/magnetic.dm (72%) create mode 100644 code/modules/projectiles/projectile/subtypes/pellet.dm rename code/modules/projectiles/projectile/{ => subtypes}/reusable.dm (73%) rename code/modules/projectiles/projectile/{ => subtypes}/scatter.dm (100%) rename code/modules/projectiles/projectile/{ => subtypes}/special.dm (80%) rename code/modules/projectiles/projectile/{ => subtypes}/trace.dm (94%) create mode 100644 icons/effects/defensive/README.md create mode 100644 icons/effects/defensive/main_parry.dmi create mode 100644 icons/items/melee/basic.dmi create mode 100644 icons/items/melee/transforming.dmi create mode 100644 icons/items/shields/basic.dmi create mode 100644 icons/items/shields/transforming.dmi create mode 100644 sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg create mode 100644 sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg create mode 100644 sound/soundbytes/effects/combat/block-metal-on-wood-1.ogg create mode 100644 sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg create mode 100644 sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg create mode 100644 sound/soundbytes/effects/combat/block-wood-on-wood-2.ogg create mode 100644 sound/soundbytes/effects/combat/parry-cycle.ogg create mode 100644 sound/soundbytes/effects/combat/parry-metal1.ogg create mode 100644 sound/soundbytes/effects/combat/parry-metal2.ogg create mode 100644 sound/soundbytes/effects/combat/parry-wood1.ogg create mode 100644 sound/soundbytes/effects/combat/parry-wood2.ogg diff --git a/.github/workflows/size_labeling.yml b/.github/workflows/size_labeling.yml index d0dfe06c837..47d3ee779c4 100644 --- a/.github/workflows/size_labeling.yml +++ b/.github/workflows/size_labeling.yml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest steps: - name: size-label - uses: pascalgn/size-label-action@v0.4.3 + uses: pascalgn/size-label-action@v0.5.4 env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" IGNORED: "**/*.bundle.*\n**/*.chunk.*" # **/*.dmm\n diff --git a/citadel.dme b/citadel.dme index 898d2771d3f..ff216dc4a2a 100644 --- a/citadel.dme +++ b/citadel.dme @@ -46,7 +46,6 @@ #include "code\__DEFINES\damage_organs.dm" #include "code\__DEFINES\directional.dm" #include "code\__DEFINES\dna.dm" -#include "code\__DEFINES\event_args.dm" #include "code\__DEFINES\fonts.dm" #include "code\__DEFINES\frames.dm" #include "code\__DEFINES\gamemode.dm" @@ -162,6 +161,7 @@ #include "code\__DEFINES\controllers\timer.dm" #include "code\__DEFINES\datums\beam.dm" #include "code\__DEFINES\datums\design.dm" +#include "code\__DEFINES\datums\event_args.dm" #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\components\riding.dm" @@ -177,33 +177,34 @@ #include "code\__DEFINES\dcs\signals\elements\signals_element_conflict_checking.dm" #include "code\__DEFINES\dcs\signals\items\signals_inducer.dm" #include "code\__DEFINES\dcs\signals\modules\signals_module_fishing.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-buckling.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-context_system.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-defense.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-reachability.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-throwing.dm" +#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom-tool_system.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_appearance.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_attack.dm" -#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_buckling.dm" -#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_context_system.dm" -#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_defense.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_main.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_mouse.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movable.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_movement.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_radiation.dm" -#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_throwing.dm" -#include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_tool_system.dm" #include "code\__DEFINES\dcs\signals\signals_atom\signals_atom_x_act.dm" #include "code\__DEFINES\dcs\signals\signals_item\signals_item-interaction.dm" #include "code\__DEFINES\dcs\signals\signals_item\signals_item_economy.dm" #include "code\__DEFINES\dcs\signals\signals_item\signals_item_inventory.dm" #include "code\__DEFINES\dcs\signals\signals_item\signals_item_mouse.dm" #include "code\__DEFINES\dcs\signals\signals_item\signals_item_storage.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob-inventory.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob-perspective.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_appearance.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_combat.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_defense.dm" -#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_inventory.dm" +#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_legacy.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_main.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_mobility.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_organs.dm" -#include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_perspectiive.dm" #include "code\__DEFINES\dcs\signals\signals_mob\signals_mob_simple.dm" #include "code\__DEFINES\economy\category.dm" #include "code\__DEFINES\economy\currency.dm" @@ -293,6 +294,7 @@ #include "code\__DEFINES\projectiles\ammo_casing.dm" #include "code\__DEFINES\projectiles\ammo_magazine.dm" #include "code\__DEFINES\projectiles\guns.dm" +#include "code\__DEFINES\projectiles\projectile.dm" #include "code\__DEFINES\projectiles\system.dm" #include "code\__DEFINES\radiation\flags.dm" #include "code\__DEFINES\radiation\ignore.dm" @@ -380,7 +382,6 @@ #include "code\__HELPERS\vector.dm" #include "code\__HELPERS\verbs.dm" #include "code\__HELPERS\animations\attack.dm" -#include "code\__HELPERS\datastructs\armor.dm" #include "code\__HELPERS\datastructs\bodytypes.dm" #include "code\__HELPERS\datastructs\filters.dm" #include "code\__HELPERS\datastructs\ingredients.dm" @@ -391,6 +392,7 @@ #include "code\__HELPERS\files\paths.dm" #include "code\__HELPERS\files\walk.dm" #include "code\__HELPERS\game\depth.dm" +#include "code\__HELPERS\game\combat\arc.dm" #include "code\__HELPERS\game\turfs\line.dm" #include "code\__HELPERS\game\turfs\offsets.dm" #include "code\__HELPERS\graphs\astar.dm" @@ -639,10 +641,11 @@ #include "code\controllers\subsystem\persistence\modules\bulk_entity_serializers\trash.dm" #include "code\controllers\subsystem\processing\chemistry.dm" #include "code\controllers\subsystem\processing\circuits.dm" -#include "code\controllers\subsystem\processing\fastprocess.dm" #include "code\controllers\subsystem\processing\instruments.dm" #include "code\controllers\subsystem\processing\moving_cameras.dm" #include "code\controllers\subsystem\processing\obj.dm" +#include "code\controllers\subsystem\processing\process_20fps.dm" +#include "code\controllers\subsystem\processing\process_5fps.dm" #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\processing\projectiles.dm" #include "code\controllers\subsystem\processing\turfs.dm" @@ -698,6 +701,7 @@ #include "code\datums\announce\location\global.dm" #include "code\datums\announce\location\main_station.dm" #include "code\datums\announce\location\overmap_sector.dm" +#include "code\datums\armor\armor.dm" #include "code\datums\armor\core.dm" #include "code\datums\armor\mecha.dm" #include "code\datums\armor\military.dm" @@ -766,7 +770,10 @@ #include "code\datums\components\atoms\radioactive.dm" #include "code\datums\components\crafting\crafting.dm" #include "code\datums\components\crafting\guncrafting.dm" +#include "code\datums\components\items\passive_parry.dm" #include "code\datums\components\items\wielding.dm" +#include "code\datums\components\mobs\block_frame.dm" +#include "code\datums\components\mobs\parry_frame.dm" #include "code\datums\components\movable\aquarium.dm" #include "code\datums\components\movable\spatial_grid.dm" #include "code\datums\components\objects\slippery.dm" @@ -896,6 +903,7 @@ #include "code\datums\repositories\unique.dm" #include "code\datums\soundbytes\_soundbyte.dm" #include "code\datums\soundbytes\announcer.dm" +#include "code\datums\soundbytes\effects\combat.dm" #include "code\datums\soundbytes\effects\explosion.dm" #include "code\datums\soundbytes\effects\sparks.dm" #include "code\datums\soundbytes\effects\spray.dm" @@ -1012,18 +1020,20 @@ #include "code\game\area\station\security_areas.dm" #include "code\game\atoms\action_feedback.dm" #include "code\game\atoms\appearance.dm" +#include "code\game\atoms\atom-construction.dm" +#include "code\game\atoms\atom-damage.dm" +#include "code\game\atoms\atom-defense.dm" #include "code\game\atoms\atom.dm" #include "code\game\atoms\buckling.dm" -#include "code\game\atoms\defense.dm" #include "code\game\atoms\defense_old.dm" #include "code\game\atoms\materials.dm" #include "code\game\atoms\movement.dm" #include "code\game\atoms\say.dm" #include "code\game\atoms\vv.dm" +#include "code\game\atoms\movable\movable-throw.dm" #include "code\game\atoms\movable\movable.dm" #include "code\game\atoms\movable\movement.dm" #include "code\game\atoms\movable\pulling.dm" -#include "code\game\atoms\movable\throwing.dm" #include "code\game\atoms\movable\vv.dm" #include "code\game\atoms\movable\special\overlay.dm" #include "code\game\atoms\movable\special\render.dm" @@ -1450,16 +1460,19 @@ #include "code\game\magic\Uristrunes.dm" #include "code\game\objects\attacks.dm" #include "code\game\objects\banners.dm" -#include "code\game\objects\defense.dm" #include "code\game\objects\empulse.dm" #include "code\game\objects\explosion.dm" #include "code\game\objects\explosion_recursive.dm" +#include "code\game\objects\items-carry_weight.dm" +#include "code\game\objects\items-defense.dm" #include "code\game\objects\items-interaction.dm" #include "code\game\objects\items.dm" #include "code\game\objects\materials.dm" #include "code\game\objects\misc.dm" #include "code\game\objects\mob_spawner.dm" -#include "code\game\objects\objs.dm" +#include "code\game\objects\obj-construction.dm" +#include "code\game\objects\obj-defense.dm" +#include "code\game\objects\obj.dm" #include "code\game\objects\structures.dm" #include "code\game\objects\stumble_into_vr.dm" #include "code\game\objects\topic.dm" @@ -1644,6 +1657,15 @@ #include "code\game\objects\items\id_cards\station_ids.dm" #include "code\game\objects\items\id_cards\syndicate_ids.dm" #include "code\game\objects\items\janitorial\soap.dm" +#include "code\game\objects\items\melee\melee.dm" +#include "code\game\objects\items\melee\types\misc.dm" +#include "code\game\objects\items\melee\types\ninja_energy_blade.dm" +#include "code\game\objects\items\melee\types\transforming.dm" +#include "code\game\objects\items\melee\types\transforming\energy.dm" +#include "code\game\objects\items\melee\types\transforming\hfmachete.dm" +#include "code\game\objects\items\melee\types\transforming\energy\axe.dm" +#include "code\game\objects\items\melee\types\transforming\energy\ionic_rapier.dm" +#include "code\game\objects\items\melee\types\transforming\energy\saber.dm" #include "code\game\objects\items\robot\gripper.dm" #include "code\game\objects\items\robot\robot_items.dm" #include "code\game\objects\items\robot\robot_parts.dm" @@ -1654,6 +1676,14 @@ #include "code\game\objects\items\scanners\reagent.dm" #include "code\game\objects\items\scanners\slime.dm" #include "code\game\objects\items\scanners\spectrometer.dm" +#include "code\game\objects\items\shield\shield.dm" +#include "code\game\objects\items\shield\types\shields_legacy.dm" +#include "code\game\objects\items\shield\types\shields_legacy_vr.dm" +#include "code\game\objects\items\shield\types\transforming.dm" +#include "code\game\objects\items\shield\types\transforming\energy.dm" +#include "code\game\objects\items\shield\types\transforming\telescopic.dm" +#include "code\game\objects\items\shield_projector\shield_matrix.dm" +#include "code\game\objects\items\shield_projector\shield_projector.dm" #include "code\game\objects\items\stacks\fifty_spawner.dm" #include "code\game\objects\items\stacks\marker_beacons.dm" #include "code\game\objects\items\stacks\matter_synth.dm" @@ -1735,7 +1765,6 @@ #include "code\game\objects\items\weapons\RPD.dm" #include "code\game\objects\items\weapons\RSF.dm" #include "code\game\objects\items\weapons\scrolls.dm" -#include "code\game\objects\items\weapons\shields.dm" #include "code\game\objects\items\weapons\stunbaton.dm" #include "code\game\objects\items\weapons\surgery_tools.dm" #include "code\game\objects\items\weapons\swords_axes_etc.dm" @@ -1787,10 +1816,6 @@ #include "code\game\objects\items\weapons\material\thrown.dm" #include "code\game\objects\items\weapons\material\twohanded.dm" #include "code\game\objects\items\weapons\material\whetstone.dm" -#include "code\game\objects\items\weapons\melee\deflect.dm" -#include "code\game\objects\items\weapons\melee\energy.dm" -#include "code\game\objects\items\weapons\melee\melee.dm" -#include "code\game\objects\items\weapons\melee\misc.dm" #include "code\game\objects\items\weapons\tanks\jetpack.dm" #include "code\game\objects\items\weapons\tanks\tank.dm" #include "code\game\objects\items\weapons\tanks\tank_types.dm" @@ -1981,8 +2006,8 @@ #include "code\game\rendering\plane_masters\plane_render.dm" #include "code\game\turfs\baseturfs.dm" #include "code\game\turfs\change_turf.dm" -#include "code\game\turfs\defense.dm" #include "code\game\turfs\simulated.dm" +#include "code\game\turfs\turf-construction.dm" #include "code\game\turfs\turf.dm" #include "code\game\turfs\turf_ao.dm" #include "code\game\turfs\turf_flick_animations.dm" @@ -2016,9 +2041,11 @@ #include "code\game\turfs\simulated\flooring\flooring_traps.dm" #include "code\game\turfs\simulated\flooring\shuttle_vr.dm" #include "code\game\turfs\simulated\misc\fancy_shuttles.dm" -#include "code\game\turfs\simulated\wall\defense.dm" #include "code\game\turfs\simulated\wall\materials.dm" #include "code\game\turfs\simulated\wall\rot.dm" +#include "code\game\turfs\simulated\wall\wall-construction.dm" +#include "code\game\turfs\simulated\wall\wall-damage.dm" +#include "code\game\turfs\simulated\wall\wall-defense.dm" #include "code\game\turfs\simulated\wall\wall.dm" #include "code\game\turfs\simulated\wall\wall_attacks.dm" #include "code\game\turfs\simulated\wall\wall_icon.dm" @@ -2481,6 +2508,7 @@ #include "code\modules\clothing\masks\gasmask.dm" #include "code\modules\clothing\masks\miscellaneous.dm" #include "code\modules\clothing\masks\voice.dm" +#include "code\modules\clothing\sets\armor\ablative.dm" #include "code\modules\clothing\shoes\_shoes.dm" #include "code\modules\clothing\shoes\boots.dm" #include "code\modules\clothing\shoes\colour.dm" @@ -3423,7 +3451,6 @@ #include "code\modules\mob\animations.dm" #include "code\modules\mob\client.dm" #include "code\modules\mob\death.dm" -#include "code\modules\mob\defense.dm" #include "code\modules\mob\emote.dm" #include "code\modules\mob\floating_message.dm" #include "code\modules\mob\gender.dm" @@ -3435,6 +3462,8 @@ #include "code\modules\mob\life.dm" #include "code\modules\mob\login.dm" #include "code\modules\mob\logout.dm" +#include "code\modules\mob\mob-damage.dm" +#include "code\modules\mob\mob-defense.dm" #include "code\modules\mob\mob-keybind-triggers.dm" #include "code\modules\mob\mob.dm" #include "code\modules\mob\mob_defines.dm" @@ -3503,13 +3532,13 @@ #include "code\modules\mob\inventory\stripping.dm" #include "code\modules\mob\living\autohiss.dm" #include "code\modules\mob\living\butchering.dm" -#include "code\modules\mob\living\damage_procs.dm" #include "code\modules\mob\living\death.dm" #include "code\modules\mob\living\default_language.dm" -#include "code\modules\mob\living\defense.dm" #include "code\modules\mob\living\health.dm" #include "code\modules\mob\living\inventory.dm" #include "code\modules\mob\living\life.dm" +#include "code\modules\mob\living\living-damage.dm" +#include "code\modules\mob\living\living-defense-legacy.dm" #include "code\modules\mob\living\living-defense.dm" #include "code\modules\mob\living\living.dm" #include "code\modules\mob\living\living_defines.dm" @@ -3595,11 +3624,12 @@ #include "code\modules\mob\living\carbon\human\emote.dm" #include "code\modules\mob\living\carbon\human\examine.dm" #include "code\modules\mob\living\carbon\human\health.dm" +#include "code\modules\mob\living\carbon\human\human-damage-legacy.dm" +#include "code\modules\mob\living\carbon\human\human-damage.dm" +#include "code\modules\mob\living\carbon\human\human-defense-legacy.dm" #include "code\modules\mob\living\carbon\human\human-defense.dm" #include "code\modules\mob\living\carbon\human\human.dm" #include "code\modules\mob\living\carbon\human\human_attackhand.dm" -#include "code\modules\mob\living\carbon\human\human_damage.dm" -#include "code\modules\mob\living\carbon\human\human_defense.dm" #include "code\modules\mob\living\carbon\human\human_defines.dm" #include "code\modules\mob\living\carbon\human\human_helpers.dm" #include "code\modules\mob\living\carbon\human\human_modular_limbs.dm" @@ -3645,6 +3675,8 @@ #include "code\modules\mob\living\silicon\offense.dm" #include "code\modules\mob\living\silicon\perspective.dm" #include "code\modules\mob\living\silicon\say.dm" +#include "code\modules\mob\living\silicon\silicon-damage.dm" +#include "code\modules\mob\living\silicon\silicon-defense-legacy.dm" #include "code\modules\mob\living\silicon\silicon.dm" #include "code\modules\mob\living\silicon\subystems.dm" #include "code\modules\mob\living\silicon\translation.dm" @@ -4228,7 +4260,6 @@ #include "code\modules\power\fission\rods.dm" #include "code\modules\power\fusion\_setup.dm" #include "code\modules\power\fusion\fusion_circuits.dm" -#include "code\modules\power\fusion\fusion_particle_catcher.dm" #include "code\modules\power\fusion\fusion_reactions.dm" #include "code\modules\power\fusion\fusion_reagents.dm" #include "code\modules\power\fusion\magpower.dm" @@ -4326,8 +4357,6 @@ #include "code\modules\projectiles\gun.dm" #include "code\modules\projectiles\gun_item_renderer.dm" #include "code\modules\projectiles\gun_mob_renderer.dm" -#include "code\modules\projectiles\projectile-tracing.dm" -#include "code\modules\projectiles\projectile.dm" #include "code\modules\projectiles\ammunition\ammo_caliber.dm" #include "code\modules\projectiles\ammunition\ammo_casing.dm" #include "code\modules\projectiles\ammunition\ammo_magazine.dm" @@ -4425,25 +4454,32 @@ #include "code\modules\projectiles\guns\projectile\caseless\pellet.dm" #include "code\modules\projectiles\guns\projectile\sniper\collapsible_sniper.dm" #include "code\modules\projectiles\projectile\arc.dm" -#include "code\modules\projectiles\projectile\blob.dm" -#include "code\modules\projectiles\projectile\bullets.dm" -#include "code\modules\projectiles\projectile\bullets_vr.dm" -#include "code\modules\projectiles\projectile\change.dm" -#include "code\modules\projectiles\projectile\energy.dm" -#include "code\modules\projectiles\projectile\energy_vr.dm" -#include "code\modules\projectiles\projectile\explosive.dm" -#include "code\modules\projectiles\projectile\force.dm" -#include "code\modules\projectiles\projectile\hook.dm" -#include "code\modules\projectiles\projectile\magic.dm" -#include "code\modules\projectiles\projectile\magnetic.dm" -#include "code\modules\projectiles\projectile\pellets.dm" -#include "code\modules\projectiles\projectile\reusable.dm" -#include "code\modules\projectiles\projectile\scatter.dm" -#include "code\modules\projectiles\projectile\special.dm" -#include "code\modules\projectiles\projectile\trace.dm" -#include "code\modules\projectiles\projectile\beam\beams.dm" -#include "code\modules\projectiles\projectile\beam\beams_vr.dm" -#include "code\modules\projectiles\projectile\beam\blaster.dm" +#include "code\modules\projectiles\projectile\helpers.dm" +#include "code\modules\projectiles\projectile\projectile-hitscan_visuals.dm" +#include "code\modules\projectiles\projectile\projectile-physics.dm" +#include "code\modules\projectiles\projectile\projectile-tracing.dm" +#include "code\modules\projectiles\projectile\projectile.dm" +#include "code\modules\projectiles\projectile\projectile_effect.dm" +#include "code\modules\projectiles\projectile\subtypes\arc.dm" +#include "code\modules\projectiles\projectile\subtypes\blob.dm" +#include "code\modules\projectiles\projectile\subtypes\bullets.dm" +#include "code\modules\projectiles\projectile\subtypes\bullets_vr.dm" +#include "code\modules\projectiles\projectile\subtypes\change.dm" +#include "code\modules\projectiles\projectile\subtypes\energy.dm" +#include "code\modules\projectiles\projectile\subtypes\energy_vr.dm" +#include "code\modules\projectiles\projectile\subtypes\explosive.dm" +#include "code\modules\projectiles\projectile\subtypes\force.dm" +#include "code\modules\projectiles\projectile\subtypes\hook.dm" +#include "code\modules\projectiles\projectile\subtypes\magic.dm" +#include "code\modules\projectiles\projectile\subtypes\magnetic.dm" +#include "code\modules\projectiles\projectile\subtypes\pellet.dm" +#include "code\modules\projectiles\projectile\subtypes\reusable.dm" +#include "code\modules\projectiles\projectile\subtypes\scatter.dm" +#include "code\modules\projectiles\projectile\subtypes\special.dm" +#include "code\modules\projectiles\projectile\subtypes\trace.dm" +#include "code\modules\projectiles\projectile\subtypes\beam\beams.dm" +#include "code\modules\projectiles\projectile\subtypes\beam\beams_vr.dm" +#include "code\modules\projectiles\projectile\subtypes\beam\blaster.dm" #include "code\modules\projectiles\targeting\targeting_client.dm" #include "code\modules\projectiles\targeting\targeting_gun.dm" #include "code\modules\projectiles\targeting\targeting_mob.dm" @@ -4608,7 +4644,6 @@ #include "code\modules\sculpting\sculpting_block.dm" #include "code\modules\security levels\keycard authentication.dm" #include "code\modules\security levels\security levels.dm" -#include "code\modules\shieldgen\directional_shield.dm" #include "code\modules\shieldgen\emergency_shield.dm" #include "code\modules\shieldgen\energy_field.dm" #include "code\modules\shieldgen\energy_shield.dm" diff --git a/code/__DEFINES/_core.dm b/code/__DEFINES/_core.dm index 041c95757dc..2a6a70722d8 100644 --- a/code/__DEFINES/_core.dm +++ b/code/__DEFINES/_core.dm @@ -12,5 +12,8 @@ /// * put the stack trace in stack trace storage #define stack_trace(message) _stack_trace(message, __FILE__, __LINE__) +/// get variable if not null or +#define VALUE_OR_DEFAULT(VAL, DEFAULT) (isnull(VAL)? (DEFAULT) : (VAL)) + /// byond bug https://secure.byond.com/forum/?post=2072419 #define BLOCK_BYOND_BUG_2072419 diff --git a/code/__DEFINES/_flags/atom_flags.dm b/code/__DEFINES/_flags/atom_flags.dm index 8b10246acf6..8a7ccb364e1 100644 --- a/code/__DEFINES/_flags/atom_flags.dm +++ b/code/__DEFINES/_flags/atom_flags.dm @@ -59,7 +59,7 @@ DEFINE_BITFIELD(atom_flags, list( #define MOVABLE_NO_THROW_DAMAGE_SCALING (1<<1) /// Do not spin when thrown. #define MOVABLE_NO_THROW_SPIN (1<<2) -/// We are currently about to be yanked by a Moved() triggering a Move() +/// We are currently about to be yanked by a Moved(), Entered(), or Exited() triggering a Move() /// /// * used so things like projectile hitscans know to yield #define MOVABLE_IN_MOVED_YANK (1<<3) @@ -85,11 +85,13 @@ DEFINE_BITFIELD(movable_flags, list( #define ATOM_PASS_OVERHEAD_THROW (1<<7) /// let buckled mobs pass always #define ATOM_PASS_BUCKLED (1<<8) +/// "please don't interact with us" +#define ATOM_PASS_INCORPOREAL (1<<9) /// all actual pass flags / maximum pass #define ATOM_PASS_ALL (ATOM_PASS_TABLE | ATOM_PASS_GLASS | ATOM_PASS_GRILLE | \ ATOM_PASS_BLOB | ATOM_PASS_MOB | ATOM_PASS_THROWN | ATOM_PASS_CLICK | \ - ATOM_PASS_OVERHEAD_THROW | ATOM_PASS_BUCKLED) + ATOM_PASS_OVERHEAD_THROW | ATOM_PASS_BUCKLED | ATOM_PASS_INCORPOREAL) DEFINE_BITFIELD(pass_flags, list( BITFIELD(ATOM_PASS_TABLE), @@ -101,6 +103,7 @@ DEFINE_BITFIELD(pass_flags, list( BITFIELD(ATOM_PASS_CLICK), BITFIELD(ATOM_PASS_OVERHEAD_THROW), BITFIELD(ATOM_PASS_BUCKLED), + BITFIELD(ATOM_PASS_INCORPOREAL), )) DEFINE_BITFIELD(pass_flags_self, list( @@ -113,6 +116,7 @@ DEFINE_BITFIELD(pass_flags_self, list( BITFIELD(ATOM_PASS_CLICK), BITFIELD(ATOM_PASS_OVERHEAD_THROW), BITFIELD(ATOM_PASS_BUCKLED), + BITFIELD(ATOM_PASS_INCORPOREAL), )) //? /atom/movable movement_type - only one primary type should be on the atom at a time, but these are flags for quick checks. diff --git a/code/__DEFINES/_planes+layers.dm b/code/__DEFINES/_planes+layers.dm index 37eaaacbeb6..b38ab1a0fdd 100644 --- a/code/__DEFINES/_planes+layers.dm +++ b/code/__DEFINES/_planes+layers.dm @@ -178,6 +178,7 @@ #define STAIRS_LAYER (TURF_LAYER+0.5) /// Layer for stairs. #define DOOR_OPEN_LAYER (TURF_LAYER+0.7) /// Under all objects if opened. 2.7 due to tables being at 2.6. #define TABLE_LAYER (TURF_LAYER+0.8) /// Just under stuff that wants to be slightly below common objects. +/// Below this layer, projectiles won't collide with things unless it's a directly clicked target. #define PROJECTILE_HIT_THRESHOLD_LAYER 2.8 #define UNDER_JUNK_LAYER (TURF_LAYER+0.9) /// Things that want to be slightly below common objects. diff --git a/code/__DEFINES/callbacks.dm b/code/__DEFINES/callbacks.dm index 33eda1bac26..1c590425eb2 100644 --- a/code/__DEFINES/callbacks.dm +++ b/code/__DEFINES/callbacks.dm @@ -1,10 +1,12 @@ /// Arbitrary sentinel value for global proc callbacks #define GLOBAL_PROC "some_magic_bullshit" +/// Arbitrary sentinel value for making sure a callback didn't sleep +#define CALLBACK_SLEEP_SENTINEL "___THE PROC SLEPT___" /// A shorthand for the callback datum, [documented here](datum/callback.html) #define CALLBACK new /datum/callback -///Per the DM reference, spawn(-1) will execute the spawned code immediately until a block is met. +/// Per the DM reference, spawn(-1) will execute the spawned code immediately until a block is met. #define MAKE_SPAWN_ACT_LIKE_WAITFOR -1 -///Create a codeblock that will not block the callstack if a block is met. +/// Create a codeblock that will not block the callstack if a block is met. #define ASYNC spawn(MAKE_SPAWN_ACT_LIKE_WAITFOR) #define INVOKE_ASYNC(proc_owner, proc_path, proc_arguments...) \ diff --git a/code/__DEFINES/combat/armor.dm b/code/__DEFINES/combat/armor.dm index f57d5e4f6f2..67ede87ad78 100644 --- a/code/__DEFINES/combat/armor.dm +++ b/code/__DEFINES/combat/armor.dm @@ -59,14 +59,26 @@ GLOBAL_REAL_LIST(armor_enums) = list( ARMOR_ACID, ) +GLOBAL_REAL_LIST(armor_types) = list( + ARMOR_MELEE, + ARMOR_BULLET, + ARMOR_LASER, + ARMOR_ENERGY, + ARMOR_BOMB, + ARMOR_BIO, + ARMOR_RAD, + ARMOR_FIRE, + ARMOR_ACID, +) + //? --- armor tiers --- -#define ARMOR_TIER_DEFAULT 0 +#define ARMOR_TIER_DEFAULT ARMOR_TIER_BASELINE #define ARMOR_TIER_LAUGHABLE -3 #define ARMOR_TIER_LOW -2 #define ARMOR_TIER_BELOW -1 -#define ARMOR_TIER_NORMAL 0 +#define ARMOR_TIER_BASELINE 0 #define ARMOR_TIER_ABOVE 1 #define ARMOR_TIER_HIGH 2 #define ARMOR_TIER_OVERWHELMING 3 @@ -76,37 +88,49 @@ GLOBAL_REAL_LIST(armor_enums) = list( //? melee -#define MELEE_TIER_DEFAULT ARMOR_TIER_DEFAULT +#define MELEE_TIER_DEFAULT MELEE_TIER_MEDIUM #define MELEE_TIER_UNARMED_DEFAULT ARMOR_TIER_LOW #define MELEE_TIER_UNARMED_FISTS ARMOR_TIER_LOW #define MELEE_TIER_UNARMED_CLAW ARMOR_TIER_BELOW -#define MELEE_TIER_LIGHT ARMOR_TIER_DEFAULT +#define MELEE_TIER_LIGHT ARMOR_TIER_BASELINE #define MELEE_TIER_MEDIUM ARMOR_TIER_ABOVE #define MELEE_TIER_HEAVY ARMOR_TIER_HIGH #define MELEE_TIER_EXTREME ARMOR_TIER_OVERWHELMING //? bullet -#define BULLET_TIER_DEFAULT ARMOR_TIER_DEFAULT - -#define BULLET_TIER_LAUGHABLE ARMOR_TIER_BELOW //! super improvised rounds / pistols / whatever. -#define BULLET_TIER_LOW ARMOR_TIER_DEFAULT //! pistols -#define BULLET_TIER_MEDIUM ARMOR_TIER_ABOVE //! smgs -#define BULLET_TIER_HIGH ARMOR_TIER_HIGH //! rifles -#define BULLET_TIER_EXTREME ARMOR_TIER_OVERWHELMING //! lmgs, light mech weapons -#define BULLET_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS //! heavy mech weapons +#define BULLET_TIER_DEFAULT BULLET_TIER_MEDIUM + +/// super improvised rounds / pistols / whatever. +#define BULLET_TIER_LAUGHABLE ARMOR_TIER_BELOW +/// pistols +#define BULLET_TIER_LOW ARMOR_TIER_BASELINE +/// smgs +#define BULLET_TIER_MEDIUM ARMOR_TIER_ABOVE +/// rifles +#define BULLET_TIER_HIGH ARMOR_TIER_HIGH +/// lmgs, light mech weapons +#define BULLET_TIER_EXTREME ARMOR_TIER_OVERWHELMING +/// heavy mech weapons +#define BULLET_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS //? laser -#define LASER_TIER_DEFAULT ARMOR_TIER_DEFAULT - -#define LASER_TIER_LAUGHABLE ARMOR_TIER_BELOW //! improvised laser focis / etc -#define LASER_TIER_LOW ARMOR_TIER_DEFAULT //! low tier lasers -#define LASER_TIER_MEDIUM ARMOR_TIER_ABOVE //! laser carbines, energy guns, etc -#define LASER_TIER_HIGH ARMOR_TIER_HIGH //! x-ray rifles, snipers -#define LASER_TIER_EXTREME ARMOR_TIER_OVERWHELMING //! mech weapons, usualy -#define LASER_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS //! power transmission laser? +#define LASER_TIER_DEFAULT LASER_TIER_MEDIUM + +/// improvised laser focis / etc +#define LASER_TIER_LAUGHABLE ARMOR_TIER_BELOW +/// low tier lasers +#define LASER_TIER_LOW ARMOR_TIER_BASELINE +/// laser carbines, energy guns, etc +#define LASER_TIER_MEDIUM ARMOR_TIER_ABOVE +/// x-ray rifles, snipers +#define LASER_TIER_HIGH ARMOR_TIER_HIGH +/// mech weapons, usualy +#define LASER_TIER_EXTREME ARMOR_TIER_OVERWHELMING +/// power transmission laser? +#define LASER_TIER_RIDICULOUS ARMOR_TIER_RIDICULOUS //? --- armor calculations --- diff --git a/code/__DEFINES/combat/attack_types.dm b/code/__DEFINES/combat/attack_types.dm index 26989b0a23c..a0b4176d80e 100644 --- a/code/__DEFINES/combat/attack_types.dm +++ b/code/__DEFINES/combat/attack_types.dm @@ -9,3 +9,10 @@ #define ATTACK_TYPE_THROWN (1<<2) /// damage source is /mob #define ATTACK_TYPE_UNARMED (1<<3) +/// we're being contacted by something +/// +/// * used internally by parry frames, mostly +/// * damage source is null +#define ATTACK_TYPE_TOUCH (1<<4) +/// a damage instance created by a block / parry frame transmuting damage and passing it to the user +#define ATTACK_TYPE_DEFENSIVE_PASSTHROUGH (1<<5) diff --git a/code/__DEFINES/combat/explosions.dm b/code/__DEFINES/combat/explosions.dm index c8709ccfd8a..a60badb34c0 100644 --- a/code/__DEFINES/combat/explosions.dm +++ b/code/__DEFINES/combat/explosions.dm @@ -22,10 +22,9 @@ #define LEGACY_EXPLOSION_SEVERE_POWER EXPLOSION_CONSTANT_SEVERE #define LEGACY_EXPLOSION_MINOR_POWER EXPLOSION_CONSTANT_MINOR -#define LEGACY_EXPLOSION_DEVASTATE_INTEGRITY 1000 +#define LEGACY_EXPLOSION_DEVASTATE_INTEGRITY 500 #define LEGACY_EXPLOSION_SEVERE_INTEGRITY 180 #define LEGACY_EXPLOSION_MINOR_INTEGRITY 50 -#define LEGACY_EXPLOSION_INTEGRITY_MULT (0.01 * rand(50, 200)) // why the extra numbers? so if someone does weird math we don't out of bounds GLOBAL_REAL(_legacy_expowers, /list) = list( @@ -47,7 +46,6 @@ GLOBAL_REAL(_legacy_ex_atom_damage, /list) = list( 0 ) -#define LEGACY_EXPLOSION_ATOM_DAMAGE(P) (global._legacy_ex_atom_damage[P] * LEGACY_EXPLOSION_INTEGRITY_MULT) // this works out becuase epxlosions are 1-3 in legacy, so we can just use it as list indices #define LEGACY_EX_ACT(ATOM, POWER, TARGET) ATOM.legacy_ex_act(POWER, TARGET); ATOM.ex_act(_legacy_expowers[POWER]); diff --git a/code/__DEFINES/combat/shieldcall.dm b/code/__DEFINES/combat/shieldcall.dm index 889d27cad26..965076dd856 100644 --- a/code/__DEFINES/combat/shieldcall.dm +++ b/code/__DEFINES/combat/shieldcall.dm @@ -1,44 +1,198 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2023 Citadel Station developers. *// -//? keys for list/additional in atom_shieldcall +//* shieldcall return status *// -// None yet +/// terminate; either fully mitigated or we're done here +#define SHIELDCALL_FLAG_TERMINATE (1<<0) +/// terminate attacker swing entirely +/// +/// * usually you don't want this +#define SHIELDCALL_FLAG_CANCEL_SWING (1<<1) +/// stop attack effects +/// +/// * this is basically the [PROJECTILE_IMPACT_BLOCKED] for shieldcalls +/// * the thing hitting won't do direct damage but aftereffects like exploding rounds still explode +#define SHIELDCALL_FLAG_ATTACK_BLOCKED (1<<2) +/// attack redirected entirely +#define SHIELDCALL_FLAG_ATTACK_REDIRECT (1<<3) +/// attack goes through +/// +/// * both this and REDIRECT should be used if the original attack should keep going +/// * also use SHIELDCALL_FLAG_ATTACK_BLOCKED if original attack shouldn't process! otherwise it might be a pierce. +/// * example: reflecting a bullet +#define SHIELDCALL_FLAG_ATTACK_PASSTHROUGH (1<<4) +/// this attack already invoked a 'specialized' shieldcall proc, and is now invoking +/// the generalized atom_shieldcall() proc. +#define SHIELDCALL_FLAG_SECOND_CALL (1<<5) +/// asks the shieldcall nicely to not make a message +#define SHIELDCALL_FLAG_SUPPRESS_MESSAGE (1<<6) +/// asks the shieldcall nicely to not make a sound +#define SHIELDCALL_FLAG_SUPPRESS_SOUND (1<<7) +/// do not call armorcalls +#define SHIELDCALL_FLAG_SKIP_ARMORCALLS (1<<8) +/// do not call shieldcalls +#define SHIELDCALL_FLAG_SKIP_SHIELDCALLS (1<<9) +/// this is a passive / single parry +#define SHIELDCALL_FLAG_SINGLE_PARRY (1<<10) -//? shieldcall "struct" - this *must* match up with /atom/proc/run/check_shieldcall! +/// these flags mean to stop processing the attack +#define SHIELDCALL_FLAGS_BLOCK_ATTACK (SHIELDCALL_FLAG_ATTACK_BLOCKED) +/// these flags means that the attack should keep going after us, regardless of if we're hit +#define SHIELDCALL_FLAGS_PIERCE_ATTACK (SHIELDCALL_FLAG_ATTACK_PASSTHROUGH) +/// stop shieldcall chain +#define SHIELDCALL_FLAGS_SHOULD_TERMINATE (SHIELDCALL_FLAG_TERMINATE) +/// these flags means something happens / should happen +#define SHIELDCALL_FLAGS_SHOULD_PROCESS (SHIELDCALL_FLAGS_BLOCK_ATTACK | SHIELDCALL_FLAGS_PIERCE_ATTACK) + +/// flags set in a projectile reflect +/// +/// * you should be using /datum/shieldcall's bullet intercept / bullet signals if possible but this works too +#define SHIELDCALL_FLAGS_FOR_PROJECTILE_DEFLECT (SHIELDCALL_FLAG_TERMINATE | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_PASSTHROUGH) +/// flags set in a full block +#define SHIELDCALL_FLAGS_FOR_COMPLETE_BLOCK (SHIELDCALL_FLAG_TERMINATE | SHIELDCALL_FLAG_ATTACK_BLOCKED) + +//* Atom Shieldcall Args *// +//* *// +//* The shieldcall system is a very low-level 'damage instance' interception API. *// +//* It's used by the shieldcalls list in atoms to perform low-level intercepts, *// +//* as well as by default features like armor to perform their damage intercepts. *// +//* *// +//* For speed reasons, shieldcalls pass a single argument list down instead of *// +//* returning new lists every call, as shieldcalls need to be able to edit many *// +//* facets of a damage instance. *// +//* *// +//* Many of these are optional. *// +//* +//* Adding new arguments should be done sparingly. +//* Removing arguments requires every single proc with SHIELDCALL_PROC_HEADER *// +//* to be audited. *// /// damage amount #define SHIELDCALL_ARG_DAMAGE 1 /// damage type -#define SHIELDCALL_ARG_DAMTYPE 2 +#define SHIELDCALL_ARG_DAMAGE_TYPE 2 /// damage tier -#define SHIELDCALL_ARG_TIER 3 +#define SHIELDCALL_ARG_DAMAGE_TIER 3 /// armor flag -#define SHIELDCALL_ARG_FLAG 4 +#define SHIELDCALL_ARG_DAMAGE_FLAG 4 /// damage mode -#define SHIELDCALL_ARG_MODE 5 +#define SHIELDCALL_ARG_DAMAGE_MODE 5 /// attack type -#define SHIELDCALL_ARG_TYPE 6 -/// attacking weapon datum - same as used in armor +#define SHIELDCALL_ARG_ATTACK_TYPE 6 +/// attacking weapon datum +/// +/// * /obj/projectile if projectile +/// * /datum/unarmed_attack if unarmed melee +/// * /obj/item if item melee +/// * /datum/thrownthing if thrown +/// * null if touch #define SHIELDCALL_ARG_WEAPON 7 -/// list for additional data -#define SHIELDCALL_ARG_ADDITIONAL 8 -/// flags returned -#define SHIELDCALL_ARG_RETVAL 9 +/// flags returned from other shieldcalls +#define SHIELDCALL_ARG_FLAGS 8 +/// hit zone; this is usually a bodypart but this is also optional +#define SHIELDCALL_ARG_HIT_ZONE 9 +/// additional list returns; usually empty, but may exist +#define SHIELDCALL_ARG_ADDITIONAL 10 +/// the clickchain of a melee attack +/// +/// * this is passed in so you can grab data like who is doing it / who started the attack. +/// * filled in sometimes but not always if unarmed or item melee. +#define SHIELDCALL_ARG_CLICKCHAIN 11 + +/// A proc header with all the shieldcall args. +/// +/// * We use this so it's easy to check where shieldcall args are being used if shieldcalls need to be refactored. +#define SHIELDCALL_PROC_HEADER damage, damage_type, damage_tier, damage_flag, damage_mode, attack_type, datum/weapon, shieldcall_flags, hit_zone, list/additional, datum/event_args/actor/clickchain/clickchain -//? shieldcall additional data keys +//* list keys for list/additional in atom shieldcalls *// // none yet -//? shieldcall return flags - -/// abort further shieldcalls - hit is totally blocked or mitigated -#define SHIELDCALL_FULLY_MITIGATED (1<<0) -/// hit was partially blocked or mitigated -#define SHIELDCALL_PARTIALLY_MITIGATED (1<<1) -/// attack was forcefully missed e.g. by reactive teleport armor -#define SHIELDCALL_FORCED_MISS (1<<2) -/// fake; this is a check -#define SHIELDCALL_JUST_CHECKING (1<<3) -/// terminate further shieldcalls -#define SHIELDCALL_CEASE (1<<4) +//* Helpers to manipulate shieldcall args *// + +#define RESOLVE_SHIELDCALL_ATTACK_TEXT(SHIELDCALL) resolve_shieldcall_attack_text(SHIELDCALL) + +/proc/resolve_shieldcall_attack_text(list/shieldcall_args) + switch(shieldcall_args[SHIELDCALL_ARG_ATTACK_TYPE]) + if(ATTACK_TYPE_PROJECTILE) + . = shieldcall_args[SHIELDCALL_ARG_WEAPON] + if(ATTACK_TYPE_THROWN) + var/datum/thrownthing/thrown = shieldcall_args[SHIELDCALL_ARG_WEAPON] + if(thrown) + . = "the impact from [thrown.thrownthing]" + if(ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED) + . = "the force of the blow" + + if(!.) + . = "the attack" + +#define RESOLVE_SHIELDCALL_WEAPON_DESCRIPTOR(SHIELDCALL) resolve_shieldcall_weapon_descriptor(SHIELDCALL) + +/proc/resolve_shieldcall_weapon_descriptor(list/shieldcall_args) + switch(shieldcall_args[SHIELDCALL_ARG_ATTACK_TYPE]) + if(ATTACK_TYPE_MELEE) + var/obj/item/weapon = shieldcall_args[SHIELDCALL_ARG_WEAPON] + return "a [weapon]" + if(ATTACK_TYPE_PROJECTILE) + var/obj/projectile/proj = shieldcall_args[SHIELDCALL_ARG_WEAPON] + return "a [proj]" + if(ATTACK_TYPE_THROWN) + var/datum/thrownthing/thrown = shieldcall_args[SHIELDCALL_ARG_WEAPON] + return "a [thrown.thrownthing]" + if(ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED) + var/datum/unarmed_attack/style = shieldcall_args[SHIELDCALL_ARG_WEAPON] + return "a [style.attack_name]" + if(!.) + . = "an attack" + +//* handle_touch - contact_flags *//. + +// broad descriptors + +/// generally helpful actions (shake up, etc) +#define SHIELDCALL_CONTACT_FLAG_HELPFUL (1<<0) +/// potentially helpful but also potentially harmful actions +#define SHIELDCALL_CONTACT_FLAG_NEUTRAL (1<<1) +/// harmful actions +#define SHIELDCALL_CONTACT_FLAG_HARMFUL (1<<2) + +// categories + +/// medical items/techniques +#define SHIELDCALL_CONTACT_FLAG_MEDICAL (1<<23) + +//* handle_touch - contact_specific *// + +/// trying to inject someone +#define SHIELDCALL_CONTACT_SPECIFIC_SYRINGE_INJECTION "inject" +/// trying to shake someone up +#define SHIELDCALL_CONTACT_SPECIFIC_SHAKE_UP "help" +/// trying to drag someone +#define SHIELDCALL_CONTACT_SPECIFIC_PULL "pull" +/// trying to grab someone, **or** intensify a grab +#define SHIELDCALL_CONTACT_SPECIFIC_GRAB "grab" +/// trying to perform a surgery step - generic +#define SHIELDCALL_CONTACT_SPECIFIC_SURGERY "surgery" +/// being sprayed with chemicals +#define SHIELDCALL_CONTACT_SPECIFIC_CHEMICAL_SPRAY "spray" + +//* handle_touch - helpers *// + +/** + * Gets text to put in say, "blocks \the [text]" when someone has a blocked touch. + */ +#define RESOLVE_SHIELDCALL_TOUCH_TEXT(CONTACT_FLAGS, CONTACT_SPECIFIC) resolve_shieldcall_touch_text(CONTACT_FLAGS, CONTACT_SPECIFIC) + +/proc/resolve_shieldcall_touch_text(flags, specific) + switch(specific) + if(SHIELDCALL_CONTACT_SPECIFIC_SYRINGE_INJECTION) + return "syringe" + if(SHIELDCALL_CONTACT_SPECIFIC_SHAKE_UP) + return "help-up" + if(SHIELDCALL_CONTACT_SPECIFIC_GRAB) + return "grab" + if(SHIELDCALL_CONTACT_SPECIFIC_SURGERY) + return "operation" + if(SHIELDCALL_CONTACT_SPECIFIC_CHEMICAL_SPRAY) + return "spray" diff --git a/code/__DEFINES/datums/event_args.dm b/code/__DEFINES/datums/event_args.dm new file mode 100644 index 00000000000..2fa589bf4e6 --- /dev/null +++ b/code/__DEFINES/datums/event_args.dm @@ -0,0 +1,5 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +// make sure a var that is either event_args/actor or a single mob/user is event args; if it's not +#define E_ARGS_WRAP_USER_TO_ACTOR(USER) USER = ismob(USER)? new /datum/event_args/actor(USER) : USER diff --git a/code/__DEFINES/dcs/flags.dm b/code/__DEFINES/dcs/flags.dm index 47695369dcd..f69925f9698 100644 --- a/code/__DEFINES/dcs/flags.dm +++ b/code/__DEFINES/dcs/flags.dm @@ -1,12 +1,10 @@ /// Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. /// `parent` must not be modified if this is to be returned. /// This will be noted in the runtime logs -#define COMPONENT_INCOMPATIBLE (1<<0) -/// Returned in PostTransfer to prevent transfer, similar to `COMPONENT_INCOMPATIBLE` -#define COMPONENT_NOTRANSFER (1<<1) +#define COMPONENT_INCOMPATIBLE -1 /// Return value to cancel attaching -#define ELEMENT_INCOMPATIBLE (1<<0) +#define ELEMENT_INCOMPATIBLE -1 // /datum/element flags /// Causes the detach proc to be called when the host object is being deleted diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_buckling.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-buckling.dm similarity index 60% rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_buckling.dm rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-buckling.dm index 29d0bc4c9c0..640048265bb 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_buckling.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-buckling.dm @@ -1,16 +1,23 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// -////! buckling. flags are always buckling opflags, see __DEFINES/procs/buckling.dm -/// called on mob buclked: (mob, flags, user, semantic) +//* -- Buckling -- *// +//* flags are always buckling opflags *// +//* see __DEFINES/procs/buckling.dm *// + +//* These are called when the buckle/unbuckle op have already happened *// + +/// called on the atom the mob buckled to: (mob, flags, user, semantic) #define COMSIG_MOVABLE_MOB_BUCKLED "mob_buckled" -/// called on mob unbuckled: (mob, flags, user, semantic) +/// called on the atom the mob unbuckled from: (mob, flags, user, semantic) #define COMSIG_MOVABLE_MOB_UNBUCKLED "mob_unbuckled" -//! weird names to be more distinct from movable signals /// called on the mob that just got buckled: (mob, flags, user, semantic) -#define COMSIG_MOB_BUCKLED "buckled" +#define COMSIG_MOB_BUCKLED_TO "buckled" /// called on the mob that just got unbuckled: (mob, flags, user, semantic) -#define COMSIG_MOB_UNBUCKLED "unbuckled" +#define COMSIG_MOB_UNBUCKLED_FROM "unbuckled" + +//* For the below, component_block/force_buckle_operation works to varying degrees on varying procs. *// -//! For the below, component_block/force_buckle_operation works to varying degrees on varying procs. /// called during mob buckling: (mob, flags, user, semantic) #define COMSIG_MOVABLE_PRE_BUCKLE_MOB "pre_buckle_mob" /// called during can buckle mob: (mob, flags, user, semantic) @@ -24,15 +31,18 @@ /// called on mob resist buckle: (mob, semantic). Can't force, can only block. #define COMSIG_MOVABLE_MOB_RESIST_BUCKLE "mob_resist_buckle" /// called during can buckle on mob: (AM, flags, user, semantic, movable_opinion) -#define COMSIG_MOB_CAN_BUCKLE "mob_can_buckle" +#define COMSIG_MOB_CAN_BUCKLE_TO "mob_can_buckle" /// called during can unbuckle on mob: (AM, flags, user, semantic, movable_opinion) -#define COMSIG_MOB_CAN_UNBUCKLE "mob_can_unbuckle" +#define COMSIG_MOB_CAN_UNBUCKLE_FROM "mob_can_unbuckle" /// block mob buckle/unbuckle **silently** - #define COMPONENT_BLOCK_BUCKLE_OPERATION (1<<0) + #define SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION (1<<0) + /// + /// * has priority over [SIGNAL_RAISE_FORCE_BUCKLE_OPERATION] where applicable /// force allow buckle/unbuckled **silently** - #define COMPONENT_FORCE_BUCKLE_OPERATION (1<<1) + #define SIGNAL_RAISE_FORCE_BUCKLE_OPERATION (1<<1) + +//* Interactions; only blocking default interactions work. *// -//! Interactions; only blocking default interactions work. /// called from drag_drop_buckle_interaction: (A, user) #define COMSIG_MOVABLE_DRAG_DROP_BUCKLE_INTERACTION "drag_drop_buckle" /// called from click_unbuckle_interaction: (user) @@ -40,4 +50,4 @@ /// called from resist_unbuckle_interaction(M) #define COMSIG_MOVABLE_RESIST_UNBUCKLE_INTERACTION "resist_unbuckle" /// cancel rest of procs - #define COMPONENT_HANDLED_BUCKLE_INTERACTION (1<<0) + #define SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED (1<<0) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_context_system.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-context_system.dm similarity index 95% rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_context_system.dm rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-context_system.dm index f6efa7cee46..a5fb0b6a804 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_context_system.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-context_system.dm @@ -1,5 +1,5 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// +//* Copyright (c) 2024 silicons *// /// from base of /atom/proc/context_query: (list/options, datum/event_args/actor/e_args) /// options list is the same format as /atom/proc/context_query, insert directly to it. diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm new file mode 100644 index 00000000000..4d20c82cc26 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-defense.dm @@ -0,0 +1,37 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) Citadel Station Developers *// + +// todo: integrity signals? + +/// called from bullet_act(): (args) +#define COMSIG_ATOM_BULLET_ACT "bullet_act" + #define BULLET_ACT_ARG_PROJECTILE 1 + /// index of arg for PROJECTILE_IMPACT_* flags + #define BULLET_ACT_ARG_FLAGS 2 + #define BULLET_ACT_ARG_ZONE 3 + #define BULLET_ACT_ARG_EFFICIENCY 4 + +/// called from run_armorcalls(): (list/shieldcall_args, fake_attack) +/// +/// * This is an extremely low-level signal. Handle with care. +#define COMSIG_ATOM_ARMORCALL "atom-armorcalls" +/// called from run_shieldcalls(): (list/shieldcall_args, fake_attack) +/// +/// * This is an extremely low-level signal. Handle with care. +#define COMSIG_ATOM_SHIELDCALL "atom-shieldcalls" + +/// called from atom_shieldcall_handle_*l: (shieldcall_type) +/// +/// * use this for stuff that should spin up a full shield when attacked but is usually inactive. +/// * this is not used for base of /atom_shieldcall(), as it already has a signal! +#define COMSIG_ATOM_SHIELDCALL_ITERATION "atom-shieldcall-iteration" + /// atom_shieldcall_handle_unarmed_melee() + #define ATOM_SHIELDCALL_ITERATING_UNARMED_MELEE (1<<1) + /// atom_shieldcall_handle_item_melee() + #define ATOM_SHIELDCALL_ITERATING_ITEM_MELEE (1<<2) + /// atom_shieldcall_handle_bullet_act() + #define ATOM_SHIELDCALL_ITERATING_BULLET_ACT (1<<3) + /// atom_shieldcall_handle_touch() + #define ATOM_SHIELDCALL_ITERATING_TOUCH (1<<4) + /// atom_shieldcall_handle_throw_impact() + #define ATOM_SHIELDCALL_ITERATING_THROW_IMPACT (1<<5) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_throwing.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-throwing.dm similarity index 79% rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_throwing.dm rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-throwing.dm index 48c615ad3e2..ae1ba120b00 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_throwing.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-throwing.dm @@ -1,8 +1,9 @@ -//! Wanna add hitpush signals? TOO BAD, DON'T. Modify the thrownthing datum these pass in! +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// -/// from base of /atom/proc/throw_impacted: (AM, thrownthing) +/// from /atom/movable/proc/_throw_do_hit: (impactor, thrownthing) #define COMSIG_ATOM_THROW_IMPACTED "throw_impacted" -/// from base of /atom/movable/proc/throw_impact: (AM, thrownthing) +/// from /atom/movable/proc/_throw_do_hit: (blocker, thrownthing) #define COMSIG_MOVABLE_THROW_IMPACT "throw_impact" // This set of returns can be for both of the above! /// cancel further actions in this hit @@ -12,9 +13,9 @@ /// completely terminate throw silently immediately. Use if you're deleting the atom. #define COMPONENT_THROW_HIT_TERMINATE (1<<2) -/// called on throws landing on something: (landed_on, thrownthing) +/// from /atom/movable/proc/_throw_finalize: (landed_on, thrownthing) #define COMSIG_MOVABLE_THROW_LAND "throw_land" -/// called on something landing on us from a throw +/// from /atom/movable/proc/_throw_finalize: (landing_movable, thrownthing) #define COMSIG_ATOM_THROW_LANDED "throw_landed" // This set of returns can be for both of the above! /// cancel further actions diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_tool_system.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-tool_system.dm similarity index 91% rename from code/__DEFINES/dcs/signals/signals_atom/signals_atom_tool_system.dm rename to code/__DEFINES/dcs/signals/signals_atom/signals_atom-tool_system.dm index 5d9c7ab9c1d..3bb7af39fc4 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_tool_system.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom-tool_system.dm @@ -1,5 +1,5 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// +//* Copyright (c) 2024 silicons *// /// from base of _tool_act: (I, user, function, flags, hint) where I = item, e_args = clickchain data, function = tool behaviour, flags = tool operation flags, hint = set by dynamic tool system /// return CLICKCHAIN_COMPONENT_SIGNAL_HANDLED to abort normal tool_act handling. diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_defense.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_defense.dm deleted file mode 100644 index 89d0a90cfe3..00000000000 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_defense.dm +++ /dev/null @@ -1,5 +0,0 @@ -//* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// - -// todo: this file left intentionally empty since shieldcalls were moved to datums -// todo: add signals for integrity diff --git a/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm b/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm index 07044903673..77773426f55 100644 --- a/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm +++ b/code/__DEFINES/dcs/signals/signals_item/signals_item_inventory.dm @@ -1,10 +1,10 @@ -/// From base of obj/item/dropped: (mob/user, flags, atom/newLoc) +/// From base of obj/item/dropped: (mob/user, inv_op_flags, atom/new_loc) #define COMSIG_ITEM_DROPPED "item_drop" #define COMPONENT_ITEM_DROPPED_RELOCATE (1<<0) #define COMPONENT_ITEM_DROPPED_SUPPRESS_SOUND (1<<1) -/// From base of obj/item/pickup: (mob/user, flags, atom/oldLoc) +/// From base of obj/item/pickup: (mob/user, inv_op_flags, atom/old_loc) #define COMSIG_ITEM_PICKUP "item_pickup" -/// From base of obj/item/equipped(): (/mob/equipper, slot, accessory) +/// From base of obj/item/equipped(): (/mob/equipper, slot, inv_op_flags) #define COMSIG_ITEM_EQUIPPED "item_equip" -/// From base of obj/item/unequipped(): (/mob/unequipped, slot, accessory) +/// From base of obj/item/unequipped(): (/mob/unequipped, slot, inv_op_flags) #define COMSIG_ITEM_UNEQUIPPED "item_unequip" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_inventory.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-inventory.dm similarity index 85% rename from code/__DEFINES/dcs/signals/signals_mob/signals_mob_inventory.dm rename to code/__DEFINES/dcs/signals/signals_mob/signals_mob-inventory.dm index 1aff02f288d..ffdc89ec747 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_inventory.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-inventory.dm @@ -1,3 +1,6 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + /// A mob has just equipped an item. Called on [/mob] from base of [/obj/item/equipped()]: (/obj/item/equipped_item, slot, inv_op_flags) #define COMSIG_MOB_ITEM_EQUIPPED "mob_equipped_item" /// A mob has just unequipped an item. Called on [/mob] from base of [/obj/item/unequipped()]: (/obj/item/equipped_item, slot, inv_op_flags) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob-perspective.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-perspective.dm new file mode 100644 index 00000000000..971e9b9a00d --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob-perspective.dm @@ -0,0 +1,7 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/// emitted by reset_perspective, but only if the perspective needed switching: (perspective) +#define COMSIG_MOB_RESET_PERSPECTIVE "reset_perspective" +/// emitted by update_perspective: () +#define COMSIG_MOB_UPDATE_PERSPECTIVE "update_perspective" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_legacy.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_legacy.dm new file mode 100644 index 00000000000..7e43f572821 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_legacy.dm @@ -0,0 +1,13 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +// todo: burn all this shit with fire + +/// used by passive parry to detect +/// the entire mob item attack system is a dumpster fire and needs rewritten +/// for now, this is a signal with (item, user, hit_zone) +#define COMSIG_MOB_LEGACY_RESOLVE_ITEM_ATTACK "legacy-mob-item-resolve-attack" +/// used by passive parry to detect +/// signal with (user, list/params) +/// :skull: +#define COMSIG_MOB_LEGACY_ATTACK_HAND_INTERCEPT "legacy-mob-legacy-attack-hand" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_perspectiive.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_perspectiive.dm deleted file mode 100644 index ecaa7708fb5..00000000000 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_perspectiive.dm +++ /dev/null @@ -1,4 +0,0 @@ -/// emitted by reset_perspective, but only if the perspective needed switching: (perspective) -#define COMSIG_MOB_RESET_PERSPECTIVE "reset_perspective" -/// emitted by update_perspective: () -#define COMSIG_MOB_UPDATE_PERSPECTIVE "update_perspective" diff --git a/code/__DEFINES/event_args.dm b/code/__DEFINES/event_args.dm deleted file mode 100644 index cfbbc03c37c..00000000000 --- a/code/__DEFINES/event_args.dm +++ /dev/null @@ -1,7 +0,0 @@ -//? for /datum/event_args/actor - -#define WRAP_MOB_TO_ACTOR_EVENT_ARGS(VARNAME) VARNAME = ismob(VARNAME)? new /datum/event_args/actor(VARNAME) : VARNAME - -//? for /datum/event_args/actor/clickchain - -#define WRAP_MOB_TO_CLICKCHAIN_EVENT_ARGS(VARNAME) VARNAME = ismob(VARNAME)? new /datum/event_args/actor/clickchain(VARNAME) : VARNAME diff --git a/code/__DEFINES/math.dm b/code/__DEFINES/math.dm index d23d6c4f7d8..4fe4d17dee5 100644 --- a/code/__DEFINES/math.dm +++ b/code/__DEFINES/math.dm @@ -2,13 +2,22 @@ // This file is quadruple wrapped for your pleasure // ( +/// The value of the mathematical constant 'e' #define NUM_E 2.71828183 - +/// The value of sqrt(2); defined for speed #define SQRT_2 1.414214 - -#define M_PI (3.14159265) -///closer then enough -#define INFINITY (1.#INF) +/// The value of the mathematical constant 'Pi' +#define M_PI 3.14159265 + +/// A quick way to write (1.#INF) +#define INFINITY (1.#INF) + +/// the highest number that does not lose precision when only using the one's place +/// +/// * floating point has serious inaccuracies; after this limit, we can no longer track to one's place +/// * you usually don't have to worry about this if you're not writing anything that requires accuracy +/// * if you are, note that accuracy is lost even below this limit for fractionals. +/// * please look up how IEEE single precision floats work for more details. #define SHORT_REAL_LIMIT 16777216 //"fancy" math for calculating time in ms from tick_usage percentage and the length of ticks @@ -28,13 +37,21 @@ #define SIGN(x) ( (x)!=0 ? (x) / abs(x) : 0 ) /// ceil() +// +// todo: get rid of this, this is native now #define ROUND_UP(x) ( -round(-(x))) /// floor() +// +// todo: get rid of this, this is native now #define ROUND_DOWN(x) (round(x)) -// x to the nearest higher multiple of y +/// x to the nearest higher multiple of y +/// +/// * This is not replaced by native ceil(), as that is always CEILING(x, 1)! #define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) -// x to the nearest lower multiple of y +/// x to the nearest lower multiple of y +/// +/// * This is not replaced by native floor(), as that is always FLOOR(x, 1)! #define FLOOR(x, y) ( round((x) / (y)) * (y) ) // Similar to clamp but the bottom rolls around to the top and vice versa. min is inclusive, max is exclusive @@ -43,16 +60,15 @@ // Real modulus that handles decimals #define MODULUS_F(x, y) ( (x) - (y) * round((x) / (y)) ) -// Cotangent +/// Cotangent #define COT(x) (1 / tan(x)) - -// Secant +/// Secant #define SEC(x) (1 / cos(x)) - -// Cosecant +/// Cosecant #define CSC(x) (1 / sin(x)) // ArcTan2. Returns the degree between two points in an x and y system. +// todo: get rid of this, this is native now /proc/arctantwo(x1,y1,x2,y2) var/dx = x2-x1 var/dy = y2-y1 diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 34d76cb7f73..eb8d894025c 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -192,14 +192,6 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s #define NTOS_EMAIL_NOTIFALREADY 1 #define NTOS_EMAIL_NEWMESSAGE 2 - -// Special return values from bullet_act(). Positive return values are already used to indicate the blocked level of the projectile. -/// If the projectile should continue flying after calling bullet_act() -#define PROJECTILE_CONTINUE -1 -/// If the projectile should treat the attack as a miss (suppresses attack and admin logs) - only applies to mobs. -#define PROJECTILE_FORCE_MISS -2 - - // Vending stuff #define CAT_NORMAL 1 #define CAT_HIDDEN 2 @@ -275,8 +267,6 @@ var/list/economy_station_departments = list( ///The number of deciseconds in a day #define MIDNIGHT_ROLLOVER 864000 -///Needed for the R-UST port -#define PIXEL_MULTIPLIER WORLD_ICON_SIZE/32 /// Maximum effective value of client.view (According to DM references) #define MAX_CLIENT_VIEW 34 diff --git a/code/__DEFINES/procs/clickcode.dm b/code/__DEFINES/procs/clickcode.dm index a064ae31258..5eaa74894e1 100644 --- a/code/__DEFINES/procs/clickcode.dm +++ b/code/__DEFINES/procs/clickcode.dm @@ -19,7 +19,9 @@ */ /// stop the click chain from proceeding past this point; usually used if we're deleting or being inserted -/// DO NOT ABUSE THIS PROC TO INTERRUPT AFTERATTACKS WITHOUT CARE; this is NOT what this is here for! +/// +/// * This is an unconditional abort. +/// * DO NOT ABUSE THIS PROC TO INTERRUPT AFTERATTACKS WITHOUT CARE; this is NOT what this is here for! #define CLICKCHAIN_DO_NOT_PROPAGATE (1<<0) /// person can reach us normally #define CLICKCHAIN_HAS_PROXIMITY (1<<1) @@ -38,6 +40,17 @@ #define CLICKCHAIN_DO_NOT_ATTACK (1<<7) /// intercepted by component #define CLICKCHAIN_COMPONENT_SIGNAL_HANDLED (1<<8) +/// this is a reflex counterattack by something +/// +/// * used to prevent loops where both parties reactively attack each other instantly. +#define CLICKCHAIN_REFLEX_COUNTER (1<<9) +/// put this in if we should entirely abort the attack +#define CLICKCHAIN_FULL_BLOCKED (1<<10) + +/// check these for 'unconditional abort' +#define CLICKCHAIN_FLAGS_UNCONDITIONAL_ABORT (CLICKCHAIN_DO_NOT_PROPAGATE) +/// check these for 'abort attack' +#define CLICKCHAIN_FLAGS_ATTACK_ABORT (CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_FULL_BLOCKED) //! Reachability Depths - checked from level of DirectAccess and turf adjacency. /// default reachability depth diff --git a/code/__DEFINES/projectiles/projectile.dm b/code/__DEFINES/projectiles/projectile.dm new file mode 100644 index 00000000000..f87cff19119 --- /dev/null +++ b/code/__DEFINES/projectiles/projectile.dm @@ -0,0 +1,119 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* pre_impact(), impact(), bullet_act(), on_impact() impact_flags *// +/// pre_impact, bullet_act, on_impact are called in that order /// + +/// pointblank hit +#define PROJECTILE_IMPACT_POINT_BLANK (1<<0) +/// piercing hit; if returned, forces pierce for current impact +/// +/// * projectile has the right to perform special behavior like reducing damage after the impact +#define PROJECTILE_IMPACT_PIERCE (1<<1) +/// was blocked from directly hitting target +/// +/// * on impact probably shouldn't do direct damage, but explosive rounds will explode, etc +#define PROJECTILE_IMPACT_BLOCKED (1<<2) +/// if sensing this flag, **immediately** destroy the projectile without elaboration +/// +/// * overrides everything else +#define PROJECTILE_IMPACT_DELETE (1<<3) +/// do not hit; if this is present, we phase through without interaction +/// +/// * bullet_act(), and on_impact() will be cancelled by this. +/// * on_phase() is called instead to allow for standard hooks to fire +#define PROJECTILE_IMPACT_PHASE (1<<4) +/// signifies that the projectile is reflected. +/// +/// * projectile is not deleted like in PIERCING or PHASE +/// * fires off on_reflect() +#define PROJECTILE_IMPACT_REFLECT (1<<5) +/// we should pass through without interaction +/// +/// * bullet_act(), on_impact(), on_reflect(), and on_phase() will all be cancelled by this. +#define PROJECTILE_IMPACT_PASSTHROUGH (1<<6) +/// instructs piercing projectiles that support this +/// to not reduce damage because the impact was so trivial +/// compared to the force of the projectile +#define PROJECTILE_IMPACT_TRIVIAL (1<<7) +/// aborting duplicate impact due to already being in impacted list of projectile +#define PROJECTILE_IMPACT_DUPLICATE (1<<8) +/// passed from another bullet_act(), +/// like from a target stake to the mounted target +#define PROJECTILE_IMPACT_INDIRECTED (1<<9) +/// used by /impact() on projectile to signal to impact_loop() +/// that the projectile should keep impacting everything on the turf it was trying to hit +#define PROJECTILE_IMPACT_CONTINUE_LOOP (1<<10) +/// target was deleted, stop processing on target side but not projectile side +#define PROJECTILE_IMPACT_TARGET_DELETED (1<<11) +/// this is an impact (usually on the ground) from a projectile expiring +#define PROJECTILE_IMPACT_IS_EXPIRING (1<<12) +/// requests that no sound is made +/// +/// * this is separate from suppression / silencing projectile-side! +/// * notably, this is an absolute that should be always obeyed; while things like special round effects can ignore normal suppression. +#define PROJECTILE_IMPACT_SUPPRESS_SOUND (1<<13) +/// requests that no message is made +/// +/// * this is separate from suppression / silencing projectile-side! +/// * notably, this is an absolute that should be always obeyed; while things like special round effects can ignore normal suppression. +#define PROJECTILE_IMPACT_SUPPRESS_MESSAGE (1<<14) // phasing? +/// do not process damage normally +/// +/// * stops generic 'inflict damage instance' from being procced automatically. +#define PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE (1<<15) + +/// any of these means the projectile should delete immediately +#define PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE (PROJECTILE_IMPACT_DELETE) +/// any of these means the projectile should not impact +#define PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT (PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH) +/// any of these means don't just delete after hit +#define PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH (PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH | PROJECTILE_IMPACT_PIERCE) +/// any of these means the projectile should abort bullet_act +#define PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT (PROJECTILE_IMPACT_DELETE | PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH) +/// any of these means the projectile should abort bullet_act, but not on_impact() for the projectile +#define PROJECTILE_IMPACT_FLAGS_TARGET_ABORT (PROJECTILE_IMPACT_DELETE | PROJECTILE_IMPACT_REFLECT | PROJECTILE_IMPACT_PHASE | PROJECTILE_IMPACT_PASSTHROUGH | PROJECTILE_IMPACT_TARGET_DELETED) + +//* projectile_type bitfield *// + +//? base types; all projectiles should have one of these ?// + +/// kinetic matter, basically +#define PROJECTILE_TYPE_KINETIC (1<<0) +/// energy projectiles that aren't a beam +#define PROJECTILE_TYPE_ENERGY (1<<1) +/// particle beam, basically +#define PROJECTILE_TYPE_BEAM (1<<2) + +//? specific types; projectiles may have one or more of these in addition to the above ?// + +/// photonic energy, basically (yes yes lasers are unrealistic i don't care) +#define PROJECTILE_TYPE_PHOTONIC (1<<22) +/// exotic energy or exotic matter +#define PROJECTILE_TYPE_EXOTIC (1<<23) + +//? special types + +/// trace projectile, aka "always let this through shields so stuff knows to fire at it" +#define PROJECTILE_TYPE_TRACE (1<<24) + +DEFINE_BITFIELD_NEW(projectile_types, list( + /obj/projectile = list( + "projectile_type", + ), + /obj/structure/prop/prism = list( + "projectile_type", + "projectile_type_cant", + ), +), list( + BITFIELD_NEW("Base - Kinetic", PROJECTILE_TYPE_KINETIC), + BITFIELD_NEW("Base - Energy", PROJECTILE_TYPE_ENERGY), + BITFIELD_NEW("Base - Beam", PROJECTILE_TYPE_BEAM), + BITFIELD_NEW("Flag - Photonic Energy", PROJECTILE_TYPE_PHOTONIC), + BITFIELD_NEW("Flag - Exotic Energy / Matter", PROJECTILE_TYPE_EXOTIC), +)) + +//* helpers *// + +/// tiles per second to pixels per decisecond +#define PROJECTILE_SPEED_FOR_TPS(tiles) (tiles * WORLD_ICON_SIZE) diff --git a/code/__HELPERS/game/combat/arc.dm b/code/__HELPERS/game/combat/arc.dm new file mode 100644 index 00000000000..e51718e2f66 --- /dev/null +++ b/code/__HELPERS/game/combat/arc.dm @@ -0,0 +1,58 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/** + * Checks if an attacking atom is within the defensive arc of a defending atom. + * + * * This does not support pixel movement. + * * A null source is always inside defensive arc. + * + * todo: verify behavior. + * + * Attacking entity can be: + * + * * /obj/projectile - treated as projectile + * * /datum/thrownthing - treated as thrown + * * anything else - treated as an /atom-ish source. + * + * @params + * * defending - the defending atom + * * attacking - the attacking entity + * * arc - the arc to check + * * round_up_arc - if the attacking atom is not a projectile or something with angle sim, should we round their defensive angle up or down? + * * use_dir - use this dir, not the defending atom's dir + * + * @return TRUE if they're within arc, FALSE otherwise + */ +/proc/check_defensive_arc_tile(atom/defending, attacking, arc, round_up_arc, use_dir = defending.dir) + // clockwise from north + var/our_angle = dir2angle(use_dir) + // clockwise from north + var/their_angle + if(istype(attacking, /obj/projectile)) + // projectile source + var/obj/projectile/proj = attacking + // projectile angle var is clockwise from north + // turn it around to get the angle from our PoV + their_angle = (proj.angle + 180) % 360 + else + // atom-ish source + var/atom/atom_source + if(istype(attacking, /datum/thrownthing)) + var/datum/thrownthing/thrown = attacking + atom_source = thrown.thrownthing + else if(isatom(attacking)) + atom_source = attacking + else + return TRUE + their_angle = dir2angle(get_dir(defending, atom_source)) + // if we're rounding up our arc, we boost our arc since it's an atom source to nearest 45 + if(round_up_arc) + arc = CEILING(arc, 45) + // normalize it to +- of our angle + their_angle -= our_angle + if(their_angle > 180) + their_angle -= 360 + return abs(their_angle) <= arc + +// todo: pixel movement variant for overmaps and others. diff --git a/code/__HELPERS/lists/string.dm b/code/__HELPERS/lists/string.dm index c6317712253..2acdf2a131b 100644 --- a/code/__HELPERS/lists/string.dm +++ b/code/__HELPERS/lists/string.dm @@ -1,8 +1,10 @@ /** * Returns a list in plain english as a string. + * + * * input - (optional) list or null; if null, we use empty_text */ /proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) - var/total = input.len + var/total = length(input) if (!total) return "[nothing_text]" else if (total == 1) diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index f9f5a1e3a70..5851e9a63a3 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -2,7 +2,7 @@ return /obj/vehicle/get_mob() - return occupants + return SAFEPICK(occupants) /obj/vehicle_old/train/get_mob() return SAFEPICK(buckled_mobs) diff --git a/code/__HELPERS/type2type/type2type.dm b/code/__HELPERS/type2type/type2type.dm index ca1a9090335..5b82e5f88c1 100644 --- a/code/__HELPERS/type2type/type2type.dm +++ b/code/__HELPERS/type2type/type2type.dm @@ -133,7 +133,9 @@ return 10 /** - * Converts an angle (degrees) into an ss13 direction. + * Gets the direction of an angle, in degrees, that is clockwise of north + * + * todo: verify math and do documentation * * @params * * degree - angle clockwise of north @@ -154,10 +156,12 @@ return SOUTHWEST if (degree < 315) return WEST - return NORTH|WEST + return NORTHWEST /** - * Returns the north-zero clockwise angle in degrees, given a direction. + * Gets the angle, in degrees, clockwise of north of a direction + * + * @return angle */ /proc/dir2angle(direction) switch (direction) diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index cbfb0473ff9..20b933d326d 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -997,7 +997,7 @@ return FALSE if(/obj/item/pickaxe/plasmacutter) return 3800 - if(/obj/item/melee/energy) + if(/obj/item/melee/transforming/energy) return 3500 else return FALSE diff --git a/code/__global_init.dm b/code/__global_init.dm index 97bb1f766de..b9b52380fb0 100644 --- a/code/__global_init.dm +++ b/code/__global_init.dm @@ -33,3 +33,15 @@ var/datum/world_debug_enabler/world_debug_enabler = new if (debug_server) LIBCALL(debug_server, "auxtools_init")() enable_debugging() + debug_loop() + +/datum/world_debug_enabler/proc/debug_loop() + set waitfor = FALSE + debug_loop_impl() + +/** + * the sole job of this is keep ticking so the debug server can still do stuff while no clients are conencted + */ +/datum/world_debug_enabler/proc/debug_loop_impl() + while(TRUE) + sleep(world.tick_lag) diff --git a/code/controllers/subsystem/ai_movement.dm b/code/controllers/subsystem/ai_movement.dm index d9540ab26a0..619744ad82f 100644 --- a/code/controllers/subsystem/ai_movement.dm +++ b/code/controllers/subsystem/ai_movement.dm @@ -83,15 +83,14 @@ SUBSYSTEM_DEF(ai_movement) var/reschedule_delay = being_processed.move(++being_processed.movement_cycle) // check if we are still ticking; if not, we got ejected, so we abort as we don't need to eject or insert again if(buckets[bucket_offset] == being_processed) - // eject; we don't change being_processed.ticking_(next|previous) - if(being_processed.movement_bucket_next == being_processed) - buckets[bucket_offset] = null - else - buckets[bucket_offset] = being_processed.movement_bucket_next - being_processed.movement_bucket_next.movement_bucket_prev = being_processed.movement_bucket_prev - being_processed.movement_bucket_prev.movement_bucket_next = being_processed.movement_bucket_next - if(reschedule_delay) + // eject; we don't change being_processed.ticking_(next|previous) + if(being_processed.movement_bucket_next == being_processed) + buckets[bucket_offset] = null + else + buckets[bucket_offset] = being_processed.movement_bucket_next + being_processed.movement_bucket_next.movement_bucket_prev = being_processed.movement_bucket_prev + being_processed.movement_bucket_prev.movement_bucket_next = being_processed.movement_bucket_next // insert; we now set its ticking_(next|previous) // note that we don't do catchup var/inject_offset = ((now_index_raw + round(DS2TICKS(reschedule_delay))) % bucket_amount) + 1 diff --git a/code/controllers/subsystem/processing/fastprocess.dm b/code/controllers/subsystem/processing/fastprocess.dm deleted file mode 100644 index 9622e021469..00000000000 --- a/code/controllers/subsystem/processing/fastprocess.dm +++ /dev/null @@ -1,6 +0,0 @@ -//Fires five times every second. - -PROCESSING_SUBSYSTEM_DEF(fastprocess) - name = "Fast Processing" - wait = 2 - stat_tag = "FP" diff --git a/code/controllers/subsystem/processing/process_20fps.dm b/code/controllers/subsystem/processing/process_20fps.dm new file mode 100644 index 00000000000..3afd5b018d4 --- /dev/null +++ b/code/controllers/subsystem/processing/process_20fps.dm @@ -0,0 +1,7 @@ +/** + * Fires 20 times a second. Kind of on the nose, huh? + */ +PROCESSING_SUBSYSTEM_DEF(process_20fps) + name = "Processing - 20 fps" + wait = 0.5 + stat_tag = "P20" diff --git a/code/controllers/subsystem/processing/process_5fps.dm b/code/controllers/subsystem/processing/process_5fps.dm new file mode 100644 index 00000000000..05dc2d48d89 --- /dev/null +++ b/code/controllers/subsystem/processing/process_5fps.dm @@ -0,0 +1,7 @@ +/** + * Fires 5 times a second. Kind of on the nose, huh? + */ +PROCESSING_SUBSYSTEM_DEF(process_5fps) + name = "Processing - 5 FPS" + wait = 2 + stat_tag = "P5" diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm index 007def420e5..edab5fc5e61 100644 --- a/code/controllers/subsystem/throwing.dm +++ b/code/controllers/subsystem/throwing.dm @@ -390,6 +390,9 @@ SUBSYSTEM_DEF(throwing) /** * get damage scaling - default handling + * + * @params + * * target - (optional) thing being hit */ /datum/thrownthing/proc/get_damage_multiplier(atom/target) if(!resist) diff --git a/code/__HELPERS/datastructs/armor.dm b/code/datums/armor/armor.dm similarity index 96% rename from code/__HELPERS/datastructs/armor.dm rename to code/datums/armor/armor.dm index 929eb064b56..7465bc2fb1e 100644 --- a/code/__HELPERS/datastructs/armor.dm +++ b/code/datums/armor/armor.dm @@ -184,6 +184,16 @@ else return 0 +/** + * The big, bad proc that deals with inbound shieldcalls. + */ +/datum/armor/proc/handle_shieldcall(list/shieldcall_args, fake_attack) + shieldcall_args[SHIELDCALL_ARG_DAMAGE] = resultant_damage( + shieldcall_args[SHIELDCALL_ARG_DAMAGE], + shieldcall_args[SHIELDCALL_ARG_DAMAGE_TIER], + shieldcall_args[SHIELDCALL_ARG_DAMAGE_FLAG], + ) + /datum/armor/proc/resultant_damage(damage, tier, flag) switch(flag) if(ARMOR_MELEE) diff --git a/code/datums/callback.dm b/code/datums/callback.dm index 721d050b5c3..2087fd6ef44 100644 --- a/code/datums/callback.dm +++ b/code/datums/callback.dm @@ -70,6 +70,9 @@ if(usr) user = WEAKREF(usr) +/datum/callback/proc/operator""() + return "callback [object] ([ref(object)])[isdatum(object) ? " ([object.type])" : ""] args: \[[english_list(arguments)]\]" + /** * Invoke this callback * @@ -103,6 +106,19 @@ return call(delegate)(arglist(calling_arguments)) return call(object, delegate)(arglist(calling_arguments)) +/** + * Invoke this callback and crash if it sleeps. + * + * * Use when a callback should never sleep, as call() cannot be verified by static analysis. + */ +/datum/callback/proc/invoke_no_sleep(...) + . = CALLBACK_SLEEP_SENTINEL + ASYNC + . = Invoke(arglist(args)) + if(. == CALLBACK_SLEEP_SENTINEL) + . = null + CRASH("Callback [src] slept on a no-sleeping invoke.") + /** * Invoke this callback async (waitfor=false) * diff --git a/code/datums/components/atoms/fishing_spot.dm b/code/datums/components/atoms/fishing_spot.dm index 77e800f7d87..3e43fd412dc 100644 --- a/code/datums/components/atoms/fishing_spot.dm +++ b/code/datums/components/atoms/fishing_spot.dm @@ -1,12 +1,12 @@ // A thing you can fish in /datum/component/fishing_spot registered_type = /datum/component/fishing_spot - + /// Defines the probabilities and fish availibilty var/datum/fish_source/fish_source /datum/component/fishing_spot/Initialize(configuration) - if(!isatom(parent) || ((. = ..()) & COMPONENT_INCOMPATIBLE)) + if(!isatom(parent) || ((. = ..()) == COMPONENT_INCOMPATIBLE)) return COMPONENT_INCOMPATIBLE if(ispath(configuration, /datum/fish_source)) // Create new one of the given type diff --git a/code/datums/components/gps_signal.dm b/code/datums/components/gps_signal.dm index 5171dce1021..e54553a884b 100644 --- a/code/datums/components/gps_signal.dm +++ b/code/datums/components/gps_signal.dm @@ -43,7 +43,7 @@ GLOBAL_LIST_EMPTY(gps_transmitters) var/registered = FALSE /datum/component/gps_signal/Initialize(gps_tag = "COM0", disabled = FALSE) - if(!isatom(parent) || ((. = ..()) & COMPONENT_INCOMPATIBLE)) + if(!isatom(parent) || ((. = ..()) == COMPONENT_INCOMPATIBLE)) return COMPONENT_INCOMPATIBLE src.gps_tag = gps_tag src.disabled = disabled diff --git a/code/datums/components/horror_aura.dm b/code/datums/components/horror_aura.dm index ea835d87974..45581a60242 100644 --- a/code/datums/components/horror_aura.dm +++ b/code/datums/components/horror_aura.dm @@ -11,7 +11,7 @@ It also serves the purposes of portraying the Lore accurate effect of "Acausal L /datum/component/horror_aura/Initialize(radius) if(radius) src.radius = radius - if(. & COMPONENT_INCOMPATIBLE) + if(. == COMPONENT_INCOMPATIBLE) return else if(!istype(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/items/active_parry.dm b/code/datums/components/items/active_parry.dm new file mode 100644 index 00000000000..1a5dc7dadb9 --- /dev/null +++ b/code/datums/components/items/active_parry.dm @@ -0,0 +1,10 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) Citadel Station Developers *// + +/** + * generic parry provider on items + */ +/datum/component/active_parry + registered_type = /datum/component/active_parry + +// todo: default implementation via active defensive hotkey diff --git a/code/datums/components/items/passive_parry.dm b/code/datums/components/items/passive_parry.dm new file mode 100644 index 00000000000..678aa914dfc --- /dev/null +++ b/code/datums/components/items/passive_parry.dm @@ -0,0 +1,348 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) Citadel Station Developers *// + +/** + * Shieldcall used as a listener for [/datum/component/passive_parry] + */ +/datum/shieldcall/bound/passive_parry + expected_type = /datum/component/passive_parry + +/** + * generic parry provider on items + * + * this is effectively the old rng block system + * + * also known as: autoparry. + */ +/datum/component/passive_parry + registered_type = /datum/component/passive_parry + + /// passive parry data + var/datum/passive_parry/parry_data + /// callback to invoke before the parry is initiated + /// + /// * must return /datum/parry_frame or null. + /// * if it returns a parry frame, it'll override the frame provided by the passive parry datum + /// * if it returns null, it'll cancel the parry + /// * invoked, if existing, with (obj/item/parent, mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + /// * this allows you to construct a custom parry frame + /// * this will be null'd, not qdel'd, when the component is qdel'd + var/datum/callback/parry_intercept + /// our registered shieldcall + var/datum/shieldcall/bound/passive_parry/hooked_shieldcall + /// the mob we're registered with right now + var/mob/hooked + +/datum/component/passive_parry/Initialize(datum/passive_parry/data, datum/callback/intercept) + . = ..() + if(. == COMPONENT_INCOMPATIBLE) + return + if(!isitem(parent)) + return COMPONENT_INCOMPATIBLE + data = fetch_data(data) + if(!data) + stack_trace("invalid data") + return COMPONENT_INCOMPATIBLE + src.parry_data = data + src.parry_intercept = intercept + +/datum/component/passive_parry/Destroy() + parry_data = null // parry data holds no refs + parry_intercept = null // i'd hope this doesn't hold a ref to us. + return ..() + +/datum/component/passive_parry/proc/fetch_data(datum/passive_parry/datalike) + if(IS_ANONYMOUS_TYPEPATH(datalike)) + return new datalike + if(ispath(datalike)) + return resolve_passive_parry_data(datalike) + if(istype(datalike)) + return datalike + +/** + * About to start a parry. Resolve parry_frame datum. + */ +/datum/component/passive_parry/proc/ignite(atom/defending, attack_type, datum/weapon) + RETURN_TYPE(/datum/parry_frame) + if(parry_intercept) + return parry_intercept.invoke_no_sleep(parent, defending, attack_type, weapon, parry_data) + else + var/obj/item/item = parent + return item.passive_parry_intercept(defending, attack_type, weapon, parry_data) + +/datum/component/passive_parry/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equipped)) + RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_dropped)) + if(!hooked) + var/obj/item/item = parent + if(item.worn_mob()) + on_equipped(item, item.worn_mob(), item.worn_slot) + +/datum/component/passive_parry/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, COMSIG_ITEM_EQUIPPED) + if(hooked) + var/obj/item/item = parent + on_unequipped(item, hooked) + +/datum/component/passive_parry/proc/on_dropped(obj/item/source, inv_op_flags, atom/new_loc) + // delete on drop to save memory + qdel(src) + +/datum/component/passive_parry/proc/on_equipped(obj/item/source, mob/user, slot) + if(!check_slot(slot)) + return + if(hooked) + return + ASSERT(user) + hooked = user + hooked_shieldcall = new(src) + user.register_shieldcall(hooked_shieldcall) + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL_ITERATION, PROC_REF(shieldcall_iterating)) + +/datum/component/passive_parry/proc/on_unequipped(obj/item/source, mob/user) + ASSERT(user == hooked) + hooked = null + user.unregister_shieldcall(hooked_shieldcall) + QDEL_NULL(hooked_shieldcall) + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL_ITERATION, PROC_REF(shieldcall_iterating)) + +/datum/component/passive_parry/proc/check_slot(slot_id) + return islist(parry_data.parry_slot_id)? (slot_id in parry_data.parry_slot_id) : (!parry_data.parry_slot_id || (parry_data.parry_slot_id == slot_id)) + +/datum/component/passive_parry/proc/shieldcall_iterating(mob/source, shieldcall_type) + SIGNAL_HANDLER + ASSERT(source == hooked) + var/datum/passive_parry/data = fetch_data(parry_data) + // normal shieldcall handlers handle it + if(!data.parry_frame_simulated) + return + var/datum/parry_frame/resolved = ignite(source) + if(!resolved) + return + // for now, we only care about if they already have a frame + // in the future, maybe this can fire as long as we aren't the source of a parry frame on them. + // todo: cooldown enforcement + // todo: mobility enforcement + // todo: full parry swing cycle? + if(!source.GetComponent(/datum/component/parry_frame)) + source.AddComponent(/datum/component/parry_frame, resolved, data.parry_frame_timing) + +//* Bindings - Bullet *// + +/datum/shieldcall/bound/passive_parry/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args) + // todo: no support for fake attacks yet + if(fake_attack) + return + // this is a definite 'do as i say, not as i do' moment + // this works because the proc names and args and types are **exactly** matching + // this is why the procs are all together + // do NOT try this at home. + return bound:handle_bullet(arglist(args)) + +/datum/component/passive_parry/proc/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args) + var/datum/passive_parry/data = parry_data + if(data.parry_frame_simulated) + return + if(!prob(isnull(data.parry_chance_projectile) ? data.parry_chance_default : data.parry_chance_projectile)) + return + if(!check_defensive_arc_tile(defending, bullet_act_args[BULLET_ACT_ARG_PROJECTILE], data.parry_arc, !data.parry_arc_round_down)) + return + // - Projectile-specific - + var/obj/projectile/proj = bullet_act_args[BULLET_ACT_ARG_PROJECTILE] + if(!(proj.projectile_type & data.parry_projectile_types)) + return + // - End - + var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_PROJECTILE, bullet_act_args[BULLET_ACT_ARG_PROJECTILE]) + if(!resolved) + return + return resolved.handle_bullet(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, bullet_act_args, parent) + +//* Bindings - Melee *// + +/datum/shieldcall/bound/passive_parry/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args) + // todo: no support for fake attacks yet + if(fake_attack) + return + // this is a definite 'do as i say, not as i do' moment + // this works because the proc names and args and types are **exactly** matching + // this is why the procs are all together + // do NOT try this at home. + return bound:handle_item_melee(arglist(args)) + +/datum/component/passive_parry/proc/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args) + var/datum/passive_parry/data = parry_data + if(data.parry_frame_simulated) + return + if(!prob(isnull(data.parry_chance_melee) ? data.parry_chance_default : data.parry_chance_melee)) + return + if(!check_defensive_arc_tile(defending, e_args.performer, data.parry_arc, !data.parry_arc_round_down)) + return + var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_MELEE, weapon) + if(!resolved) + return + return resolved.handle_item_melee(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, weapon, e_args, parent) + +/datum/shieldcall/bound/passive_parry/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args) + // todo: no support for fake attacks yet + if(fake_attack) + return + // this is a definite 'do as i say, not as i do' moment + // this works because the proc names and args and types are **exactly** matching + // this is why the procs are all together + // do NOT try this at home. + return bound:handle_unarmed_melee(arglist(args)) + +/datum/component/passive_parry/proc/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args) + var/datum/passive_parry/data = parry_data + if(data.parry_frame_simulated) + return + if(!prob(isnull(data.parry_chance_melee) ? data.parry_chance_default : data.parry_chance_melee)) + return + if(!check_defensive_arc_tile(defending, e_args.performer, data.parry_arc, !data.parry_arc_round_down)) + return + var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_UNARMED, style) + if(!resolved) + return + return resolved.handle_unarmed_melee(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, style, e_args, parent) + +/datum/shieldcall/bound/passive_parry/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific) + // todo: no support for fake attacks yet + if(fake_attack) + return + // this is a definite 'do as i say, not as i do' moment + // this works because the proc names and args and types are **exactly** matching + // this is why the procs are all together + // do NOT try this at home. + return bound:handle_touch(arglist(args)) + +/datum/component/passive_parry/proc/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific) + var/datum/passive_parry/data = parry_data + if(data.parry_frame_simulated) + return + if(!prob(isnull(data.parry_chance_touch) ? data.parry_chance_default : data.parry_chance_touch)) + return + if(!check_defensive_arc_tile(defending, e_args.performer, data.parry_arc, !data.parry_arc_round_down)) + return + var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_TOUCH, null) + if(!resolved) + return + return resolved.handle_touch(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, e_args, contact_flags, contact_specific, parent) + +//* Bindings - Thrown *// + +/datum/shieldcall/bound/passive_parry/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown) + // todo: no support for fake attacks yet + if(fake_attack) + return + // this is a definite 'do as i say, not as i do' moment + // this works because the proc names and args and types are **exactly** matching + // this is why the procs are all together + // do NOT try this at home. + return bound:handle_throw_impact(arglist(args)) + +/datum/component/passive_parry/proc/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown) + var/datum/passive_parry/data = parry_data + if(data.parry_frame_simulated) + return + if(!prob(isnull(data.parry_chance_thrown) ? data.parry_chance_default : data.parry_chance_thrown)) + return + if(!check_defensive_arc_tile(defending, thrown, data.parry_arc, !data.parry_arc_round_down)) + return + var/datum/parry_frame/resolved = ignite(defending, ATTACK_TYPE_THROWN, thrown) + if(!resolved) + return + return resolved.handle_throw_impact(defending, shieldcall_returns | SHIELDCALL_FLAG_SINGLE_PARRY, fake_attack, data.parry_frame_efficiency, thrown, parent) + +//* Item *// + +/** + * Called by /datum/component/passive_parry when we're about to start up the parry frame + * Called if parry intercept callback isn't set. + * + * @params + * * defending - mob being defended + * * attack_type - (optional) attack type + * * weapon - (optional) the weapon + * * parry_data - (optional) the existing parry data + * + * @return parry frame datum to use, or null to cancel + */ +/obj/item/proc/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + return parry_data.parry_frame + +//* Data *// + +GLOBAL_LIST_EMPTY(passive_parry_data) +/** + * get a cached version of a passive paary datum + */ +/proc/resolve_passive_parry_data(datum/passive_parry/datalike) + if(isnull(datalike)) + return + if(IS_ANONYMOUS_TYPEPATH(datalike)) + return new datalike + if(istype(datalike)) + return datalike + if(!GLOB.passive_parry_data[datalike]) + GLOB.passive_parry_data[datalike] = new datalike + return GLOB.passive_parry_data[datalike] + +/** + * datum for holding data on passive parrying + */ +/datum/passive_parry + /// parry chance for harmful melee: [0, 100] + var/parry_chance_melee + /// parry chance for (seemingly) benign melee: [0, 100] + var/parry_chance_touch + /// parry chance for inbound projectile: [0, 100] + var/parry_chance_projectile + /// parry chance for inbound throw + var/parry_chance_thrown + /// default parry chance if one of the above is null + var/parry_chance_default = 0 + + /// passive parry arc + var/parry_arc = 180 + /// passive parry arc should round down for non-projectiles + /// + /// * at 136 (1 more than 135), having this TRUE means non-projectiles can hit them from behind as behind is 180. + /// * at 136 (1 more than 135), having this be FALSE means non-projectiles **cannot** hit them from behind as behind is 180. + var/parry_arc_round_down = TRUE + + /// valid slot ids; null for all, list for multiple, singular for single + var/parry_slot_id = SLOT_ID_HANDS + + /// projectile types we autoparry on + var/parry_projectile_types = ALL + + /// parry frame data to use by default + /// + /// * can be a typepath + /// * can be an anonymous typepath + /// * can be a datum + var/parry_frame = /datum/parry_frame/passive_block + /// simulate a full parry frame with carry-through and duration, or just run the frame once + var/parry_frame_simulated = FALSE + /// if not simulated, what's our efficiency? [0, 1] + /// + /// * only used if not simulated + var/parry_frame_efficiency = 1 + /// if simulated, how far in do we start? [0, infinity] + /// + /// * only used if simulated + var/parry_frame_timing = 0 + +/datum/passive_parry/New() + if(IS_ANONYMOUS_TYPEPATH(parry_frame)) + parry_frame = new parry_frame + else if(ispath(parry_frame)) + parry_frame = new parry_frame + else if(istype(parry_frame, /datum/parry_frame)) + else + CRASH("invalid parry frame") + +/datum/parry_frame/passive_block + parry_can_prevent_contact = TRUE diff --git a/code/datums/components/items/shield_block.dm b/code/datums/components/items/shield_block.dm new file mode 100644 index 00000000000..9e1adc21168 --- /dev/null +++ b/code/datums/components/items/shield_block.dm @@ -0,0 +1,11 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) Citadel Station Developers *// + +/** + * generic shield-like block provider on items + */ +/datum/component/shield_block + registered_type = /datum/component/shield_block + +// todo: default implementation via toggled defensive hotkey +// todo: default 'passive' mode for when you have it held but haven't toggled defensive mode. diff --git a/code/datums/components/items/wielding.dm b/code/datums/components/items/wielding.dm index 1294b37e5b2..96fc1e4bfe7 100644 --- a/code/datums/components/items/wielding.dm +++ b/code/datums/components/items/wielding.dm @@ -1,7 +1,7 @@ // todo: can element this by usign 3 signals instead of 2, one to receive a keybind signal. /datum/component/wielding registered_type = /datum/component/wielding - + /// hands needed var/hands /// lazylist @@ -16,7 +16,7 @@ /datum/component/wielding/Initialize(hands = 2, datum/callback/on_wield, datum/callback/on_unwield) if(!isitem(parent)) return COMPONENT_INCOMPATIBLE - if((. = ..()) & COMPONENT_INCOMPATIBLE) + if((. = ..()) == COMPONENT_INCOMPATIBLE) return src.hands = hands src.on_wield = on_wield @@ -91,6 +91,9 @@ /datum/component/wielding/proc/offhand_destroyed(obj/item/offhand/wielding/I) unwield() + +//* Offhands *// + /obj/item/offhand/wielding name = "wielding offhand" desc = "You shouldn't be able to see this." @@ -103,7 +106,8 @@ host = null return ..() -// item procs +//* Item Hooks *// + /obj/item/proc/on_wield(mob/user, hands) return diff --git a/code/datums/components/mobs/block_frame.dm b/code/datums/components/mobs/block_frame.dm new file mode 100644 index 00000000000..bbbbedd27b3 --- /dev/null +++ b/code/datums/components/mobs/block_frame.dm @@ -0,0 +1,75 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) Citadel Station Developers *// + +/** + * ## Active Defensives + * + * Datastructure for active block on mobs. + * + * * One, or more, may exist at a time; all of them will be shieldcall-registered, be very careful when doing this. + * * Items should generally not allow adding another packet while one is active + */ +/datum/component/block_frame + registered_type = /datum/component/block_frame + + /// active defensive data + var/datum/block_frame/active_block + /// current number of processed hits + var/hit_count = 0 + /// world.time of start + var/start_time + +/datum/shieldcall/bound/block_frame + expected_type = /datum/component/parry_frame + +// todo: default implementation of a hold-down blocking system. + +/** + * Datastructure for block data, now far more simplified. + * + * * Please avoid anonymous typing this where possible, this is a heavy datum and caching helps a lot. + * * The reason this is separate from parrying is because block system is far more focused on exact damage simulation, while parrying is focused on deflecting a hit and handling the effects of that. + * + * todo: this should be a serializable prototype + */ +/datum/block_frame + /// shield arc, in both CW/CCW from user facing direction + /// + /// * given RP doesn't have combat mode, you should really just keep this at 180 + /// * realistically the cutoffs are 45, 90, 135, and 180 for anything that's not a projectile as only those sim physics + var/block_arc = 180 + /// maximum block per attack instance + var/block_damage_max = INFINITY + /// damage block % above minimum + var/block_damage_ratio = 0 + /// damage block minimum + var/block_damage_min = 0 + /// if set, use this armor datum for processing how much damage to block + /// + /// * use this for tiered simulation + /// * [block_damage_max] is the only other variable used for calculations if this is set, all others are on armor already + /// * set to typepath or instance + var/datum/armor/block_via_armor + + /// attack types we are allowed to parry + var/block_attack_types = NONE + + /// if 100% of damage is blocked, do we set SHIELDCALL_BLOCKED and similar flags? + /// + /// * this means things like syringes would be blocked from injecting. + var/block_can_prevent_contact = FALSE + /// always add BLOCKED, even if not 100% mitigated / transmuted + var/block_always_prevents_contact = FALSE + + /// ratio [0, INFINITY] of inbound damage to convert to another type + var/block_transmute = 0 + /// damage type to transmute to + var/block_transmute_type = HALLOSS + /// damage flag the transmuted damage counts as; null = inherit from attack + /// + /// * only used if block_transmute_simulation is on + var/block_transmute_flag = null + /// the transmuted damage is directly applied with full melee sim, instead of just a damage instance + /// + /// * DO NOT TURN THIS ON WITHOUT GOOD REASON. Melee sim is several times more expensive than armor / low-level intercepts for damage instances. + var/block_transmute_simulation = FALSE diff --git a/code/datums/components/mobs/parry_frame.dm b/code/datums/components/mobs/parry_frame.dm new file mode 100644 index 00000000000..d3fd3c85a77 --- /dev/null +++ b/code/datums/components/mobs/parry_frame.dm @@ -0,0 +1,561 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) Citadel Station Developers *// + +/** + * ## Active Parry + * + * Datastructure for active parry on mobs. + * + * * One, or more, may exist at a time; all of them will be shieldcall-registered, be very careful when doing this. + * * Items should generally not allow adding another parry frame while one is active + */ +/datum/component/parry_frame + registered_type = /datum/component/parry_frame + + /// active defensive data + var/datum/parry_frame/active_parry + /// current number of processed hits + var/hit_count = 0 + /// world.time of start + var/start_time + /// world time of drop + var/drop_time + /// registered shieldcall + var/datum/shieldcall/bound/parry_frame/shieldcall + +/** + * * frame - the parry frame + * * kick_time_forwards - start this many deciseconds into the frame. + */ +/datum/component/parry_frame/Initialize(datum/parry_frame/frame, kick_time_forwards) + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + . = ..() + if(. == COMPONENT_INCOMPATIBLE) + return + src.active_parry = frame + src.start_time = world.time - kick_time_forwards + src.drop_time = src.start_time + max(frame.parry_timing_active, frame.parry_timing_perfect) + frame.parry_timing_stop + if(src.drop_time < world.time) + . = COMPONENT_INCOMPATIBLE + CRASH("attempted to start a parry that ended in the past.") + shieldcall = new(src) + shieldcall.tool_text = parent + var/delete_in = src.drop_time - world.time + QDEL_IN(src, delete_in) + // this is non-transferable, duh + new frame.parry_vfx(null, parent, frame) + +/datum/component/parry_frame/Destroy() + . = ..() + // shieldcall must be deleted after unregister + QDEL_NULL(shieldcall) + +/datum/component/parry_frame/RegisterWithParent() + var/atom/movable/AM = parent + AM.register_shieldcall(shieldcall) + +/datum/component/parry_frame/UnregisterFromParent() + var/atom/movable/AM = parent + AM.unregister_shieldcall(shieldcall) + +/datum/component/parry_frame/proc/on_parry(attack_type, datum/weapon, shieldcall_returns, efficiency) + ++hit_count + // check drop + if(hit_count > active_parry.parry_drop_after_hits) + qdel(src) + return + +//* -- Shieldcall -- *// + +/** + * Shieldcall used as a listener for [/datum/component/parry_frame] + */ +/datum/shieldcall/bound/parry_frame + expected_type = /datum/component/parry_frame + + /// text to describe the used tool, if any + var/tool_text + +//* -- Parry Frame -- *// + +/** + * Datastructure for parry data, now far more simplified. + * + * * Please avoid anonymous typing this where possible, this is a heavy datum and caching helps a lot. + * * While this is very close to /datum/block_frame, it is different in separate major ways. + * * Parrying tends to be more powerful and complex, as it's meant to simulate a very dynamic action. + * * Parrying is more expensive to deal with than blocking. + * + * todo: this should be a serializable prototype + * todo: estimated_severity for audiovisuals needs a revisit; sound is not linear. + */ +/datum/parry_frame + //* Arc *// + /// shield arc, in both CW/CCW from user facing direction + /// + /// * given RP doesn't have combat mode, you should really just keep this at 180 + /// * realistically the cutoffs are 45, 90, 135, and 180 for anything that's not a projectile as only those sim physics + var/parry_arc = 180 + /// should we round parry arc down for non-projectiles? + /// + /// * this means 179 can't cover behind us + /// * this is needed because non-projectiles don't have exact angles + var/parry_arc_round_down = TRUE + + //* Timing *// + /// spinup time + /// + /// * keep this at 0 in most cases + /// * the parry does nothing while it's spinning up + /// * parries landing on the tick this ends count as having spun up + /// * overrules both perfect and active timing + var/parry_timing_start = 0 SECONDS + /// perfect time + /// + /// * this is the amount of time we are a perfect parry after tick 0 + /// * this means that this overlaps with [parry_timing_start]! + /// * this is done for performance reasons. + /// * parries landing on the tick this ends still count as perfect + /// * overrules active timing + /// * overruled by start timing + var/parry_timing_perfect = 0 SECONDS + /// no-falloff time + /// + /// * this is the amount of time we are at full [parry_efficiency_active] after tick 0 + /// * this means that this overlaps with both [parry_timing_start] and [parry_timing_perfect]. + /// * this is done for performance reasons. + /// * parries landing on the tick this ends still count as fully active + /// * overruled by start and perfect timing + var/parry_timing_active = 0 SECONDS + /// end time + /// + /// * this is the amount of time we are still active after [parry_timing_active] or [parry_timing_perfect], whatever is longer + /// * efficiency linearly drops from active efficiency to 0 during this time + /// * the parry immediately drops after + /// * parries landing on the tick this ends are dropped + var/parry_timing_stop = 0 SECONDS + + //* Attack Types *// + /// attack types we are allowed to parry + var/parry_attack_types = NONE + + //* Efficiency *// + /// parry efficiency at perfect; [0, 1] + /// + /// * parry efficiency is ratio of damage to block + var/parry_efficiency_perfect = 1 + /// parry efficiency at active; [0, 1] + /// + /// * parry efficiency is ratio of damage to block + var/parry_efficiency_active = 1 + /// minimum efficiency to drop to + var/parry_efficiency_floor = 0 + /// parry efficiency at which we count as a full block + var/parry_efficiency_blocked = 1 + /// parry efficiency at which redirection occurs + var/parry_efficiency_redirection = 1 + + //* Defender Effects *// + /// action-lock the defender while parrying + // todo: implement + var/parry_lock_defender = TRUE + /// drop action-lock on defender when a parry succeeds + // todo: implement + var/parry_free_defender_on_success = TRUE + + //* Configuration *// + /// immediately drop the parry after this many hits + var/parry_drop_after_hits = 1 + + //* Counter Effects *// + /// counterattack on hit + /// + /// * keep this off, this is a good exercise in 'just because you can doesn't mean you should' + // todo: implement + var/parry_counter_attack = FALSE + /// status effects to apply on hit to attacker + /// + /// supports: + /// * /datum/status_effect status effects; associate to duration + /// + /// does not support: + /// * any status effect supertype that isn't listed above. right now, that's grouped and stacking. + var/list/parry_counter_effects + + //* Counter Effects - Projectiles / Vector *// + /// default handling: reflect attack types + /// + /// * yeah you probably shouldn't put anything other than ATTACK_TYPE_PROJECTILE in here. + var/parry_redirect_attack_types = NONE + /// default handling: reflect attack back at attacker + /// + /// * yeah you probably should leave this off + var/parry_redirect_return_to_sender = FALSE + /// redirection arc CW/CCW of angle of incidence + /// + /// * if return_to_sender is off, this is the valid arc from attack source it can be reflected to + /// * if return_to_sender is on, this is the arc in error from attack source we can reflect to + var/parry_redirect_arc = 45 + + //* Defense - Damage *// + /// if 100% of damage is blocked, do we set SHIELDCALL_BLOCKED and similar flags? + /// + /// * this means things like syringes would be blocked from injecting. + var/parry_can_prevent_contact = FALSE + /// always add BLOCKED, even if not 100% mitigated / transmuted + var/parry_always_prevents_contact = FALSE + /// maximum damage blocked per attack instance + // todo: implement + var/parry_damage_max = INFINITY + + //* Defense - Transmute *// + // todo: implement + /// ratio [0, INFINITY] of **blocked** damage to convert to another type + var/parry_transmute = 0 + /// damage type to transmute to; null to default to attacking damage type + var/parry_transmute_type = null + /// damage tier used for transmuted damage; null to default to attacking tier + var/parry_transmute_tier = null + /// damage mode used for transmuted damage; null to default to attacking mode + var/parry_transmute_mode = null + /// damage flag the transmuted damage counts as; null = inherit from attack + var/parry_transmute_flag = null + /// the transmuted damage should be simulated as close to a proper melee hit as possible, + /// instead of just going through run_damage_instance() + /// + /// * DO NOT TURN THIS ON WITHOUT GOOD REASON. Melee sim is several times more expensive than armor / low-level intercepts for damage instances. + /// * Currently does nothing, as we do not have a way to simulate a standard melee hit arbitrarily without side effects. + var/parry_transmute_simulation = FALSE + + //* Defender Cooldown *// + /// hard cooldown to apply to parrying with the thing parrying wth + // todo: implement + var/parry_cooldown_tool = 2 SECONDS + /// hard cooldown to apply to parrying at all as the mob + // todo: implement + var/parry_cooldown_user = 0 SECONDS + /// is the parry cooldown ignored if a successful parry was made + // todo: implement + var/parry_cooldown_on_success = FALSE + + //* Audiovisuals & Feedback *// + /// a sound, or a list of sounds that can be played when we're hit + /// list can be weighted by associated number for relative chance + /// + /// * sound can be a file + /// * sound can be a /datum/soundbyte typepath or instance + var/list/parry_sfx = /datum/soundbyte/grouped/metal_parry + /// a typepath of /atom/movable/parry_frame to use as our visual; this is placed in the defending atom's vis_contents + var/parry_vfx = /atom/movable/render/parry_frame/default + /// "[person] [start_verb] with [item]" + // todo: implement + var/start_verb = "shifts into a defensive stance" + /// "[person] [block_verb] [attack_source_descriptor] with [item]" + var/block_verb = "parries" + /// "[person] [deflect_verb] [attack_source_descriptor] with [item]" + /// + /// * used if an attack was redirected and not just blocked + var/deflect_verb = "deflects" + +/** + * * 0 if in spinup + * * perfect efficiency if in perfect (from 0) + * * active efficiency if in active (from 0) + * * linear falloff to 0 if in spindown (from active time end) + */ +/datum/parry_frame/proc/calculate_parry_efficiency(time_into_parry) + if(time_into_parry < parry_timing_start) + return 0 + if(time_into_parry <= parry_timing_perfect) + return parry_timing_perfect + if(time_into_parry <= parry_timing_active) + return parry_timing_active + var/drop_time = parry_timing_active + parry_timing_stop + if(time_into_parry >= drop_time) + return 0 + var/time_into_drop = time_into_parry - parry_timing_active + return parry_efficiency_active - (parry_efficiency_active - parry_efficiency_floor) * ((time_into_drop) / parry_timing_stop) + +/** + * @params + * * time_into_parry - ds elapsed since parry start + * + * @return TRUE / FALSE + */ +/datum/parry_frame/proc/is_parry_perfect(time_into_parry) + return (time_into_parry >= parry_timing_start) && (time_into_parry <= parry_timing_perfect) + +/** + * Called when parrying something + * + * @params + * * defending - thing being defended against an attack + * * attack_type - (optional) type of attack + * * efficiency - (optional) parry efficiency + * * weapon - (optional) incoming weapon, depends on ATTACK_TYPE + * * shieldcall_flags - (optional) the attack's shieldcall flags + * * severity - (optional) arbitrary 0 to 100 severity of how bad the hit is estimated to be + * * attack_source_descriptor - (optional) text, or an entity to describe the attack. entities will be automatically handled. + * * tool_text - (optional) text to describe the parry tool + */ +/datum/parry_frame/proc/perform_audiovisuals(atom/defending, attack_type, efficiency, datum/weapon, shieldcall_flags, severity = 75, attack_source_descriptor, tool_text) + playsound(defending, (islist(parry_sfx) && length(parry_sfx)) ? pick(parry_sfx) : parry_sfx , severity, TRUE) + new parry_vfx(null, defending, src, shieldcall_flags & SHIELDCALL_FLAG_SINGLE_PARRY) + + var/parry_verb + if((attack_type & parry_redirect_attack_types) && (efficiency >= parry_efficiency_redirection)) + parry_verb = deflect_verb + else + parry_verb = block_verb + + var/attack_descriptor = "the attack" + if(attack_source_descriptor) + // generic processing + attack_descriptor = "\the [attack_source_descriptor]" + // item processing + if(isitem(attack_source_descriptor)) + var/obj/item/item_source_descriptor = attack_source_descriptor + var/mob/mob_holding_item = item_source_descriptor.worn_mob() + if(mob_holding_item) + attack_descriptor = "[mob_holding_item]'s [item_source_descriptor]" + + defending.visible_message( + SPAN_DANGER("[defending] [parry_verb] [attack_descriptor][tool_text && " with \the [tool_text]"]!"), + ) + +/** + * Called when something is parried + * + * @params + * * defending - thing being defended against an attack + * * attack_type - (optional) type of attack + * * efficiency - (optional) parry efficiency + * * weapon - (optional) incoming weapon, depends on ATTACK_TYPE + * * shieldcall_flags - (optional) the attack's shieldcall flags + * * e_args - (optional) for melee, the event args of the attack + * + * @return SHIELDCALL_* flags; these override the caller's! + */ +/datum/parry_frame/proc/perform_aftereffects(atom/defending, attack_type, efficiency, datum/weapon, shieldcall_flags, datum/event_args/actor/clickchain/e_args) + . = shieldcall_flags + + var/atom/movable/aggressor + + // detect aggressor + if(istype(weapon, /obj/projectile)) + var/obj/projectile/weapon_proj = weapon + aggressor = weapon_proj.firer + else if(e_args) + aggressor = e_args.performer + else if(istype(weapon, /datum/thrownthing)) + var/datum/thrownthing/weapon_thrown = weapon + aggressor = weapon_thrown.thrower + + switch(attack_type) + if(ATTACK_TYPE_PROJECTILE) + var/obj/projectile/proj = weapon + if((parry_redirect_attack_types & ATTACK_TYPE_PROJECTILE) && (efficiency >= parry_efficiency_redirection)) + var/outgoing_angle + if(parry_redirect_return_to_sender && aggressor) + outgoing_angle = arctan(aggressor.y - defending.y, aggressor.x - defending.x) + else + // todo: this should be angle of incidence maybe? + outgoing_angle = turn(proj.angle, 180) + rand(-parry_redirect_arc, parry_redirect_arc) + proj.set_angle(outgoing_angle) + . |= SHIELDCALL_FLAG_ATTACK_REDIRECT + + // effects that are only valid if we have a retaliation target + if(aggressor) + if(ismob(aggressor)) + var/mob/aggressor_mob = aggressor + for(var/key in parry_counter_effects) + var/value = parry_counter_effects[key] + if(ispath(key, /datum/status_effect)) + aggressor_mob.apply_status_effect(key, value) + + if(parry_counter_attack) + // RIP AND TEAR + // todo: counterattacks are offline due to clickchain not being entirely nailed down + // we need clickchain flags to be actually checked in active block/parry so that REFLEX_COUNTER flagged clicks don't get re-parried. + pass() + +/** + * Called to transmute an instance of damage into another instance of damage and apply it to the defender. + */ +/datum/parry_frame/proc/perform_transmuted_damage(atom/defending, damage, damage_tier, damage_type, damage_mode, damage_flag, hit_zone, shieldcall_flags) + // todo: parry_transmute_simulation + defending.run_damage_instance( + parry_transmute * damage, + isnull(parry_transmute_type) ? damage_type : parry_transmute_type, + isnull(parry_transmute_tier) ? damage_tier : parry_transmute_tier, + isnull(parry_transmute_flag) ? damage_flag : parry_transmute_flag, + isnull(parry_transmute_mode) ? damage_mode : parry_transmute_mode, + ATTACK_TYPE_DEFENSIVE_PASSTHROUGH, + null, + SHIELDCALL_FLAG_SECOND_CALL, + hit_zone, + ) + +//* Bindings - Bullet *// + +/datum/shieldcall/bound/parry_frame/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args) + var/datum/component/parry_frame/frame = bound + if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_PROJECTILE)) + return + if(!check_defensive_arc_tile(defending, bullet_act_args[BULLET_ACT_ARG_PROJECTILE], frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down)) + return + var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time) + . = frame.active_parry.handle_bullet(defending, shieldcall_returns, fake_attack, efficiency, bullet_act_args, tool_text) + frame.on_parry(ATTACK_TYPE_PROJECTILE, bullet_act_args[BULLET_ACT_ARG_PROJECTILE], ., efficiency) + +/datum/parry_frame/proc/handle_bullet(atom/defending, shieldcall_returns, fake_attack, efficiency, list/bullet_act_args, tool_text) + . = shieldcall_returns + // todo: doesn't take into account any damage randomization + var/obj/projectile/proj = bullet_act_args[BULLET_ACT_ARG_PROJECTILE] + var/estimated_severity = clamp(proj.damage / 20 * 75, 0, 100) + bullet_act_args[BULLET_ACT_ARG_EFFICIENCY] = bullet_act_args[BULLET_ACT_ARG_EFFICIENCY] * clamp(1 - efficiency, 0, 1) + . = perform_aftereffects(defending, ATTACK_TYPE_PROJECTILE, efficiency, proj, .) + perform_audiovisuals(defending, ATTACK_TYPE_PROJECTILE, efficiency, proj, ., estimated_severity, proj, tool_text) + if(. & (SHIELDCALL_FLAG_ATTACK_PASSTHROUGH | SHIELDCALL_FLAG_ATTACK_REDIRECT)) + bullet_act_args[BULLET_ACT_ARG_FLAGS] |= PROJECTILE_IMPACT_REFLECT + if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) + bullet_act_args[BULLET_ACT_ARG_FLAGS] |= PROJECTILE_IMPACT_BLOCKED + +//* Bindings - Melee *// + +/datum/shieldcall/bound/parry_frame/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args) + var/datum/component/parry_frame/frame = bound + if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_MELEE)) + return + if(e_args && !check_defensive_arc_tile(defending, e_args.performer, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down)) + return + var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time) + . = frame.active_parry.handle_item_melee(defending, shieldcall_returns, fake_attack, efficiency, weapon, e_args, tool_text) + frame.on_parry(ATTACK_TYPE_MELEE, weapon, ., efficiency) + +/datum/parry_frame/proc/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, efficiency, obj/item/weapon, datum/event_args/actor/clickchain/e_args, tool_text) + . = shieldcall_returns + // todo: doesn't take into account any damage randomization + var/estimated_severity = clamp(weapon.damage_force * e_args.damage_multiplier / 20 * 75, 0, 100) + e_args.damage_multiplier *= clamp(1 - efficiency, 0, 1) + . = perform_aftereffects(defending, ATTACK_TYPE_MELEE, efficiency, weapon, ., e_args) + perform_audiovisuals(defending, ATTACK_TYPE_MELEE, efficiency, weapon, ., estimated_severity, weapon, tool_text) + if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) + . |= SHIELDCALL_FLAG_ATTACK_BLOCKED + +/datum/shieldcall/bound/parry_frame/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args) + var/datum/component/parry_frame/frame = bound + if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_UNARMED)) + return + if(e_args && !check_defensive_arc_tile(defending, e_args.performer, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down)) + return + var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time) + . = frame.active_parry.handle_unarmed_melee(defending, shieldcall_returns, fake_attack, efficiency, style, e_args, tool_text) + frame.on_parry(ATTACK_TYPE_UNARMED, style, ., efficiency) + +/datum/parry_frame/proc/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, efficiency, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args, tool_text) + . = shieldcall_returns + // todo: doesn't take into account any damage randomization + var/estimated_severity = clamp(style.damage * e_args.damage_multiplier / 20 * 75, 0, 100) + e_args.damage_multiplier *= clamp(1 - efficiency, 0, 1) + . = perform_aftereffects(defending, ATTACK_TYPE_UNARMED, efficiency, style, ., e_args) + perform_audiovisuals(defending, ATTACK_TYPE_UNARMED, efficiency, style, ., estimated_severity, style, tool_text) + if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) + . |= SHIELDCALL_FLAG_ATTACK_BLOCKED + +/datum/shieldcall/bound/parry_frame/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific) + var/datum/component/parry_frame/frame = bound + if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_TOUCH)) + return + if(e_args && !check_defensive_arc_tile(defending, e_args.performer, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down)) + return + var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time) + . = frame.active_parry.handle_touch(defending, shieldcall_returns, fake_attack, efficiency, e_args, contact_flags, contact_specific, tool_text) + frame.on_parry(ATTACK_TYPE_TOUCH, null, ., efficiency) + +/datum/parry_frame/proc/handle_touch(atom/defending, shieldcall_returns, fake_attack, efficiency, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific, tool_text) + . = shieldcall_returns + // todo: doesn't take into account any damage randomization + var/estimated_severity = 50 + e_args.damage_multiplier *= clamp(1 - efficiency, 0, 1) + . = perform_aftereffects(defending, ATTACK_TYPE_TOUCH, efficiency, null, ., e_args) + perform_audiovisuals(defending, ATTACK_TYPE_TOUCH, efficiency, null, ., estimated_severity, e_args.performer, tool_text) + if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) + . |= SHIELDCALL_FLAG_ATTACK_BLOCKED + +//* Bindings - Thrown *// + +/datum/shieldcall/bound/parry_frame/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown) + var/datum/component/parry_frame/frame = bound + if(!(frame.active_parry.parry_attack_types & ATTACK_TYPE_THROWN)) + return + if(!check_defensive_arc_tile(defending, thrown, frame.active_parry.parry_arc, !frame.active_parry.parry_arc_round_down)) + return + var/efficiency = frame.active_parry.calculate_parry_efficiency(frame.start_time - world.time) + . = frame.active_parry.handle_throw_impact(defending, shieldcall_returns, fake_attack, efficiency, thrown, tool_text) + frame.on_parry(ATTACK_TYPE_THROWN, thrown, ., efficiency) + +/datum/parry_frame/proc/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, efficiency, datum/thrownthing/thrown, tool_text) + . = shieldcall_returns + // todo: doesn't take into account any damage randomization + // todo: why isn't thrownthing just with a get_damage() or a better inflict_damage() and get_damage_tuple() idfk man + var/estimated_severity = clamp(thrown.thrownthing.throw_force * thrown.get_damage_multiplier() / 20 * 75, 0, 100) + thrown.damage_multiplier *= clamp(1 - efficiency, 0, 1) + . = perform_aftereffects(defending, ATTACK_TYPE_THROWN, efficiency, thrown, ., thrown.thrownthing, tool_text) + perform_audiovisuals(defending, ATTACK_TYPE_THROWN, efficiency, thrown, ., estimated_severity) + if(parry_always_prevents_contact || (parry_can_prevent_contact && (efficiency >= parry_efficiency_blocked))) + . |= SHIELDCALL_FLAG_ATTACK_BLOCKED + +//* -- VFX Render -- *// + +INITIALIZE_IMMEDIATE(/atom/movable/render/parry_frame) +/** + * A visualizer for a parry frame. + */ +/atom/movable/render/parry_frame + var/atom/movable/bound + /// set this in your custom procs for cycle and single/spinup/spindown + var/qdel_time = 1 SECONDS + +/atom/movable/render/parry_frame/Initialize(mapload, atom/movable/bind_to, datum/parry_frame/frame, single_deflect) + SHOULD_CALL_PARENT(FALSE) + if(!istype(frame)) + . = INITIALIZE_HINT_QDEL + CRASH("no valid frame, this is bad") + src.bound = bind_to + bind_to.vis_contents += src + cycle(frame, single_deflect) + QDEL_IN(src, qdel_time) + return INITIALIZE_HINT_NORMAL + +/atom/movable/render/parry_frame/Destroy() + bound.vis_contents -= src + bound = null + return ..() + +/atom/movable/render/parry_frame/proc/cycle(datum/parry_frame/frame, single_deflect) + if(single_deflect) + single() + return + spinup(frame.parry_timing_start) + addtimer(CALLBACK(src, PROC_REF(spindown), frame.parry_timing_stop), max(frame.parry_timing_active, frame.parry_timing_perfect)) + +/atom/movable/render/parry_frame/proc/single() + return + +/atom/movable/render/parry_frame/proc/spinup(start_time) + return + +/atom/movable/render/parry_frame/proc/spindown(stop_time) + return + +//* -- VFX Default -- *// + +/atom/movable/render/parry_frame/default + icon = 'icons/effects/defensive/main_parry.dmi' + icon_state = "hold" + +/atom/movable/render/parry_frame/default/single() + animate(src, time = 0.3 SECONDS, alpha = 0) + qdel_time = 0.3 SECONDS diff --git a/code/datums/components/movable/spatial_grid.dm b/code/datums/components/movable/spatial_grid.dm index a549c642d1a..ca650de950c 100644 --- a/code/datums/components/movable/spatial_grid.dm +++ b/code/datums/components/movable/spatial_grid.dm @@ -16,7 +16,7 @@ /datum/component/spatial_grid/Initialize(datum/spatial_grid/grid) . = ..() - if(. & COMPONENT_INCOMPATIBLE) + if(. == COMPONENT_INCOMPATIBLE) return if(!ismovable(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/riding/riding_filter.dm b/code/datums/components/riding/riding_filter.dm index 464e7c9f7fa..8ab7fcbf011 100644 --- a/code/datums/components/riding/riding_filter.dm +++ b/code/datums/components/riding/riding_filter.dm @@ -43,7 +43,7 @@ /datum/component/riding_filter/Initialize(handler_typepath) . = ..() - if(. & COMPONENT_INCOMPATIBLE) + if(. == COMPONENT_INCOMPATIBLE) return if(!istype(parent, expected_typepath)) return COMPONENT_INCOMPATIBLE @@ -80,11 +80,11 @@ /datum/component/riding_filter/proc/signal_hook_user_buckle(atom/movable/source, mob/M, flags, mob/user, semantic) SIGNAL_HANDLER_DOES_SLEEP - return check_user_mount(M, flags, user, semantic)? COMPONENT_FORCE_BUCKLE_OPERATION : COMPONENT_BLOCK_BUCKLE_OPERATION + return check_user_mount(M, flags, user, semantic)? SIGNAL_RAISE_FORCE_BUCKLE_OPERATION : SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION /datum/component/riding_filter/proc/signal_hook_pre_buckle(atom/movable/source, mob/M, flags, mob/user, semantic) SIGNAL_HANDLER - return on_mount_attempt(M, flags, user, semantic)? COMPONENT_FORCE_BUCKLE_OPERATION : COMPONENT_BLOCK_BUCKLE_OPERATION + return on_mount_attempt(M, flags, user, semantic)? SIGNAL_RAISE_FORCE_BUCKLE_OPERATION : SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION /datum/component/riding_filter/proc/signal_hook_post_buckle(atom/movable/source, mob/M, flags, mob/user, semantic) SIGNAL_HANDLER @@ -106,7 +106,7 @@ */ /datum/component/riding_filter/proc/signal_hook_can_buckle(atom/movable/source, mob/M, flags, mob/user, semantic) SIGNAL_HANDLER - return check_mount_attempt(M, flags, user, semantic)? COMPONENT_FORCE_BUCKLE_OPERATION : COMPONENT_BLOCK_BUCKLE_OPERATION + return check_mount_attempt(M, flags, user, semantic)? SIGNAL_RAISE_FORCE_BUCKLE_OPERATION : SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION /** * called on buckling process right before point of no return diff --git a/code/datums/components/riding/riding_handler.dm b/code/datums/components/riding/riding_handler.dm index 10230638e7f..0b260810dbb 100644 --- a/code/datums/components/riding/riding_handler.dm +++ b/code/datums/components/riding/riding_handler.dm @@ -77,7 +77,7 @@ /datum/component/riding_handler/Initialize() . = ..() - if(. & COMPONENT_INCOMPATIBLE) + if(. == COMPONENT_INCOMPATIBLE) return if(!istype(parent, expected_typepath)) return COMPONENT_INCOMPATIBLE @@ -127,7 +127,7 @@ /datum/component/riding_handler/proc/signal_hook_pre_buckle_mob(atom/movable/source, mob/M, flags, mob/user, semantic) SIGNAL_HANDLER_DOES_SLEEP if(!check_rider(M, semantic, TRUE, user = user)) - return COMPONENT_BLOCK_BUCKLE_OPERATION + return SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION /datum/component/riding_handler/proc/signal_hook_pixel_offset_changed(atom/movable/source) full_update_riders(null, TRUE) diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm index 6909f279a40..c93c2373e7b 100644 --- a/code/datums/datumvars.dm +++ b/code/datums/datumvars.dm @@ -11,7 +11,12 @@ datum_flags |= DF_VAR_EDITED return TRUE -/datum/proc/vv_get_var(var_name) +/** + * @params + * * var_name - name of the variable + * * resolve - automatically resolve the variable if it's lazy loaded? + */ +/datum/proc/vv_get_var(var_name, resolve) switch(var_name) if ("vars") return debug_variable(var_name, list(), 0, src) diff --git a/code/datums/elements/conflict_checking.dm b/code/datums/elements/conflict_checking.dm index f298d184f01..8c8e35db698 100644 --- a/code/datums/elements/conflict_checking.dm +++ b/code/datums/elements/conflict_checking.dm @@ -1,5 +1,7 @@ /** * Simple conflict checking for getting number of conflicting things on someone with the same ID. + * + * todo: this is a bit slow innit. */ /datum/element/conflict_checking element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH diff --git a/code/datums/event_args/clickchain.dm b/code/datums/event_args/clickchain.dm index 50858fa0553..66fc14937bd 100644 --- a/code/datums/event_args/clickchain.dm +++ b/code/datums/event_args/clickchain.dm @@ -2,6 +2,8 @@ * used to hold data about a click (melee/ranged/other) action * * the click may be real or fake. + * + * * This is required for item swings / interaction, usually, not just base /event_args/actor. */ /datum/event_args/actor/clickchain /// optional: attack intent @@ -11,6 +13,15 @@ /// optional: target atom var/atom/target + //* Attack Data *// + + /// Overall damage multiplier + /// + /// todo: implement; needs slight clickchain/melee overhaul + /// + /// * Allowed to be changed by shieldcalls and other intercepts + var/damage_multiplier = 1 + /datum/event_args/actor/clickchain/New(mob/performer, mob/initiator, atom/target, list/params, intent) ..() src.target = target diff --git a/code/datums/outfits/ghostrole.dm b/code/datums/outfits/ghostrole.dm index 896dc9c5726..7ab69512758 100644 --- a/code/datums/outfits/ghostrole.dm +++ b/code/datums/outfits/ghostrole.dm @@ -69,13 +69,13 @@ /datum/outfit/pirate/immigrant name = "Pirate - Immigrant" belt = /obj/item/gun/ballistic/pirate - r_pocket = /obj/item/melee/energy/sword/pirate + r_pocket = /obj/item/melee/transforming/energy/sword/cutlass /datum/outfit/pirate/dilettante name = "Pirate - Dilettante" uniform = /obj/item/clothing/under/surplus shoes = /obj/item/clothing/shoes/boots/jackboots - belt = /obj/item/melee/energy/sword/pirate + belt = /obj/item/melee/transforming/energy/sword/cutlass l_hand = /obj/item/shield/makeshift /datum/outfit/pirate/professional @@ -85,5 +85,5 @@ shoes = /obj/item/clothing/shoes/boots/jackboots mask = /obj/item/clothing/mask/balaclava belt = /obj/item/gun/energy/zip - r_pocket = /obj/item/melee/energy/sword/pirate + r_pocket = /obj/item/melee/transforming/energy/sword/cutlass r_hand = /obj/item/shield/makeshift diff --git a/code/datums/outfits/horror_killers.dm b/code/datums/outfits/horror_killers.dm index 87ce4e99270..a1f7e22b70c 100644 --- a/code/datums/outfits/horror_killers.dm +++ b/code/datums/outfits/horror_killers.dm @@ -41,7 +41,7 @@ gloves = /obj/item/clothing/gloves/black l_ear = /obj/item/radio/headset glasses = /obj/item/clothing/glasses/sunglasses - l_pocket = /obj/item/melee/energy/sword + l_pocket = /obj/item/melee/transforming/energy/sword mask = /obj/item/clothing/mask/gas/clown_hat id_slot = SLOT_ID_WORN_ID diff --git a/code/datums/outfits/outfit.dm b/code/datums/outfits/outfit.dm index e9f5223c5e0..85823242f51 100644 --- a/code/datums/outfits/outfit.dm +++ b/code/datums/outfits/outfit.dm @@ -263,8 +263,8 @@ belt = /obj/item/storage/belt/security/tactical/bandolier l_pocket = /obj/item/cell/device/weapon r_pocket = /obj/item/cell/device/weapon - r_hand = /obj/item/melee/energy/sword/imperial - l_hand = /obj/item/shield/energy/imperial + r_hand = /obj/item/melee/transforming/energy/sword/imperial + l_hand = /obj/item/shield/transforming/energy/imperial suit_store = /obj/item/gun/energy/imperial /datum/outfit/imperial/officer @@ -279,6 +279,6 @@ belt = /obj/item/storage/belt/security/tactical/bandolier l_pocket = /obj/item/cell/device/weapon r_pocket = /obj/item/cell/device/weapon - r_hand = /obj/item/melee/energy/sword/imperial - l_hand = /obj/item/shield/energy/imperial + r_hand = /obj/item/melee/transforming/energy/sword/imperial + l_hand = /obj/item/shield/transforming/energy/imperial suit_store = /obj/item/gun/energy/imperial diff --git a/code/datums/outfits/pirates.dm b/code/datums/outfits/pirates.dm index 3208e284a18..c2a061eb277 100644 --- a/code/datums/outfits/pirates.dm +++ b/code/datums/outfits/pirates.dm @@ -6,7 +6,7 @@ shoes = /obj/item/clothing/shoes/brown head = /obj/item/clothing/head/bandana glasses = /obj/item/clothing/glasses/eyepatch - l_hand = /obj/item/melee/energy/sword/pirate + l_hand = /obj/item/melee/transforming/energy/sword/cutlass /datum/outfit/pirate/norm @@ -25,7 +25,7 @@ gloves = /obj/item/clothing/gloves/light_brown mask = /obj/item/clothing/mask/breath back = /obj/item/tank/vox - l_hand = /obj/item/melee/energy/sword/pirate + l_hand = /obj/item/melee/transforming/energy/sword/cutlass r_hand = /obj/item/gun/ballistic/shotgun/pump/rifle/vox_hunting l_pocket = /obj/item/ammo_magazine/a7_62mm/clip r_pocket = /obj/item/ammo_magazine/a7_62mm/clip diff --git a/code/datums/position_point_vector.dm b/code/datums/position_point_vector.dm index 6f9121469b9..3babfe0fb1d 100644 --- a/code/datums/position_point_vector.dm +++ b/code/datums/position_point_vector.dm @@ -119,10 +119,13 @@ /** * angle is clockwise from north + * + * @return self */ /datum/point/proc/shift_in_projectile_angle(angle, distance) x += sin(angle) * distance y += cos(angle) * distance + return src /** * doesn't use set base pixel x/y diff --git a/code/datums/shieldcall.dm b/code/datums/shieldcall.dm index 8f66b776107..37f612450fe 100644 --- a/code/datums/shieldcall.dm +++ b/code/datums/shieldcall.dm @@ -1,15 +1,43 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// +//* Copyright (c) 2024 silicons *// + +GLOBAL_LIST_EMPTY(cached_shieldcall_datums) + +/** + * return a globally cached shieldcall + * mostly for hardcoded ones that always work a certain way regardless of what it's actually representing + */ +/proc/fetch_cached_shieldcall(path) + if(GLOB.cached_shieldcall_datums[path]) + return GLOB.cached_shieldcall_datums[path] + GLOB.cached_shieldcall_datums[path] = new path + return GLOB.cached_shieldcall_datums[path] /** * Shieldcall handling datums + * + * These are semi-expensive datums that act as hooks + * to intercept arbitrary attacks at the /atom level. + * + * Please be careful with their usage. + * They hook most attacks, and as such are pretty expensive to have on an atom, compute-wise. + * This is pretty much only efficient for single-target; if it's possible, any suppression hook for attacks + * is better than using this on a large number of targets. */ /datum/shieldcall /// priority ; should never change once we're registered on something. lower has higher priority. var/priority = 0 /// goes to mob when in inventory - whether equipped to slot or in hand /// do not modify while applied. it will not un/register properly. + /// + /// * Turn this off if you're doing your own handling. var/shields_in_inventory = TRUE + /// Allow interception of `atom_shieldcall`. + /// + /// * "Yes, I read and understand the terms and conditions" + /// * "Yes, I will handle SHIELDCALL_FLAG_SECOND_CALL to prevent a double-block scenario" + /// * "Yes, I understand that atom_shieldcall is low level and is called in addition to other shieldcall handling procs" + var/low_level_intercept = FALSE /** * sent over from the atom @@ -17,6 +45,127 @@ * @params * * defending - the atom in question * * shieldcall_args - indexed list of shieldcall args. + * * fake_attack - just checking! + * + * @return nothing, because you should be modifying the return flags via shieldcall_args list! + */ +/datum/shieldcall/proc/handle_shieldcall(atom/defending, list/shieldcall_args, fake_attack) + return + +//* Melee Handling *// + +/** + * sent over from the atom + * + * * this is generic pre-intercept for melee; please keep this cheap + * * for stuff like reactive teleport armor, use this because it will stop the hit entirely. + * * for damage modification, inject directly into e_args + * + * @params + * * defending - the atom being attacked + * * shieldcall_returns - existing returns from other shieldcalls + * * fake_attack - just checking! + * * weapon - the item being used to swing with + * * e_args - (optional) the clickchain event, if any; **This is mutable.** + * + * @return SHIELDCALL_FLAG_* flags + */ +/datum/shieldcall/proc/handle_item_melee(atom/defending, shieldcall_returns, fake_attack, obj/item/weapon, datum/event_args/actor/clickchain/e_args) + return NONE + +/** + * sent over from the atom + * + * * this is generic pre-intercept for melee; please keep this cheap + * * for stuff like reactive teleport armor, use this because it will stop the hit entirely. + * * for damage modification, inject directly into e_args + * + * @params + * * defending - the atom in question + * * shieldcall_returns - existing returns from other shieldcalls + * * fake_attack - just checking! + * * style - the unarmed_attack datum being used + * * e_args (optional) the clickchain event, if any; **This is mutable.** + * + * @return SHIELDCALL_FLAG_* flags + */ +/datum/shieldcall/proc/handle_unarmed_melee(atom/defending, shieldcall_returns, fake_attack, datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args) + return NONE + +//* Interaction Handling *// + +/** + * sent over from the atom + * + * * for generic 'tried to touch' things + * * this is really funny because it lets us do things like teleport the RD on a hug + * + * @params + * * defending - the atom in question + * * shieldcall_returns - existing returns from other shieldcalls + * * fake_attack - just checking! + * * e_args (optional) the clickchain event, if any; **This is mutable.** + * * contact_flags - SHIELDCALL_CONTACT_FLAG_* + * * contact_specific - SHIELDCALL_CONTACT_SPECIFIC_* + * + * @return SHIELDCALL_FLAG_* flags + */ +/datum/shieldcall/proc/handle_touch(atom/defending, shieldcall_returns, fake_attack, datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific) + return NONE + +//* Projectile Handling *// + +/** + * sent over from the atom + * + * * this is pre-intercept for projectiles; please keep this cheap. + * * for stuff like reactive teleport armor, use this because it will stop the hit entirely. + * * passed in bullet act args is mutable. + * * we DO NOT process SHIELDCALL_FLAG flags other than _TERMINATE, because we have direct access to impact_flags of the bullet! + * + * @params + * * defending - the atom in question + * * shieldcall_returns - existing returns from other shieldcalls + * * fake_attack - just checking! + * * bullet_act_args - indexed list of bullet_act args. + * + * @return SHIELDCALL_FLAG_TERMINATE or NONE */ -/datum/shieldcall/proc/handle_shieldcall(atom/defending, list/shieldcall_args) +/datum/shieldcall/proc/handle_bullet(atom/defending, shieldcall_returns, fake_attack, list/bullet_act_args) + return NONE + +//* Throw Handling *// + +/** + * sent over from the atom + * + * * this is pre-intercept for throwns + * * for stuff like reactive teleport armor, use this because it will stop the hit entirely + * + * todo: implement for turf + * todo: implement for obj + * + * @params + * * defending - the thing being hit + * * shieldcall_returns - existing returns from other shieldcalls + * * fake_attack - just checking! + * * thrown - the thrown object's data + * + * @return SHIELDCALL_FLAG_* flags + */ +/datum/shieldcall/proc/handle_throw_impact(atom/defending, shieldcall_returns, fake_attack, datum/thrownthing/thrown) return + +//* Bound Variant *// + +/datum/shieldcall/bound + var/expected_type + var/datum/bound + +/datum/shieldcall/bound/New(datum/binding) + ASSERT(expected_type && istype(binding, expected_type)) + src.bound = binding + +/datum/shieldcall/bound/Destroy() + src.bound = null + return ..() diff --git a/code/datums/soundbytes/effects/combat.dm b/code/datums/soundbytes/effects/combat.dm new file mode 100644 index 00000000000..dd1dd5ef5dd --- /dev/null +++ b/code/datums/soundbytes/effects/combat.dm @@ -0,0 +1,47 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/datum/soundbyte/parry_start + name = "Active Parry (Start)" + is_sfx = TRUE + path = 'sound/soundbytes/effects/combat/parry-cycle.ogg' + +/datum/soundbyte/grouped/metal_parry + name = "Parry (Metal-Metal)" + is_sfx = TRUE + path = list( + 'sound/soundbytes/effects/combat/parry-metal1.ogg', + 'sound/soundbytes/effects/combat/parry-metal2.ogg', + ) + +/datum/soundbyte/grouped/wood_parry + name = "Parry (Wood-Wood)" + is_sfx = TRUE + path = list( + 'sound/soundbytes/effects/combat/parry-wood1.ogg', + 'sound/soundbytes/effects/combat/parry-wood2.ogg', + ) + +/datum/soundbyte/grouped/block_metal_with_wood + name = "Block (Metal-Wood)" + is_sfx = TRUE + path = list( + 'sound/soundbytes/effects/combat/block-metal-on-wood-1.ogg', + 'sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg', + ) + +/datum/soundbyte/grouped/block_metal_with_metal + name = "Block (Metal-Metal)" + is_sfx = TRUE + path = list( + 'sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg', + 'sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg', + ) + +/datum/soundbyte/grouped/block_wood_with_wood + name = "Block (Wood-Wood)" + is_sfx = TRUE + path = list( + 'sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg', + 'sound/soundbytes/effects/combat/block-wood-on-wood-2.ogg', + ) diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm index e0980c2b8de..010e8d88d0c 100644 --- a/code/datums/status_effects/status_effect.dm +++ b/code/datums/status_effects/status_effect.dm @@ -6,6 +6,9 @@ * * each effect potentially has its own amount of variable arguments that * can be passed into apply_status_effect. they will be detailed per-file. + * + * todo: /datum/prototype/status_effect + * todo: /datum/prototype/status_effect/simple for normal ones. */ /datum/status_effect abstract_type = /datum/status_effect @@ -196,9 +199,11 @@ /** * remove a status effect * + * * will remove grouped effects entirely. + * * @params * * path - path to effect - * * stacks - stacks to remove for grouped and stacking, default is all. + * * stacks - stacks to remove for stacking, default is all. * * @return stacks **left**. for single effects this is probably 0. */ diff --git a/code/datums/unarmed_attack.dm b/code/datums/unarmed_attack.dm index 6179ef081fe..ff6e629e7b9 100644 --- a/code/datums/unarmed_attack.dm +++ b/code/datums/unarmed_attack.dm @@ -52,6 +52,9 @@ GLOBAL_LIST_EMPTY(unarmed_attack_cache) var/eye_attack_text var/eye_attack_text_victim +/datum/unarmed_attack/proc/operator""() + return pick(attack_verb_legacy) + //* Feedback /datum/unarmed_attack/proc/get_sparring_variant() diff --git a/code/datums/uplink/visible_weapons.dm b/code/datums/uplink/visible_weapons.dm index 04a3acad211..776ebc499b9 100644 --- a/code/datums/uplink/visible_weapons.dm +++ b/code/datums/uplink/visible_weapons.dm @@ -17,17 +17,17 @@ /datum/uplink_item/item/visible_weapons/energy_sword name = "Energy Sword, Colorable" item_cost = 40 - path = /obj/item/melee/energy/sword + path = /obj/item/melee/transforming/energy/sword /datum/uplink_item/item/visible_weapons/energy_sword_pirate name = "Energy Cutlass, Colorable" item_cost = 40 - path = /obj/item/melee/energy/sword/pirate + path = /obj/item/melee/transforming/energy/sword/cutlass -/datum/uplink_item/item/visible_weapons/energy_spear - name = "Energy Spear, Colorable" - item_cost = 50 - path = /obj/item/melee/energy/spear +// /datum/uplink_item/item/visible_weapons/energy_spear +// name = "Energy Spear, Colorable" +// item_cost = 50 +// path = /obj/item/melee/transforming/energy/spear /datum/uplink_item/item/visible_weapons/claymore name = "Claymore" diff --git a/code/game/antagonist/outsider/deathsquad.dm b/code/game/antagonist/outsider/deathsquad.dm index 931ae8626d4..26517f96697 100644 --- a/code/game/antagonist/outsider/deathsquad.dm +++ b/code/game/antagonist/outsider/deathsquad.dm @@ -49,7 +49,7 @@ var/datum/antagonist/deathsquad/deathsquad player.equip_to_slot_or_del(new /obj/item/gun/ballistic/revolver/combat(player), SLOT_ID_BELT) player.equip_to_slot_or_del(new /obj/item/gun/energy/pulse_rifle(player), /datum/inventory_slot/abstract/hand/right) player.equip_to_slot_or_del(new /obj/item/hardsuit/ert/assetprotection(player), SLOT_ID_BACK) - player.equip_to_slot_or_del(new /obj/item/melee/energy/sword(player), SLOT_ID_SUIT_STORAGE) + player.equip_to_slot_or_del(new /obj/item/melee/transforming/energy/sword(player), SLOT_ID_SUIT_STORAGE) // player.implant_loyalty() var/obj/item/card/id/id = create_id("Asset Protection", player) diff --git a/code/game/atoms/atom-construction.dm b/code/game/atoms/atom-construction.dm new file mode 100644 index 00000000000..94d85815a35 --- /dev/null +++ b/code/game/atoms/atom-construction.dm @@ -0,0 +1,60 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Deconstruction *// + +/** + * called to semantically deconstruct an atom + * + * @params + * * method - how we were deconstructed + */ +/atom/proc/deconstruct(method = ATOM_DECONSTRUCT_DISASSEMBLED) + SHOULD_NOT_OVERRIDE(TRUE) + + // send signal + // todo: signal + // do da funny logic + deconstructed(method) + // drop things after so things that rely on having objects don't break + drop_products(method, drop_location()) + // goodbye, cruel world + break_apart(method) + +/** + * called to actually destroy ourselves + */ +/atom/proc/break_apart(method) + qdel(src) + +/** + * called when we are deconstructed + * + * **do not drop products in here** + * + * @params + * - method - how we were deconstructed + */ +/atom/proc/deconstructed(method) + return + +/** + * called to drop the products of deconstruction + * + * @params + * * method - how we were deconstructed + * * where - where to drop products; set in base if null to drop_location(). + */ +/atom/proc/drop_products(method, atom/where = drop_location()) + return + +/** + * called to move a product to a place + * + * @params + * * method - how we were deconstructed + * * dropping - movable in question + * * where - where to move to + */ +/atom/proc/drop_product(method, atom/movable/dropping, atom/where) + dropping.forceMove(where || drop_location()) diff --git a/code/game/atoms/atom-damage.dm b/code/game/atoms/atom-damage.dm new file mode 100644 index 00000000000..61ebc475db1 --- /dev/null +++ b/code/game/atoms/atom-damage.dm @@ -0,0 +1,333 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Damage Instance Handling *// + +/** + * standard damage handling - process an instance of damage + * + * args are the same as shieldcall args, because this directly invokes shield/armorcalls + * + * * additional args past the normal shieldcall args are allowed, but not before! + * * the entire args list is extracted via return, allowing for handling by caller. + * * damage is assumed to be zone'd if def_zone is set; otherwise it's overall + * * please note that overall damage generally doesn't check armor properly for speed reasons! + * + * @return modified args + */ +/atom/proc/run_damage_instance(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + process_damage_instance(args, hit_zone) + if(shieldcall_flags & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return args.Copy() // args are only valid during a call; it's destroyed after. + inflict_damage_instance(arglist(args)) + return args.Copy() // args are only valid during a call; it's destroyed after. + +/** + * [/atom/proc/run_damage_instance()], but doesn't actually do damage. + * + * @return modified args + */ +/atom/proc/check_damage_instance(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + process_damage_instance(args, hit_zone, TRUE) + return args.Copy() // args are only valid during a call; it's destroyed after. + +/** + * process an instance of damage through defense handling. + */ +/atom/proc/process_damage_instance(list/shieldcall_args, filter_zone, fake_attack) + if(!(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_SKIP_SHIELDCALLS)) + run_shieldcalls(shieldcall_args, fake_attack) + if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & (SHIELDCALL_FLAGS_SHOULD_TERMINATE | SHIELDCALL_FLAGS_BLOCK_ATTACK)) + return + if(!(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_SKIP_ARMORCALLS)) + run_armorcalls(shieldcall_args, fake_attack, filter_zone) + +/** + * inflict an instance of damage. + * + * * this happens after shieldcalls, armor checks, etc, all resolve. + * * at this point, nothing should modify damage + * * for things like limb damage and armor handling, check the armor/etc in process_damage_instance + * * for this reason, we do not allow any returns. + * * if hit_zone is not specified, this is considered overall damage. + * * overall damage is implementation-defined, so it's recommended to, ironically, not try to standardize that too much. + * * this is pretty much the hand-off proc where damage goes from hit processing / defense checks to the damage system for an entity + * * the damage system can be a medical system or just the atom integrity system. + */ +/atom/proc/inflict_damage_instance(SHIELDCALL_PROC_HEADER) + if(!integrity_enabled) + return + if(inflict_damage_type_special(args)) + return + switch(damage_type) + if(BRUTE) + if(BURN) + else + return // normal atoms can't take non brute-burn damage + // default atom damage handling + inflict_atom_damage( + damage, + damage_type, + damage_tier, + damage_flag, + damage_mode, + hit_zone, + attack_type, + weapon, + ) + +/** + * decodes damage type to what it should actually do + * + * * this is for hybrid / semantic damage types like bio-acid and searing damage to work + * + * @return TRUE to handle the damage type. + */ +/atom/proc/inflict_damage_type_special(list/shieldcall_args) + return FALSE + +//* Damage Processing API *// + +/** + * takes damage from a generic attack, taking into account armor but not shields. + * this does not handle playing sounds / anything, this is strictly generic damage handling + * usable by anything. + * + * * This does **not** invoke the shieldcall API! + * * This does **not** invoke the armor API! + * * This is because damage instance processing should be processing that. + * + * @params + * * damage - raw damage + * * damage_type - (optional) damage type to inflict + * * damage_tier - (optional) resulting damage tier + * * damage_flag - (optional) resulting damage armor flag from [code/__DEFINES/combat/armor.dm] + * * damage_mode - (optional) DAMAGE_MODE_* flags + * * hit_zone - (optional) the zone being hit + * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] + * * weapon - (optional) attacking datum; same format as shieldcall API. See shieldcalls for more information. + * + * @return raw damage taken + */ +/atom/proc/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon) + if(!integrity_enabled) + return 0 + if(integrity_flags & INTEGRITY_INDESTRUCTIBLE) + return 0 + if(!damage) + return 0 + . = integrity + damage_integrity(damage) + . = . - integrity + + +//* Integrity - Direct Manipulation *// + +/** + * damages integrity directly, ignoring armor / shields + * + * @params + * * amount - how much + * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this. + * * do_not_break - skip calling atom_break + */ +/atom/proc/damage_integrity(amount, gradual, do_not_break) + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + var/was_working = integrity > integrity_failure + integrity = max(0, integrity - amount) + if(was_working && integrity <= integrity_failure && !do_not_break) + atom_break() + if(integrity <= 0) + atom_destruction() + +/** + * heals integrity directly + * + * @params + * * amount - how much + * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this. + * * do_not_fix - skip calling atom_fix + * + * @return amount healed + */ +/atom/proc/heal_integrity(amount, gradual, do_not_fix) + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + var/was_failing = integrity <= integrity_failure + . = integrity + integrity = min(integrity_max, integrity + amount) + . = integrity - . + if(was_failing && integrity > integrity_failure && !do_not_fix) + atom_fix() + +/** + * directly sets integrity - ignores armor / sihelds + * + * will not call [damage_integrity] or [heal_integrity] + * will call [atom_break], [atom_fix], [atom_destruction] + * + * @params + * * amount - how much to set to? + */ +/atom/proc/set_integrity(amount) + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + var/was_failing = integrity <= integrity_failure + integrity = clamp(amount, 0, integrity_max) + if(!was_failing && integrity <= integrity_failure) + atom_break() + else if(was_failing && integrity > integrity_failure) + atom_fix() + if(integrity <= 0) + atom_destruction() + +/** + * sets max integrity - will automatically reduce integrity if it's above max. + * + * will not call [damage_integrity] + * will call [atom_break], [atom_fix], [atom_destruction] + * + * @params + * * amount - how much to set to + */ +/atom/proc/set_max_integrity(amount) + integrity_max = max(amount, 0) + if(integrity < integrity_max) + return + var/was_broken = integrity <= integrity_failure + integrity = integrity_max + if(!was_broken && (integrity <= integrity_failure)) + atom_break() + if(integrity <= 0) + atom_destruction() + +/** + * sets integrity and max integrity - will automatically reduce integrity if it's above max. + * + * will not call [damage_integrity] + * will call [atom_break], [atom_fix], [atom_destruction] + * + * @params + * * integrity - how much to set integrity to + * * integrity_max - how much to set integrity_max to + */ +/atom/proc/set_full_integrity(integrity, integrity_max) + src.integrity_max = max(integrity_max, 0) + var/was_broken = src.integrity <= integrity_failure + src.integrity = clamp(integrity, 0, integrity_max) + var/now_broken = integrity <= integrity_failure + if(!was_broken && now_broken) + atom_break() + else if(was_broken && !now_broken) + atom_fix() + if(integrity <= 0) + atom_destruction() + +/** + * Set integrity to a multiple of initial. + * + * And fully restore it if specified. + * Otherwise, will retain the last percentage. + */ +/atom/proc/set_multiplied_integrity(factor, restore) + var/was_broken = src.integrity <= integrity_failure + if(restore) + integrity = integrity_max = initial(integrity_max) * factor + if(was_broken && integrity > integrity_failure) + atom_fix() + return + var/ratio = integrity / integrity_max + integrity_max = initial(integrity_max) * factor + integrity = integrity_max * ratio + var/now_broken = integrity <= integrity_failure + if(!was_broken && now_broken) + atom_break() + else if(was_broken && !now_broken) + atom_fix() + if(integrity <= 0) + atom_destruction() + +/** + * adjusts integrity - routes directly to [damage_integrity] and [heal_integrity] + * + * will call [damage_integrity] + * will call [atom_break], [atom_fix], [atom_destruction] + * + * @params + * * amount - how much + * * gradual - burst or gradual? + * * no_checks - do not call fix/break + */ +/atom/proc/adjust_integrity(amount, gradual, no_checks) + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + if(amount > 0) + return heal_integrity(amount, gradual, no_checks) + else + return damage_integrity(amount, gradual, no_checks) + +/** + * adjusts max integrity - will automatically reduce integrity if it's above max. + * + * will call [damage_integrity] + * will call [atom_break], [atom_fix], [atom_destruction] + * + * @params + * * amount - how much to adjust by + * * gradual - burst or gradual? + */ +/atom/proc/adjust_max_integrity(amount, gradual) + // lazy route lmao + set_max_integrity(integrity_max + amount) + +//* Integrity - Check / Getters *// + +/** + * percent integrity, rounded. + */ +/atom/proc/percent_integrity(round_to = 0.1) + return integrity_max? round(integrity / integrity_max, round_to) : 0 + +/atom/proc/is_integrity_broken() + return atom_flags & ATOM_BROKEN + +/atom/proc/is_integrity_damaged() + return integrity < integrity_max + +//* Integrity - Events *// + +/** + * called when integrity reaches 0 from a non 0 value + */ +/atom/proc/atom_destruction() + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + if(!(integrity_flags & INTEGRITY_NO_DECONSTRUCT)) + deconstruct(ATOM_DECONSTRUCT_DESTROYED) + +/** + * called when integrity drops below or at integrity_failure + * + * if integrity_failure is 0, this is called before destruction. + */ +/atom/proc/atom_break() + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + if(integrity > integrity_failure) + damage_integrity(integrity - integrity_failure, do_not_break = TRUE) + atom_flags |= ATOM_BROKEN + +/** + * called when integrity rises above integrity_failure + * + * if integrity_failure is 0, this still works. + */ +/atom/proc/atom_fix() + SHOULD_CALL_PARENT(TRUE) + SHOULD_NOT_SLEEP(TRUE) + if(integrity < integrity_failure) + heal_integrity(integrity_failure - integrity + 1, do_not_fix = TRUE) + atom_flags &= ~ATOM_BROKEN diff --git a/code/game/atoms/atom-defense.dm b/code/game/atoms/atom-defense.dm new file mode 100644 index 00000000000..3f2f7141d7b --- /dev/null +++ b/code/game/atoms/atom-defense.dm @@ -0,0 +1,532 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//! Welcome to hell. + +// todo: everything needs comsigs comsigs comsigs + +//* External API / Damage Receiving *// + +/** + * todo: implement on most atoms/generic damage system + * todo: replace legacy_ex_act entirely with this + * + * React to being hit by an explosive shockwave + * + * ? Tip for overrides: . = ..() when you want signal to be sent, mdify power before if you need to; to ignore parent + * ? block power, just `return power` in your proc after . = ..(). + * + * @params + * - power - power our turf was hit with + * - direction - DIR_BIT bits; can bwe null if it wasn't a wave explosion!! + * - explosion - explosion automata datum; can be null + * + * @return power after falloff (e.g. hit with 30 power, return 20 to apply 10 falloff) + */ +/atom/proc/ex_act(power, dir, datum/automata/wave/explosion/E) + SHOULD_CALL_PARENT(TRUE) + SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, power, dir, E) + return power + +/** + * called on melee hit + * + * todo: add clickchain datum, instead of multiplier + * + * * check CLICKCHAIN_FLAGS_* as needed, especially UNCONDITIONAL_ABORT and ATTACK_ABORT + * * clickchain flags are sent down through parent calls. + * + * @params + * * user - person attacking + * * weapon - weapon used + * * target_zone - zone targeted + * * mult - damage multiplier + * + * @return clickchain flags to append + */ +/atom/proc/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain) + return CLICKCHAIN_DO_NOT_ATTACK + +/** + * called on unarmed melee hit + * + * todo: add clickchain datum, instead of multiplier + * + * @params + * * user - person attacking + * * style - unarmed attack datum + * * target_zone - zone targeted + * * mult - damage multiplier + * + * @return clickchain flags to append + */ +/atom/proc/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain) + return CLICKCHAIN_DO_NOT_ATTACK + +/** + * Because this is the proc that calls on_impact(), handling is necessarily + * done in here for a lot of things. + * + * * Overrides should modify proc args directly (e.g. impact_flags) and then call ..() + * if it needs to be taken account by default handling. + * * Overrides should not edit args after ..(), as args are only passed up, not down. + * * Overrides should, for that reason, always call ..() last. + * * This semantically means 'we are **about** to be hit, do anything for special processing'. + * * If you need to delete a projectile on impact, use `on_bullet_act()`; that's called after the contact actually happens. + * + * Things to keep in mind + * * 'efficiency' arg is **extremely** powerful. Please don't lower it to dismal values for no reason. + * * use PROJECTILE_IMPACT_BLOCKED instead of setting efficiency to 0 if an impact is entirely blocked + * * semantically, efficiency 0 means shield from all damages, IMPACT_BLOCKED means it hit something else + * * bullet_act is this way because we don't make a `/datum/event_args/actor/clickchain` with every call, so it needs a way of propagating blocking behavior and impact flags up/down the chain. + * + * @params + * * proj - the projectile + * * impact_flags - PROJECTILE_IMPACT_* flags + * * def_zone - impacting zone; calculated by projectile side, usually + * * efficiency - 0 to 1, inclusive. ratio of effects, including damage, to pass through. + * + * todo: add PROJECTILE_IMPACT_DELETE_AFTER as opposed to DELETE? so rest of effects can still run + * todo: shieldcalls still fire if target aborts without unconditional abort, they should not do that. + * + * @return new impact_flags + */ +/atom/proc/bullet_act(obj/projectile/proj, impact_flags, def_zone, efficiency = 1) + SHOULD_NOT_SLEEP(TRUE) + SHOULD_CALL_PARENT(TRUE) + // lower calls can change flags before we trigger + // check if we're still hitting + if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return impact_flags + // 0. fire signal + SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, args) + // check if we're still hitting + if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return impact_flags + // 1. fire shieldcalls + atom_shieldcall_handle_bullet(args, FALSE, NONE) + // check if we're still hitting + if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return impact_flags + // 2. fire on_bullet_act + impact_flags |= on_bullet_act(proj, impact_flags, args) + if(impact_flags & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return impact_flags + // 3. fire projectile-side on_impact + return proj.on_impact(src, impact_flags, def_zone, efficiency) + +/** + * So, turns out, BYOND passes `args` list **down** on a ..() via arglist(args) equivalent but not back up. + * + * This means that modified bullet act args can't actually propagate through. + * To handle this, we have this function to actually perform the effects of said bullet act. + * + * * This basically semantically means 'we are being hit, do effects for it'. + * * This is called before the projectile-side impact, which is where the damage is usually inflicted. + * * Modify `impact_flags` directly before ..(), and `.` after ..() + * * Check `.` after ..() if it isn't the last call so you know when to abort the processing as needed. + * * Args will propagate **up** (closer to /atom) `..()` calls, but not back down (if base `/atom` changes something you won't get it on your sub-type) + * * For this reason `bullet_act_args` is provided so you can mutably edit it. Do **not** edit the projectile or the impact flags; return the impact flags for automatic addition. + * + * @params + * * proj - hitting projectile; immutable + * * impact_flags - impact flags; immutable. edit directly before ..() call, return edited values after. + * * bullet_act_args - access to the rest of the args in `bullet_act`. Mutable, except for the projectile and the impact flags. + */ +/atom/proc/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + return impact_flags + +//* Hitsound API *// + +// todo: stuff like metal limbs punching walls making special sounds +// todo: this probably needs a rework + +/** + * gets hitsound override. return a value to be fed into playsound, or null for default. + * + * @params + * * damage_type - damage type like brute / burn / etc + * * damage_mode - damage mode for piercing / whatnot + * * attack_type - attack type enum like melee / projectile / thrown / unarmed / etc + * * weapon - attacking /obj/item for melee / thrown, /obj/projectile for ranged, /mob for unarmed + */ +/atom/proc/hitsound_override(damage_type, damage_mode, attack_type, datum/weapon) + return // default is null + +/atom/proc/hitsound_melee(obj/item/I) + . = I.attacksound_override(src, ATTACK_TYPE_MELEE) + if(!isnull(.)) + return + . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_MELEE, I) + if(.) + return + . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound + if(.) + return + switch(I.damtype) + if(BRUTE) + return "swing_hit" + if(BURN) + return "'sound/items/welder.ogg" + else + return "swing_hit" + +/atom/proc/hitsound_projectile(obj/projectile/P) + //? todo: projectile gets final say + . = hitsound_override(P.damtype, P.damage_mode, ATTACK_TYPE_PROJECTILE, P) + if(.) + return + return islist(P.impact_sounds)? pick(P.impact_sounds) : P.impact_sounds + +/atom/proc/hitsound_throwhit(obj/item/I) + . = I.attacksound_override(src, ATTACK_TYPE_THROWN) + if(!isnull(.)) + return + . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_THROWN, I) + if(.) + return + . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound + if(.) + return + switch(I.damtype) + if(BRUTE) + return "swing_hit" + if(BURN) + return 'sound/items/welder.ogg' + else + return "swing_hit" + +/atom/proc/hitsound_unarmed(mob/M, datum/unarmed_attack/style) + //? todo: style gets final say + . = hitsound_override(M, style.damage_mode, ATTACK_TYPE_UNARMED, style) + if(.) + return + // todo: way to override this from style side? we don't just want hitsound brute/burn. + . = (style.damage_type == BURN? hit_sound_burn : hit_sound_brute) || style.attack_sound + + +//* Armor *// + +/** + * resets our armor to initial values + */ +/atom/proc/reset_armor() + set_armor(initial(armor_type)) + +/** + * sets our armor + * + * @params + * * what - list of armor values or a /datum/armor path + */ +/atom/proc/set_armor(what) + armor = fetch_armor_struct(what) + +/** + * gets our armor datum or otherwise make sure it exists + */ +/atom/proc/fetch_armor() + RETURN_TYPE(/datum/armor) + return armor || (armor = generate_armor()) + +/** + * get default armor datum + */ +/atom/proc/generate_armor() + return fetch_armor_struct(armor_type) + +/** + * runs an attack against armor + * + * * side effects are **not** allowed + * * this is the 'just checking' version. + * + * params are modified and then returned as a list + * + * * See [atom_shieldcall()] for what is going on here. + * * SHIELDCALL_ARG_* are used as the return list's indices. + * + * @params + * * damage - raw damage + * * damtype - damage type + * * tier - penetration / attack tier + * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] + * * mode - damage_mode + * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] + * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm] + * * flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm] + * * hit_zone - where were they hit? + * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm] + * * clickchain - the clickchain for melee attacks. + * + * @return args, modified, as list. + */ +/atom/proc/check_armor(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_SLEEP(TRUE) + run_armorcalls(args, TRUE) + return args.Copy() + +/** + * runs an attack against armor + * + * * side effects are allowed + * + * params are modified and then returned as a list + * + * * See [atom_shieldcall()] for what is going on here. + * * SHIELDCALL_ARG_* are used as the return list's indices. + * + * @params + * * damage - raw damage + * * damtype - damage type + * * tier - penetration / attack tier + * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] + * * mode - damage_mode + * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] + * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm] + * * flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm] + * * hit_zone - where were they hit? + * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm] + * * clickchain - the clickchain for melee attacks. + * + * @return args, modified, as list. + */ +/atom/proc/run_armor(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_SLEEP(TRUE) + run_armorcalls(args, FALSE) + return args.Copy() + +//* Shieldcalls *// + +/** + * runs an attack against shields + * + * * side effects are **not** allowed + * * this is the 'just checking' version. + * + * params are modified and then returned as a list + * + * * This is the dynamic shieldcall system. It's best to do what you want in specific shieldcall hooks if possible. + * * What this means is that this can't, say, redirect or delete a projectile, because bullet act handling is where that happens. + * * This more or less just lets you modify incoming damage instances sometimes. + * * The args are not copied! They're passed back directly. This has implications. + * * Make sure you pass in SHIELDCALL_FLAG_SECOND_CALL if **any** kind of shieldcall invocation has happened during this attack. + * * SECOND_CALL is required to tell things that something is not the first time, so you don't get doubled blocking efficiency. + * + * @params + * * damage - raw damage + * * damtype - damage type + * * damage_tier - penetration / attack tier + * * damage_flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] + * * damage_mode - damage_mode + * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] + * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm] + * * shieldcall_flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm] + * * hit_zone - where were they hit? + * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm] + * * clickchain - the clickchain for melee attacks. + * + * @return args, modified, as list. + */ +/atom/proc/atom_shieldcheck(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_SLEEP(TRUE) + run_shieldcalls(args, TRUE) + return args.Copy() + +/** + * runs an attack against shields + * + * * side effects are allowed + * * this is run during an actual attack + * + * params are modified and then returned as a list + * + * * This is the dynamic shieldcall system. It's best to do what you want in specific shieldcall hooks if possible. + * * What this means is that this can't, say, redirect or delete a projectile, because bullet act handling is where that happens. + * * This more or less just lets you modify incoming damage instances sometimes. + * * The args are not copied! They're passed back directly. This has implications. + * * Make sure you pass in SHIELDCALL_FLAG_SECOND_CALL if **any** kind of shieldcall invocation has happened during this attack. + * * SECOND_CALL is required to tell things that something is not the first time, so you don't get doubled blocking efficiency. + * + * @params + * * damage - raw damage + * * damtype - damage type + * * damage_tier - penetration / attack tier + * * damage_flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] + * * damage_mode - damage_mode + * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] + * * weapon - (optional) the attacking weapon datum; see [code/__DEFINES/combat/shieldcall.dm] + * * shieldcall_flags - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm] + * * hit_zone - where were they hit? + * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm] + * * clickchain - the clickchain for melee attacks. + * + * @return args, modified, as list. + */ +/atom/proc/atom_shieldcall(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_SLEEP(TRUE) + run_shieldcalls(args, FALSE) + return args.Copy() + +/** + * Runs a damage instance against shieldcalls + * + * * This is a low level proc. Make sure you undersatnd how shieldcalls work [__DEFINES/combat/shieldcall.dm]. + */ +/atom/proc/run_shieldcalls(list/shieldcall_args, fake_attack) + SHOULD_NOT_SLEEP(TRUE) + SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL, shieldcall_args, fake_attack) + if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE) + return + for(var/datum/shieldcall/calling as anything in shieldcalls) + if(!calling.low_level_intercept) + continue + calling.handle_shieldcall(src, args, fake_attack) + if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE) + break + +/** + * Runs a damage instance against armor + * + * * This is a low level proc. Make sure you undersatnd how shieldcalls work [__DEFINES/combat/shieldcall.dm]. + */ +/atom/proc/run_armorcalls(list/shieldcall_args, fake_attack) + SHOULD_NOT_SLEEP(TRUE) + SEND_SIGNAL(src, COMSIG_ATOM_ARMORCALL, shieldcall_args, fake_attack) + if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE) + return + var/datum/armor/our_armor = fetch_armor() + our_armor.handle_shieldcall(shieldcall_args, fake_attack) + +/atom/proc/register_shieldcall(datum/shieldcall/delegate) + SHOULD_NOT_SLEEP(TRUE) + LAZYINITLIST(shieldcalls) + BINARY_INSERT(delegate, shieldcalls, /datum/shieldcall, delegate, priority, COMPARE_KEY) + +/atom/proc/unregister_shieldcall(datum/shieldcall/delegate) + SHOULD_NOT_SLEEP(TRUE) + LAZYREMOVE(shieldcalls, delegate) + +//* Shieldcalls - Focused / High-Level *// + +/** + * Runs shieldcalls for handle_touch + * + * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up. + * + * @params + * * e_args (optional) the clickchain event, if any; **This is mutable.** + * * contact_flags - SHIELDCALL_CONTACT_FLAG_* + * * contact_specific - SHIELDCALL_CONTACT_SPECIFIC_* + * * fake_attack - just checking! + * * shieldcall_flags - shieldcall flags. [code/__DEFINES/combat/shieldcall.dm] + * * clickchain_flags - clickchain flags. [code/__DEFINES/procs/clickcode.dm] + * + * @return SHIELDCALL_FLAG_* flags + */ +/atom/proc/atom_shieldcall_handle_touch(datum/event_args/actor/clickchain/e_args, contact_flags, contact_specific, fake_attack, shieldcall_flags) + SHOULD_NOT_SLEEP(TRUE) + // cannot parry yourself + if(e_args.performer == src) + return shieldcall_flags + // send query signal + SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_TOUCH) + . = shieldcall_flags + for(var/datum/shieldcall/shieldcall as anything in shieldcalls) + . |= shieldcall.handle_touch(src, ., fake_attack, e_args, contact_flags, contact_specific) + +/** + * Runs shieldcalls for handle_unarmed_melee + * + * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up. + * + * @params + * * style - the unarmed_attack datum being used + * * e_args (optional) the clickchain event, if any; **This is mutable.** + * * fake_attack - (optional) just checking! + * * shieldcall_flags - (optional) shieldcall flags. [code/__DEFINES/combat/shieldcall.dm] + * * clickchain_flags - (optional) clickchain flags. [code/__DEFINES/procs/clickcode.dm] + * + * @return SHIELDCALL_FLAG_* flags + */ +/atom/proc/atom_shieldcall_handle_unarmed_melee(datum/unarmed_attack/style, datum/event_args/actor/clickchain/e_args, fake_attack, shieldcall_flags, clickchain_flags) + SHOULD_NOT_SLEEP(TRUE) + // cannot parry yourself + if(e_args.performer == src) + return shieldcall_flags + // send query signal + SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_UNARMED_MELEE) + . = shieldcall_flags + for(var/datum/shieldcall/shieldcall as anything in shieldcalls) + . |= shieldcall.handle_unarmed_melee(src, ., fake_attack, style, e_args) + +/** + * Runs shieldcalls for handle_item_melee + * + * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up. + * + * @params + * * weapon - the item being used to swing with + * * e_args - (optional) the clickchain event, if any; **This is mutable.** + * * fake_attack - (optional) just checking! + * * shieldcall_flags - (optional) shieldcall flags. [code/__DEFINES/combat/shieldcall.dm] + * * clickchain_flags - (optional) clickchain flags. [code/__DEFINES/procs/clickcode.dm] + * + * @return SHIELDCALL_FLAG_* flags + */ +/atom/proc/atom_shieldcall_handle_item_melee(obj/item/weapon, datum/event_args/actor/clickchain/e_args, fake_attack, shieldcall_flags, clickchain_flags) + SHOULD_NOT_SLEEP(TRUE) + // cannot parry yourself + if(e_args.performer == src) + return shieldcall_flags + // send query signal + SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_ITEM_MELEE) + . = shieldcall_flags + for(var/datum/shieldcall/shieldcall as anything in shieldcalls) + . |= shieldcall.handle_item_melee(src, ., fake_attack, weapon, e_args) + +/** + * Runs shieldcalls for handle_bullet + * + * * Use this instead of manually looping, as it fires a signal that makes things like /datum/passive_parry spin up. + * + * @params + * * bullet_act_args - indexed list of bullet_act args. + * * shieldcall_returns - existing returns from other shieldcalls + * * fake_attack - just checking! + * * shieldcall_flags - shieldcall flags. [code/__DEFINES/combat/shieldcall.dm] + * + * @return SHIELDCALL_FLAG_TERMINATE or NONE + */ +/atom/proc/atom_shieldcall_handle_bullet(list/bullet_act_args, fake_attack, shieldcall_flags) + SHOULD_NOT_SLEEP(TRUE) + // cannot parry yourself + var/obj/projectile/proj = bullet_act_args[BULLET_ACT_ARG_PROJECTILE] + if(proj.firer == src && (bullet_act_args[BULLET_ACT_ARG_FLAGS] & PROJECTILE_IMPACT_POINT_BLANK)) + return shieldcall_flags + // send query signal + SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_BULLET_ACT) + . = shieldcall_flags + for(var/datum/shieldcall/shieldcall as anything in shieldcalls) + . |= shieldcall.handle_bullet(src, ., fake_attack, bullet_act_args) + +/** + * Runs shieldcalls for handle_throw_impact + * + * @params + * * thrown - the thrown object's data + * * fake_attack - just checking! + * * shieldcall_flags - shieldcall flags. [code/__DEFINES/combat/shieldcall.dm] + * + * @return SHIELDCALL_FLAG_* flags + */ +/atom/proc/atom_shieldcall_handle_throw_impact(datum/thrownthing/thrown, fake_attack, shieldcall_flags) + SHOULD_NOT_SLEEP(TRUE) + // cannot parry yourself + if(thrown.thrower == src && thrown.dist_travelled <= 1) + return shieldcall_flags + // send query signal + SEND_SIGNAL(src, COMSIG_ATOM_SHIELDCALL_ITERATION, ATOM_SHIELDCALL_ITERATING_THROW_IMPACT) + . = shieldcall_flags + for(var/datum/shieldcall/shieldcall as anything in shieldcalls) + . |= shieldcall.handle_throw_impact(src, ., fake_attack, thrown) diff --git a/code/game/atoms/atom.dm b/code/game/atoms/atom.dm index b9bcee5fe40..95403d73eb5 100644 --- a/code/game/atoms/atom.dm +++ b/code/game/atoms/atom.dm @@ -882,6 +882,7 @@ return T.has_gravity() +// todo: annihilate this in favor of ATOM_PASS_INCORPOREAL /atom/proc/is_incorporeal() return FALSE diff --git a/code/game/atoms/buckling.dm b/code/game/atoms/buckling.dm index 45e68574979..b286d57f88e 100644 --- a/code/game/atoms/buckling.dm +++ b/code/game/atoms/buckling.dm @@ -1,5 +1,5 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// +//* Copyright (c) 2024 silicons *// /atom/movable/MouseDroppedOn(atom/dropping, mob/user, proximity, params) if(drag_drop_buckle_interaction(dropping, user)) @@ -40,7 +40,7 @@ return FALSE if(!user.Adjacent(src) || !A.Adjacent(src)) return FALSE - if(SEND_SIGNAL(src, COMSIG_MOVABLE_DRAG_DROP_BUCKLE_INTERACTION, A, user) & COMPONENT_HANDLED_BUCKLE_INTERACTION) + if(SEND_SIGNAL(src, COMSIG_MOVABLE_DRAG_DROP_BUCKLE_INTERACTION, A, user) & SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED) return TRUE if(!buckle_allowed || (buckle_flags & BUCKLING_NO_DEFAULT_BUCKLE)) return FALSE @@ -69,7 +69,7 @@ // todo: refactor below if(user.incapacitated()) return TRUE - if(SEND_SIGNAL(src, COMSIG_MOVABLE_CLICK_UNBUCKLE_INTERACTION, user) & COMPONENT_HANDLED_BUCKLE_INTERACTION) + if(SEND_SIGNAL(src, COMSIG_MOVABLE_CLICK_UNBUCKLE_INTERACTION, user) & SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED) return TRUE if(!buckle_allowed || (buckle_flags & BUCKLING_NO_DEFAULT_UNBUCKLE)) return FALSE @@ -92,7 +92,7 @@ /atom/movable/proc/user_unbuckle_mob(mob/M, flags, mob/user, semantic) SHOULD_CALL_PARENT(TRUE) . = SEND_SIGNAL(src, COMSIG_MOVABLE_USER_UNBUCKLE_MOB, M, flags, user, semantic) - if(. & COMPONENT_BLOCK_BUCKLE_OPERATION) + if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION) return FALSE . = unbuckle_mob(M, flags, user, semantic) if(!.) @@ -126,7 +126,7 @@ /atom/movable/proc/user_buckle_mob(mob/M, flags, mob/user, semantic) SHOULD_CALL_PARENT(TRUE) . = SEND_SIGNAL(src, COMSIG_MOVABLE_USER_BUCKLE_MOB, M, flags, user, semantic) - if(. & COMPONENT_BLOCK_BUCKLE_OPERATION) + if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION) return FALSE if((buckle_flags & BUCKLING_NO_USER_BUCKLE_OTHER_TO_SELF) && (user == src)) return FALSE @@ -166,7 +166,7 @@ if(M == src) return FALSE - if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_BUCKLE_MOB, M, flags, user, semantic) & COMPONENT_BLOCK_BUCKLE_OPERATION) + if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_BUCKLE_MOB, M, flags, user, semantic) & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION) return FALSE if(!(flags & BUCKLE_OP_FORCE) && !can_buckle_mob(M, flags, user, semantic)) @@ -244,9 +244,9 @@ /atom/movable/proc/can_buckle_mob(mob/M, flags, mob/user, semantic) SHOULD_CALL_PARENT(TRUE) . = SEND_SIGNAL(src, COMSIG_MOVABLE_CAN_BUCKLE_MOB, M, flags, user, semantic) - if(. & COMPONENT_BLOCK_BUCKLE_OPERATION) + if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION) return FALSE - else if(. & COMPONENT_FORCE_BUCKLE_OPERATION) + else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION) return TRUE if(!(flags & BUCKLE_OP_IGNORE_LOC) && !M.Adjacent(src)) return FALSE @@ -276,9 +276,9 @@ /atom/movable/proc/can_unbuckle_mob(mob/M, flags, mob/user, semantic) SHOULD_CALL_PARENT(TRUE) . = SEND_SIGNAL(src, COMSIG_MOVABLE_CAN_UNBUCKLE_MOB, M, flags, user, semantic) - if(. & COMPONENT_BLOCK_BUCKLE_OPERATION) + if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION) return FALSE - else if(. & COMPONENT_FORCE_BUCKLE_OPERATION) + else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION) return TRUE return TRUE /** @@ -329,7 +329,7 @@ /atom/movable/proc/resist_unbuckle_interaction(mob/M) set waitfor = FALSE ASSERT(M in buckled_mobs) - if(SEND_SIGNAL(src, COMSIG_MOVABLE_RESIST_UNBUCKLE_INTERACTION, M) & COMPONENT_HANDLED_BUCKLE_INTERACTION) + if(SEND_SIGNAL(src, COMSIG_MOVABLE_RESIST_UNBUCKLE_INTERACTION, M) & SIGNAL_RAISE_BUCKLE_INTERACTION_HANDLED) return if(!buckle_allowed || (buckle_flags & BUCKLING_NO_DEFAULT_RESIST)) return FALSE @@ -410,14 +410,14 @@ */ /mob/proc/buckled(atom/movable/AM, flags, mob/user, semantic) SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_MOB_BUCKLED, AM, flags, user, semantic) + SEND_SIGNAL(src, COMSIG_MOB_BUCKLED_TO, AM, flags, user, semantic) /** * called when we're unbuckled from something */ /mob/proc/unbuckled(atom/movable/AM, flags, mob/user, semantic) SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_MOB_UNBUCKLED, AM, flags, user, semantic) + SEND_SIGNAL(src, COMSIG_MOB_UNBUCKLED_FROM, AM, flags, user, semantic) /** * can we buckle to something? @@ -426,10 +426,10 @@ */ /mob/proc/can_buckle(atom/movable/AM, flags, mob/user, semantic, movable_opinion) SHOULD_CALL_PARENT(TRUE) - . = SEND_SIGNAL(src, COMSIG_MOB_CAN_BUCKLE, AM, flags, user, semantic, movable_opinion) - if(. & COMPONENT_BLOCK_BUCKLE_OPERATION) + . = SEND_SIGNAL(src, COMSIG_MOB_CAN_BUCKLE_TO, AM, flags, user, semantic, movable_opinion) + if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION) return FALSE - else if(. & COMPONENT_FORCE_BUCKLE_OPERATION) + else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION) return TRUE return movable_opinion @@ -440,10 +440,10 @@ */ /mob/proc/can_unbuckle(atom/movable/AM, flags, mob/user, semantic, movable_opinion) SHOULD_CALL_PARENT(TRUE) - . = SEND_SIGNAL(src, COMSIG_MOB_CAN_UNBUCKLE, AM, flags, user, semantic, movable_opinion) - if(. & COMPONENT_BLOCK_BUCKLE_OPERATION) + . = SEND_SIGNAL(src, COMSIG_MOB_CAN_UNBUCKLE_FROM, AM, flags, user, semantic, movable_opinion) + if(. & SIGNAL_RAISE_BLOCK_BUCKLE_OPERATION) return FALSE - else if(. & COMPONENT_FORCE_BUCKLE_OPERATION) + else if(. & SIGNAL_RAISE_FORCE_BUCKLE_OPERATION) return TRUE return movable_opinion diff --git a/code/game/atoms/defense.dm b/code/game/atoms/defense.dm deleted file mode 100644 index 6fbf61c69c7..00000000000 --- a/code/game/atoms/defense.dm +++ /dev/null @@ -1,547 +0,0 @@ -//* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// - -//! Welcome to the atom damage module. -//! Enjoy the bitfield and #define vomit. - -// todo: everything needs comsigs comsigs comsigs - -//? Hooks / External - -/** - * todo: implement on most atoms/generic damage system - * todo: replace legacy_ex_act entirely with this - * - * React to being hit by an explosive shockwave - * - * ? Tip for overrides: . = ..() when you want signal to be sent, mdify power before if you need to; to ignore parent - * ? block power, just `return power` in your proc after . = ..(). - * - * @params - * - power - power our turf was hit with - * - direction - DIR_BIT bits; can bwe null if it wasn't a wave explosion!! - * - explosion - explosion automata datum; can be null - * - * @return power after falloff (e.g. hit with 30 power, return 20 to apply 10 falloff) - */ -/atom/proc/ex_act(power, dir, datum/automata/wave/explosion/E) - SHOULD_CALL_PARENT(TRUE) - SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, power, dir, E) - return power - -/** - * called on melee hit - * - * @params - * * user - person attacking - * * weapon - weapon used - * * target_zone - zone targeted - * * mult - damage multiplier - * - * @return clickchain flags to append - */ -/atom/proc/melee_act(mob/user, obj/item/weapon, target_zone, mult = 1) - return CLICKCHAIN_DO_NOT_ATTACK - -/** - * called on unarmed melee hit - * - * @params - * * user - person attacking - * * style - unarmed attack datum - * * target_zone - zone targeted - * * mult - damage multiplier - * - * @return clickchain flags to append - */ -/atom/proc/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult = 1) - return CLICKCHAIN_DO_NOT_ATTACK - -//? Damage API - -/** - * takes damage from a generic attack, taking into account armor but not shields. - * this does not handle playing sounds / anything, this is strictly generic damage handling - * usable by anything. - * - * @params - * * damage - raw damage - * * tier - (optional) penetration / attack tier - * * flag - (optional) armor flag as seen in [code/__DEFINES/combat/armor.dm]; leave out to not run armor. - * * mode - (optional) damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * * gradual - loud effects like glass clanging should not happen. use for stuff like acid and fire. - * - * @return raw damage taken - */ -/atom/proc/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual) - if(!integrity_enabled) - return 0 - if(integrity_flags & INTEGRITY_INDESTRUCTIBLE) - return 0 - if(flag) - var/list/returned = run_armor(arglist(args)) - damage = returned[1] - mode = returned[4] - if(!damage) - return - . = integrity - damage_integrity(damage) - . = . - integrity - -//? Hitsound API - -// todo: stuff like metal limbs punching walls making special sounds -// todo: this probably needs a rework - -/** - * gets hitsound override. return a value to be fed into playsound, or null for default. - * - * @params - * * damage_type - damage type like brute / burn / etc - * * damage_mode - damage mode for piercing / whatnot - * * attack_type - attack type enum like melee / projectile / thrown / unarmed / etc - * * weapon - attacking /obj/item for melee / thrown, /obj/projectile for ranged, /mob for unarmed - */ -/atom/proc/hitsound_override(damage_type, damage_mode, attack_type, datum/weapon) - return // default is null - -/atom/proc/hitsound_melee(obj/item/I) - . = I.attacksound_override(src, ATTACK_TYPE_MELEE) - if(!isnull(.)) - return - . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_MELEE, I) - if(.) - return - . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound - if(.) - return - switch(I.damtype) - if(BRUTE) - return "swing_hit" - if(BURN) - return "'sound/items/welder.ogg" - else - return "swing_hit" - -/atom/proc/hitsound_projectile(obj/projectile/P) - //? todo: projectile gets final say - . = hitsound_override(P.damtype, P.damage_mode, ATTACK_TYPE_PROJECTILE, P) - if(.) - return - return islist(P.impact_sounds)? pick(P.impact_sounds) : P.impact_sounds - -/atom/proc/hitsound_throwhit(obj/item/I) - . = I.attacksound_override(src, ATTACK_TYPE_THROWN) - if(!isnull(.)) - return - . = hitsound_override(I.damtype, I.damage_mode, ATTACK_TYPE_THROWN, I) - if(.) - return - . = (I.damtype == BURN? hit_sound_burn : hit_sound_brute) || I.attack_sound - if(.) - return - switch(I.damtype) - if(BRUTE) - return "swing_hit" - if(BURN) - return 'sound/items/welder.ogg' - else - return "swing_hit" - -/atom/proc/hitsound_unarmed(mob/M, datum/unarmed_attack/style) - //? todo: style gets final say - . = hitsound_override(M, style.damage_mode, ATTACK_TYPE_UNARMED, style) - if(.) - return - // todo: way to override this from style side? we don't just want hitsound brute/burn. - . = (style.damage_type == BURN? hit_sound_burn : hit_sound_brute) || style.attack_sound - -//? Direct Integrity - -/** - * damages integrity directly, ignoring armor / shields - * - * @params - * * amount - how much - * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this. - * * do_not_break - skip calling atom_break - */ -/atom/proc/damage_integrity(amount, gradual, do_not_break) - SHOULD_CALL_PARENT(TRUE) - SHOULD_NOT_SLEEP(TRUE) - var/was_working = integrity > integrity_failure - integrity = max(0, integrity - amount) - if(was_working && integrity <= integrity_failure && !do_not_break) - atom_break() - if(integrity <= 0) - atom_destruction() - -/** - * heals integrity directly - * - * @params - * * amount - how much - * * gradual - burst or gradual? if you want to play a sound or something, you usually want to check this. - * * do_not_fix - skip calling atom_fix - * - * @return amount healed - */ -/atom/proc/heal_integrity(amount, gradual, do_not_fix) - SHOULD_CALL_PARENT(TRUE) - SHOULD_NOT_SLEEP(TRUE) - var/was_failing = integrity <= integrity_failure - . = integrity - integrity = min(integrity_max, integrity + amount) - . = integrity - . - if(was_failing && integrity > integrity_failure && !do_not_fix) - atom_fix() - -/** - * directly sets integrity - ignores armor / sihelds - * - * will not call [damage_integrity] or [heal_integrity] - * will call [atom_break], [atom_fix], [atom_destruction] - * - * @params - * * amount - how much to set to? - */ -/atom/proc/set_integrity(amount) - SHOULD_CALL_PARENT(TRUE) - SHOULD_NOT_SLEEP(TRUE) - var/was_failing = integrity <= integrity_failure - integrity = clamp(amount, 0, integrity_max) - if(!was_failing && integrity <= integrity_failure) - atom_break() - else if(was_failing && integrity > integrity_failure) - atom_fix() - if(integrity <= 0) - atom_destruction() - -/** - * sets max integrity - will automatically reduce integrity if it's above max. - * - * will not call [damage_integrity] - * will call [atom_break], [atom_fix], [atom_destruction] - * - * @params - * * amount - how much to set to - */ -/atom/proc/set_max_integrity(amount) - integrity_max = max(amount, 0) - if(integrity < integrity_max) - return - var/was_broken = integrity <= integrity_failure - integrity = integrity_max - if(!was_broken && (integrity <= integrity_failure)) - atom_break() - if(integrity <= 0) - atom_destruction() - -/** - * sets integrity and max integrity - will automatically reduce integrity if it's above max. - * - * will not call [damage_integrity] - * will call [atom_break], [atom_fix], [atom_destruction] - * - * @params - * * integrity - how much to set integrity to - * * integrity_max - how much to set integrity_max to - */ -/atom/proc/set_full_integrity(integrity, integrity_max) - src.integrity_max = max(integrity_max, 0) - var/was_broken = src.integrity <= integrity_failure - src.integrity = clamp(integrity, 0, integrity_max) - var/now_broken = integrity <= integrity_failure - if(!was_broken && now_broken) - atom_break() - else if(was_broken && !now_broken) - atom_fix() - if(integrity <= 0) - atom_destruction() - -/** - * Set integrity to a multiple of initial. - * - * And fully restore it if specified. - * Otherwise, will retain the last percentage. - */ -/atom/proc/set_multiplied_integrity(factor, restore) - var/was_broken = src.integrity <= integrity_failure - if(restore) - integrity = integrity_max = initial(integrity_max) * factor - if(was_broken && integrity > integrity_failure) - atom_fix() - return - var/ratio = integrity / integrity_max - integrity_max = initial(integrity_max) * factor - integrity = integrity_max * ratio - var/now_broken = integrity <= integrity_failure - if(!was_broken && now_broken) - atom_break() - else if(was_broken && !now_broken) - atom_fix() - if(integrity <= 0) - atom_destruction() - -/** - * adjusts integrity - routes directly to [damage_integrity] and [heal_integrity] - * - * will call [damage_integrity] - * will call [atom_break], [atom_fix], [atom_destruction] - * - * @params - * * amount - how much - * * gradual - burst or gradual? - * * no_checks - do not call fix/break - */ -/atom/proc/adjust_integrity(amount, gradual, no_checks) - SHOULD_CALL_PARENT(TRUE) - SHOULD_NOT_SLEEP(TRUE) - if(amount > 0) - return heal_integrity(amount, gradual, no_checks) - else - return damage_integrity(amount, gradual, no_checks) - -/** - * adjusts max integrity - will automatically reduce integrity if it's above max. - * - * will call [damage_integrity] - * will call [atom_break], [atom_fix], [atom_destruction] - * - * @params - * * amount - how much to adjust by - * * gradual - burst or gradual? - */ -/atom/proc/adjust_max_integrity(amount, gradual) - // lazy route lmao - set_max_integrity(integrity_max + amount) - -/** - * percent integrity, rounded. - */ -/atom/proc/percent_integrity(round_to = 0.1) - return integrity_max? round(integrity / integrity_max, round_to) : 0 - -/atom/proc/is_integrity_broken() - return atom_flags & ATOM_BROKEN - -/atom/proc/is_integrity_damaged() - return integrity < integrity_max - -//? Thresholds & Events - -/** - * called when integrity reaches 0 from a non 0 value - */ -/atom/proc/atom_destruction() - SHOULD_CALL_PARENT(TRUE) - SHOULD_NOT_SLEEP(TRUE) - if(!(integrity_flags & INTEGRITY_NO_DECONSTRUCT)) - deconstruct(ATOM_DECONSTRUCT_DESTROYED) - -/** - * called when integrity drops below or at integrity_failure - * - * if integrity_failure is 0, this is called before destruction. - */ -/atom/proc/atom_break() - SHOULD_CALL_PARENT(TRUE) - SHOULD_NOT_SLEEP(TRUE) - if(integrity > integrity_failure) - damage_integrity(integrity - integrity_failure, do_not_break = TRUE) - atom_flags |= ATOM_BROKEN - -/** - * called when integrity rises above integrity_failure - * - * if integrity_failure is 0, this still works. - */ -/atom/proc/atom_fix() - SHOULD_CALL_PARENT(TRUE) - SHOULD_NOT_SLEEP(TRUE) - if(integrity < integrity_failure) - heal_integrity(integrity_failure - integrity + 1, do_not_fix = TRUE) - atom_flags &= ~ATOM_BROKEN - -//? Deconstruction - -/** - * called to semantically deconstruct an atom - * - * @params - * * method - how we were deconstructed - */ -/atom/proc/deconstruct(method = ATOM_DECONSTRUCT_DISASSEMBLED) - SHOULD_NOT_OVERRIDE(TRUE) - - // send signal - // todo: signal - // do da funny logic - deconstructed(method) - // drop things after so things that rely on having objects don't break - drop_products(method, drop_location()) - // goodbye, cruel world - break_apart(method) - -/** - * called to actually destroy ourselves - */ -/atom/proc/break_apart(method) - qdel(src) - -/** - * called when we are deconstructed - * - * **do not drop products in here** - * - * @params - * - method - how we were deconstructed - */ -/atom/proc/deconstructed(method) - return - -/** - * called to drop the products of deconstruction - * - * @params - * * method - how we were deconstructed - * * where - where to drop products; set in base if null to drop_location(). - */ -/atom/proc/drop_products(method, atom/where = drop_location()) - return - -/** - * called to move a product to a place - * - * @params - * * method - how we were deconstructed - * * dropping - movable in question - * * where - where to move to - */ -/atom/proc/drop_product(method, atom/movable/dropping, atom/where) - dropping.forceMove(where || drop_location()) - -//? Armor - -/** - * resets our armor to initial values - */ -/atom/proc/reset_armor() - set_armor(initial(armor_type)) - -/** - * sets our armor - * - * @params - * * what - list of armor values or a /datum/armor path - */ -/atom/proc/set_armor(what) - armor = fetch_armor_struct(what) - -/** - * gets our armor datum or otherwise make sure it exists - */ -/atom/proc/fetch_armor() - RETURN_TYPE(/datum/armor) - return armor || (armor = generate_armor()) - -/** - * get default armor datum - */ -/atom/proc/generate_armor() - return fetch_armor_struct(armor_type) - -/** - * calculates the resulting damage from an attack, taking into account our armor and soak - * - * @params - * * damage - raw damage - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * - * @return args as list. - */ -/atom/proc/check_armor(damage, tier, flag, mode, attack_type, datum/weapon) - damage = fetch_armor().resultant_damage(damage, tier, flag) - return args.Copy() - -/** - * runs armor against an incoming attack - * this proc can have side effects - * - * @params - * * damage - raw damage - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * - * @return args as list. - */ -/atom/proc/run_armor(damage, tier, flag, mode, attack_type, datum/weapon) - damage = fetch_armor().resultant_damage(damage, tier, flag) - return args.Copy() - -//? shieldcalls - -/** - * checks for shields - * not always accurate - * - * params are modified and then returned as a list. - * - * @params - * * damage - raw damage - * * damtype - damage type - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/dcs/signals/atoms/signals_atom_defense.dm] - * * retval - shieldcall flags passed through components. [code/__DEFINES/dcs/signals/atoms/signals_atom_defense.dm] - * - * @return modified args as list - */ -/atom/proc/atom_shieldcheck(damage, damtype, tier, flag, mode, attack_type, datum/weapon, list/additional = list(), retval = NONE) - retval |= SHIELDCALL_JUST_CHECKING - for(var/datum/shieldcall/calling as anything in shieldcalls) - calling.handle_shieldcall(src, args) - return args.Copy() - -/** - * runs an attack against shields - * side effects are allowed - * - * params are modified and then returned as a list - * - * @params - * * damage - raw damage - * * damtype - damage type - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * * additional - a way to retrieve data out of the shieldcall, passed in by attacks. [code/__DEFINES/combat/shieldcall.dm] - * * retval - shieldcall flags passed through components. [code/__DEFINES/combat/shieldcall.dm] - * - * @return modified args as list - */ -/atom/proc/atom_shieldcall(damage, damtype, tier, flag, mode, attack_type, datum/weapon, list/additional = list(), retval = NONE) - for(var/datum/shieldcall/calling as anything in shieldcalls) - calling.handle_shieldcall(src, args) - return args.Copy() - -/atom/proc/register_shieldcall(datum/shieldcall/delegate) - LAZYINITLIST(shieldcalls) - BINARY_INSERT(delegate, shieldcalls, /datum/shieldcall, delegate, priority, COMPARE_KEY) - -/atom/proc/unregister_shieldcall(datum/shieldcall/delegate) - LAZYREMOVE(shieldcalls, delegate) diff --git a/code/game/atoms/defense_old.dm b/code/game/atoms/defense_old.dm index ab7d141eed0..d162a947cbf 100644 --- a/code/game/atoms/defense_old.dm +++ b/code/game/atoms/defense_old.dm @@ -12,10 +12,6 @@ // todo: SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity) -/atom/proc/bullet_act(obj/projectile/P, def_zone) - P.on_hit(src, 0, def_zone) - . = 0 - // Called when a blob expands onto the tile the atom occupies. /atom/proc/blob_act() return diff --git a/code/game/atoms/movable/throwing.dm b/code/game/atoms/movable/movable-throw.dm similarity index 99% rename from code/game/atoms/movable/throwing.dm rename to code/game/atoms/movable/movable-throw.dm index b6b21f4ce20..f329f1ab5bd 100644 --- a/code/game/atoms/movable/throwing.dm +++ b/code/game/atoms/movable/movable-throw.dm @@ -1,5 +1,5 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// +//* Copyright (c) 2024 silicons *// //! Welcome to unoptimized hell. Enjoy your comsigs. diff --git a/code/game/atoms/movable/movement.dm b/code/game/atoms/movable/movement.dm index 64c2e193c2b..a0d0b0c016b 100644 --- a/code/game/atoms/movable/movement.dm +++ b/code/game/atoms/movable/movement.dm @@ -438,13 +438,21 @@ * Make sure you know what you're doing if you override or call this. * * This *must* be a pure proc. You cannot act on the atom if you override this! Use Bump() for that. + * + * * **warning**: `newloc` is a ss13 construct. BYOND-native pixel movement doesn't have that. + * + * @params + * * AM - the thing trying to un-overlap us + * * newloc - (optional) where they're going */ /atom/movable/Uncross(atom/movable/AM, atom/newloc) . = TRUE if(SEND_SIGNAL(src, COMSIG_MOVABLE_UNCROSS, AM) & COMPONENT_MOVABLE_BLOCK_UNCROSS) return FALSE - if(isturf(newloc) && !CheckExit(AM, newloc)) - return FALSE + if(isturf(newloc)) + var/our_opinion = CheckExit(AM, newloc) + if(!our_opinion && (AM.generic_canpass || !AM.CanPassThrough(src, newloc, our_opinion))) + return FALSE /** * Called when something uncrosses us. @@ -514,7 +522,7 @@ var/list/old_grabbed if(allow_grabbed) old_grabbed = list() - for(var/mob/M in grabbing()) + for(var/mob/M in get_grabbing()) if(check_grab(M) < allow_grabbed) continue old_grabbed += M @@ -542,7 +550,7 @@ /mob/getLocationTransitForceMoveTargets(atom/destination, recurse_levels = 0, allow_buckled = TRUE, allow_pulled = TRUE, allow_grabbed = GRAB_PASSIVE) . = ..() if(allow_grabbed) - var/list/grabbing = grabbing() + var/list/grabbing = get_grabbing() for(var/mob/M in grabbing) if(check_grab(M) < allow_grabbed) continue diff --git a/code/game/atoms/vv.dm b/code/game/atoms/vv.dm index 2373c129b6a..f36655a135e 100644 --- a/code/game/atoms/vv.dm +++ b/code/game/atoms/vv.dm @@ -78,7 +78,7 @@ else if(was_failing && !now_failing) atom_fix() -/atom/vv_get_var(var_name) +/atom/vv_get_var(var_name, resolve) switch(var_name) if(NAMEOF(src, base_layer)) if(isnull(base_layer)) diff --git a/code/game/click/context.dm b/code/game/click/context.dm index 41001e80a43..1a0686db1d6 100644 --- a/code/game/click/context.dm +++ b/code/game/click/context.dm @@ -25,10 +25,14 @@ return TRUE return FALSE +/** + * @params + * * e_args - the actor data or a single mob + */ /atom/proc/context_menu(datum/event_args/actor/e_args) set waitfor = FALSE // admin proccall support - WRAP_MOB_TO_ACTOR_EVENT_ARGS(e_args) + E_ARGS_WRAP_USER_TO_ACTOR(e_args) // todo: dynamically rebuild menu based on distance? var/client/receiving = e_args.initiator.client if(isnull(receiving)) diff --git a/code/game/click/items.dm b/code/game/click/items.dm index 9514e4b15f6..607c9c2a174 100644 --- a/code/game/click/items.dm +++ b/code/game/click/items.dm @@ -10,6 +10,9 @@ /** * Called when trying to click something that the user can Reachability() to. * + * todo: this should allow passing in a clickchain datum instead. + * todo: lazy_melee_attack() for when you don't want to. + * * @params * - target - thing hitting * - user - user using us @@ -46,6 +49,9 @@ /** * Called when trying to click something that the user can't Reachability() to. * + * todo: this should allow passing in a clickchain datum instead. + * todo: lazy_ranged_attack() for when you don't want to. + * * @params * - target - thing hitting * - user - user using us @@ -313,6 +319,8 @@ * called when we're used to attack a non-mob * this doesn't actually need to be an obj. * + * todo: purge mult + * * @params * * target - atom being attacked * * clickchain - the /datum/event_args/actor/clickchain arguments included @@ -338,17 +346,21 @@ return CLICKCHAIN_DO_NOT_PROPAGATE //? legacy: decloak clickchain.performer.break_cloak() + // set mult + clickchain.damage_multiplier *= mult // click cooldown // todo: clickcd rework clickchain.performer.setClickCooldown(clickchain.performer.get_attack_speed(src)) // animation clickchain.performer.animate_swing_at_target(target) // perform the hit - . = melee_object_hit(target, clickchain, clickchain_flags, mult) + . = melee_object_hit(target, clickchain, clickchain_flags) /** * called at base of attack_object after standard melee attack misses * + * todo: purge mult + * * @return clickchain flags to append * * @params @@ -378,7 +390,7 @@ * * clickchain_flags - __DEFINES/procs/clickcode.dm flags * * mult - damage multiplier */ -/obj/item/proc/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags, mult = 1) +/obj/item/proc/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags) SHOULD_CALL_PARENT(TRUE) // harmless, just tap them and leave @@ -406,7 +418,7 @@ visible = SPAN_DANGER("[target] has been [islist(attack_verb)? pick(attack_verb) : attack_verb] with [src] by [clickchain.performer]!") ) // damage - target.melee_act(clickchain.performer, src, mult = mult) + target.melee_act(clickchain.performer, src, null, clickchain) // animate target.animate_hit_by_weapon(clickchain.performer, src) diff --git a/code/game/click/mobs.dm b/code/game/click/mobs.dm index 7ac0ad4c737..3f0f13fb0bb 100644 --- a/code/game/click/mobs.dm +++ b/code/game/click/mobs.dm @@ -1,6 +1,9 @@ /** * Called when trying to click on someone we can Reachability() to without an item in hand. * + * todo: this should allow passing in a clickchain datum instead. + * todo: lazy_melee_attack() for when you don't want to. + * * @params * - target - thing we're clicking * - clickchain_flags - see [code/__DEFINES/procs/clickcode.dm] @@ -98,7 +101,7 @@ log_attack(key_name(src), ismob(target)? key_name(target) : "[target] ([ref(target)])", "attacked with [style.attack_name] newhp ~[newhp || "unknown"]") /mob/proc/melee_attack_hit(atom/target, datum/event_args/actor/clickchain/clickchain, datum/unarmed_attack/style, clickchain_flags, target_zone, mult) - . = target.unarmed_act(src, style, target_zone, mult) + . = target.unarmed_act(src, style, target_zone, clickchain) if(. & CLICKCHAIN_ATTACK_MISSED) return . | melee_attack_miss(target, clickchain, style, clickchain_flags, target_zone, mult) // todo: the rest of this proc not qdel-safe diff --git a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm index 18172ed42ca..c5dfcecbd01 100644 --- a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm +++ b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/contraband.dm @@ -96,7 +96,7 @@ name = "sapper's kit" /obj/item/storage/box/cargo_null_entry_kit/sapper/legacy_spawn_contents() - new /obj/item/melee/energy/sword/ionic_rapier(src) + new /obj/item/melee/transforming/energy/sword/ionic_rapier(src) new /obj/item/storage/box/syndie_kit/space(src) new /obj/item/storage/box/syndie_kit/demolitions(src) new /obj/item/multitool/ai_detector(src) diff --git a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm index d3fc78511e7..23757c6fe2c 100644 --- a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm +++ b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/munitions.dm @@ -32,7 +32,7 @@ name = "Weapons - Experimental weapons crate" contains = list( /obj/item/gun/energy/xray = 2, - /obj/item/shield/energy = 2, + /obj/item/shield/transforming/energy = 2, ) container_type = /obj/structure/closet/crate/secure/corporate/nanotrasen container_name = "Experimental weapons crate" diff --git a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm index bb09c938ed6..fd0841c892d 100644 --- a/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm +++ b/code/game/content/factions/corporations/nanotrasen/nanotrasen-supply/security.dm @@ -466,7 +466,7 @@ /obj/item/clothing/accessory/badge/holo/hos, /obj/item/clothing/accessory/holster/waist, /obj/item/melee/telebaton, - /obj/item/shield/riot/tele, + /obj/item/shield/transforming/telescopic, /obj/item/clothing/head/beret/sec/corporate/hos, /obj/item/flashlight/maglight, ) diff --git a/code/game/gamemodes/changeling/powers/armblade.dm b/code/game/gamemodes/changeling/powers/armblade.dm index db77320eed6..bc89f0efdef 100644 --- a/code/game/gamemodes/changeling/powers/armblade.dm +++ b/code/game/gamemodes/changeling/powers/armblade.dm @@ -47,6 +47,14 @@ return 1 return 0 +// todo: full rework of all of this; changeling weapons are balanced by a numbskull holy shit fuck bay +// - block chances are way, way too high +// - insufficient armor penetration (ironically) for citrp combat balancing directives +// - need to rethink changeling defensives in general, they shouldn't be reliant on parrying + +/datum/parry_frame/passive_block/armblade + parry_sfx = 'sound/weapons/slash.ogg' + /obj/item/melee/changeling name = "arm weapon" desc = "A grotesque weapon made out of bone and flesh that cleaves through people as a hot knife through butter." @@ -62,8 +70,11 @@ var/weapType = "weapon" var/weapLocation = "arm" - defend_chance = 40 // The base chance for the weapon to parry. - projectile_parry_chance = 15 // The base chance for a projectile to be deflected. + passive_parry = /datum/passive_parry/melee{ + parry_chance_default = 40; + parry_chance_projectile = 15; + parry_frame = /datum/parry_frame/passive_block/armblade; + } /obj/item/melee/changeling/Initialize(mapload) . = ..() @@ -106,28 +117,6 @@ host.embedded -= src qdel(src) -/obj/item/melee/changeling/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(default_parry_check(user, attacker, damage_source) && prob(defend_chance)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/slash.ogg', 50, 1) - return 1 - if(unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance)) - user.visible_message("\The [user] deflects [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/slash.ogg', 50, 1) - return 1 - - return 0 - -/obj/item/melee/changeling/unique_parry_check(mob/user, mob/attacker, atom/damage_source) - if(user.incapacitated() || !istype(damage_source, /obj/projectile)) - return 0 - - var/bad_arc = global.reverse_dir[user.dir] - if(!check_shield_arc(user, bad_arc, damage_source, attacker)) - return 0 - - return 1 - /obj/item/melee/changeling/arm_blade name = "arm blade" desc = "A grotesque blade made out of bone and flesh that cleaves through people as a hot knife through butter." @@ -138,15 +127,23 @@ edge = 1 pry = 1 attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - defend_chance = 60 - projectile_parry_chance = 25 + + passive_parry = /datum/passive_parry/melee{ + parry_chance_default = 60; + parry_chance_projectile = 25; + parry_frame = /datum/parry_frame/passive_block/armblade; + } /obj/item/melee/changeling/arm_blade/greater name = "arm greatblade" desc = "A grotesque blade made out of bone and flesh that cleaves through people and armor as a hot knife through butter." armor_penetration = 30 - defend_chance = 70 - projectile_parry_chance = 35 + + passive_parry = /datum/passive_parry/melee{ + parry_chance_default = 70; + parry_chance_projectile = 35; + parry_frame = /datum/parry_frame/passive_block/armblade; + } /obj/item/melee/changeling/claw name = "hand claw" @@ -156,13 +153,21 @@ sharp = 1 edge = 1 attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - defend_chance = 50 - projectile_parry_chance = 15 + + passive_parry = /datum/passive_parry/melee{ + parry_chance_default = 50; + parry_chance_projectile = 15; + parry_frame = /datum/parry_frame/passive_block/armblade; + } /obj/item/melee/changeling/claw/greater name = "hand greatclaw" damage_force = 20 armor_penetration = 20 pry = 1 - defend_chance = 60 - projectile_parry_chance = 25 + + passive_parry = /datum/passive_parry/melee{ + parry_chance_default = 60; + parry_chance_projectile = 25; + parry_frame = /datum/parry_frame/passive_block/armblade; + } diff --git a/code/game/gamemodes/cult/cult_structures.dm b/code/game/gamemodes/cult/cult_structures.dm index 21fc3fd4d9c..c9e899bd85c 100644 --- a/code/game/gamemodes/cult/cult_structures.dm +++ b/code/game/gamemodes/cult/cult_structures.dm @@ -46,12 +46,13 @@ /obj/structure/cult/pylon/attackby(obj/item/W as obj, mob/user as mob) attackpylon(user, W.damage_force) -/obj/structure/cult/pylon/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual) +/obj/structure/cult/pylon/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon) pylonhit(damage) return damage -/obj/structure/cult/pylon/bullet_act(var/obj/projectile/Proj) - pylonhit(Proj.get_structure_damage()) +/obj/structure/cult/pylon/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + pylonhit(proj.get_structure_damage()) /obj/structure/cult/pylon/proc/pylonhit(var/damage) if(!isbroken) diff --git a/code/game/gamemodes/events/black_hole.dm b/code/game/gamemodes/events/black_hole.dm index 1bc471d0be1..38e926507bd 100644 --- a/code/game/gamemodes/events/black_hole.dm +++ b/code/game/gamemodes/events/black_hole.dm @@ -11,7 +11,7 @@ /obj/effect/bhole/Initialize(mapload) . = ..() - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) /obj/effect/bhole/process() if(!isturf(loc)) diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm index 3e5e2ef1749..7d7e72d4aa4 100644 --- a/code/game/gamemodes/meteor/meteors.dm +++ b/code/game/gamemodes/meteor/meteors.dm @@ -162,7 +162,7 @@ if(T) if(istype(T, /turf/simulated/wall)) var/turf/simulated/wall/W = T - W.inflict_atom_damage(wall_power, flag = ARMOR_BOMB) // Stronger walls can halt asteroids. + W.inflict_atom_damage(wall_power, damage_flag = ARMOR_BOMB) // Stronger walls can halt asteroids. /obj/effect/meteor/proc/get_shield_damage() return max(((max(hits, 2)) * (heavy + 1) * rand(6, 12)) / hitpwr , 0) diff --git a/code/game/gamemodes/sandbox/h_sandbox.dm b/code/game/gamemodes/sandbox/h_sandbox.dm index 9ac85720d74..9ecdb22d753 100644 --- a/code/game/gamemodes/sandbox/h_sandbox.dm +++ b/code/game/gamemodes/sandbox/h_sandbox.dm @@ -139,7 +139,7 @@ datum/hSB continue if(istype(O, /obj/item/dummy)) continue - if(istype(O, /obj/item/melee/energy/sword)) + if(istype(O, /obj/item/melee/transforming/energy/sword)) continue if(istype(O, /obj/structure)) continue diff --git a/code/game/gamemodes/technomancer/devices/shield_armor.dm b/code/game/gamemodes/technomancer/devices/shield_armor.dm index 6bee50e73dd..73892bfbcc7 100644 --- a/code/game/gamemodes/technomancer/devices/shield_armor.dm +++ b/code/game/gamemodes/technomancer/devices/shield_armor.dm @@ -33,7 +33,25 @@ qdel(spark_system) return ..() -/obj/item/clothing/suit/armor/shield/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") +/obj/item/clothing/suit/armor/shield/equipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + // if you're reading this: this is not the right way to do shieldcalls + // this is just a lazy implementation + // signals have highest priority, this as a piece of armor shouldn't have that. + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall)) + +/obj/item/clothing/suit/armor/shield/unequipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL) + +/obj/item/clothing/suit/armor/shield/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack) + var/damage = shieldcall_args[SHIELDCALL_ARG_DAMAGE] + var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON] + //Since this is a pierce of armor that is passive, we do not need to check if the user is incapacitated. if(!active) return 0 @@ -66,12 +84,11 @@ P.agony -= agony_blocked P.damage = P.damage - damage_blocked - user.visible_message("\The [user]'s [src] absorbs [attack_text]!") + user.visible_message("\The [user]'s [src] absorbs the attack!") to_chat(user, "Your shield has absorbed most of \the [damage_source].") spark_system.start() playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return 0 // This shield does not block all damage, so returning 0 is needed to tell the game to apply the new damage. /obj/item/clothing/suit/armor/shield/attack_self(mob/user) . = ..() diff --git a/code/game/gamemodes/technomancer/devices/tesla_armor.dm b/code/game/gamemodes/technomancer/devices/tesla_armor.dm index d731b5b37af..c3fe7f29bc9 100644 --- a/code/game/gamemodes/technomancer/devices/tesla_armor.dm +++ b/code/game/gamemodes/technomancer/devices/tesla_armor.dm @@ -21,7 +21,27 @@ var/normal_icon_state = "tesla_armor_0" var/cooldown_to_charge = 15 SECONDS -/obj/item/clothing/suit/armor/tesla/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") +/obj/item/clothing/suit/armor/tesla/equipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + // if you're reading this: this is not the right way to do shieldcalls + // this is just a lazy implementation + // signals have highest priority, this as a piece of armor shouldn't have that. + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall)) + +/obj/item/clothing/suit/armor/tesla/unequipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL) + +/obj/item/clothing/suit/armor/tesla/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack) + var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON] + + var/datum/event_args/actor/clickchain/clickchain = shieldcall_args[SHIELDCALL_ARG_CLICKCHAIN] + var/mob/attacker = clickchain?.performer + //First, some retaliation. if(active) if(istype(damage_source, /obj/projectile)) @@ -47,7 +67,7 @@ ready = 1 update_icon() to_chat(user, "\The [src] is ready to protect you once more.") - visible_message("\The [user]'s [src.name] blocks [attack_text]!") + visible_message("\The [user]'s [src.name] blocks the attack!") update_icon() return 1 return 0 diff --git a/code/game/gamemodes/technomancer/spells/energy_siphon.dm b/code/game/gamemodes/technomancer/spells/energy_siphon.dm index cc51d809667..b05bc7fb267 100644 --- a/code/game/gamemodes/technomancer/spells/energy_siphon.dm +++ b/code/game/gamemodes/technomancer/spells/energy_siphon.dm @@ -172,30 +172,22 @@ icon_state = "lightning" range = WORLD_ICON_SIZE * 6 power = 5 // This fires really fast, so this may add up if someone keeps standing in the beam. - penetrating = 5 + legacy_penetrating = 5 -/obj/projectile/beam/lightning/energy_siphon/Bump(atom/A as mob|obj|turf|area, forced=0) - if(A == firer) // For this, you CAN shoot yourself. - on_impact(A) - - density = 0 - invisibility = 101 - - qdel(src) - return 1 - ..() - -/obj/projectile/beam/lightning/energy_siphon/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0) - if(target_mob == firer) // This shouldn't actually occur due to Bump(), but just in-case. - return 1 +/obj/projectile/beam/lightning/energy_siphon/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + var/mob/living/target_mob = target + if(!isliving(target_mob)) + return if(ishuman(target_mob)) // Otherwise someone else stood in the beam and is going to pay for it. var/mob/living/carbon/human/H = target_mob var/obj/item/organ/external/affected = H.get_organ(check_zone(BP_TORSO)) H.electrocute_act(power, src, H.get_siemens_coefficient_organ(affected), affected, 0) else target_mob.electrocute_act(power, src, 0.75, BP_TORSO) - return 0 // Since this is a continous beam, it needs to keep flying until it hits the Technomancer. - + return PROJECTILE_IMPACT_PIERCE #undef SIPHON_CELL_TO_ENERGY #undef SIPHON_FBP_TO_ENERGY diff --git a/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm b/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm index 8d4254e3f33..75160bb0252 100644 --- a/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm +++ b/code/game/gamemodes/technomancer/spells/projectile/chain_lightning.dm @@ -34,7 +34,15 @@ var/list/hit_mobs = list() //Mobs which were already hit. var/power = 35 //How hard it will hit for with electrocute_act(), decreases with each bounce. -/obj/projectile/beam/chain_lightning/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0) +// todo: rework this shit :/ + +/obj/projectile/beam/chain_lightning/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & (PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT | PROJECTILE_IMPACT_BLOCKED)) + return + var/mob/living/target_mob = target + if(!isliving(target_mob)) + return //First we shock the guy we just hit. if(ishuman(target_mob)) var/mob/living/carbon/human/H = target_mob @@ -70,11 +78,9 @@ if(new_target) var/turf/curloc = get_turf(target_mob) curloc.visible_message("\The [src] bounces to \the [new_target]!") - redirect(new_target.x, new_target.y, curloc, firer) + legacy_redirect(new_target.x, new_target.y, curloc, firer) bounces-- - - return 0 - return 1 + return PROJECTILE_IMPACT_PIERCE diff --git a/code/game/gamemodes/technomancer/spells/projectile/lightning.dm b/code/game/gamemodes/technomancer/spells/projectile/lightning.dm index 4ba595480de..58e3bd19292 100644 --- a/code/game/gamemodes/technomancer/spells/projectile/lightning.dm +++ b/code/game/gamemodes/technomancer/spells/projectile/lightning.dm @@ -32,7 +32,13 @@ var/power = 60 //How hard it will hit for with electrocute_act(). -/obj/projectile/beam/lightning/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier=0) +/obj/projectile/beam/lightning/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & (PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT | PROJECTILE_IMPACT_BLOCKED)) + return + var/mob/living/target_mob = target + if(!isliving(target_mob)) + return if(ishuman(target_mob)) var/mob/living/carbon/human/H = target_mob var/obj/item/organ/external/affected = H.get_organ(check_zone(BP_TORSO)) diff --git a/code/game/gamemodes/technomancer/spells/reflect.dm b/code/game/gamemodes/technomancer/spells/reflect.dm index b1bd2526260..293f5de0827 100644 --- a/code/game/gamemodes/technomancer/spells/reflect.dm +++ b/code/game/gamemodes/technomancer/spells/reflect.dm @@ -30,64 +30,79 @@ spark_system = null return ..() -/obj/item/spell/reflect/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") +/obj/item/spell/reflect/pickup(mob/user, flags, atom/oldLoc) + . = ..() + // if you're reading this: this is not the right way to do shieldcalls + // this is just a lazy implementation + // signals have highest priority, this as a piece of armor shouldn't have that. + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall)) + +/obj/item/spell/reflect/dropped(mob/user, flags, atom/newLoc) + . = ..() + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL) + +/obj/item/spell/reflect/proc/shieldcall(datum/source, list/shieldcall_args, fake_attack) + if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE) + return + var/mob/user = source if(user.incapacitated()) - return 0 + return - var/damage_to_energy_cost = (damage_to_energy_multiplier * damage) + var/mob/attacker + var/damage_to_energy_cost = (damage_to_energy_multiplier * shieldcall_args[SHIELDCALL_ARG_DAMAGE]) + var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON] if(!pay_energy(damage_to_energy_cost)) to_chat(owner, "Your shield fades due to lack of energy!") qdel(src) - return 0 - - //block as long as they are not directly behind us - var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block - if(check_shield_arc(user, bad_arc, damage_source, attacker)) + return - if(istype(damage_source, /obj/projectile)) - var/obj/projectile/P = damage_source + if(istype(damage_source, /obj/projectile)) + var/obj/projectile/P = damage_source + attacker = P.firer - if(P.starting && !P.reflected) - visible_message("\The [user]'s [src.name] reflects [attack_text]!") + if(P.starting && !P.reflected) + visible_message("\The [user]'s [src.name] reflects [P]!") - var/turf/curloc = get_turf(user) + var/turf/curloc = get_turf(user) - // redirect the projectile - P.redirect(P.starting.x, P.starting.y, curloc, user) - P.reflected = 1 - if(check_for_scepter()) - P.damage = P.damage * 1.5 + // redirect the projectile + P.legacy_redirect(P.starting.x, P.starting.y, curloc, user) + P.reflected = 1 + if(check_for_scepter()) + P.damage = P.damage * 1.5 - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - // now send a log so that admins don't think they're shooting themselves on purpose. - log_and_message_admins("[user] reflected [attacker]'s attack back at them.") - - if(!reflecting) - reflecting = 1 - spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected. - to_chat(owner, "Your shield fades due being used up!") - qdel(src) - - return PROJECTILE_CONTINUE // complete projectile permutation - - else if(istype(damage_source, /obj/item)) - var/obj/item/W = damage_source + spark_system.start() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + // now send a log so that admins don't think they're shooting themselves on purpose. if(attacker) - W.melee_interaction_chain(attacker) - to_chat(attacker, "Your [damage_source.name] goes through \the [src] in one location, comes out \ - on the same side, and hits you!") - - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - log_and_message_admins("[user] reflected [attacker]'s attack back at them.") - if(!reflecting) - reflecting = 1 - spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected. - to_chat(owner, "Your shield fades due being used up!") - qdel(src) - return 1 - return 0 + if(!reflecting) + reflecting = 1 + spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected. + to_chat(owner, "Your shield fades due being used up!") + qdel(src) + + shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_PASSTHROUGH | SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_TERMINATE + + else if(istype(damage_source, /obj/item)) + var/obj/item/W = damage_source + var/datum/event_args/actor/clickchain/clickchain = shieldcall_args[SHIELDCALL_ARG_CLICKCHAIN] + attacker = clickchain.performer + if(attacker) + W.melee_interaction_chain(attacker, attacker) + to_chat(attacker, "Your [damage_source] goes through \the [src] in one location, comes out \ + on the same side, and hits you!") + + spark_system.start() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + + log_and_message_admins("[user] reflected [attacker]'s attack back at them.") + + if(!reflecting) + reflecting = 1 + spawn(2 SECONDS) //To ensure that most or all of a burst fire cycle is reflected. + to_chat(owner, "Your shield fades due being used up!") + qdel(src) + shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_TERMINATE diff --git a/code/game/gamemodes/technomancer/spells/shield.dm b/code/game/gamemodes/technomancer/spells/shield.dm index ad005ef6572..cd487b6917e 100644 --- a/code/game/gamemodes/technomancer/spells/shield.dm +++ b/code/game/gamemodes/technomancer/spells/shield.dm @@ -28,7 +28,24 @@ spark_system = null return ..() -/obj/item/spell/shield/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") +/obj/item/spell/shield/equipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + // if you're reading this: this is not the right way to do shieldcalls + // this is just a lazy implementation + // signals have highest priority, this as a piece of armor shouldn't have that. + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall)) + +/obj/item/spell/shield/unequipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL) + +/obj/item/spell/shield/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack) + var/damage = shieldcall_args[SHIELDCALL_ARG_DAMAGE] + if(user.incapacitated()) return 0 @@ -50,12 +67,8 @@ qdel(src) return 0 - //block as long as they are not directly behind us - var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block - if(check_shield_arc(user, bad_arc, damage_source, attacker)) - user.visible_message("\The [user]'s [src] blocks [attack_text]!") - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - adjust_instability(2) - return 1 - return 0 + + user.visible_message("\The [user]'s [src] blocks the attack!") + spark_system.start() + playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) + adjust_instability(2) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index dfb3d955096..3c8879162bf 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -156,7 +156,7 @@ ///Volume of interface sounds. var/clickvol = 40 var/obj/item/circuitboard/circuit = null - ///If false, SSmachines. If true, SSfastprocess. + ///If false, SSmachines. If true, SSprocess_5fps. var/speed_process = FALSE var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_SET_MACHINE @@ -176,7 +176,7 @@ if(!speed_process) START_MACHINE_PROCESSING(src) else - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) if(!mapload) // area handles this power_change() @@ -186,7 +186,7 @@ if(!speed_process) STOP_MACHINE_PROCESSING(src) else - STOP_PROCESSING(SSfastprocess, src) + STOP_PROCESSING(SSprocess_5fps, src) if(component_parts) for(var/atom/A in component_parts) if(A.loc == src) // If the components are inside the machine, delete them. @@ -236,22 +236,6 @@ panel_open = panel_opened update_appearance() -/obj/machinery/legacy_ex_act(severity) - switch(severity) - if(1.0) - qdel(src) - return - if(2.0) - if(prob(50)) - qdel(src) - return - if(3.0) - if(prob(25)) - qdel(src) - return - else - return - /obj/machinery/vv_edit_var(var_name, new_value) if(var_name == NAMEOF(src, use_power)) update_use_power(new_value) diff --git a/code/game/machinery/doors/airlock/airlock.dm b/code/game/machinery/doors/airlock/airlock.dm index 819a31bca72..541fb37f259 100644 --- a/code/game/machinery/doors/airlock/airlock.dm +++ b/code/game/machinery/doors/airlock/airlock.dm @@ -909,7 +909,7 @@ About the new airlock wires panel: for(var/turf/turf in locs) for(var/atom/movable/AM in turf) if(AM.airlock_crush(DOOR_CRUSH_DAMAGE)) - inflict_atom_damage(DOOR_CRUSH_DAMAGE, flag = ARMOR_MELEE) + inflict_atom_damage(DOOR_CRUSH_DAMAGE, damage_flag = ARMOR_MELEE) use_power(360) //360 W seems much more appropriate for an actuator moving an industrial door capable of crushing people has_beeped = 0 diff --git a/code/game/machinery/doors/defense.dm b/code/game/machinery/doors/defense.dm index eb2c3b43491..850c13bc2a3 100644 --- a/code/game/machinery/doors/defense.dm +++ b/code/game/machinery/doors/defense.dm @@ -3,5 +3,5 @@ if(exposed_temperature > maxtemperature) var/burndamage = log(RAND_F(0.9, 1.1) * (exposed_temperature - maxtemperature)) - inflict_atom_damage(burndamage, flag = ARMOR_FIRE, gradual = TRUE) + inflict_atom_damage(burndamage, damage_flag = ARMOR_FIRE, damage_mode = DAMAGE_MODE_GRADUAL) return ..() diff --git a/code/game/machinery/doors/unpowered.dm b/code/game/machinery/doors/unpowered.dm index 6236374a105..1b6b5fb3023 100644 --- a/code/game/machinery/doors/unpowered.dm +++ b/code/game/machinery/doors/unpowered.dm @@ -9,7 +9,7 @@ return /obj/machinery/door/unpowered/attackby(obj/item/I as obj, mob/user as mob) - if(istype(I, /obj/item/melee/energy/blade)) return + if(istype(I, /obj/item/melee/ninja_energy_blade)) return if(src.locked) return ..() return diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 9bdb1357afc..95921c715c2 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -194,7 +194,7 @@ return //Emags and ninja swords? You may pass. - if (istype(I, /obj/item/melee/energy/blade)) + if (istype(I, /obj/item/melee/ninja_energy_blade)) if(emag_act(10, user)) var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() spark_system.set_up(5, 0, src.loc) diff --git a/code/game/machinery/fire_alarm.dm b/code/game/machinery/fire_alarm.dm index 1ee973b3665..40757b9a3aa 100644 --- a/code/game/machinery/fire_alarm.dm +++ b/code/game/machinery/fire_alarm.dm @@ -121,8 +121,9 @@ CREATE_WALL_MOUNTING_TYPES_SHIFTED(/obj/machinery/fire_alarm/alarms_hidden, 21) /obj/machinery/fire_alarm/attack_ai(mob/user) return attack_hand(user) -/obj/machinery/fire_alarm/bullet_act() - return alarm() +/obj/machinery/fire_alarm/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + alarm() /obj/machinery/fire_alarm/emp_act(severity) if(prob(50 / severity)) diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm index 77f4f9f052b..c2d4b3020ad 100644 --- a/code/game/machinery/iv_drip.dm +++ b/code/game/machinery/iv_drip.dm @@ -240,7 +240,7 @@ if(!speed_process) START_MACHINE_PROCESSING(src) else - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) update_appearance() //! Plumbing Signal diff --git a/code/game/machinery/turnstile.dm b/code/game/machinery/turnstile.dm index 36e45c60134..e694c18177b 100644 --- a/code/game/machinery/turnstile.dm +++ b/code/game/machinery/turnstile.dm @@ -18,11 +18,6 @@ /obj/machinery/turnstile/CanAtmosPass(turf/T) return TRUE -/* -/obj/machinery/turnstile/bullet_act(obj/item/projectile/P, def_zone) - return -1 //Pass through! -*/ - /obj/machinery/turnstile/proc/allowed_access(var/mob/B) if(B.pulledby && ismob(B.pulledby)) return allowed(B.pulledby) | allowed(B) diff --git a/code/game/machinery/turrets/subtypes/misc.dm b/code/game/machinery/turrets/subtypes/misc.dm index 83e75bc795d..e7ed365d0ea 100644 --- a/code/game/machinery/turrets/subtypes/misc.dm +++ b/code/game/machinery/turrets/subtypes/misc.dm @@ -75,7 +75,7 @@ integrity_max = 200 turret_type = "industrial" -/obj/machinery/porta_turret/industrial/bullet_act(obj/projectile/Proj) +/obj/machinery/porta_turret/industrial/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() if(enabled) if(!attacked && !emagged) diff --git a/code/game/machinery/turrets/turret-ai_holder.dm b/code/game/machinery/turrets/turret-ai_holder.dm index 6c2a3d1a666..01590029a9e 100644 --- a/code/game/machinery/turrets/turret-ai_holder.dm +++ b/code/game/machinery/turrets/turret-ai_holder.dm @@ -161,7 +161,8 @@ */ /datum/ai_holder/turret/proc/trace_trajectory(atom/target, angle) var/obj/projectile/trace/trace = new(agent.loc) - trace.prepare_trace(target, null, TRUE) + trace.only_opacity = TRUE + trace.prepare_trace(target) trace.fire(angle) return trace.could_hit_target diff --git a/code/game/machinery/turrets/turret.dm b/code/game/machinery/turrets/turret.dm index 265f3c8afd8..02a1860c9e1 100644 --- a/code/game/machinery/turrets/turret.dm +++ b/code/game/machinery/turrets/turret.dm @@ -463,16 +463,18 @@ if(!gradual && prob(45) && amount > 5) spark_system.start() -/obj/machinery/porta_turret/bullet_act(obj/projectile/P, def_zone) +/obj/machinery/porta_turret/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() aggro_for(6 SECONDS) - if(P.firer) + if(proj.firer) // todo: proper AI provoke API. var/datum/ai_holder/turret/snowflake_ai_holder = ai_holder - snowflake_ai_holder.retaliate(P.firer) - return ..() + snowflake_ai_holder.retaliate(proj.firer) -/obj/machinery/porta_turret/melee_act(mob/user, obj/item/weapon, target_zone, mult) +/obj/machinery/porta_turret/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain) . = ..() + if(. & CLICKCHAIN_FLAGS_ATTACK_ABORT) + return if(. > 0) aggro_for(6 SECONDS, user) // todo: proper AI provoke API. diff --git a/code/game/objects/defense.dm b/code/game/objects/defense.dm deleted file mode 100644 index 29786cc77d4..00000000000 --- a/code/game/objects/defense.dm +++ /dev/null @@ -1,82 +0,0 @@ -//* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2023 Citadel Station developers. *// - -/obj/ex_act(power, dir, datum/automata/wave/explosion/E) - . = ..() - // todo: wave explosions - if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs) - return - inflict_atom_damage(power * (1 / 2.5), flag = ARMOR_BOMB) - -/obj/legacy_ex_act(severity, target) - . = ..() - if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs) - return - inflict_atom_damage(global._legacy_ex_atom_damage[severity], flag = ARMOR_BOMB) - -/obj/melee_act(mob/user, obj/item/weapon, target_zone, mult) - if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs) - return - inflict_atom_damage(weapon.damage_force, weapon.damage_tier, weapon.damage_flag, weapon.damage_mode, ATTACK_TYPE_MELEE, weapon) - return NONE - -/obj/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult) - // todo: this should just be style.attack(attacker, src) - if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs) - return - inflict_atom_damage(style.get_unarmed_damage(attacker, src), style.damage_tier, style.damage_flag, style.damage_mode, ATTACK_TYPE_UNARMED, attacker) - return NONE - -/obj/bullet_act(obj/projectile/P, def_zone) - . = ..() - // todo: should this really be here? - if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs) - return - inflict_atom_damage(P.get_structure_damage(), P.damage_tier, P.damage_flag, P.damage_mode, ATTACK_TYPE_PROJECTILE, P) - -/obj/throw_impacted(atom/movable/AM, datum/thrownthing/TT) - . = ..() - // todo: /atom/movable/proc/throw_impact_attack(atom/target) - if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs) - return - if(isitem(AM)) - var/obj/item/I = AM - inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM) - else - inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM) - // if we got destroyed - if(QDELETED(src) && (obj_flags & OBJ_ALLOW_THROW_THROUGH)) - . |= COMPONENT_THROW_HIT_PIERCE - -/obj/blob_act(obj/structure/blob/blob) - . = ..() - inflict_atom_damage(100, flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE) - -/obj/drop_products(method, atom/where) - . = ..() - if(obj_storage?.drop_on_deconstruction_methods & method) - obj_storage.drop_everything_at(where) - -/obj/hitsound_melee(obj/item/I) - if(!isnull(material_primary)) - var/datum/material/primary = get_primary_material() - . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute - if(!isnull(.)) - return - return ..() - -/obj/hitsound_throwhit(obj/item/I) - if(!isnull(material_primary)) - var/datum/material/primary = get_primary_material() - . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute - if(!isnull(.)) - return - return ..() - -/obj/hitsound_unarmed(mob/M, datum/unarmed_attack/style) - if(!isnull(material_primary)) - var/datum/material/primary = get_primary_material() - . = style.damage_type == BURN? primary.sound_melee_burn : primary.sound_melee_brute - if(!isnull(.)) - return - return ..() diff --git a/code/game/objects/effects/_effect.dm b/code/game/objects/effects/_effect.dm index f2f3ff7e6f1..14ae0396a50 100644 --- a/code/game/objects/effects/_effect.dm +++ b/code/game/objects/effects/_effect.dm @@ -10,6 +10,7 @@ * however, at a certain point, do consider using /structure or /machinery instead. */ /obj/effect + anchored = TRUE integrity_enabled = FALSE density = FALSE opacity = FALSE diff --git a/code/game/objects/effects/effect_system.dm b/code/game/objects/effects/effect_system.dm index bc62c5021dc..5e949319b07 100644 --- a/code/game/objects/effects/effect_system.dm +++ b/code/game/objects/effects/effect_system.dm @@ -406,7 +406,7 @@ steam.start() -- spawns the effect return if(!ismovable(holder)) return - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) on = TRUE /datum/effect_system/ion_trail_follow/process(wait) @@ -425,7 +425,7 @@ steam.start() -- spawns the effect return oldposition = null on = FALSE - STOP_PROCESSING(SSfastprocess, src) + STOP_PROCESSING(SSprocess_5fps, src) ///////////////////////////////////////////// //////// Attach a steam trail to an object (eg. a reacting beaker) that will follow it diff --git a/code/game/objects/effects/item_pickup_ghost.dm b/code/game/objects/effects/item_pickup_ghost.dm index 0944e2753ee..58675c8ff70 100644 --- a/code/game/objects/effects/item_pickup_ghost.dm +++ b/code/game/objects/effects/item_pickup_ghost.dm @@ -1,11 +1,17 @@ /obj/effect/temporary_effect/item_pickup_ghost - anchored = 1 plane = MOB_PLANE layer = ABOVE_MOB_LAYER - mouse_opacity = 0//just in case something dumb happens + mouse_opacity = MOUSE_OPACITY_TRANSPARENT time_to_die = 0.5 SECONDS var/lifetime = 0.25 SECONDS //so it doesn't die before animation ends +/obj/effect/temporary_effect/item_pickup_ghost/Initialize(mapload, obj/item/from_item, atom/towards_target) + . = ..() + if(from_item) + assumeform(from_item) + if(towards_target) + animate_towards(towards_target) + /obj/effect/temporary_effect/item_pickup_ghost/proc/assumeform(var/obj/item/picked_up) icon = picked_up.icon icon_state = picked_up.icon_state diff --git a/code/game/objects/effects/mines.dm b/code/game/objects/effects/mines.dm index 88e0f990487..8338325c57e 100644 --- a/code/game/objects/effects/mines.dm +++ b/code/game/objects/effects/mines.dm @@ -18,6 +18,8 @@ wires = new(src) /obj/effect/mine/proc/explode(var/mob/living/M) + if(QDELETED(src)) + return var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread() triggered = 1 s.set_up(3, 1, src) @@ -27,8 +29,9 @@ qdel(s) qdel(src) -/obj/effect/mine/bullet_act() - if(prob(50)) +/obj/effect/mine/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + spawn(0) explode() /obj/effect/mine/legacy_ex_act(severity) @@ -167,7 +170,7 @@ var/turf/O = get_turf(src) if(!O) return - src.fragmentate(O, 20, 7, list(/obj/projectile/bullet/pellet/fragment)) //only 20 weak fragments because you're stepping directly on it + shrapnel_explosion(20, 7, /obj/projectile/bullet/pellet/fragment) visible_message("\The [src.name] detonates!") spawn(0) qdel(s) diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm index 1ce7e665ac4..d03e8a0d3c4 100644 --- a/code/game/objects/effects/misc.dm +++ b/code/game/objects/effects/misc.dm @@ -116,25 +116,5 @@ icon_state = "begin" anchored = TRUE -/obj/effect/list_container - name = "list container" - -/obj/effect/list_container/mobl - name = "mobl" - var/master = null - - var/list/container = list( ) - -/obj/effect/stop - icon_state = "empty" - name = "Geas" - desc = "You can't resist." - var/atom/movable/victim - -/obj/effect/stop/Uncross(atom/movable/AM) - . = ..() - if(AM == victim) - return FALSE - /obj/effect/spawner name = "object spawner" diff --git a/code/game/objects/effects/spiders.dm b/code/game/objects/effects/spiders.dm index 557cd497a54..a0b957a0783 100644 --- a/code/game/objects/effects/spiders.dm +++ b/code/game/objects/effects/spiders.dm @@ -15,10 +15,10 @@ if(exposed_temperature > 300 + T0C) damage_integrity(5) -/obj/effect/spider/melee_act(mob/user, obj/item/weapon, target_zone, mult) - if(weapon.damtype == BURN) - mult *= 2 - return ..() +/obj/effect/spider/process_damage_instance(list/shieldcall_args, filter_zone) + . = ..() + if(shieldcall_args[SHIELDCALL_ARG_DAMAGE_TYPE]) + shieldcall_args[SHIELDCALL_ARG_DAMAGE] *= 2 /obj/effect/spider/stickyweb icon_state = "stickyweb1" diff --git a/code/game/objects/effects/traps.dm b/code/game/objects/effects/traps.dm index 01a3f46511b..42855a8026c 100644 --- a/code/game/objects/effects/traps.dm +++ b/code/game/objects/effects/traps.dm @@ -554,7 +554,7 @@ Add those other swinging traps you mentioned above! /* This is all per-tick processing stuff. It isn't working the way I want, so I'm reverting it. if (istype(AM, /mob/living)) - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) var/mob/living/M = AM M.visible_message("[M] is slashed by the spinning blades!", \ "You are slashed by the spinning blades!") @@ -565,7 +565,7 @@ if (istype(AM, /mob/living)) M.apply_damage(damage, BRUTE) /obj/effect/trap/pop_up/pillar/Destroy() - STOP_PROCESSING(SSfastprocess, src) + STOP_PROCESSING(SSprocess_5fps, src) return ..() */ diff --git a/code/game/objects/items-carry_weight.dm b/code/game/objects/items-carry_weight.dm new file mode 100644 index 00000000000..f26b7114f7f --- /dev/null +++ b/code/game/objects/items-carry_weight.dm @@ -0,0 +1,74 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Carry Weight *// +//* The carry weight system is a modular system used to discourage *// +//* carrying too much, through both weight (recursive weight) and *// +//* encumbrance (only on the stuff worn, and potentially also in hand) *// + +/obj/item/proc/get_weight() + return weight + obj_storage?.get_containing_weight() + +/obj/item/proc/get_encumbrance() + return encumbrance + +/obj/item/proc/get_flat_encumbrance() + return flat_encumbrance + +/obj/item/proc/update_weight() + if(isnull(weight_registered)) + return null + . = get_weight() + if(. == weight_registered) + return 0 + . -= weight_registered + weight_registered += . + var/mob/living/wearer = worn_mob() + if(istype(wearer)) + wearer.adjust_current_carry_weight(.) + +/obj/item/proc/update_encumbrance() + if(isnull(encumbrance_registered)) + return null + . = get_encumbrance() + if(. == encumbrance_registered) + return 0 + . -= encumbrance_registered + encumbrance_registered += . + var/mob/living/wearer = worn_mob() + if(istype(wearer)) + wearer.adjust_current_carry_encumbrance(.) + +/obj/item/proc/update_flat_encumbrance() + var/mob/living/wearer = worn_mob() + if(istype(wearer)) + wearer.recalculate_carry() + +/obj/item/proc/set_weight(amount) + if(amount == weight) + return + var/old = weight + weight = amount + update_weight() + propagate_weight(old, weight) + +/obj/item/proc/set_encumbrance(amount) + if(amount == encumbrance) + return + encumbrance = amount + update_encumbrance() + +/obj/item/proc/set_flat_encumbrance(amount) + if(amount == flat_encumbrance) + return + flat_encumbrance = amount + update_flat_encumbrance() + +/obj/item/proc/set_slowdown(amount) + if(amount == slowdown) + return + slowdown = amount + worn_mob()?.update_item_slowdown() + +/obj/item/proc/propagate_weight(old_weight, new_weight) + loc?.on_contents_weight_change(src, old_weight, new_weight) diff --git a/code/game/objects/items-defense.dm b/code/game/objects/items-defense.dm new file mode 100644 index 00000000000..4ad604fcbcd --- /dev/null +++ b/code/game/objects/items-defense.dm @@ -0,0 +1,12 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Armor *// + +/** + * Called during a mob armor call cycle + */ +/obj/item/proc/mob_armorcall(mob/defending, list/shieldcall_args, fake_attack) + // use our own armor + var/datum/armor/our_armor = fetch_armor() + our_armor.handle_shieldcall(shieldcall_args, fake_attack) diff --git a/code/game/objects/items-interaction.dm b/code/game/objects/items-interaction.dm index 8e557d574c8..a1b37b7c17c 100644 --- a/code/game/objects/items-interaction.dm +++ b/code/game/objects/items-interaction.dm @@ -1,6 +1,165 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2024 silicons *// +//* Attack Hand *// + +/obj/item/on_attack_hand(datum/event_args/actor/clickchain/e_args) + . = ..() + if(.) + return + + if(e_args.performer.is_in_inventory(src)) + if(e_args.performer.is_holding(src)) + if(obj_storage?.allow_open_via_offhand_click && obj_storage.auto_handle_interacted_open(e_args)) + return TRUE + else + if(obj_storage?.allow_open_via_equipped_click && obj_storage.auto_handle_interacted_open(e_args)) + return TRUE + if(!e_args.performer.is_holding(src)) + if(should_attempt_pickup(e_args) && attempt_pickup(e_args.performer)) + return TRUE + +/obj/item/proc/should_attempt_pickup(datum/event_args/actor/actor) + return TRUE + +/** + * @params + * * actor - (optional) person doing it + * * silent - suppress feedback + */ +/obj/item/proc/should_allow_pickup(datum/event_args/actor/actor, silent) + if(anchored) + if(!silent) + actor?.chat_feedback( + SPAN_NOTICE("\The [src] won't budge, you can't pick it up!"), + target = src, + ) + return FALSE + return TRUE + +/obj/item/proc/attempt_pickup(mob/user) + . = TRUE + if (!user) + return + + if(!should_allow_pickup(new /datum/event_args/actor(user))) + return FALSE + + if(!CHECK_MOBILITY(user, MOBILITY_CAN_PICKUP)) + user.action_feedback(SPAN_WARNING("You can't do that right now."), src) + return + + // todo: rewrite this part iin hand rewrite + if (hasorgans(user)) + var/mob/living/carbon/human/H = user + var/obj/item/organ/external/temp = H.organs_by_name[H.hand? "l_hand" : "r_hand"] + if(!temp) + to_chat(user, "You try to use your hand, but realize it is no longer attached!") + return + if(!temp.is_usable()) + to_chat(user, "You try to move your [temp.name], but cannot!") + return + + var/old_loc = src.loc + var/obj/item/actually_picked_up = src + var/has_to_drop_to_ground_on_fail = FALSE + + if(isturf(old_loc)) + // if picking up from floor + throwing?.terminate() + else if(item_flags & ITEM_IN_STORAGE) + // trying to take out of backpack + var/datum/object_system/storage/resolved + if(istype(loc, /atom/movable/storage_indirection)) + var/atom/movable/storage_indirection/holder = loc + resolved = holder.parent + else if(isobj(loc)) + var/obj/obj_loc = loc + resolved = obj_loc.obj_storage + if(isnull(resolved)) + item_flags &= ~ITEM_IN_STORAGE + CRASH("in storage at [loc] ([REF(loc)]) ([loc?.type || "NULL"]) but cannot resolve storage system") + actually_picked_up = resolved.try_remove(src, user, new /datum/event_args/actor(user)) + // they're in user, but not equipped now. this is so it doesn't touch the ground first. + has_to_drop_to_ground_on_fail = TRUE + + if(isnull(actually_picked_up)) + to_chat(user, SPAN_WARNING("[src] somehow slips through your grasp. What just happened?")) + return + if(!user.put_in_hands(actually_picked_up)) + if(has_to_drop_to_ground_on_fail) + actually_picked_up.forceMove(user.drop_location()) + return + // success + if(isturf(old_loc)) + new /obj/effect/temporary_effect/item_pickup_ghost(old_loc, actually_picked_up, user) + +//* Drag / Drop *// + +/obj/item/OnMouseDrop(atom/over, mob/user, proximity, params) + . = ..() + if(. & CLICKCHAIN_DO_NOT_PROPAGATE) + return + if(anchored) // Don't. + return + if(user.restrained()) + return // don't. + // todo: restraint levels, e.g. handcuffs vs straightjacket + if(!user.is_in_inventory(src)) + // not being held + if(!isturf(loc)) // yea nah + return ..() + if(user.Adjacent(src)) + // check for equip + if(istype(over, /atom/movable/screen/inventory/hand)) + var/atom/movable/screen/inventory/hand/H = over + user.put_in_hand(src, H.index) + return CLICKCHAIN_DO_NOT_PROPAGATE + else if(istype(over, /atom/movable/screen/inventory/slot)) + var/atom/movable/screen/inventory/slot/S = over + user.equip_to_slot_if_possible(src, S.slot_id) + return CLICKCHAIN_DO_NOT_PROPAGATE + // check for slide + if(Adjacent(over) && user.CanSlideItem(src, over) && (istype(over, /obj/structure/table/rack) || istype(over, /obj/structure/table) || istype(over, /turf))) + var/turf/old = get_turf(src) + if(over == old) // same tile don't bother + return CLICKCHAIN_DO_NOT_PROPAGATE + if(!Move(get_turf(over))) + return CLICKCHAIN_DO_NOT_PROPAGATE + //! todo: i want to strangle the mofo who did planes instead of invisibility, which makes it computationally infeasible to check ghost invisibility in get hearers in view + //! :) FUCK YOU. + //! this if check is all for you. FUCK YOU. + if(!isobserver(user)) + user.visible_message(SPAN_NOTICE("[user] slides [src] over."), SPAN_NOTICE("You slide [src] over."), range = MESSAGE_RANGE_COMBAT_SUBTLE) + log_inventory("[user] slid [src] from [COORD(old)] to [COORD(over)]") + return CLICKCHAIN_DO_NOT_PROPAGATE + else + // being held, check for attempt unequip + if(istype(over, /atom/movable/screen/inventory/hand)) + var/atom/movable/screen/inventory/hand/H = over + user.put_in_hand(src, H.index) + return CLICKCHAIN_DO_NOT_PROPAGATE + else if(istype(over, /atom/movable/screen/inventory/slot)) + var/atom/movable/screen/inventory/slot/S = over + user.equip_to_slot_if_possible(src, S.slot_id) + return CLICKCHAIN_DO_NOT_PROPAGATE + else if(istype(over, /turf)) + user.drop_item_to_ground(src) + return CLICKCHAIN_DO_NOT_PROPAGATE + +// funny! +// todo: move to mob files +/mob/proc/CanSlideItem(obj/item/I, turf/over) + return FALSE + +// todo: move to mob files +/mob/living/CanSlideItem(obj/item/I, turf/over) + return Adjacent(I) && !incapacitated() && !stat && !restrained() + +// todo: move to mob files +/mob/observer/dead/CanSlideItem(obj/item/I, turf/over) + return is_spooky + //* Inhand Triggers *// /** diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index bd331dd7d3a..e61d13a6489 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -27,7 +27,18 @@ /// the action will check for var/item_action_mobility_flags = MOBILITY_CAN_HOLD | MOBILITY_CAN_USE - //? Flags + //* Combat *// + /// passive parry data / frame + /// + /// * anonymous typepath is allowed + /// * typepath is allowed + /// * instance is allowed + /// + /// note that the component will not be modified while held; + /// if this is changed, the component needs to be remade. + var/passive_parry + + //* Flags *// /// Item flags. /// These flags are listed in [code/__DEFINES/inventory/item_flags.dm]. var/item_flags = ITEM_ENCUMBERS_WHILE_HELD @@ -423,14 +434,6 @@ /obj/item/ui_action_click(datum/action/action, datum/event_args/actor/actor) attack_self(usr) -//RETURN VALUES -//handle_shield should return a positive value to indicate that the attack is blocked and should be prevented. -//If a negative value is returned, it should be treated as a special return value for bullet_act() and handled appropriately. -//For non-projectile attacks this usually means the attack is blocked. -//Otherwise should return 0 to indicate that the attack is not affected in any way. -/obj/item/proc/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - return 0 - /obj/item/proc/get_loc_turf() var/atom/L = loc while(L && !istype(L, /turf/)) @@ -526,7 +529,7 @@ if (!..()) return 0 - if(istype(src, /obj/item/melee/energy)) + if(istype(src, /obj/item/melee/transforming/energy)) return //if we haven't made our blood_overlay already @@ -759,27 +762,6 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. var/datum/action/action = item_actions action.revoke(user.inventory.actions) -//* Armor *// - -/** - * called to be checked for mob armor - * - * @returns copy of args with modified values - */ -/obj/item/proc/checking_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - damage = fetch_armor().resultant_damage(damage, tier, flag) - return args.Copy() - -/** - * called to be used as mob armor - * side effects are allowed - * - * @returns copy of args with modified values - */ -/obj/item/proc/running_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - damage = fetch_armor().resultant_damage(damage, tier, flag) - return args.Copy() - //* Attack *// /** @@ -830,7 +812,20 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. /obj/item/proc/is_shred(strict) return (damage_mode & DAMAGE_MODE_SHRED) -//* Interaction *// +//* Combat *// + +/obj/item/proc/load_passive_parry() + if(!passive_parry) + return + passive_parry = resolve_passive_parry_data(passive_parry) + var/datum/component/passive_parry/loaded = GetComponent(/datum/component/passive_parry) + if(loaded) + loaded.parry_data = passive_parry + +/obj/item/proc/reload_passive_parry() + load_passive_parry() + +//* Interactions *// /obj/item/attackby(obj/item/I, mob/user, list/params, clickchain_flags, damage_multiplier) if(isturf(loc) && I.obj_storage?.allow_mass_gather && I.obj_storage.allow_mass_gather_via_click) @@ -859,237 +854,8 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. /obj/item/proc/attacksound_override(atom/target, attack_type) return -//* Carry Weight *// - -/obj/item/proc/get_weight() - return weight + obj_storage?.get_containing_weight() - -/obj/item/proc/get_encumbrance() - return encumbrance - -/obj/item/proc/get_flat_encumbrance() - return flat_encumbrance - -/obj/item/proc/update_weight() - if(isnull(weight_registered)) - return null - . = get_weight() - if(. == weight_registered) - return 0 - . -= weight_registered - weight_registered += . - var/mob/living/wearer = worn_mob() - if(istype(wearer)) - wearer.adjust_current_carry_weight(.) - -/obj/item/proc/update_encumbrance() - if(isnull(encumbrance_registered)) - return null - . = get_encumbrance() - if(. == encumbrance_registered) - return 0 - . -= encumbrance_registered - encumbrance_registered += . - var/mob/living/wearer = worn_mob() - if(istype(wearer)) - wearer.adjust_current_carry_encumbrance(.) - -/obj/item/proc/update_flat_encumbrance() - var/mob/living/wearer = worn_mob() - if(istype(wearer)) - wearer.recalculate_carry() - -/obj/item/proc/set_weight(amount) - if(amount == weight) - return - var/old = weight - weight = amount - update_weight() - propagate_weight(old, weight) - -/obj/item/proc/set_encumbrance(amount) - if(amount == encumbrance) - return - encumbrance = amount - update_encumbrance() - -/obj/item/proc/set_flat_encumbrance(amount) - if(amount == flat_encumbrance) - return - flat_encumbrance = amount - update_flat_encumbrance() - -/obj/item/proc/set_slowdown(amount) - if(amount == slowdown) - return - slowdown = amount - worn_mob()?.update_item_slowdown() - -/obj/item/proc/propagate_weight(old_weight, new_weight) - loc?.on_contents_weight_change(src, old_weight, new_weight) - -//* Interactions *// - -/obj/item/on_attack_hand(datum/event_args/actor/clickchain/e_args) - . = ..() - if(.) - return - - if(e_args.performer.is_in_inventory(src)) - if(e_args.performer.is_holding(src)) - if(obj_storage?.allow_open_via_offhand_click && obj_storage.auto_handle_interacted_open(e_args)) - return TRUE - else - if(obj_storage?.allow_open_via_equipped_click && obj_storage.auto_handle_interacted_open(e_args)) - return TRUE - if(!e_args.performer.is_holding(src)) - if(should_attempt_pickup(e_args) && attempt_pickup(e_args.performer)) - return TRUE - -/obj/item/OnMouseDrop(atom/over, mob/user, proximity, params) - . = ..() - if(. & CLICKCHAIN_DO_NOT_PROPAGATE) - return - if(anchored) // Don't. - return - if(user.restrained()) - return // don't. - // todo: restraint levels, e.g. handcuffs vs straightjacket - if(!user.is_in_inventory(src)) - // not being held - if(!isturf(loc)) // yea nah - return ..() - if(user.Adjacent(src)) - // check for equip - if(istype(over, /atom/movable/screen/inventory/hand)) - var/atom/movable/screen/inventory/hand/H = over - user.put_in_hand(src, H.index) - return CLICKCHAIN_DO_NOT_PROPAGATE - else if(istype(over, /atom/movable/screen/inventory/slot)) - var/atom/movable/screen/inventory/slot/S = over - user.equip_to_slot_if_possible(src, S.slot_id) - return CLICKCHAIN_DO_NOT_PROPAGATE - // check for slide - if(Adjacent(over) && user.CanSlideItem(src, over) && (istype(over, /obj/structure/table/rack) || istype(over, /obj/structure/table) || istype(over, /turf))) - var/turf/old = get_turf(src) - if(over == old) // same tile don't bother - return CLICKCHAIN_DO_NOT_PROPAGATE - if(!Move(get_turf(over))) - return CLICKCHAIN_DO_NOT_PROPAGATE - //! todo: i want to strangle the mofo who did planes instead of invisibility, which makes it computationally infeasible to check ghost invisibility in get hearers in view - //! :) FUCK YOU. - //! this if check is all for you. FUCK YOU. - if(!isobserver(user)) - user.visible_message(SPAN_NOTICE("[user] slides [src] over."), SPAN_NOTICE("You slide [src] over."), range = MESSAGE_RANGE_COMBAT_SUBTLE) - log_inventory("[user] slid [src] from [COORD(old)] to [COORD(over)]") - return CLICKCHAIN_DO_NOT_PROPAGATE - else - // being held, check for attempt unequip - if(istype(over, /atom/movable/screen/inventory/hand)) - var/atom/movable/screen/inventory/hand/H = over - user.put_in_hand(src, H.index) - return CLICKCHAIN_DO_NOT_PROPAGATE - else if(istype(over, /atom/movable/screen/inventory/slot)) - var/atom/movable/screen/inventory/slot/S = over - user.equip_to_slot_if_possible(src, S.slot_id) - return CLICKCHAIN_DO_NOT_PROPAGATE - else if(istype(over, /turf)) - user.drop_item_to_ground(src) - return CLICKCHAIN_DO_NOT_PROPAGATE - -// funny! -// todo: move to mob files -/mob/proc/CanSlideItem(obj/item/I, turf/over) - return FALSE - -// todo: move to mob files -/mob/living/CanSlideItem(obj/item/I, turf/over) - return Adjacent(I) && !incapacitated() && !stat && !restrained() - -// todo: move to mob files -/mob/observer/dead/CanSlideItem(obj/item/I, turf/over) - return is_spooky - //* Inventory *// -/obj/item/proc/should_attempt_pickup(datum/event_args/actor/actor) - return TRUE - -/** - * @params - * * actor - (optional) person doing it - * * silent - suppress feedback - */ -/obj/item/proc/should_allow_pickup(datum/event_args/actor/actor, silent) - if(anchored) - if(!silent) - actor?.chat_feedback( - SPAN_NOTICE("\The [src] won't budge, you can't pick it up!"), - target = src, - ) - return FALSE - return TRUE - -/obj/item/proc/attempt_pickup(mob/user) - . = TRUE - if (!user) - return - - if(!should_allow_pickup(new /datum/event_args/actor(user))) - return FALSE - - if(!CHECK_MOBILITY(user, MOBILITY_CAN_PICKUP)) - user.action_feedback(SPAN_WARNING("You can't do that right now."), src) - return - - if (hasorgans(user)) - var/mob/living/carbon/human/H = user - var/obj/item/organ/external/temp = H.organs_by_name["r_hand"] - if (H.hand) - temp = H.organs_by_name["l_hand"] - if(temp && !temp.is_usable()) - to_chat(user, "You try to move your [temp.name], but cannot!") - return - if(!temp) - to_chat(user, "You try to use your hand, but realize it is no longer attached!") - return - - var/old_loc = src.loc - var/obj/item/actually_picked_up = src - var/has_to_drop_to_ground_on_fail = FALSE - - if(isturf(old_loc)) - // if picking up from floor - throwing?.terminate() - else if(item_flags & ITEM_IN_STORAGE) - // trying to take out of backpack - var/datum/object_system/storage/resolved - if(istype(loc, /atom/movable/storage_indirection)) - var/atom/movable/storage_indirection/holder = loc - resolved = holder.parent - else if(isobj(loc)) - var/obj/obj_loc = loc - resolved = obj_loc.obj_storage - if(isnull(resolved)) - item_flags &= ~ITEM_IN_STORAGE - CRASH("in storage at [loc] ([REF(loc)]) ([loc?.type || "NULL"]) but cannot resolve storage system") - actually_picked_up = resolved.try_remove(src, user, new /datum/event_args/actor(user)) - // they're in user, but not equipped now. this is so it doesn't touch the ground first. - has_to_drop_to_ground_on_fail = TRUE - - if(isnull(actually_picked_up)) - to_chat(user, SPAN_WARNING("[src] somehow slips through your grasp. What just happened?")) - return - if(!user.put_in_hands(actually_picked_up)) - if(has_to_drop_to_ground_on_fail) - actually_picked_up.forceMove(user.drop_location()) - return - // success - if(isturf(old_loc)) - var/obj/effect/temporary_effect/item_pickup_ghost/ghost = new(old_loc) - ghost.assumeform(actually_picked_up) - ghost.animate_towards(user) - /** * Called when someone clisk us on a storage, before the storage handler's * 'put item in' runs. Return FALSE to deny. @@ -1172,8 +938,18 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. //* VV *// +/obj/item/vv_get_var(var_name, resolve) + switch(var_name) + if(NAMEOF(src, passive_parry)) + if(resolve) + load_passive_parry() + return ..() + /obj/item/vv_edit_var(var_name, var_value, mass_edit, raw_edit) switch(var_name) + if(NAMEOF(src, passive_parry)) + . = ..() + reload_passive_parry() if(NAMEOF(src, item_flags)) var/requires_update = (item_flags & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD)) != (var_value & (ITEM_ENCUMBERS_WHILE_HELD | ITEM_ENCUMBERS_ONLY_HELD)) . = ..() @@ -1193,7 +969,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. L.update_carry_slowdown() if(NAMEOF(src, slowdown)) . = ..() - if(. ) + if(.) var/mob/living/L = worn_mob() // check, as worn_mob() returns /mob, not /living if(istype(L)) diff --git a/code/game/objects/items/contraband.dm b/code/game/objects/items/contraband.dm index a54ee11a41e..e8e86f7fc15 100644 --- a/code/game/objects/items/contraband.dm +++ b/code/game/objects/items/contraband.dm @@ -109,12 +109,12 @@ /obj/item/grenade/flashbang/clusterbang, /obj/item/grenade/flashbang/clusterbang, /obj/item/grenade/spawnergrenade/spesscarp, - /obj/item/melee/energy/sword/ionic_rapier, + /obj/item/melee/transforming/energy/sword/ionic_rapier, /obj/item/clothing/shoes/syndigaloshes, /obj/item/storage/backpack/dufflebag/syndie, /obj/item/binoculars, /obj/item/storage/firstaid/combat, - /obj/item/melee/energy/sword, + /obj/item/melee/transforming/energy/sword, /obj/item/melee/telebaton, /obj/item/pen/reagent/paralysis, /obj/item/pickaxe/diamonddrill, @@ -124,7 +124,7 @@ /obj/item/reagent_containers/food/snacks/xenomeat, /obj/item/reagent_containers/glass/beaker/neurotoxin, /obj/item/hardsuit/combat, - /obj/item/shield/energy, + /obj/item/shield/transforming/energy, /obj/item/stamp/centcom, /obj/item/stamp/oricon, /obj/item/storage/fancy/cigar/havana, @@ -217,13 +217,13 @@ /obj/item/grenade/flashbang/clusterbang, /obj/item/grenade/flashbang/clusterbang, /obj/item/grenade/spawnergrenade/spesscarp, - /obj/item/melee/energy/sword, + /obj/item/melee/transforming/energy/sword, /obj/item/melee/telebaton, /obj/item/pen/reagent/paralysis, /obj/item/pickaxe/diamonddrill, /obj/item/reagent_containers/glass/beaker/neurotoxin, /obj/item/hardsuit/combat, - /obj/item/shield/energy, + /obj/item/shield/transforming/energy, /obj/item/stamp/centcom, /obj/item/stamp/oricon, /obj/item/storage/fancy/cigar/havana, diff --git a/code/game/objects/items/devices/chameleonproj.dm b/code/game/objects/items/devices/chameleonproj.dm index 40a96b4cd4f..366b6f29a8a 100644 --- a/code/game/objects/items/devices/chameleonproj.dm +++ b/code/game/objects/items/devices/chameleonproj.dm @@ -120,10 +120,10 @@ to_chat(M, "Your chameleon-projector deactivates.") master.disrupt() -/obj/effect/dummy/chameleon/bullet_act() +/obj/effect/dummy/chameleon/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() for(var/mob/M in src) to_chat(M, "Your chameleon-projector deactivates.") - ..() master.disrupt() /obj/effect/dummy/chameleon/relaymove(var/mob/user, direction) diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm index a2712ccf6d4..009374244b8 100644 --- a/code/game/objects/items/devices/gps.dm +++ b/code/game/objects/items/devices/gps.dm @@ -212,7 +212,7 @@ hud_bound?.add_screen(hud_arrow) hud_arrow.set_disabled(FALSE) update_tracking() - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) return TRUE /** @@ -225,7 +225,7 @@ tracking = null // just kick it out hud_arrow?.set_disabled(TRUE) - STOP_PROCESSING(SSfastprocess, src) + STOP_PROCESSING(SSprocess_5fps, src) return TRUE /obj/item/gps/process(delta_time) diff --git a/code/game/objects/items/devices/spy_bug.dm b/code/game/objects/items/devices/spy_bug.dm index 8165600c4cb..fd10b126503 100644 --- a/code/game/objects/items/devices/spy_bug.dm +++ b/code/game/objects/items/devices/spy_bug.dm @@ -109,14 +109,15 @@ qdel(src) ..() -/obj/item/camerabug/bullet_act() +/obj/item/camerabug/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return visible_message("The [src] lens shatters!") new brokentype(get_turf(src)) if(linkedmonitor) linkedmonitor.unpair(src) linkedmonitor = null - spawn(0) - qdel(src) /obj/item/camerabug/Destroy() if(linkedmonitor) diff --git a/code/game/objects/items/id_cards/station_ids.dm b/code/game/objects/items/id_cards/station_ids.dm index f14e2d1b912..ccbca7e089f 100644 --- a/code/game/objects/items/id_cards/station_ids.dm +++ b/code/game/objects/items/id_cards/station_ids.dm @@ -71,7 +71,6 @@ . += "" if(Adjacent(user)) . += SPAN_NOTICE("Alt-click to [extra_info_visible ? "close" : "open"] the confidential information flap.") - return . /obj/item/card/id/get_description_info() . = ..() diff --git a/code/game/objects/items/latexballoon.dm b/code/game/objects/items/latexballoon.dm index 8c5ecb1731b..5b469f669e5 100644 --- a/code/game/objects/items/latexballoon.dm +++ b/code/game/objects/items/latexballoon.dm @@ -30,16 +30,8 @@ item_state = "lgloves" loc.assume_air(air_contents) -/obj/item/latexballon/legacy_ex_act(severity) - burst() - switch(severity) - if (1) - qdel(src) - if (2) - if (prob(50)) - qdel(src) - -/obj/item/latexballon/bullet_act() +/obj/item/latexballon/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon) + . = ..() burst() /obj/item/latexballon/fire_act(datum/gas_mixture/air, temperature, volume) diff --git a/code/game/objects/items/melee/melee.dm b/code/game/objects/items/melee/melee.dm new file mode 100644 index 00000000000..ec0b7865676 --- /dev/null +++ b/code/game/objects/items/melee/melee.dm @@ -0,0 +1,22 @@ +/datum/passive_parry/melee + parry_arc = 155 + parry_arc_round_down = TRUE + +/** + * this is like /device and /weapon but a little less dumb + * + * this has some simple wrappers for default defense stuff, so 'common'ly melee weapons + * like knives, armblades, etc, can easily use them. + * + * * Certain things may or may not 'graduate' to /obj/item level later. + */ +/obj/item/melee + icon = 'icons/obj/weapons.dmi' + attack_sound = "swing_hit" + item_icons = list( + SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', + SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', + ) + passive_parry = /datum/passive_parry{ + parry_chance_melee = 5; + } diff --git a/code/game/objects/items/weapons/melee/misc.dm b/code/game/objects/items/melee/types/misc.dm similarity index 94% rename from code/game/objects/items/weapons/melee/misc.dm rename to code/game/objects/items/melee/types/misc.dm index 41774bbc0db..ec2cf5250f4 100644 --- a/code/game/objects/items/weapons/melee/misc.dm +++ b/code/game/objects/items/melee/types/misc.dm @@ -68,13 +68,10 @@ can_speak = 1 var/list/voice_mobs = list() //The curse of the sword is that it has someone trapped inside. - -/obj/item/melee/cursedblade/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(default_parry_check(user, attacker, damage_source) && prob(50)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 + passive_parry = /datum/passive_parry/melee{ + parry_chance_projectile = 15; + parry_chance_default = 50; + } /obj/item/melee/cursedblade/proc/ghost_inhabit(var/mob/candidate) if(!isobserver(candidate)) @@ -329,19 +326,14 @@ var/wieldsound = null var/unwieldsound = null -//Allow a small chance of parrying melee attacks when wielded - maybe generalize this to other weapons someday -/obj/item/melee/twohanded/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(wielded && default_parry_check(user, attacker, damage_source) && prob(15)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 + passive_parry = /datum/passive_parry/melee{ + parry_chance_melee = 15; + } /obj/item/melee/twohanded/attack_self(mob/user) . = ..() if(.) return - . = ..() if(!wielded) wielded = 1 else if(wielded) diff --git a/code/game/objects/items/melee/types/ninja_energy_blade.dm b/code/game/objects/items/melee/types/ninja_energy_blade.dm new file mode 100644 index 00000000000..c7d110bc483 --- /dev/null +++ b/code/game/objects/items/melee/types/ninja_energy_blade.dm @@ -0,0 +1,66 @@ +/obj/item/melee/ninja_energy_blade + name = "energy blade" + desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." + icon_state = "ninja_energy_blade" + item_state = "ninja_energy_blade" + damage_force = 40 //Normal attacks deal very high damage - about the same as wielded fire axe + armor_penetration = 100 + sharp = 1 + edge = 1 + anchored = 1 // Never spawned outside of inventory, should be fine. + throw_force = 1 //Throwing or dropping the item deletes it. + throw_speed = 1 + throw_range = 1 + w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such. + atom_flags = NOBLOODY + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + + var/mob/living/creator + var/datum/effect_system/spark_spread/spark_system + + var/lrange = 2 + var/lpower = 2 + var/lcolor = "#00FF00" + + passive_parry = /datum/passive_parry{ + parry_chance_default = 60; + parry_chance_projectile = 60; + } + +/obj/item/melee/ninja_energy_blade/Initialize(mapload) + . = ..() + spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + + START_PROCESSING(SSobj, src) + set_light(lrange, lpower, lcolor) + +/obj/item/melee/ninja_energy_blade/Destroy() + STOP_PROCESSING(SSobj, src) + return ..() + +/obj/item/melee/ninja_energy_blade/attack_self(mob/user) + . = ..() + if(.) + return + qdel(src) + +/obj/item/melee/ninja_energy_blade/dropped(mob/user, atom_flags, atom/newLoc) + . = ..() + qdel(src) + +/obj/item/melee/ninja_energy_blade/process(delta_time) + if(!creator || loc != creator || !creator.is_holding(src)) + // Tidy up a bit. + if(istype(loc,/mob/living)) + var/mob/living/carbon/human/host = loc + if(istype(host)) + for(var/obj/item/organ/external/organ in host.organs) + for(var/obj/item/O in organ.implants) + if(O == src) + organ.implants -= src + host.pinned -= src + host.embedded -= src + host._handle_inventory_hud_remove(src) + qdel(src) diff --git a/code/game/objects/items/melee/types/transforming.dm b/code/game/objects/items/melee/types/transforming.dm new file mode 100644 index 00000000000..fe7f4160f93 --- /dev/null +++ b/code/game/objects/items/melee/types/transforming.dm @@ -0,0 +1,177 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/** + * single-toggle weapons + */ +/obj/item/melee/transforming + icon = 'icons/items/melee/transforming.dmi' + damage_mode = NONE + item_icons = null + + /// are we active? + var/active = FALSE + /// when active, do we use an overlay instead of an icon state? + var/active_via_overlay = FALSE + + /// activation sound; also deactivation if it's not specified + var/activation_sound = 'sound/weapons/empty.ogg' + var/deactivation_sound + /// sound volume + var/toggle_sound_volume = 50 + + /// do not allow passive parry while off + var/no_block_while_off = TRUE + + //* active / inactive damage *// + + var/active_damage_force + var/inactive_damage_force + + var/active_damage_mode + var/inactive_damage_mode + + var/active_damage_tier + var/inactive_damage_tier + + var/active_damage_type + var/inactive_damage_type + + //* active / inactive effects *// + + var/list/active_attack_verb + var/list/inactive_attack_verb + + //* active / inactive inventory costs *// + + var/active_weight_class + var/inactive_weight_class + + var/active_weight_volume + var/inactive_weight_volume + + //* active / inactive throwing *// + + var/active_throw_force + var/inactive_throw_force + + var/active_throw_resist + var/inactive_throw_resist + + var/active_throw_range + var/inactive_throw_range + + var/active_throw_speed + var/inactive_throw_speed + +/obj/item/melee/transforming/Initialize(mapload) + . = ..() + if(islist(active_attack_verb)) + active_attack_verb = typelist(NAMEOF(src, active_attack_verb), active_attack_verb) + if(islist(inactive_attack_verb)) + inactive_attack_verb = typelist(NAMEOF(src, inactive_attack_verb), inactive_attack_verb) + +/obj/item/melee/transforming/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + if(!active && no_block_while_off) + return // cancel + return ..() + +/obj/item/melee/transforming/update_icon_state() + icon_state = "[base_icon_state || initial(icon_state)][active && !active_via_overlay ? "-active" : ""]" + return ..() + +/obj/item/melee/transforming/update_overlays() + . = ..() + if(!active || !active_via_overlay) + return + . += build_active_overlay() + +/obj/item/melee/transforming/proc/build_active_overlay() + RETURN_TYPE(/image) + var/image/creating = image(icon, "[base_icon_state || icon_state]-active") + return creating + +/obj/item/melee/transforming/proc/build_active_worn_overlay(worn_state) + RETURN_TYPE(/image) + var/image/creating = image(icon, "[worn_state]-active") + return creating + +/obj/item/melee/transforming/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used) + . = ..() + if(active_via_overlay && active) + MA.overlays += build_active_worn_overlay(MA.icon_state) + +/obj/item/melee/transforming/on_attack_self(datum/event_args/actor/e_args) + . = ..() + if(.) + return + add_fingerprint(e_args.performer) + toggle(e_args) + return CLICKCHAIN_DO_NOT_PROPAGATE + +/** + * actor can be /datum/event_args/actor or a single mob. + */ +/obj/item/melee/transforming/proc/toggle(datum/event_args/actor/actor, silent) + set_activation(!active, actor, silent) + +/obj/item/melee/transforming/proc/set_activation(state, datum/event_args/actor/actor, silent) + active = state + if(active) + on_activate(actor, silent) + else + on_deactivate(actor, silent) + update_icon() + update_worn_icon() + +/** + * actor can be /datum/event_args/actor or a single mob. + */ +/obj/item/melee/transforming/proc/on_activate(datum/event_args/actor/actor, silent) + E_ARGS_WRAP_USER_TO_ACTOR(actor) + + damage_force = VALUE_OR_DEFAULT(active_damage_force, initial(damage_force)) + damage_tier = VALUE_OR_DEFAULT(active_damage_tier, initial(damage_tier)) + damage_mode = VALUE_OR_DEFAULT(active_damage_mode, initial(damage_mode)) + damtype = VALUE_OR_DEFAULT(active_damage_type, initial(damtype)) + + throw_force = VALUE_OR_DEFAULT(active_throw_force, initial(throw_force)) + throw_resist = VALUE_OR_DEFAULT(active_throw_resist, initial(throw_resist)) + throw_range = VALUE_OR_DEFAULT(active_throw_range, initial(throw_range)) + throw_speed = VALUE_OR_DEFAULT(active_throw_speed, initial(throw_speed)) + + set_weight_class(VALUE_OR_DEFAULT(active_weight_class, initial(w_class))) + set_weight_volume(VALUE_OR_DEFAULT(active_weight_volume, initial(weight_volume))) + + attack_verb = active_attack_verb + + if(!silent && activation_sound) + playsound(src, activation_sound, toggle_sound_volume, TRUE) + + // todo: logging + +/** + * actor can be /datum/event_args/actor or a single mob. + */ +/obj/item/melee/transforming/proc/on_deactivate(datum/event_args/actor/actor, silent) + E_ARGS_WRAP_USER_TO_ACTOR(actor) + + damage_force = VALUE_OR_DEFAULT(inactive_damage_force, initial(damage_force)) + damage_tier = VALUE_OR_DEFAULT(inactive_damage_tier, initial(damage_tier)) + damage_mode = VALUE_OR_DEFAULT(inactive_damage_mode, initial(damage_mode)) + damtype = VALUE_OR_DEFAULT(inactive_damage_type, initial(damtype)) + + throw_force = VALUE_OR_DEFAULT(inactive_throw_force, initial(throw_force)) + throw_resist = VALUE_OR_DEFAULT(inactive_throw_resist, initial(throw_resist)) + throw_range = VALUE_OR_DEFAULT(inactive_throw_range, initial(throw_range)) + throw_speed = VALUE_OR_DEFAULT(inactive_throw_speed, initial(throw_speed)) + + set_weight_class(VALUE_OR_DEFAULT(inactive_weight_class, initial(w_class))) + set_weight_volume(VALUE_OR_DEFAULT(inactive_weight_volume, initial(weight_volume))) + + attack_verb = inactive_attack_verb + + if(!silent && (activation_sound || deactivation_sound)) + playsound(src, deactivation_sound || activation_sound, toggle_sound_volume, TRUE) + + // todo: logging diff --git a/code/game/objects/items/melee/types/transforming/energy.dm b/code/game/objects/items/melee/types/transforming/energy.dm new file mode 100644 index 00000000000..f011dec3091 --- /dev/null +++ b/code/game/objects/items/melee/types/transforming/energy.dm @@ -0,0 +1,176 @@ +/datum/passive_parry/melee/energy + parry_frame = /datum/parry_frame/passive_block/energy + +/datum/parry_frame/passive_block/energy + parry_sfx = 'sound/weapons/blade1.ogg' + +/obj/item/melee/transforming/energy + armor_penetration = 50 + atom_flags = NOCONDUCT | NOBLOODY + active_via_overlay = TRUE + + var/lrange = 2 + var/lpower = 2 + var/lcolor = "#0099FF" + var/colorable = FALSE + var/rainbow = FALSE + // If it uses energy. + var/use_cell = FALSE + var/hitcost = 120 + var/obj/item/cell/bcell = null + var/cell_type = /obj/item/cell/device + + passive_parry = /datum/passive_parry/melee/energy + + activation_sound = 'sound/weapons/saberon.ogg' + deactivation_sound = 'sound/weapons/saberoff.ogg' + +/obj/item/melee/transforming/energy/examine(mob/user, dist) + . = ..() + if(colorable) + . += SPAN_NOTICE("Alt-click to recolor it.") + +/obj/item/melee/transforming/energy/on_activate(datum/event_args/actor/actor, silent) + . = ..() + set_light(lrange, lpower, lcolor) + +/obj/item/melee/transforming/energy/on_deactivate(datum/event_args/actor/actor, silent) + . = ..() + set_light(0) + +/obj/item/melee/transforming/energy/proc/use_charge(var/cost) + if(active) + if(bcell) + if(bcell.checked_use(cost)) + return 1 + else + return 0 + return null + +/obj/item/melee/transforming/energy/examine(mob/user, dist) + . = ..() + if(use_cell) + if(bcell) + . += "The blade is [round(bcell.percent())]% charged." + if(!bcell) + . += "The blade does not have a power source installed." + +/obj/item/melee/transforming/energy/toggle(datum/event_args/actor/actor, silent) + if(use_cell) + if((!bcell || bcell.charge < hitcost) && !active) + if(!silent) + actor.chat_feedback( + "\The [src] does not seem to have power.", + target = src, + ) + return FALSE + return ..() + +/obj/item/melee/transforming/energy/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) + . = ..() + if(active && use_cell) + if(!use_charge(hitcost)) + set_activation(FALSE) + visible_message("\The [src]'s blade flickers, before deactivating.") + +/obj/item/melee/transforming/energy/attackby(obj/item/W, mob/user) + if(istype(W, /obj/item/multitool) && colorable && !active) + if(!rainbow) + rainbow = TRUE + else + rainbow = FALSE + to_chat(user, "You manipulate the color controller in [src].") + update_icon() + if(use_cell) + if(istype(W, cell_type)) + if(!bcell) + if(!user.attempt_insert_item_for_installation(W, src)) + return + bcell = W + to_chat(user, "You install a cell in [src].") + update_icon() + else + to_chat(user, "[src] already has a cell.") + else if(W.is_screwdriver() && bcell) + bcell.update_icon() + bcell.forceMove(get_turf(loc)) + bcell = null + to_chat(user, "You remove the cell from \the [src].") + set_activation(FALSE) + update_icon() + return + return ..() + +/obj/item/melee/transforming/energy/get_cell(inducer) + return bcell + +/obj/item/melee/transforming/energy/build_active_overlay() + var/image/creating = ..() + if(rainbow) + creating.icon_state += "-rainbow" + else + creating.color = lcolor + return creating + +/obj/item/melee/transforming/energy/build_active_worn_overlay() + var/image/creating = ..() + if(rainbow) + creating.icon_state += "-rainbow" + else + creating.color = lcolor + return creating + +/obj/item/melee/transforming/energy/AltClick(mob/living/user) + if(!colorable) //checks if is not colorable + return + if(!in_range(src, user)) //Basic checks to prevent abuse + return + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + return + + if(alert("Are you sure you want to recolor your blade?", "Confirm Recolor", "Yes", "No") == "Yes") + var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null + if(energy_color_input) + lcolor = "#[sanitize_hexcolor(energy_color_input)]" + color = lcolor + update_icon() + return ..() + +// todo: no inhand! +// /obj/item/melee/transforming/energy/spear +// name = "energy spear" +// desc = "Concentrated energy forming a sharp tip at the end of a long rod." +// icon_state = "espear" +// armor_penetration = 75 +// sharp = 1 +// edge = 1 +// damage_force = 5 +// throw_force = 10 +// throw_speed = 7 +// throw_range = 11 +// reach = 2 +// w_class = WEIGHT_CLASS_BULKY +// active_damage_force = 25 +// active_throw_force = 30 +// active_weight_class = WEIGHT_CLASS_HUGE +// colorable = TRUE +// lcolor = "#800080" + +// passive_parry = /datum/passive_parry/melee/energy{ +// parry_chance_default = 50 +// } + +// /obj/item/melee/transforming/energy/spear/activate(mob/living/user) +// if(!active) +// to_chat(user, "\The [src] is now energised.") +// ..() +// attack_verb = list("jabbed", "stabbed", "impaled") +// AddComponent(/datum/component/jousting) + +// /obj/item/melee/transforming/energy/spear/deactivate(mob/living/user) +// if(active) +// to_chat(user, "\The [src] deactivates!") +// ..() +// attack_verb = list("whacked", "beat", "slapped", "thonked") +// DelComponent(/datum/component/jousting) diff --git a/code/game/objects/items/melee/types/transforming/energy/axe.dm b/code/game/objects/items/melee/types/transforming/energy/axe.dm new file mode 100644 index 00000000000..42d6e7be1c9 --- /dev/null +++ b/code/game/objects/items/melee/types/transforming/energy/axe.dm @@ -0,0 +1,49 @@ + +/obj/item/melee/transforming/energy/axe + name = "energy axe" + desc = "An energised battle axe." + icon_state = "energy_axe" + base_icon_state = "energy_axe" + damage_force = 20 + throw_force = 10 + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_NORMAL + origin_tech = list(TECH_MAGNET = 3, TECH_COMBAT = 4) + attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") + sharp = 1 + edge = 1 + can_cleave = TRUE + + active_damage_force = 60 + active_throw_force = 35 + active_weight_class = WEIGHT_CLASS_HUGE + active_damage_type = SEARING + +/obj/item/melee/transforming/energy/axe/on_activate(datum/event_args/actor/actor, silent) + . = ..() + actor.chat_feedback( + SPAN_WARNING("You energize \the [src]."), + target = src, + ) + +/obj/item/melee/transforming/energy/axe/on_deactivate(datum/event_args/actor/actor, silent) + . = ..() + actor.chat_feedback( + SPAN_WARNING("You de-energize \the [src]."), + target = src, + ) + +/obj/item/melee/transforming/energy/axe/charge + name = "charge axe" + desc = "An energised axe." + active_damage_force = 30 + active_throw_force = 20 + armor_penetration = 25 + damage_force = 15 + use_cell = TRUE + hitcost = 120 + +/obj/item/melee/transforming/energy/axe/charge/loaded/Initialize(mapload) + . = ..() + bcell = new/obj/item/cell/device/weapon(src) diff --git a/code/game/objects/items/melee/types/transforming/energy/ionic_rapier.dm b/code/game/objects/items/melee/types/transforming/energy/ionic_rapier.dm new file mode 100644 index 00000000000..16c15a514bb --- /dev/null +++ b/code/game/objects/items/melee/types/transforming/energy/ionic_rapier.dm @@ -0,0 +1,56 @@ +/obj/item/melee/transforming/energy/sword/ionic_rapier + name = "ionic rapier" + desc = "Designed specifically for disrupting electronics at close range, it is extremely deadly against synthetics, but almost harmless to pure organic targets." + description_info = "This is a dangerous melee weapon that will deliver a moderately powerful electromagnetic pulse to whatever it strikes. \ + Striking a lesser robotic entity will compel it to attack you, as well. It also does extra burn damage to robotic entities, but it does \ + very little damage to purely organic targets." + icon_state = "ionrapier" + item_state = "ionrapier" + active_damage_force = 10 + active_throw_force = 3 + sharp = 1 + edge = 1 + armor_penetration = 0 + atom_flags = NOBLOODY + lrange = 2 + lpower = 2 + lcolor = "#0000FF" + + passive_parry = /datum/passive_parry{ + parry_chance_default = 60; + parry_chance_projectile = 30; + } + +/obj/item/melee/transforming/energy/sword/ionic_rapier/afterattack(atom/target, mob/user, clickchain_flags, list/params) + if(istype(target, /obj) && (clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) && active) + // EMP stuff. + var/obj/O = target + O.emp_act(3) // A weaker severity is used because this has infinite uses. + playsound(get_turf(O), 'sound/effects/EMPulse.ogg', 100, 1) + user.setClickCooldown(user.get_attack_speed(src)) // A lot of objects don't set click delay. + return ..() + +/obj/item/melee/transforming/energy/sword/ionic_rapier/melee_mob_hit(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) + . = ..() + var/mob/living/L = target + if(!istype(L)) + return + if(L.isSynthetic() && active) + // Do some extra damage. Not a whole lot more since emp_act() is pretty nasty on FBPs already. + L.emp_act(3) // A weaker severity is used because this has infinite uses. + playsound(get_turf(L), 'sound/effects/EMPulse.ogg', 100, 1) + L.adjustFireLoss(damage_force * 3) // 15 Burn, for 20 total. + playsound(get_turf(L), 'sound/weapons/blade1.ogg', 100, 1) + + // Make lesser robots really mad at us. + if(L.mob_class & MOB_CLASS_SYNTHETIC) + if(L.has_polaris_AI()) + L.taunt(user) + L.adjustFireLoss(damage_force * 6) // 30 Burn, for 50 total. + +/obj/item/melee/transforming/energy/sword/ionic_rapier/lance + name = "zero-point lance" + desc = "Designed specifically for disrupting electronics at relatively close range, however it is still capable of dealing some damage to living beings." + active_damage_force = 20 + armor_penetration = 15 + reach = 2 diff --git a/code/game/objects/items/melee/types/transforming/energy/saber.dm b/code/game/objects/items/melee/types/transforming/energy/saber.dm new file mode 100644 index 00000000000..48e1eb49140 --- /dev/null +++ b/code/game/objects/items/melee/types/transforming/energy/saber.dm @@ -0,0 +1,141 @@ +/obj/item/melee/transforming/energy/sword + name = "energy saber" + desc = "May the fourth be within you." + icon_state = "saber" + base_icon_state = "saber" + damage_force = 3 + throw_force = 5 + throw_speed = 1 + throw_range = 5 + w_class = WEIGHT_CLASS_SMALL + atom_flags = NOBLOODY + origin_tech = list(TECH_MAGNET = 3, TECH_ILLEGAL = 4) + colorable = TRUE + drop_sound = 'sound/items/drop/sword.ogg' + pickup_sound = 'sound/items/pickup/sword.ogg' + + active_damage_force = 30 + active_throw_force = 20 + active_weight_class = WEIGHT_CLASS_BULKY + active_damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE + active_attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + + passive_parry = /datum/passive_parry{ + parry_chance_default = 60; + parry_chance_projectile = 65; + } + +/obj/item/melee/transforming/energy/sword/on_activate(datum/event_args/actor/actor, silent) + . = ..() + actor.chat_feedback( + SPAN_WARNING("You energize \the [src]."), + target = src, + ) + +/obj/item/melee/transforming/energy/sword/on_deactivate(datum/event_args/actor/actor, silent) + . = ..() + actor.chat_feedback( + SPAN_WARNING("You de-energize \the [src]."), + target = src, + ) + +/obj/item/melee/transforming/energy/sword/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + . = ..() + if(!.) + return + + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(5, 0, defending.loc) + spark_system.start() + +/obj/item/melee/transforming/energy/sword/attackby(obj/item/W, mob/living/user, params) + if(istype(W, /obj/item/melee/transforming/energy/sword)) + if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP)) + to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!") + return + if(istype(W, /obj/item/melee/transforming/energy/sword/charge)) + to_chat(user,"These blades are incompatible, you can't attach them to each other!") + return + else + to_chat(user, "You combine the two energy swords, making a single supermassive blade! You're cool.") + new /obj/item/melee/transforming/energy/sword/dual(user.drop_location()) + qdel(W) + qdel(src) + else + return ..() + +/obj/item/melee/transforming/energy/sword/cutlass + name = "energy cutlass" + desc = "Arrrr matey." + icon_state = "cutlass" + base_icon_state = "cutlass" + colorable = TRUE + +/obj/item/melee/transforming/energy/sword/dual + name = "double-bladed energy sword" + desc = "Handle with care." + icon_state = "saber-dual" + base_icon_state = "saber-dual" + damage_force = 3 + active_damage_force = 60 + throw_force = 5 + throw_speed = 3 + armor_penetration = 35 + colorable = TRUE + attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + + passive_parry = /datum/passive_parry{ + parry_chance_default = 60; + parry_chance_projectile = 85; + } + +/obj/item/melee/transforming/energy/sword/charge + name = "charge sword" + desc = "A small, handheld device which emits a high-energy 'blade'." + origin_tech = list(TECH_COMBAT = 5, TECH_MAGNET = 3, TECH_ILLEGAL = 4) + active_damage_force = 25 + armor_penetration = 25 + colorable = TRUE + use_cell = TRUE + hitcost = 75 + +/obj/item/melee/transforming/energy/sword/charge/loaded/Initialize(mapload) + . = ..() + bcell = new/obj/item/cell/device/weapon(src) + +/obj/item/melee/transforming/energy/sword/charge/attackby(obj/item/W, mob/living/user, params) + if(istype(W, /obj/item/melee/transforming/energy/sword/charge)) + if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP)) + to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!") + return + else + to_chat(user, "You combine the two charge swords, making a single supermassive blade! You're cool.") + new /obj/item/melee/transforming/energy/sword/charge/dualsaber(user.drop_location()) + qdel(W) + qdel(src) + else + return ..() + +/obj/item/melee/transforming/energy/sword/charge/dualsaber + name = "double-bladed charge sword" + desc = "Make sure you bought batteries." + icon_state = "saber-dual" + base_icon_state = "saber-dual" + damage_force = 3 + active_damage_force = 50 + throw_force = 5 + throw_speed = 3 + armor_penetration = 30 + colorable = TRUE + hitcost = 150 + + passive_parry = /datum/passive_parry{ + parry_chance_default = 60; + parry_chance_projectile = 65; + } + +/obj/item/melee/transforming/energy/sword/imperial + name = "imperial sword" + desc = "What the hell is this?" + icon_state = "imperial_sword" + base_icon_state = "imperial_sword" diff --git a/code/game/objects/items/melee/types/transforming/hfmachete.dm b/code/game/objects/items/melee/types/transforming/hfmachete.dm new file mode 100644 index 00000000000..5b5653a84f7 --- /dev/null +++ b/code/game/objects/items/melee/types/transforming/hfmachete.dm @@ -0,0 +1,50 @@ +/obj/item/melee/transforming/hfmachete + name = "high-frequency machete" + desc = "A high-frequency broad blade used either as an implement or in combat like a short sword." + icon_state = "hfmachete" + base_icon_state = "hfmachete" + damage_mode = DAMAGE_MODE_SHARP | DAMAGE_MODE_EDGE + damage_force = 20 // You can be crueler than that, Jack. + throw_force = 40 + throw_speed = 8 + throw_range = 8 + w_class = WEIGHT_CLASS_NORMAL + siemens_coefficient = 1 + origin_tech = list(TECH_COMBAT = 3, TECH_ILLEGAL = 3) + attack_verb = list("attacked", "diced", "cleaved", "torn", "cut", "slashed") + armor_penetration = 50 + var/base_state = "hfmachete" + attack_sound = "machete_hit_sound" // dont mind the meaty hit sounds if you hit something that isnt meaty + can_cleave = TRUE + embed_chance = 0 // let's not + + active_damage_force = 40 + + activation_sound = 'sound/weapons/hf_machete/hfmachete1.ogg' + deactivation_sound = 'sound/weapons/hf_machete/hfmachete0.ogg' + + active_weight_class = WEIGHT_CLASS_BULKY + inactive_weight_class = WEIGHT_CLASS_NORMAL + +/obj/item/melee/transforming/hfmachete/on_activate(datum/event_args/actor/actor, silent) + . = ..() + actor.chat_feedback( + SPAN_WARNING("You energize \the [src]."), + target = src, + ) + +/obj/item/melee/transforming/hfmachete/on_deactivate(datum/event_args/actor/actor, silent) + . = ..() + actor.chat_feedback( + SPAN_WARNING("You de-energize \the [src]."), + target = src, + ) + +/obj/item/melee/transforming/hfmachete/afterattack(atom/target, mob/user, clickchain_flags, list/params) + if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY)) + return + ..() + if(target) + if(istype(target,/obj/effect/plant)) + var/obj/effect/plant/P = target + P.die_off() diff --git a/code/game/objects/items/shield/shield.dm b/code/game/objects/items/shield/shield.dm new file mode 100644 index 00000000000..c55b07df859 --- /dev/null +++ b/code/game/objects/items/shield/shield.dm @@ -0,0 +1,17 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/datum/passive_parry/shield + parry_arc = 155 + parry_arc_round_down = TRUE + parry_frame = /datum/parry_frame/passive_block/shield + +/datum/parry_frame/passive_block/shield + parry_sfx = /datum/soundbyte/grouped/block_metal_with_metal + block_verb = "blocks" // full block for now + +/obj/item/shield + name = "shield" + passive_parry = /datum/passive_parry/shield{ + parry_chance_default = 50; + } diff --git a/code/game/objects/items/shield/types/shields_legacy.dm b/code/game/objects/items/shield/types/shields_legacy.dm new file mode 100644 index 00000000000..1b73e20897f --- /dev/null +++ b/code/game/objects/items/shield/types/shields_legacy.dm @@ -0,0 +1,204 @@ +/obj/item/shield/riot + name = "riot shield" + desc = "A shield adept for close quarters engagement. It's also capable of protecting from less powerful projectiles." + icon = 'icons/items/shields/basic.dmi' + icon_state = "riot" + slot_flags = SLOT_BACK + damage_force = 5 + throw_force = 5 + throw_speed = 1 + throw_range = 4 + w_class = WEIGHT_CLASS_BULKY + origin_tech = list(TECH_MATERIAL = 2) + materials_base = list(MAT_GLASS = 7500, MAT_STEEL = 1000) + attack_verb = list("shoved", "bashed") + worth_intrinsic = 300 + var/cooldown = 0 //shield bash cooldown. based on world.time + +/obj/item/shield/riot/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + if(istype(weapon, /obj/projectile)) + var/obj/projectile/proj = weapon + if(((is_sharp(proj) && proj.armor_penetration >= 10) || proj.damage_tier >= ARMOR_TIER_HIGH || istype(proj, /obj/projectile/beam)) && prob(50)) + //If we're at this point, the bullet/beam is going to go through the shield, however it will hit for less damage. + //Bullets get slowed down, while beams are diffused as they hit the shield, so these shields are not /completely/ + //useless. Extremely penetrating projectiles will go through the shield without less damage. + defending.visible_message("\The [defending]'s [src.name] is pierced by [proj]!") + proj.dampen_on_pierce_experimental(src, 20, ARMOR_TIER_HIGH) + playsound(src, /datum/soundbyte/grouped/block_metal_with_metal, 65, TRUE) + return null + return ..() + +/obj/item/shield/riot/attackby(obj/item/W as obj, mob/user as mob) + if(istype(W, /obj/item/melee/baton)) + if(cooldown < world.time - 25) + user.visible_message("[user] bashes [src] with [W]!") + playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1) + cooldown = world.time + else + ..() + +/obj/item/shield/riot/flash + name = "strobe shield" + desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." + icon_state = "riot-flash" + item_state = "riot-flash" + var/obj/item/flash/embedded_flash + var/flashfail = 0 + +/obj/item/shield/riot/flash/Initialize(mapload) + . = ..() + embedded_flash = new(src) + +/obj/item/shield/riot/flash/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) + if(user.a_intent == INTENT_HARM) + return ..() + return embedded_flash.attack_mob(arglist(args)) + +/obj/item/shield/riot/flash/attack_self(mob/user) + . = ..() + if(.) + return + . = embedded_flash.attack_self(user) + update_icon() + +/obj/item/shield/riot/flash/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + . = ..() + if(!.) + return + if(embedded_flash.broken) + return + if(!(attack_type & (ATTACK_TYPE_MELEE | ATTACK_TYPE_UNARMED))) + return + // var/datum/event_args/actor/clickchain/clickchain = shieldcall_args[SHIELDCALL_ARG_CLICKCHAIN] + // var/mob/attacker = clickchain?.performer + // if(attacker) + // log_attack(key_name(attacker), key_name(defending), "flash shield auto-invoke") + // embedded_flash.melee_interaction_chain(attacker, defending) + // else + log_attack(key_name(defending), "none (AoE)", "flash shield auto-invoke") + embedded_flash.attack_self(defending) + update_icon() + +/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) + if(istype(W, /obj/item/flash)) + var/obj/item/flash/flash = W + if(flashfail) + to_chat(user, "No sense replacing it with a broken bulb!") + return + else + to_chat(user, "You begin to replace the bulb...") + if(do_after(user, 20, target = user)) + if(flashfail || !flash || QDELETED(flash)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) + qdel(embedded_flash) + embedded_flash = flash + flash.forceMove(src) + update_icon() + return + ..() + +/obj/item/shield/riot/flash/emp_act(severity) + . = ..() + embedded_flash.emp_act(severity) + update_icon() + +/obj/item/shield/riot/flash/update_icon_state() + . = ..() + if(!embedded_flash || embedded_flash.broken) + icon_state = "riot" + item_state = "riot" + else + icon_state = "flashshield" + item_state = "flashshield" + +/obj/item/shield/riot/flash/examine(mob/user, dist) + . = ..() + if (embedded_flash?.broken) + . += "The mounted bulb has burnt out. You can try replacing it with a new one." + +/obj/item/shield/makeshift + name = "metal shield" + desc = "A large shield made of wired and welded sheets of metal. The handle is made of cloth and leather, making it unwieldy." + icon = 'icons/obj/weapons.dmi' + icon_state = "makeshift" + inhand_state = "metal" + slot_flags = null + damage_force = 10 + throw_force = 7 + +/obj/item/shield/riot/tower + name = "tower shield" + desc = "An immense tower shield. Designed to ensure maximum protection to the user, at the expense of mobility." + item_state = "metal" + damage_force = 16 + encumbrance = ITEM_ENCUMBRANCE_SHIELD_TOWER + throw_force = 15 //Massive piece of metal + w_class = WEIGHT_CLASS_HUGE + +/obj/item/shield/riot/tower/swat + name = "swat shield" + +/obj/item/shield/riot/energy_proof + name = "energy resistant shield" + desc = "An ablative shield designed to absorb and disperse energy attacks. This comes at significant cost to its ability to withstand ballistics and kinetics, breaking apart easily." + icon_state = "riot-laser" + +/obj/item/shield/riot/kinetic_proof + name = "kinetic resistant shield" + desc = "A polymer and ceramic shield designed to absorb ballistic projectiles and kinetic force. It doesn't do very well into energy attacks, especially from weapons that inflict burns." + icon_state = "riot-bullet" + +//Exotics/Costume Shields +/obj/item/shield/riot/roman + name = "scutum" + desc = "A replica shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles." + icon = 'icons/obj/weapons.dmi' + icon_state = "roman" + slot_flags = SLOT_BACK + materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000) + +/obj/item/shield/fluff/roman + name = "replica scutum" + desc = "A replica shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more." + icon = 'icons/obj/weapons.dmi' + icon_state = "roman" + slot_flags = SLOT_BACK + damage_force = 5.0 + throw_force = 5.0 + throw_speed = 2 + throw_range = 6 + +/obj/item/shield/riot/buckler + name = "buckler" + desc = "A wrist mounted round shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles." + icon = 'icons/obj/weapons.dmi' + icon_state = "buckler" + slot_flags = SLOT_BACK | SLOT_BELT + materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000) + +/obj/item/shield/riot/foam + name = "foam riot shield" + desc = "A shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more." + icon = 'icons/obj/weapons.dmi' + icon_state = "foam" + slot_flags = SLOT_BACK + damage_force = 0 + throw_force = 0 + throw_speed = 2 + throw_range = 6 + materials_base = list(MAT_PLASTIC = 7500, "foam" = 1000) + +/obj/item/shield/riot/foam/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + var/allowed = FALSE + if(isobj(weapon)) + var/obj/casted_object = weapon + if(istype(casted_object, /obj/projectile)) + var/obj/projectile/casted_projectile = casted_object + if(istype(casted_projectile, /obj/projectile/bullet/reusable/foam)) + allowed = TRUE + else if(casted_object.get_primary_material_id() == /datum/material/toy_foam::id) + allowed = TRUE + if(!allowed) + return + return ..() diff --git a/code/game/objects/items/shield/types/shields_legacy_vr.dm b/code/game/objects/items/shield/types/shields_legacy_vr.dm new file mode 100644 index 00000000000..d0d00bba9d9 --- /dev/null +++ b/code/game/objects/items/shield/types/shields_legacy_vr.dm @@ -0,0 +1,14 @@ + +/obj/item/shield/fluff/wolfgirlshield + name = "Autumn Shield" + desc = "A shiny silvery shield with a large red leaf symbol in the center." + icon = 'icons/obj/weapons_vr.dmi' + icon_state = "wolfgirlshield" + slot_flags = SLOT_BACK | SLOT_OCLOTHING + damage_force = 5.0 + throw_force = 5.0 + throw_speed = 2 + throw_range = 6 + item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', SLOT_ID_BACK = 'icons/vore/custom_items_vr.dmi', SLOT_ID_SUIT = 'icons/vore/custom_items_vr.dmi') + attack_verb = list("shoved", "bashed") + allowed = list(/obj/item/melee/fluffstuff/wolfgirlsword) diff --git a/code/game/objects/items/shield/types/transforming.dm b/code/game/objects/items/shield/types/transforming.dm new file mode 100644 index 00000000000..d60a1880540 --- /dev/null +++ b/code/game/objects/items/shield/types/transforming.dm @@ -0,0 +1,110 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/** + * toggleable shields, like energy combat shields and telescoping shields + */ +/obj/item/shield/transforming + /// are we active? + var/active = FALSE + /// when active, do we use an overlay instead of an icon state? + /// + /// * applies to regular overlay + /// * applies to worn overlay as well + var/active_via_overlay = FALSE + + var/active_weight_class = WEIGHT_CLASS_BULKY + var/inactive_weight_class + var/active_weight_volume + var/inactive_weight_volume + + var/active_damage_force + var/inactive_damage_force + + /// activation sound; also deactivation if it's not specified + var/activation_sound = 'sound/weapons/empty.ogg' + var/deactivation_sound + /// sound volume + var/toggle_sound_volume = 50 + +/obj/item/shield/transforming/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + if(!active) + return // cancel + return ..() + +/obj/item/shield/transforming/update_icon_state() + icon_state = "[initial(icon_state)][active && !active_via_overlay ? "-active" : ""]" + return ..() + +/obj/item/shield/transforming/update_overlays() + . = ..() + if(!active || !active_via_overlay) + return + . += build_active_overlay() + +/obj/item/shield/transforming/proc/build_active_overlay() + RETURN_TYPE(/image) + var/image/creating = image(icon, "[base_icon_state || icon_state]-active") + return creating + +/obj/item/shield/transforming/proc/build_active_worn_overlay(worn_state) + RETURN_TYPE(/image) + var/image/creating = image(icon, "[worn_state]-active") + return creating + +/obj/item/shield/transforming/render_apply_overlays(mutable_appearance/MA, bodytype, inhands, datum/inventory_slot/slot_meta, icon_used) + . = ..() + if(active_via_overlay && active) + MA.overlays += build_active_worn_overlay(MA.icon_state) + +/obj/item/shield/transforming/on_attack_self(datum/event_args/actor/e_args) + . = ..() + if(.) + return + add_fingerprint(e_args.performer) + toggle(e_args) + return CLICKCHAIN_DO_NOT_PROPAGATE + +/** + * actor can be /datum/event_args/actor or a single mob. + */ +/obj/item/shield/transforming/proc/toggle(datum/event_args/actor/actor, silent) + active = !active + if(active) + on_activate(actor, silent) + else + on_deactivate(actor, silent) + update_icon() + update_worn_icon() + +/** + * actor can be /datum/event_args/actor or a single mob. + */ +/obj/item/shield/transforming/proc/on_activate(datum/event_args/actor/actor, silent) + E_ARGS_WRAP_USER_TO_ACTOR(actor) + + damage_force = VALUE_OR_DEFAULT(active_damage_force, initial(damage_force)) + + set_weight_class(VALUE_OR_DEFAULT(active_weight_class, initial(w_class))) + set_weight_volume(VALUE_OR_DEFAULT(active_weight_volume, initial(weight_volume))) + + if(!silent && activation_sound) + playsound(src, activation_sound, toggle_sound_volume, TRUE) + + // todo: logging + +/** + * actor can be /datum/event_args/actor or a single mob. + */ +/obj/item/shield/transforming/proc/on_deactivate(datum/event_args/actor/actor, silent) + E_ARGS_WRAP_USER_TO_ACTOR(actor) + + damage_force = VALUE_OR_DEFAULT(inactive_damage_force, initial(damage_force)) + + set_weight_class(VALUE_OR_DEFAULT(inactive_weight_class, initial(w_class))) + set_weight_volume(VALUE_OR_DEFAULT(inactive_weight_volume, initial(weight_volume))) + + if(!silent && (activation_sound || deactivation_sound)) + playsound(src, deactivation_sound || activation_sound, toggle_sound_volume, TRUE) + + // todo: logging diff --git a/code/game/objects/items/shield/types/transforming/energy.dm b/code/game/objects/items/shield/types/transforming/energy.dm new file mode 100644 index 00000000000..143665070c6 --- /dev/null +++ b/code/game/objects/items/shield/types/transforming/energy.dm @@ -0,0 +1,111 @@ +/obj/item/shield/transforming/energy + name = "energy combat shield" + desc = "A shield capable of stopping most projectile and melee attacks. It can be retracted, expanded, and stored anywhere." + icon = 'icons/items/shields/transforming.dmi' + icon_state = "energy" + base_icon_state = "energy" + slot_flags = SLOT_EARS + atom_flags = NOCONDUCT + + damage_force = 3 + active_damage_force = 10 + + w_class = WEIGHT_CLASS_TINY + active_weight_class = WEIGHT_CLASS_BULKY + + throw_force = 5.0 + throw_speed = 1 + throw_range = 4 + + passive_parry = /datum/passive_parry/shield{ + parry_chance_default = 50; + parry_frame = /datum/parry_frame/passive_block/energy; + } + + active_via_overlay = TRUE + + var/lrange = 1.5 + var/lpower = 1.5 + var/lcolor = "#006AFF" + + origin_tech = list(TECH_MATERIAL = 4, TECH_MAGNET = 3, TECH_ILLEGAL = 4) + attack_verb = list("shoved", "bashed") + + /// drop projectiles sometimes? + var/legacy_projectile_damage_drop = TRUE + /// divisor to projectile damage before we drop the hit + var/legacy_projectile_damage_drop_divisor = 1.6 + + activation_sound = 'sound/weapons/saberon.ogg' + deactivation_sound = 'sound/weapons/saberoff.ogg' + +/obj/item/shield/transforming/energy/passive_parry_intercept(mob/defending, attack_type, datum/weapon, datum/passive_parry/parry_data) + if(istype(weapon, /obj/projectile)) + var/obj/projectile/casted_projectile = weapon + if(legacy_projectile_damage_drop) + if((is_sharp(casted_projectile) && casted_projectile.damage > 10) || (casted_projectile.projectile_type & PROJECTILE_TYPE_BEAM)) + if(prob(casted_projectile.damage / legacy_projectile_damage_drop_divisor)) + return // drop the shield + . = ..() + if(!.) + return + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() + spark_system.set_up(3, 0, defending.loc) + spark_system.start() + +/obj/item/shield/transforming/energy/on_activate(datum/event_args/actor/actor, silent) + . = ..() + slot_flags = NONE + if(!silent) + actor.chat_feedback( + SPAN_WARNING("You activate \the [src]."), + target = src, + ) + set_light(lrange, lpower, lcolor) + +/obj/item/shield/transforming/energy/on_deactivate(datum/event_args/actor/actor, silent) + . = ..() + slot_flags = SLOT_EARS + if(!silent) + actor.chat_feedback( + SPAN_WARNING("You collapse \the [src]."), + target = src, + ) + set_light(0) + +/obj/item/shield/transforming/energy/build_active_overlay() + var/image/built = ..() + if(lcolor) + built.color = lcolor + return built + +/obj/item/shield/transforming/energy/build_active_worn_overlay() + var/image/built = ..() + if(lcolor) + built.color = lcolor + return built + +// todo: legacy below + +/obj/item/shield/transforming/energy/AltClick(mob/living/user) + if(!in_range(src, user)) //Basic checks to prevent abuse + return + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + return + if(alert("Are you sure you want to recolor your shield?", "Confirm Recolor", "Yes", "No") == "Yes") + var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null + if(energy_color_input) + lcolor = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) + update_icon() + +/obj/item/shield/transforming/energy/examine(mob/user, dist) + . = ..() + . += "Alt-click to recolor it." + +/obj/item/shield/transforming/energy/imperial + name = "imperial shield" + desc = "What the hell is this?" + desc = "It's really easy to mispronounce the name of this shield if you've only read it in books." + icon_state = "imperial_shield" + base_icon_state = "imperial_shield" diff --git a/code/game/objects/items/shield/types/transforming/telescopic.dm b/code/game/objects/items/shield/types/transforming/telescopic.dm new file mode 100644 index 00000000000..76e96d790f6 --- /dev/null +++ b/code/game/objects/items/shield/types/transforming/telescopic.dm @@ -0,0 +1,37 @@ +/obj/item/shield/transforming/telescopic + name = "telescopic shield" + desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." + icon = 'icons/items/shields/transforming.dmi' + icon_state = "telescopic" + base_icon_state = "telescopic" + slot_flags = NONE + + damage_force = 5 + active_damage_force = 10 + inactive_damage_force = 5 + + w_class = WEIGHT_CLASS_NORMAL + active_weight_class = WEIGHT_CLASS_BULKY + inactive_weight_class = WEIGHT_CLASS_NORMAL + + activation_sound = 'sound/weapons/empty.ogg' + + throw_force = 5 + throw_speed = 3 + throw_range = 5 + +/obj/item/shield/transforming/telescopic/on_activate(datum/event_args/actor/actor, silent) + . = ..() + slot_flags = SLOT_BACK + actor.chat_feedback( + SPAN_WARNING("You extend \the [src]."), + target = src, + ) + +/obj/item/shield/transforming/telescopic/on_deactivate(datum/event_args/actor/actor, silent) + . = ..() + slot_flags = NONE + actor.chat_feedback( + SPAN_WARNING("You collapse \the [src]."), + target = src, + ) diff --git a/code/game/objects/items/shield_projector/shield_matrix.dm b/code/game/objects/items/shield_projector/shield_matrix.dm new file mode 100644 index 00000000000..eee6c5f280c --- /dev/null +++ b/code/game/objects/items/shield_projector/shield_matrix.dm @@ -0,0 +1,79 @@ +// todo: /obj/effect/shield_matrix +// This is the actual shield. The projector is a different item. +/obj/effect/directional_shield + name = "directional combat shield" + desc = "A wide shield, which has the property to block incoming projectiles but allow outgoing projectiles to pass it. \ + Slower moving objects are not blocked, so people can walk in and out of the barrier, and things can be thrown into and out \ + of it." + icon = 'icons/effects/effects.dmi' + icon_state = "directional_shield" + density = FALSE // People can move pass these shields. + opacity = FALSE + anchored = TRUE + integrity_flags = INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF + layer = MOB_LAYER + 0.1 + mouse_opacity = FALSE + var/obj/item/shield_projector/projector = null // The thing creating the shield. + var/x_offset = 0 // Offset from the 'center' of where the projector is, so that if it moves, the shield can recalc its position. + var/y_offset = 0 // Ditto. + +/obj/effect/directional_shield/New(var/newloc, var/new_projector) + if(new_projector) + projector = new_projector + var/turf/us = get_turf(src) + var/turf/them = get_turf(projector) + if(them) + x_offset = us.x - them.x + y_offset = us.y - them.y + else + update_color() + ..(newloc) + +/obj/effect/directional_shield/proc/relocate() + if(!projector) + return // Nothing to follow. + var/turf/T = get_turf(projector) + if(!T) + return + var/turf/new_pos = locate(T.x + x_offset, T.y + y_offset, T.z) + if(new_pos) + forceMove(new_pos) + else + qdel(src) + +/obj/effect/directional_shield/proc/update_color(var/new_color) + if(!projector) + color = "#0099FF" + else + animate(src, color = new_color, 5) +// color = new_color + +/obj/effect/directional_shield/Destroy() + if(projector) + projector.active_shields -= src + projector = null + return ..() + +/obj/effect/directional_shield/CanPass(atom/movable/mover, turf/target) + . = ..() + if(istype(mover, /obj/projectile)) + var/obj/projectile/P = mover + if(P.projectile_type & PROJECTILE_TYPE_TRACE) + return TRUE + if(check_defensive_arc_tile(src, P, 90, null, dir)) + return FALSE + return TRUE + +/obj/effect/directional_shield/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + impact_flags &= ~PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + adjust_health(-proj.get_structure_damage()) + playsound(src, 'sound/effects/EMPulse.ogg', 75, 1) + +// All the shields tied to their projector are one 'unit', and don't have individualized health values like most other shields. +/obj/effect/directional_shield/proc/adjust_health(amount) + if(projector) + projector.adjust_health(amount) // Projector will kill the shield if needed. + // If the shield lacks a projector, then it was probably spawned in by an admin for bus, so it's indestructable. diff --git a/code/modules/shieldgen/directional_shield.dm b/code/game/objects/items/shield_projector/shield_projector.dm similarity index 81% rename from code/modules/shieldgen/directional_shield.dm rename to code/game/objects/items/shield_projector/shield_projector.dm index fd1bc9df0f6..e9b3547cf28 100644 --- a/code/modules/shieldgen/directional_shield.dm +++ b/code/game/objects/items/shield_projector/shield_projector.dm @@ -1,81 +1,3 @@ -// This is the actual shield. The projector is a different item. -/obj/effect/directional_shield - name = "directional combat shield" - desc = "A wide shield, which has the property to block incoming projectiles but allow outgoing projectiles to pass it. \ - Slower moving objects are not blocked, so people can walk in and out of the barrier, and things can be thrown into and out \ - of it." - icon = 'icons/effects/effects.dmi' - icon_state = "directional_shield" - density = FALSE // People can move pass these shields. - opacity = FALSE - anchored = TRUE - integrity_flags = INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF - layer = MOB_LAYER + 0.1 - mouse_opacity = FALSE - var/obj/item/shield_projector/projector = null // The thing creating the shield. - var/x_offset = 0 // Offset from the 'center' of where the projector is, so that if it moves, the shield can recalc its position. - var/y_offset = 0 // Ditto. - -/obj/effect/directional_shield/New(var/newloc, var/new_projector) - if(new_projector) - projector = new_projector - var/turf/us = get_turf(src) - var/turf/them = get_turf(projector) - if(them) - x_offset = us.x - them.x - y_offset = us.y - them.y - else - update_color() - ..(newloc) - -/obj/effect/directional_shield/proc/relocate() - if(!projector) - return // Nothing to follow. - var/turf/T = get_turf(projector) - if(!T) - return - var/turf/new_pos = locate(T.x + x_offset, T.y + y_offset, T.z) - if(new_pos) - forceMove(new_pos) - else - qdel(src) - -/obj/effect/directional_shield/proc/update_color(var/new_color) - if(!projector) - color = "#0099FF" - else - animate(src, color = new_color, 5) -// color = new_color - -/obj/effect/directional_shield/Destroy() - if(projector) - projector.active_shields -= src - projector = null - return ..() - -/obj/effect/directional_shield/CanPass(atom/movable/mover, turf/target) - . = ..() - if(istype(mover, /obj/projectile)) - var/obj/projectile/P = mover - if(istype(P, /obj/projectile/test)) // Turrets need to try to kill the shield and so their test bullet needs to penetrate. - return TRUE - - var/bad_arc = global.reverse_dir[dir] // Arc of directions from which we cannot block. - if(check_shield_arc(src, bad_arc, P)) // This is actually for mobs but it will work for our purposes as well. - return FALSE - return TRUE - -/obj/effect/directional_shield/bullet_act(var/obj/projectile/P) - adjust_health(-P.get_structure_damage()) - P.on_hit() - playsound(src, 'sound/effects/EMPulse.ogg', 75, 1) - -// All the shields tied to their projector are one 'unit', and don't have individualized health values like most other shields. -/obj/effect/directional_shield/proc/adjust_health(amount) - if(projector) - projector.adjust_health(amount) // Projector will kill the shield if needed. - // If the shield lacks a projector, then it was probably spawned in by an admin for bus, so it's indestructable. - // This actually creates the shields. It's an item so that it can be carried, but it could also be placed inside a stationary object if desired. // It should work inside the contents of any mob. diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 0d19a6ecb53..3f2ad6ff22e 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -75,12 +75,14 @@ desc = "A shooting target that looks vaguely human shaped but not enough to cause controversy." hp = 1800 // alium onest too kinda -/obj/item/target/bullet_act(var/obj/projectile/Proj) - var/p_x = Proj.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset Proj.p_x!" - var/p_y = Proj.p_y + pick(0,0,0,0,0,-1,1) +/obj/item/target/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + + var/p_x = proj.p_x + pick(0,0,0,0,0,-1,1) // really ugly way of coding "sometimes offset proj.p_x!" + var/p_y = proj.p_y + pick(0,0,0,0,0,-1,1) var/decaltype = 1 // 1 - scorch, 2 - bullet - if(istype(/obj/projectile/bullet, Proj)) + if(istype(/obj/projectile/bullet, proj)) decaltype = 2 @@ -88,7 +90,7 @@ if( virtualIcon.GetPixel(p_x, p_y) ) // if the located pixel isn't blank (null) - hp -= Proj.damage + hp -= proj.damage if(hp <= 0) for(var/mob/O in oviewers()) if ((O.client && !( O.has_status_effect(/datum/status_effect/sight/blindness) ))) @@ -109,7 +111,7 @@ bmark.pixel_x-- bmark.pixel_y-- - if(Proj.damage >= 20 || istype(Proj, /obj/projectile/beam/practice)) + if(proj.damage >= 20 || istype(proj, /obj/projectile/beam/practice)) bmark.icon_state = "scorch" bmark.setDir(pick(NORTH,SOUTH,EAST,WEST)) // random scorch design @@ -121,12 +123,12 @@ // Bullets are hard. They make dents! bmark.icon_state = "dent" - if(Proj.damage >= 10 && bulletholes.len <= 35) // maximum of 35 bullet holes + if(proj.damage >= 10 && bulletholes.len <= 35) // maximum of 35 bullet holes if(decaltype == 2) // bullet - if(prob(Proj.damage+30)) // bullets make holes more commonly! + if(prob(proj.damage+30)) // bullets make holes more commonly! new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole else // Lasers! - if(prob(Proj.damage-10)) // lasers make holes less commonly + if(prob(proj.damage-10)) // lasers make holes less commonly new/datum/bullethole(src, bmark.pixel_x, bmark.pixel_y) // create new bullet hole // draw bullet holes @@ -141,8 +143,7 @@ return - return PROJECTILE_CONTINUE // the bullet/projectile goes through the target! - + return . | PROJECTILE_IMPACT_PASSTHROUGH // Small memory holder entity for transparent bullet holes /datum/bullethole diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index aecfe62bf7f..2e9fea68495 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -262,8 +262,8 @@ /obj/item/cell/device/weapon, /obj/item/material/butterfly, /obj/item/material/knife, - /obj/item/melee/energy/sword, - /obj/item/shield/energy, + /obj/item/melee/transforming/energy/sword, + /obj/item/shield/transforming/energy, /obj/item/ammo_casing/, /obj/item/ammo_magazine/, /obj/item/storage/box/beanbags, diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index 558830cbd48..98673ed4e5c 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -36,7 +36,7 @@ return else to_chat(user, "Access Denied") - else if(istype(W, /obj/item/melee/energy/blade)) + else if(istype(W, /obj/item/melee/ninja_energy_blade)) if(emag_act(INFINITY, user, W, "The locker has been sliced open by [user] with an energy blade!", "You hear metal being sliced and sparks flying.")) var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() spark_system.set_up(5, 0, src.loc) diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm index ee732cef7fa..1c4f05bca68 100644 --- a/code/game/objects/items/storage/secure.dm +++ b/code/game/objects/items/storage/secure.dm @@ -33,7 +33,7 @@ /obj/item/storage/secure/attackby(obj/item/W as obj, mob/user as mob) if(locked) - if (istype(W, /obj/item/melee/energy/blade) && emag_act(INFINITY, user, "You slice through the lock of \the [src]")) + if (istype(W, /obj/item/melee/ninja_energy_blade) && emag_act(INFINITY, user, "You slice through the lock of \the [src]")) var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() spark_system.set_up(5, 0, src.loc) spark_system.start() diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index 710acf38d92..03498f3fb1e 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -28,7 +28,7 @@ new /obj/item/plastique(src) if("murder") - new /obj/item/melee/energy/sword(src) + new /obj/item/melee/transforming/energy/sword(src) new /obj/item/clothing/glasses/thermal/syndi(src) new /obj/item/card/emag(src) new /obj/item/clothing/shoes/syndigaloshes(src) diff --git a/code/game/objects/items/tools/switchtool.dm b/code/game/objects/items/tools/switchtool.dm index c6d9b2bd754..ae87558fd9e 100644 --- a/code/game/objects/items/tools/switchtool.dm +++ b/code/game/objects/items/tools/switchtool.dm @@ -169,11 +169,6 @@ /obj/item/switchtool/proc/get_switchtool_enum(obj/item/I) return tools[I] -/obj/item/switchtool/handle_shield(mob/user) - if(get_switchtool_enum(deployed) == SWITCHTOOL_SHIELD) - return TRUE - return FALSE - /obj/item/switchtool/update_overlays() . = ..() if(!deployed) @@ -344,8 +339,8 @@ /obj/item/multitool/holoswitch = SWITCHTOOL_MULTITOOL, /obj/item/flashlight/holoswitch = SWITCHTOOL_LIGHT, /obj/item/soap/holoswitch = SWITCHTOOL_SOAP, - /obj/item/melee/energy/sword/holoswitch = SWITCHTOOL_SWORD, - /obj/item/shield/holoswitch = SWITCHTOOL_SHIELD + /obj/item/melee/transforming/energy/sword/holoswitch = SWITCHTOOL_SWORD, + // /obj/item/shield/holoswitch = SWITCHTOOL_SHIELD ) tool_functions = list( TOOL_SCALPEL, @@ -484,7 +479,7 @@ desc = "This should not exist." tool_speed = 0.9 -/obj/item/melee/energy/sword/holoswitch +/obj/item/melee/transforming/energy/sword/holoswitch name = "hardlight blade" desc = "This should not exist." diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm index 630b93d5e7c..58db3effd42 100644 --- a/code/game/objects/items/weapons/cigs_lighters.dm +++ b/code/game/objects/items/weapons/cigs_lighters.dm @@ -289,8 +289,8 @@ CIGARETTE PACKETS ARE IN FANCY.DM /obj/item/clothing/mask/smokable/cigarette/attackby(obj/item/W as obj, mob/user as mob) ..() - if(istype(W, /obj/item/melee/energy/sword)) - var/obj/item/melee/energy/sword/S = W + if(istype(W, /obj/item/melee/transforming/energy/sword)) + var/obj/item/melee/transforming/energy/sword/S = W if(S.active) light("[user] swings their [W], barely missing their nose. They light their [name] in the process.") @@ -464,7 +464,7 @@ CIGARETTE PACKETS ARE IN FANCY.DM quench() /obj/item/clothing/mask/smokable/pipe/attackby(obj/item/W as obj, mob/user as mob) - if(istype(W, /obj/item/melee/energy/sword)) + if(istype(W, /obj/item/melee/transforming/energy/sword)) return ..() diff --git a/code/game/objects/items/weapons/grenades/concussion.dm b/code/game/objects/items/weapons/grenades/concussion.dm index b8d5da08343..c3ea4db24d4 100644 --- a/code/game/objects/items/weapons/grenades/concussion.dm +++ b/code/game/objects/items/weapons/grenades/concussion.dm @@ -84,5 +84,5 @@ var/turf/O = get_turf(src) if(!O) return - src.fragmentate(O, num_fragments, spread_range, fragment_types) + shrapnel_explosion(num_fragments, spread_range, fragment_types) ..() diff --git a/code/game/objects/items/weapons/grenades/explosive.dm b/code/game/objects/items/weapons/grenades/explosive.dm index 0eae29d84cc..c750cac6162 100644 --- a/code/game/objects/items/weapons/grenades/explosive.dm +++ b/code/game/objects/items/weapons/grenades/explosive.dm @@ -21,7 +21,7 @@ if(explosion_size) on_explosion(O) - src.fragmentate(O, num_fragments, spread_range, fragment_types) + shrapnel_explosion(num_fragments, spread_range, fragment_types) qdel(src) /obj/item/grenade/explosive/proc/on_explosion(var/turf/O) @@ -38,34 +38,6 @@ fragment_types = list(/obj/projectile/bullet/pellet/fragment) num_fragments = 200 //total number of fragments produced by the grenade - - -/obj/proc/fragmentate(var/turf/T=get_turf(src), var/fragment_number = 30, var/spreading_range = 5, var/list/fragtypes=list(/obj/projectile/bullet/pellet/fragment/)) - set waitfor = 0 - var/list/target_turfs = getcircle(T, spreading_range) - var/fragments_per_projectile = round(fragment_number/target_turfs.len) - - for(var/turf/O in target_turfs) - sleep(0) - var/fragment_type = pickweight(fragtypes) - var/obj/projectile/bullet/pellet/fragment/P = new fragment_type(T) - P.pellets = fragments_per_projectile - P.shot_from = name - - P.old_style_target(O) - P.fire() - - //Make sure to hit any mobs in the source turf - for(var/mob/living/M in T) - //lying on a frag grenade while the grenade is on the ground causes you to absorb most of the shrapnel. - //you will most likely be dead, but others nearby will be spared the fragments that hit you instead. - if(M.lying && isturf(src.loc)) - P.projectile_attack_mob(M, 0, 5) - else if(!M.lying && src.loc != get_turf(src)) //if it's not on the turf, it must be in the mob! - P.projectile_attack_mob(M, 0, 25) //you're holding a grenade, dude! - else - P.projectile_attack_mob(M, 0, 100) //otherwise, allow a decent amount of fragments to pass - /obj/item/grenade/explosive/mini name = "mini fragmentation grenade" desc = "A miniaturized fragmentation grenade, this one poses relatively little threat on its own." diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index 7018d5f8d96..1a447da35f2 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -110,7 +110,7 @@ var/turf/O = get_turf(src) if(!O) return - src.fragmentate(O, num_fragments, spread_range, fragment_types) + shrapnel_explosion(num_fragments, spread_range, fragment_types) ..() /obj/item/grenade/flashbang/stingbang/shredbang diff --git a/code/game/objects/items/weapons/grenades/projectile.dm b/code/game/objects/items/weapons/grenades/projectile.dm index 3b781ef4611..2621d12150b 100644 --- a/code/game/objects/items/weapons/grenades/projectile.dm +++ b/code/game/objects/items/weapons/grenades/projectile.dm @@ -7,6 +7,7 @@ //The radius of the circle used to launch projectiles. Lower values mean less projectiles are used but if set too low gaps may appear in the spread pattern var/spread_range = 7 + var/total_pellets = 1 // default value of 1 just forces one per turf as we round up loadable = FALSE @@ -17,12 +18,9 @@ if(!O) return - src.launch_many_projectiles(O, spread_range, projectile_types) - + shrapnel_explosion(total_pellets, spread_range, projectile_types) qdel(src) - - /obj/item/grenade/shooter/rubber name = "rubber pellet grenade" desc = "An anti-riot grenade that fires a cloud of rubber projectiles upon detonation." @@ -47,30 +45,3 @@ /obj/item/grenade/shooter/energy/tesla name = "tesla grenade" projectile_types = list(/obj/projectile/beam/chain_lightning/lesser) - - -// This is just fragmentate, but less specific. Don't know how to make either of them less awful, at the moment -/obj/proc/launch_many_projectiles(var/turf/T=get_turf(src), var/spreading_range = 5, var/list/projectiletypes=list(/obj/projectile/bullet/pistol/rubber)) - set waitfor = 0 - var/list/target_turfs = getcircle(T, spreading_range) - - for(var/turf/O in target_turfs) - sleep(0) - var/shot_type = pick(projectiletypes) - - var/obj/projectile/P = new shot_type(T) - P.shot_from = src.name - - P.old_style_target(O) - P.fire() - - //Make sure to hit any mobs in the source turf - for(var/mob/living/M in T) - //lying on a frag grenade while the grenade is on the ground causes you to absorb most of the shrapnel. - //you will most likely be dead, but others nearby will be spared the fragments that hit you instead. - if(M.lying && isturf(src.loc)) - P.projectile_attack_mob(M, 0, 5) - else if(!M.lying && src.loc != get_turf(src)) //if it's not on the turf, it must be in the mob! - P.projectile_attack_mob(M, 0, 25) //you're holding a grenade, dude! - else - P.projectile_attack_mob(M, 0, 100) //otherwise, allow a decent amount of fragments to pass diff --git a/code/game/objects/items/weapons/material/knives.dm b/code/game/objects/items/weapons/material/knives.dm index 2c7c409f1a7..a7fa5162c08 100644 --- a/code/game/objects/items/weapons/material/knives.dm +++ b/code/game/objects/items/weapons/material/knives.dm @@ -171,12 +171,10 @@ item_state = "armblade" slot_flags = NONE -/obj/item/material/knife/machete/armblade/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(default_parry_check(user, attacker, damage_source) && prob(33)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return TRUE - return FALSE + passive_parry = /datum/passive_parry{ + parry_chance_projectile = 0; + parry_chance_default = 33; + } /obj/item/material/knife/machete/armblade/hardsuit var/obj/item/hardsuit_module/armblade/storing_module diff --git a/code/game/objects/items/weapons/material/material.dm b/code/game/objects/items/weapons/material/material.dm index f1ebf5f3b95..c3394a87aea 100644 --- a/code/game/objects/items/weapons/material/material.dm +++ b/code/game/objects/items/weapons/material/material.dm @@ -36,6 +36,7 @@ // var/dulled_divisor = 0.1 //Just drops the damage to a tenth // var/drops_debris = 1 + /obj/item/material/Initialize(mapload, material) if(!isnull(material)) material_parts = material diff --git a/code/game/objects/items/weapons/material/swords.dm b/code/game/objects/items/weapons/material/swords.dm index 0b4cdc519d4..b29f86dd53b 100644 --- a/code/game/objects/items/weapons/material/swords.dm +++ b/code/game/objects/items/weapons/material/swords.dm @@ -11,19 +11,17 @@ pickup_sound = 'sound/items/pickup/sword.ogg' force_multiplier = 1.5 + passive_parry = /datum/passive_parry/melee{ + parry_chance_default = 50; + parry_chance_projectile = 10; + } + /obj/item/material/sword/plasteel material_parts = /datum/material/plasteel /obj/item/material/sword/durasteel material_parts = /datum/material/durasteel -/obj/item/material/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(unique_parry_check(user, attacker, damage_source) && prob(50)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 - /obj/item/material/sword/suicide_act(mob/user) var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()] viewers(user) << "[user] is falling on the [src.name]! It looks like [TU.he] [TU.is] trying to commit suicide." @@ -63,31 +61,13 @@ SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', ) -/obj/item/material/sword/sabre/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(default_parry_check(user, attacker, damage_source) && prob(60)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - - playsound(user.loc, 'sound/items/drop/knife.ogg', 50, 1) - return 1 - if(unique_parry_check(user, attacker, damage_source) && prob(50)) - user.visible_message("\The [user] deflects [attack_text] with \the [src]!") - - playsound(user.loc, 'sound/weapons/plasma_cutter.ogg', 50, 1) - return 1 - - return 0 - -/obj/item/material/sword/sabre/unique_parry_check(mob/user, mob/attacker, atom/damage_source) - if(user.incapacitated() || !istype(damage_source, /obj/projectile/)) - return 0 - - var/bad_arc = global.reverse_dir[user.dir] - if(!check_shield_arc(user, bad_arc, damage_source, attacker)) - return 0 - - return 1 + passive_parry = /datum/passive_parry/melee{ + parry_chance_default = 50; + } -//meant to play when unsheathing the blade from the sabre sheath. +// meant to play when unsheathing the blade from the sabre sheath. +// todo: -_- this should be on the sheath +// todo: we need a better way to do unsheath sounds for weapons and storage... /obj/item/material/sword/sabre/on_enter_storage(datum/object_system/storage/storage) . = ..() playsound(loc, 'sound/effects/holster/sheathin.ogg', 50, 1) diff --git a/code/game/objects/items/weapons/material/twohanded.dm b/code/game/objects/items/weapons/material/twohanded.dm index 4e615154fed..16f3fb71cd2 100644 --- a/code/game/objects/items/weapons/material/twohanded.dm +++ b/code/game/objects/items/weapons/material/twohanded.dm @@ -28,6 +28,10 @@ drop_sound = 'sound/items/drop/sword.ogg' pickup_sound = 'sound/items/pickup/sword.ogg' + passive_parry = /datum/passive_parry/melee{ + parry_chance_melee = 15; + } + /obj/item/material/twohanded/update_held_icon() var/mob/living/M = loc if(istype(M) && M.can_wield_item(src) && is_held_twohanded(M)) @@ -50,14 +54,6 @@ . = ..() update_icon() -//Allow a small chance of parrying melee attacks when wielded - maybe generalize this to other weapons someday -/obj/item/material/twohanded/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(wielded && default_parry_check(user, attacker, damage_source) && prob(15)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 - /obj/item/material/twohanded/update_icon() icon_state = "[base_icon][wielded]" item_state = icon_state diff --git a/code/game/objects/items/weapons/melee/deflect.dm b/code/game/objects/items/weapons/melee/deflect.dm deleted file mode 100644 index e951f4d5c5a..00000000000 --- a/code/game/objects/items/weapons/melee/deflect.dm +++ /dev/null @@ -1,31 +0,0 @@ -/* - * The home of basic deflect / defense code. - */ - -/obj/item/melee - var/defend_chance = 5 // The base chance for the weapon to parry. - var/projectile_parry_chance = 0 // The base chance for a projectile to be deflected. - -/obj/item/melee/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(.) - return . - if(default_parry_check(user, attacker, damage_source) && prob(defend_chance)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - return 1 - if(unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance)) - user.visible_message("\The [user] deflects [attack_text] with \the [src]!") - return 1 - - return 0 - -/obj/item/melee/unique_parry_check(mob/user, mob/attacker, atom/damage_source) - if(.) - return . - if(user.incapacitated() || !istype(damage_source, /obj/projectile)) - return 0 - - var/bad_arc = global.reverse_dir[user.dir] - if(!check_shield_arc(user, bad_arc, damage_source, attacker)) - return 0 - - return 1 diff --git a/code/game/objects/items/weapons/melee/energy.dm b/code/game/objects/items/weapons/melee/energy.dm deleted file mode 100644 index 467de03f361..00000000000 --- a/code/game/objects/items/weapons/melee/energy.dm +++ /dev/null @@ -1,729 +0,0 @@ -/obj/item/melee/energy - var/active = 0 - var/active_force - var/active_throwforce - var/active_w_class - var/active_embed_chance = 0 //In the off chance one of these is supposed to embed, you can just tweak this var - icon = 'icons/obj/weapons.dmi' - sharp = 0 - edge = 0 - armor_penetration = 50 - atom_flags = NOCONDUCT | NOBLOODY - var/lrange = 2 - var/lpower = 2 - var/lcolor = "#0099FF" - var/colorable = FALSE - var/rainbow = FALSE - // If it uses energy. - var/use_cell = FALSE - var/hitcost = 120 - var/obj/item/cell/bcell = null - var/cell_type = /obj/item/cell/device - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) - -/obj/item/melee/energy/proc/activate(mob/living/user) - if(active) - return - active = 1 - if(rainbow) - item_state = "[icon_state]_blade_rainbow" - else - item_state = "[icon_state]_blade" - embed_chance = active_embed_chance - damage_force = active_force - throw_force = active_throwforce - sharp = 1 - edge = 1 - set_weight_class(active_w_class) - playsound(user, 'sound/weapons/saberon.ogg', 50, 1) - update_icon() - set_light(lrange, lpower, lcolor) - to_chat(user, "Alt-click to recolor it.") - -/obj/item/melee/energy/proc/deactivate(mob/living/user) - if(!active) - return - playsound(user, 'sound/weapons/saberoff.ogg', 50, 1) - item_state = "[icon_state]" - active = 0 - embed_chance = initial(embed_chance) - damage_force = initial(damage_force) - throw_force = initial(throw_force) - sharp = initial(sharp) - edge = initial(edge) - set_weight_class(initial(w_class)) - update_icon() - set_light(0,0) - -/obj/item/melee/energy/proc/use_charge(var/cost) - if(active) - if(bcell) - if(bcell.checked_use(cost)) - return 1 - else - return 0 - return null - -/obj/item/melee/energy/examine(mob/user, dist) - . = ..() - if(use_cell) - if(bcell) - . += "The blade is [round(bcell.percent())]% charged." - if(!bcell) - . += "The blade does not have a power source installed." - -/obj/item/melee/energy/attack_self(mob/user) - . = ..() - if(.) - return - if(use_cell) - if((!bcell || bcell.charge < hitcost) && !active) - to_chat(user, "\The [src] does not seem to have power.") - return - - var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()] - if (active) - if ((MUTATION_CLUMSY in user.mutations) && prob(50)) - user.visible_message("\The [user] accidentally cuts [TU.himself] with \the [src].",\ - "You accidentally cut yourself with \the [src].") - var/mob/living/carbon/human/H = ishuman(user)? user : null - H.take_random_targeted_damage(brute = 5, burn = 5) - deactivate(user) - else - activate(user) - - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - - add_fingerprint(user) - return - -/obj/item/melee/energy/suicide_act(mob/user) - var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()] - if(active) - user.visible_message(pick("\The [user] is slitting [TU.his] stomach open with \the [src]! It looks like [TU.he] [TU.is] trying to commit seppuku.",\ - "\The [user] is falling on \the [src]! It looks like [TU.he] [TU.is] trying to commit suicide.")) - return (BRUTELOSS|FIRELOSS) - -/obj/item/melee/energy/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) - . = ..() - if(active && use_cell) - if(!use_charge(hitcost)) - deactivate(user) - visible_message("\The [src]'s blade flickers, before deactivating.") - -/obj/item/melee/energy/attackby(obj/item/W, mob/user) - if(istype(W, /obj/item/multitool) && colorable && !active) - if(!rainbow) - rainbow = TRUE - else - rainbow = FALSE - to_chat(user, "You manipulate the color controller in [src].") - update_icon() - if(use_cell) - if(istype(W, cell_type)) - if(!bcell) - if(!user.attempt_insert_item_for_installation(W, src)) - return - bcell = W - to_chat(user, "You install a cell in [src].") - update_icon() - else - to_chat(user, "[src] already has a cell.") - else if(W.is_screwdriver() && bcell) - bcell.update_icon() - bcell.forceMove(get_turf(loc)) - bcell = null - to_chat(user, "You remove the cell from \the [src].") - deactivate() - update_icon() - return - return ..() - -/obj/item/melee/energy/get_cell(inducer) - return bcell - -/obj/item/melee/energy/update_icon() - . = ..() - var/mutable_appearance/blade_overlay = mutable_appearance(icon, "[icon_state]_blade") - blade_overlay.color = lcolor - color = lcolor - if(rainbow) - blade_overlay = mutable_appearance(icon, "[icon_state]_blade_rainbow") - blade_overlay.color = "FFFFFF" - color = "FFFFFF" - cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other - if(active) - add_overlay(blade_overlay) - if(istype(usr,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = usr - H.update_inv_l_hand() - H.update_inv_r_hand() - -/obj/item/melee/energy/AltClick(mob/living/user) - if(!colorable) //checks if is not colorable - return - if(!in_range(src, user)) //Basic checks to prevent abuse - return - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return - - if(alert("Are you sure you want to recolor your blade?", "Confirm Recolor", "Yes", "No") == "Yes") - var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null - if(energy_color_input) - lcolor = "#[sanitize_hexcolor(energy_color_input)]" - color = lcolor - deactivate() - update_icon() - . = ..() - - -/* - * Energy Axe - */ -/obj/item/melee/energy/axe - name = "energy axe" - desc = "An energised battle axe." - icon_state = "eaxe" - item_state = "eaxe" - //active_force = 150 //holy... - active_force = 60 - active_throwforce = 35 - active_w_class = WEIGHT_CLASS_HUGE - //damage_force = 40 - //throw_force = 25 - damage_force = 20 - throw_force = 10 - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_NORMAL - origin_tech = list(TECH_MAGNET = 3, TECH_COMBAT = 4) - attack_verb = list("attacked", "chopped", "cleaved", "torn", "cut") - sharp = 1 - edge = 1 - can_cleave = TRUE - -/obj/item/melee/energy/axe/activate(mob/living/user) - ..() - damtype = SEARING - to_chat(user, "\The [src] is now energised.") - -/obj/item/melee/energy/axe/deactivate(mob/living/user) - ..() - damtype = BRUTE - to_chat(user, "\The [src] is de-energised. It's just a regular axe now.") - -/obj/item/melee/energy/axe/suicide_act(mob/user) - var/datum/gender/TU = GLOB.gender_datums[user.get_visible_gender()] - visible_message("\The [user] swings \the [src] towards [TU.his] head! It looks like [TU.he] [TU.is] trying to commit suicide.") - return (BRUTELOSS|FIRELOSS) - -/obj/item/melee/energy/axe/charge - name = "charge axe" - desc = "An energised axe." - active_force = 35 - active_throwforce = 20 - damage_force = 15 - use_cell = TRUE - hitcost = 120 - -/obj/item/melee/energy/axe/charge/loaded/Initialize(mapload) - . = ..() - bcell = new/obj/item/cell/device/weapon(src) - -/* - * Energy Sword - */ -/obj/item/melee/energy/sword - color - name = "energy sword" - desc = "May the damage_force be within you." - icon_state = "esword" - item_state = "esword" - active_force = 30 - active_throwforce = 20 - active_w_class = WEIGHT_CLASS_BULKY - damage_force = 3 - throw_force = 5 - throw_speed = 1 - throw_range = 5 - w_class = WEIGHT_CLASS_SMALL - atom_flags = NOBLOODY - origin_tech = list(TECH_MAGNET = 3, TECH_ILLEGAL = 4) - sharp = 1 - edge = 1 - colorable = TRUE - drop_sound = 'sound/items/drop/sword.ogg' - pickup_sound = 'sound/items/pickup/sword.ogg' - projectile_parry_chance = 65 - -/obj/item/melee/energy/sword/dropped(mob/user, atom_flags, atom/newLoc) - . = ..() - if(!istype(loc,/mob)) - deactivate(user) - -/obj/item/melee/energy/sword/activate(mob/living/user) - if(!active) - to_chat(user, "\The [src] is now energised.") - - ..() - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - -/obj/item/melee/energy/sword/deactivate(mob/living/user) - if(active) - to_chat(user, "\The [src] deactivates!") - ..() - attack_verb = list() - -/obj/item/melee/energy/sword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(active && default_parry_check(user, attacker, damage_source) && prob(60)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return 1 - if(active && unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance)) - user.visible_message("\The [user] deflects [attack_text] with \the [src]!") - - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return 1 - - return 0 - -/obj/item/melee/energy/sword/unique_parry_check(mob/user, mob/attacker, atom/damage_source) - if(user.incapacitated() || !istype(damage_source, /obj/projectile/)) - return 0 - - var/bad_arc = global.reverse_dir[user.dir] - if(!check_shield_arc(user, bad_arc, damage_source, attacker)) - return 0 - - return 1 - -/obj/item/melee/energy/sword/attackby(obj/item/W, mob/living/user, params) - if(istype(W, /obj/item/melee/energy/sword)) - if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP)) - to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!") - return - if(istype(W, /obj/item/melee/energy/sword/charge)) - to_chat(user,"These blades are incompatible, you can't attach them to each other!") - return - else - to_chat(user, "You combine the two energy swords, making a single supermassive blade! You're cool.") - new /obj/item/melee/energy/sword/dualsaber(user.drop_location()) - qdel(W) - qdel(src) - else - return ..() - -/obj/item/melee/energy/sword/pirate - name = "energy cutlass" - desc = "Arrrr matey." - icon_state = "cutlass" - item_state = "cutlass" - colorable = TRUE - -//Return of the King -/obj/item/melee/energy/sword/dualsaber - name = "double-bladed energy sword" - desc = "Handle with care." - icon_state = "dualsaber" - item_state = "dualsaber" - damage_force = 3 - active_force = 60 - throw_force = 5 - throw_speed = 3 - armor_penetration = 35 - colorable = TRUE - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - projectile_parry_chance = 85 - -/* - *Ionic Rapier - */ - -/obj/item/melee/energy/sword/ionic_rapier - name = "ionic rapier" - desc = "Designed specifically for disrupting electronics at close range, it is extremely deadly against synthetics, but almost harmless to pure organic targets." - description_info = "This is a dangerous melee weapon that will deliver a moderately powerful electromagnetic pulse to whatever it strikes. \ - Striking a lesser robotic entity will compel it to attack you, as well. It also does extra burn damage to robotic entities, but it does \ - very little damage to purely organic targets." - icon_state = "ionrapier" - item_state = "ionrapier" - active_force = 5 - active_throwforce = 3 - active_embed_chance = 0 - sharp = 1 - edge = 1 - armor_penetration = 0 - atom_flags = NOBLOODY - lrange = 2 - lpower = 2 - lcolor = "#0000FF" - projectile_parry_chance = 30 // It's not specifically designed for cutting and slashing, but it can still, maybe, save your life. - -/obj/item/melee/energy/sword/ionic_rapier/afterattack(atom/target, mob/user, clickchain_flags, list/params) - if(istype(target, /obj) && (clickchain_flags & CLICKCHAIN_HAS_PROXIMITY) && active) - // EMP stuff. - var/obj/O = target - O.emp_act(3) // A weaker severity is used because this has infinite uses. - playsound(get_turf(O), 'sound/effects/EMPulse.ogg', 100, 1) - user.setClickCooldown(user.get_attack_speed(src)) // A lot of objects don't set click delay. - return ..() - -/obj/item/melee/energy/sword/ionic_rapier/melee_mob_hit(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) - . = ..() - var/mob/living/L = target - if(!istype(L)) - return - if(L.isSynthetic() && active) - // Do some extra damage. Not a whole lot more since emp_act() is pretty nasty on FBPs already. - L.emp_act(3) // A weaker severity is used because this has infinite uses. - playsound(get_turf(L), 'sound/effects/EMPulse.ogg', 100, 1) - L.adjustFireLoss(damage_force * 3) // 15 Burn, for 20 total. - playsound(get_turf(L), 'sound/weapons/blade1.ogg', 100, 1) - - // Make lesser robots really mad at us. - if(L.mob_class & MOB_CLASS_SYNTHETIC) - if(L.has_polaris_AI()) - L.taunt(user) - L.adjustFireLoss(damage_force * 6) // 30 Burn, for 50 total. - -/obj/item/melee/energy/sword/ionic_rapier/lance - name = "zero-point lance" - desc = "Designed specifically for disrupting electronics at relatively close range, however it is still capable of dealing some damage to living beings." - active_force = 20 - armor_penetration = 15 - reach = 2 - -/* - * Charge blade. Uses a cell, and costs energy per strike. - */ - -/obj/item/melee/energy/sword/charge - name = "charge sword" - desc = "A small, handheld device which emits a high-energy 'blade'." - origin_tech = list(TECH_COMBAT = 5, TECH_MAGNET = 3, TECH_ILLEGAL = 4) - active_force = 25 - armor_penetration = 25 - projectile_parry_chance = 40 - colorable = TRUE - use_cell = TRUE - hitcost = 75 - -/obj/item/melee/energy/sword/charge/loaded/Initialize(mapload) - . = ..() - bcell = new/obj/item/cell/device/weapon(src) - -/obj/item/melee/energy/sword/charge/attackby(obj/item/W, mob/living/user, params) - if(istype(W, /obj/item/melee/energy/sword/charge)) - if(HAS_TRAIT(W, TRAIT_ITEM_NODROP) || HAS_TRAIT(src, TRAIT_ITEM_NODROP)) - to_chat(user, "\the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? src : W] is stuck to your hand, you can't attach it to \the [HAS_TRAIT(src, TRAIT_ITEM_NODROP) ? W : src]!") - return - else - to_chat(user, "You combine the two charge swords, making a single supermassive blade! You're cool.") - new /obj/item/melee/energy/sword/charge/dualsaber(user.drop_location()) - qdel(W) - qdel(src) - else - return ..() - -//Charge Type Double Esword -/obj/item/melee/energy/sword/charge/dualsaber - name = "double-bladed charge sword" - desc = "Make sure you bought batteries." - icon_state = "dualsaber" - item_state = "dualsaber" - damage_force = 3 - active_force = 50 - throw_force = 5 - throw_speed = 3 - armor_penetration = 30 - colorable = TRUE - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - projectile_parry_chance = 65 - hitcost = 150 - -//Energy Blade (ninja uses this) - -//Can't be activated or deactivated, so no reason to be a subtype of energy -/obj/item/melee/energy/blade - name = "energy blade" - desc = "A concentrated beam of energy in the shape of a blade. Very stylish... and lethal." - icon_state = "blade" - item_state = "blade" - damage_force = 40 //Normal attacks deal very high damage - about the same as wielded fire axe - armor_penetration = 100 - sharp = 1 - edge = 1 - anchored = 1 // Never spawned outside of inventory, should be fine. - throw_force = 1 //Throwing or dropping the item deletes it. - throw_speed = 1 - throw_range = 1 - w_class = WEIGHT_CLASS_BULKY//So you can't hide it in your pocket or some such. - atom_flags = NOBLOODY - attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") - var/mob/living/creator - var/datum/effect_system/spark_spread/spark_system - projectile_parry_chance = 60 - lcolor = "#00FF00" - -/obj/item/melee/energy/blade/Initialize(mapload) - . = ..() - spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, src) - spark_system.attach(src) - - START_PROCESSING(SSobj, src) - set_light(lrange, lpower, lcolor) - -/obj/item/melee/energy/blade/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/melee/energy/blade/attack_self(mob/user) - . = ..() - if(.) - return - qdel(src) - -/obj/item/melee/energy/blade/dropped(mob/user, atom_flags, atom/newLoc) - . = ..() - qdel(src) - -/obj/item/melee/energy/blade/process(delta_time) - if(!creator || loc != creator || !creator.is_holding(src)) - // Tidy up a bit. - if(istype(loc,/mob/living)) - var/mob/living/carbon/human/host = loc - if(istype(host)) - for(var/obj/item/organ/external/organ in host.organs) - for(var/obj/item/O in organ.implants) - if(O == src) - organ.implants -= src - host.pinned -= src - host.embedded -= src - host._handle_inventory_hud_remove(src) - qdel(src) - -/obj/item/melee/energy/blade/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(default_parry_check(user, attacker, damage_source) && prob(60)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return 1 - if(unique_parry_check(user, attacker, damage_source) && prob(projectile_parry_chance)) - user.visible_message("\The [user] deflects [attack_text] with \the [src]!") - - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return 1 - - return 0 - -/obj/item/melee/energy/blade/unique_parry_check(mob/user, mob/attacker, atom/damage_source) - - if(user.incapacitated() || !istype(damage_source, /obj/projectile/)) - return 0 - - var/bad_arc = global.reverse_dir[user.dir] - if(!check_shield_arc(user, bad_arc, damage_source, attacker)) - return 0 - - return 1 - -//Energy Spear - -/obj/item/melee/energy/spear - name = "energy spear" - desc = "Concentrated energy forming a sharp tip at the end of a long rod." - icon_state = "espear" - armor_penetration = 75 - sharp = 1 - edge = 1 - damage_force = 5 - throw_force = 10 - throw_speed = 7 - throw_range = 11 - reach = 2 - w_class = WEIGHT_CLASS_BULKY - active_force = 25 - active_throwforce = 30 - active_w_class = WEIGHT_CLASS_HUGE - colorable = TRUE - lcolor = "#800080" - -/obj/item/melee/energy/spear/activate(mob/living/user) - if(!active) - to_chat(user, "\The [src] is now energised.") - ..() - attack_verb = list("jabbed", "stabbed", "impaled") - AddComponent(/datum/component/jousting) - - -/obj/item/melee/energy/spear/deactivate(mob/living/user) - if(active) - to_chat(user, "\The [src] deactivates!") - ..() - attack_verb = list("whacked", "beat", "slapped", "thonked") - DelComponent(/datum/component/jousting) - -/obj/item/melee/energy/spear/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(active && default_parry_check(user, attacker, damage_source) && prob(50)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return 1 - return 0 - -/obj/item/melee/energy/hfmachete // ported from /vg/station - vgstation-coders/vgstation13#13913, fucked up by hatterhat - name = "high-frequency machete" - desc = "A high-frequency broad blade used either as an implement or in combat like a short sword." - icon_state = "hfmachete0" - sharp = TRUE - edge = TRUE - damage_force = 20 // You can be crueler than that, Jack. - throw_force = 40 - throw_speed = 8 - throw_range = 8 - w_class = WEIGHT_CLASS_NORMAL - siemens_coefficient = 1 - origin_tech = list(TECH_COMBAT = 3, TECH_ILLEGAL = 3) - attack_verb = list("attacked", "diced", "cleaved", "torn", "cut", "slashed") - armor_penetration = 50 - var/base_state = "hfmachete" - attack_sound = "machete_hit_sound" // dont mind the meaty hit sounds if you hit something that isnt meaty - can_cleave = TRUE - embed_chance = 0 // let's not - -/obj/item/melee/energy/hfmachete/update_icon() - icon_state = "[base_state][active]" - -/obj/item/melee/energy/hfmachete/attack_self(mob/user) - toggleActive(user) - add_fingerprint(user) - -/obj/item/melee/energy/hfmachete/proc/toggleActive(mob/user, var/togglestate = "") - switch(togglestate) - if("on") - active = 1 - if("off") - active = 0 - else - active = !active - if(active) - damage_force = 40 - throw_force = 20 - throw_speed = 3 - // sharpness = 1.7 - // sharpness_flags += HOT_EDGE | CUT_WALL | CUT_AIRLOCK - if only there a good sharpness system - armor_penetration = 100 - to_chat(user, " [src] starts vibrating.") - playsound(user, 'sound/weapons/hf_machete/hfmachete1.ogg', 40, 0) - set_weight_class(WEIGHT_CLASS_BULKY) - // user.lazy_register_event(/lazy_event/on_moved, src, PROC_REF(mob_moved)) - else - damage_force = initial(damage_force) - throw_force = initial(throw_force) - throw_speed = initial(throw_speed) - // sharpness = initial(sharpness) - // sharpness_flags = initial(sharpness_flags) - if only there was a good sharpness system - armor_penetration = initial(armor_penetration) - to_chat(user, " [src] stops vibrating.") - playsound(user, 'sound/weapons/hf_machete/hfmachete0.ogg', 40, 0) - set_weight_class(WEIGHT_CLASS_NORMAL) - // user.lazy_unregister_event(/lazy_event/on_moved, src, PROC_REF(mob_moved)) - update_icon() - -/obj/item/melee/energy/hfmachete/afterattack(atom/target, mob/user, clickchain_flags, list/params) - if(!(clickchain_flags & CLICKCHAIN_HAS_PROXIMITY)) - return - ..() - if(target) - if(istype(target,/obj/effect/plant)) - var/obj/effect/plant/P = target - P.die_off() - -/* -/obj/item/melee/energy/hfmachete/dropped(mob/user, atom_flags, atom/newLoc) - user.lazy_unregister_event(/lazy_event/on_moved, src, PROC_REF(mob_moved)) - -/obj/item/melee/energy/hfmachete/throw_at_old(atom/target, range, speed, thrower) // todo: get silicons to interpret this because >sleeps - if(!usr) - return ..() - spawn() - playsound(src, get_sfx("machete_throw"),30, 0) - animate(src, transform = turn(matrix(), -30), time = 1, loop = -1) - animate(transform = turn(matrix(), -60), time = 1) - animate(transform = turn(matrix(), -90), time = 1) - animate(transform = turn(matrix(), -120), time = 1) - animate(transform = turn(matrix(), -150), time = 1) - animate(transform = null, time = 1) - while(throwing) - sleep(5) - animate(src) - ..(target, range, speed = 3, thrower) -*/ - -// none of these are working properly in testing which is something you absolutely hate to see -/* -/obj/item/melee/energy/hfmachete/throw_at_old(atom/target, range, speed, thrower) - playsound(src, get_sfx("machete_throw"), 30, 0) - . = ..() - -/obj/item/melee/energy/hfmachete/throw_impact(atom/hit_atom, speed) - if(isturf(hit_atom)) - for(var/mob/M in hit_atom) - playsound(M, get_sfx("machete_throw_hit"), 60, 0) - ..() - -/obj/item/melee/energy/hfmachete/attack(mob/M, mob/living/user) - playsound(M, get_sfx("machete_hit"), 50, 0) - ..() -*/ -/* -/obj/item/melee/energy/hfmachete/proc/mob_moved(atom/movable/mover) - if(iscarbon(mover) && active) - for(var/obj/effect/plantsegment/P in range(mover,0)) - qdel(P) - -/obj/item/melee/energy/hfmachete/attackby(obj/item/W, mob/living/user) - ..() - if(istype(W, /obj/item/melee/energy/hfmachete)) - to_chat(user, "You combine the two [W] together, making a single scissor-bladed weapon! You feel fucking invincible!") - qdel(W) - W = null - qdel(src) - var/B = new /obj/item/bloodlust(user.loc) - user.put_in_hands(B) - // blust one day lads. -*/ - -/obj/item/melee/energy/sword/imperial - name = "energy gladius" - desc = "A broad, short energy blade. You'll be glad to have this in a fight." - icon_state = "sword0" - icon = 'icons/obj/weapons_vr.dmi' - item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi') - -/obj/item/melee/energy/sword/imperial/activate(mob/living/user) - ..() - icon_state = "sword1" diff --git a/code/game/objects/items/weapons/melee/melee.dm b/code/game/objects/items/weapons/melee/melee.dm deleted file mode 100644 index 0856d665d16..00000000000 --- a/code/game/objects/items/weapons/melee/melee.dm +++ /dev/null @@ -1,7 +0,0 @@ -/obj/item/melee - icon = 'icons/obj/weapons.dmi' - attack_sound = "swing_hit" - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm deleted file mode 100644 index b036965c9ba..00000000000 --- a/code/game/objects/items/weapons/shields.dm +++ /dev/null @@ -1,494 +0,0 @@ -//** Shield Helpers -//These are shared by various items that have shield-like behaviour - -//bad_arc is the ABSOLUTE arc of directions from which we cannot block. If you want to fix it to e.g. the user's facing you will need to rotate the dirs yourself. -/proc/check_shield_arc(mob/user, var/bad_arc, atom/damage_source = null, mob/attacker = null) - //check attack direction - var/attack_dir = 0 //direction from the user to the source of the attack - if(istype(damage_source, /obj/projectile)) - var/obj/projectile/P = damage_source - attack_dir = get_dir(get_turf(user), P.starting) - else if(attacker) - attack_dir = get_dir(get_turf(user), get_turf(attacker)) - else if(damage_source) - attack_dir = get_dir(get_turf(user), get_turf(damage_source)) - - if(!(attack_dir && (attack_dir & bad_arc))) - return 1 - return 0 - -/proc/default_parry_check(mob/user, mob/attacker, atom/damage_source) - //parry only melee attacks - if(istype(damage_source, /obj/projectile) || (attacker && get_dist(user, attacker) > 1) || user.incapacitated()) - return 0 - - //block as long as they are not directly behind us - var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block - if(!check_shield_arc(user, bad_arc, damage_source, attacker)) - return 0 - - return 1 - -/obj/item/proc/unique_parry_check(mob/user, mob/attacker, atom/damage_source) // An overrideable version of the above proc. - return default_parry_check(user, attacker, damage_source) - -/obj/item/shield - name = "shield" - var/base_block_chance = 50 - preserve_item = 1 - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) - -/obj/item/shield/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(user.incapacitated()) - return 0 - - //block as long as they are not directly behind us - var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block - if(check_shield_arc(user, bad_arc, damage_source, attacker)) - if(prob(get_block_chance(user, damage, damage_source, attacker))) - user.visible_message("\The [user] blocks [attack_text] with \the [src]!") - return 1 - return 0 - -/obj/item/shield/proc/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null) - return base_block_chance - -/obj/item/shield/riot - name = "riot shield" - desc = "A shield adept for close quarters engagement. It's also capable of protecting from less powerful projectiles." - icon = 'icons/obj/weapons.dmi' - icon_state = "riot" - slot_flags = SLOT_BACK - damage_force = 5.0 - throw_force = 5.0 - throw_speed = 1 - throw_range = 4 - w_class = WEIGHT_CLASS_BULKY - origin_tech = list(TECH_MATERIAL = 2) - materials_base = list(MAT_GLASS = 7500, MAT_STEEL = 1000) - attack_verb = list("shoved", "bashed") - worth_intrinsic = 300 - var/cooldown = 0 //shield bash cooldown. based on world.time - -/obj/item/shield/riot/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(user.incapacitated()) - return 0 - - //block as long as they are not directly behind us - var/bad_arc = global.reverse_dir[user.dir] //arc of directions from which we cannot block - if(check_shield_arc(user, bad_arc, damage_source, attacker)) - if(prob(get_block_chance(user, damage, damage_source, attacker))) - //At this point, we succeeded in our roll for a block attempt, however these kinds of shields struggle to stand up - //to strong bullets and lasers. They still do fine to pistol rounds of all kinds, however. - if(istype(damage_source, /obj/projectile)) - var/obj/projectile/P = damage_source - if((is_sharp(P) && P.armor_penetration >= 10) || istype(P, /obj/projectile/beam)) - //If we're at this point, the bullet/beam is going to go through the shield, however it will hit for less damage. - //Bullets get slowed down, while beams are diffused as they hit the shield, so these shields are not /completely/ - //useless. Extremely penetrating projectiles will go through the shield without less damage. - user.visible_message("\The [user]'s [src.name] is pierced by [attack_text]!") - if(P.armor_penetration < 30) //PTR bullets and x-rays will bypass this entirely. - P.damage = P.damage / 2 - return 0 - //Otherwise, if we're here, we're gonna stop the attack entirely. - user.visible_message("\The [user] blocks [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/Genhit.ogg', 50, 1) - return 1 - return 0 - -/obj/item/shield/riot/attackby(obj/item/W as obj, mob/user as mob) - if(istype(W, /obj/item/melee/baton)) - if(cooldown < world.time - 25) - user.visible_message("[user] bashes [src] with [W]!") - playsound(user.loc, 'sound/effects/shieldbash.ogg', 50, 1) - cooldown = world.time - else - ..() - -/obj/item/shield/riot/flash - name = "strobe shield" - desc = "A shield with a built in, high intensity light capable of blinding and disorienting suspects. Takes regular handheld flashes as bulbs." - icon_state = "flashshield" - item_state = "flashshield" - var/obj/item/flash/embedded_flash - var/flashfail = 0 - -/obj/item/shield/riot/flash/Initialize(mapload) - . = ..() - embedded_flash = new(src) - -/obj/item/shield/riot/flash/attack_mob(mob/target, mob/user, clickchain_flags, list/params, mult, target_zone, intent) - if(user.a_intent == INTENT_HARM) - return ..() - return embedded_flash.attack_mob(arglist(args)) - -/obj/item/shield/riot/flash/attack_self(mob/user) - . = ..() - if(.) - return - . = embedded_flash.attack_self(user) - update_icon() - -/obj/item/shield/riot/flash/handle_shield(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) - . = ..() - if (. && damage && !embedded_flash.broken) - embedded_flash.melee_interaction_chain() - update_icon() - -/obj/item/shield/riot/flash/attackby(obj/item/W, mob/user) - if(istype(W, /obj/item/flash)) - var/obj/item/flash/flash = W - if(flashfail) - to_chat(user, "No sense replacing it with a broken bulb!") - return - else - to_chat(user, "You begin to replace the bulb...") - if(do_after(user, 20, target = user)) - if(flashfail || !flash || QDELETED(flash)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) - qdel(embedded_flash) - embedded_flash = flash - flash.forceMove(src) - update_icon() - return - ..() - -/obj/item/shield/riot/flash/emp_act(severity) - . = ..() - embedded_flash.emp_act(severity) - update_icon() - -/obj/item/shield/riot/flash/update_icon_state() - . = ..() - if(!embedded_flash || embedded_flash.broken) - icon_state = "riot" - item_state = "riot" - else - icon_state = "flashshield" - item_state = "flashshield" - -/obj/item/shield/riot/flash/examine(mob/user, dist) - . = ..() - if (embedded_flash?.broken) - . += "The mounted bulb has burnt out. You can try replacing it with a new one." - -/obj/item/shield/makeshift - name = "metal shield" - desc = "A large shield made of wired and welded sheets of metal. The handle is made of cloth and leather, making it unwieldy." - icon = 'icons/obj/weapons.dmi' - icon_state = "makeshift_shield" - item_state = "metal" - slot_flags = null - damage_force = 10 - throw_force = 7 - -/obj/item/shield/riot/tower - name = "tower shield" - desc = "An immense tower shield. Designed to ensure maximum protection to the user, at the expense of mobility." - item_state = "metal" - icon_state = "metal" - damage_force = 16 - encumbrance = ITEM_ENCUMBRANCE_SHIELD_TOWER - throw_force = 15 //Massive piece of metal - w_class = WEIGHT_CLASS_HUGE - -/obj/item/shield/riot/tower/swat - name = "swat shield" - -/* I don't know if I really want this in the game. I DO want the code though. -/obj/item/shield/riot/implant - name = "hardlight shield implant" - desc = "A hardlight plane of force projected from the implant. While it is capable of withstanding immense amounts of abuse, it will eventually overload from sustained impacts, especially against energy attacks. Recharges while retracted." - item_state = "holoshield" - icon_state = "holoshield" - slowdown = 1 - shield_flags = SHIELD_FLAGS_DEFAULT - integrity_max = 100 - obj_integrity = 100 - can_shatter = FALSE - clothing_flags = ITEM_CAN_BLOCK - shield_flags = SHIELD_FLAGS_DEFAULT | SHIELD_KINETIC_STRONG | SHIELD_DISABLER_DISRUPTED - var/recharge_timerid - var/recharge_delay = 15 SECONDS - -/// Entirely overriden take_damage. This shouldn't exist outside of an implant (other than maybe christmas). -/obj/item/shield/riot/implant/take_damage_legacy(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir, armour_penetration = 0) - obj_integrity -= damage_amount - if(obj_integrity < 0) - obj_integrity = 0 - if(obj_integrity == 0) - if(ismob(loc)) - var/mob/living/L = loc - playsound(src, /datum/soundbyte/grouped/sparks, 100, TRUE) - L.visible_message("[src] overloads from the damage sustained!") - L.dropItemToGround(src) //implant component catch hook will grab it. - -/obj/item/shield/riot/implant/Moved() - . = ..() - if(istype(loc, /obj/item/organ/cyberimp/arm/shield)) - recharge_timerid = addtimer(CALLBACK(src, PROC_REF(recharge)), recharge_delay, flags = TIMER_STOPPABLE) - else //extending - if(recharge_timerid) - deltimer(recharge_timerid) - recharge_timerid = null - -/obj/item/shield/riot/implant/proc/recharge() - if(obj_integrity == integrity_max) - return - obj_integrity = integrity_max - if(ismob(loc.loc)) //cyberimplant.user - to_chat(loc, "[src] has recharged its reinforcement matrix and is ready for use!") -*/ - -/obj/item/shield/riot/energy_proof - name = "energy resistant shield" - desc = "An ablative shield designed to absorb and disperse energy attacks. This comes at significant cost to its ability to withstand ballistics and kinetics, breaking apart easily." - icon_state = "riot_laser" - -/obj/item/shield/riot/kinetic_proof - name = "kinetic resistant shield" - desc = "A polymer and ceramic shield designed to absorb ballistic projectiles and kinetic force. It doesn't do very well into energy attacks, especially from weapons that inflict burns." - icon_state = "riot_bullet" - -//Exotics/Costume Shields -/obj/item/shield/riot/roman - name = "scutum" - desc = "A replica shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles." - icon = 'icons/obj/weapons.dmi' - icon_state = "roman_shield" - slot_flags = SLOT_BACK - materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000) - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) - -/obj/item/shield/riot/buckler - name = "buckler" - desc = "A wrist mounted round shield for close quarters engagement. Its modern materials are also capable of protecting from less powerful projectiles." - icon = 'icons/obj/weapons.dmi' - icon_state = "buckler" - slot_flags = SLOT_BACK | SLOT_BELT - materials_base = list(MAT_WOOD = 7500, MAT_STEEL = 1000) - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) - -/* - * Energy Shield - */ - -/obj/item/shield/energy - name = "energy combat shield" - desc = "A shield capable of stopping most projectile and melee attacks. It can be retracted, expanded, and stored anywhere." - icon = 'icons/obj/weapons.dmi' - icon_state = "eshield" - item_state = "eshield" - slot_flags = SLOT_EARS - atom_flags = NOCONDUCT - damage_force = 3.0 - throw_force = 5.0 - throw_speed = 1 - throw_range = 4 - w_class = WEIGHT_CLASS_SMALL - var/lrange = 1.5 - var/lpower = 1.5 - var/lcolor = "#006AFF" - origin_tech = list(TECH_MATERIAL = 4, TECH_MAGNET = 3, TECH_ILLEGAL = 4) - attack_verb = list("shoved", "bashed") - var/active = 0 - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) - worth_intrinsic = 500 // op as balls - -/obj/item/shield/energy/handle_shield(mob/user) - if(!active) - return 0 //turn it on first! - . = ..() - - if(.) - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - -/obj/item/shield/energy/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null) - if(istype(damage_source, /obj/projectile)) - var/obj/projectile/P = damage_source - if((is_sharp(P) && damage > 10) || istype(P, /obj/projectile/beam)) - return (base_block_chance - round(damage / 3)) //block bullets and beams using the old block chance - return base_block_chance - -/obj/item/shield/energy/attack_self(mob/user) - . = ..() - if(.) - return - if ((MUTATION_CLUMSY in user.mutations) && prob(50)) - to_chat(user, "You beat yourself in the head with [src].") - var/mob/living/carbon/human/H = ishuman(user)? user : null - H?.take_random_targeted_damage(brute = 5) - active = !active - if (active) - damage_force = 10 - update_icon() - set_weight_class(WEIGHT_CLASS_BULKY) - slot_flags = null - playsound(user, 'sound/weapons/saberon.ogg', 50, 1) - to_chat(user, "\The [src] is now active.") - - else - damage_force = 3 - update_icon() - set_weight_class(WEIGHT_CLASS_TINY) - slot_flags = SLOT_EARS - playsound(user, 'sound/weapons/saberoff.ogg', 50, 1) - to_chat(user, "\The [src] can now be concealed.") - - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - - add_fingerprint(user) - return - -/obj/item/shield/energy/update_icon() - var/mutable_appearance/blade_overlay = mutable_appearance(icon, "[icon_state]_blade") - if(lcolor) - blade_overlay.color = lcolor - cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other - if(active) - add_overlay(blade_overlay) - item_state = "[icon_state]_blade" - set_light(lrange, lpower, lcolor) - else - set_light(0) - item_state = "[icon_state]" - -/obj/item/shield/energy/AltClick(mob/living/user) - if(!in_range(src, user)) //Basic checks to prevent abuse - return - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return - if(alert("Are you sure you want to recolor your shield?", "Confirm Recolor", "Yes", "No") == "Yes") - var/energy_color_input = input(usr,"","Choose Energy Color",lcolor) as color|null - if(energy_color_input) - lcolor = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) - update_icon() - -/obj/item/shield/energy/examine(mob/user, dist) - . = ..() - . += "Alt-click to recolor it." - -/obj/item/shield/riot/tele - name = "telescopic shield" - desc = "An advanced riot shield made of lightweight materials that collapses for easy storage." - icon = 'icons/obj/weapons.dmi' - icon_state = "teleriot0" - slot_flags = null - damage_force = 3 - throw_force = 3 - throw_speed = 3 - throw_range = 4 - w_class = WEIGHT_CLASS_NORMAL - var/active = 0 -/* -/obj/item/shield/energy/IsShield() - if(active) - return 1 - else - return 0 -*/ -/obj/item/shield/riot/tele/attack_self(mob/user) - . = ..() - if(.) - return - active = !active - icon_state = "teleriot[active]" - playsound(src.loc, 'sound/weapons/empty.ogg', 50, 1) - - if(active) - damage_force = 8 - throw_force = 5 - throw_speed = 2 - set_weight_class(WEIGHT_CLASS_BULKY) - slot_flags = SLOT_BACK - to_chat(user, "You extend \the [src].") - else - damage_force = 3 - throw_force = 3 - throw_speed = 3 - set_weight_class(WEIGHT_CLASS_NORMAL) - slot_flags = null - to_chat(user, "[src] can now be concealed.") - - if(istype(user,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = user - H.update_inv_l_hand() - H.update_inv_r_hand() - - add_fingerprint(user) - return - -/obj/item/shield/energy/imperial - name = "energy scutum" - desc = "It's really easy to mispronounce the name of this shield if you've only read it in books." - icon = 'icons/obj/weapons_vr.dmi' - icon_state = "eshield0" // eshield1 for expanded - item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi') - -/obj/item/shield/fluff/wolfgirlshield - name = "Autumn Shield" - desc = "A shiny silvery shield with a large red leaf symbol in the center." - icon = 'icons/obj/weapons_vr.dmi' - icon_state = "wolfgirlshield" - slot_flags = SLOT_BACK | SLOT_OCLOTHING - damage_force = 5.0 - throw_force = 5.0 - throw_speed = 2 - throw_range = 6 - item_icons = list(SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', SLOT_ID_BACK = 'icons/vore/custom_items_vr.dmi', SLOT_ID_SUIT = 'icons/vore/custom_items_vr.dmi') - attack_verb = list("shoved", "bashed") - var/cooldown = 0 //shield bash cooldown. based on world.time - allowed = list(/obj/item/melee/fluffstuff/wolfgirlsword) - -/obj/item/shield/fluff/roman - name = "replica scutum" - desc = "A replica shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more." - icon = 'icons/obj/weapons.dmi' - icon_state = "roman_shield" - slot_flags = SLOT_BACK - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) - damage_force = 5.0 - throw_force = 5.0 - throw_speed = 2 - throw_range = 6 - -//Foam Shield -/obj/item/shield/riot/foam - name = "foam riot shield" - desc = "A shield for close quarters engagement. It looks sturdy enough to withstand foam weapons, and nothing more." - icon = 'icons/obj/weapons.dmi' - icon_state = "foamriot" - slot_flags = SLOT_BACK - base_block_chance = 5 - damage_force = 0 - throw_force = 0 - throw_speed = 2 - throw_range = 6 - materials_base = list(MAT_PLASTIC = 7500, "foam" = 1000) - item_icons = list( - SLOT_ID_LEFT_HAND = 'icons/mob/items/lefthand_melee.dmi', - SLOT_ID_RIGHT_HAND = 'icons/mob/items/righthand_melee.dmi', - ) diff --git a/code/game/objects/items/weapons/swords_axes_etc.dm b/code/game/objects/items/weapons/swords_axes_etc.dm index dd467649551..b5c7a599daa 100644 --- a/code/game/objects/items/weapons/swords_axes_etc.dm +++ b/code/game/objects/items/weapons/swords_axes_etc.dm @@ -38,7 +38,9 @@ icon_state = "tonfa" item_state = "tonfa" atom_flags = NOBLOODY - defend_chance = 15 + passive_parry = /datum/passive_parry{ + parry_chance_melee = 15; + } //Telescopic baton /obj/item/melee/telebaton @@ -145,12 +147,15 @@ ) item_state = "armblade" damage_force = 15 // same damage_force as a drill - defend_chance = 20 // did you know melee weapons have a default 5% chance to block frontal melee? sharp = TRUE edge = TRUE var/SA_bonus_damage = 35 // 50 total against animals and aberrations. var/SA_vulnerability = MOB_CLASS_ANIMAL | MOB_CLASS_ABERRATION + passive_parry = /datum/passive_parry{ + parry_chance_melee = 20; + } + /obj/item/melee/disruptor/afterattack(atom/target, mob/user, clickchain_flags, list/params) . = ..() if(isliving(target)) diff --git a/code/game/objects/items/weapons/tanks/tank.dm b/code/game/objects/items/weapons/tanks/tank.dm index 4a3291e1b99..24bb915b4fa 100644 --- a/code/game/objects/items/weapons/tanks/tank.dm +++ b/code/game/objects/items/weapons/tanks/tank.dm @@ -455,7 +455,19 @@ var/list/global/tank_gauge_cache = list() var/num_fragments = round(rand(8,10) * sqrt(strength * mult)) - src.fragmentate(T, num_fragments, rand(5) + 7, list(/obj/projectile/bullet/pellet/fragment/tank/small = 7,/obj/projectile/bullet/pellet/fragment/tank = 2,/obj/projectile/bullet/pellet/fragment/strong = 1)) + shrapnel_explosion( + num_fragments, + rand(5, 7), + list( + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank, + /obj/projectile/bullet/pellet/fragment/tank, + /obj/projectile/bullet/pellet/fragment/strong = 1, + ), + ) if(istype(loc, /obj/item/transfer_valve)) var/obj/item/transfer_valve/TTV = loc @@ -482,13 +494,25 @@ var/list/global/tank_gauge_cache = list() visible_message("[icon2html(thing = src, target = world)] \The [src] flies apart!", "You hear a bang!") T.hotspot_expose(air_contents.temperature, 70, 1) - var/strength = 1+((pressure-TANK_LEAK_PRESSURE)/TANK_FRAGMENT_SCALE) var/mult = (air_contents.total_moles**2/3)/((29*0.64) **2/3) //tanks appear to be experiencing a reduction on scale of about 0.64 total moles var/num_fragments = round(rand(6,8) * sqrt(strength * mult)) //Less chunks, but bigger - src.fragmentate(T, num_fragments, 7, list(/obj/projectile/bullet/pellet/fragment/tank/small = 1,/obj/projectile/bullet/pellet/fragment/tank = 5,/obj/projectile/bullet/pellet/fragment/strong = 4)) + + shrapnel_explosion( + num_fragments, + rand(5, 7), + list( + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank/small, + /obj/projectile/bullet/pellet/fragment/tank, + /obj/projectile/bullet/pellet/fragment/tank, + /obj/projectile/bullet/pellet/fragment/strong = 1, + ), + ) if(istype(loc, /obj/item/transfer_valve)) var/obj/item/transfer_valve/TTV = loc diff --git a/code/game/objects/obj-construction.dm b/code/game/objects/obj-construction.dm new file mode 100644 index 00000000000..6b1ec1001e8 --- /dev/null +++ b/code/game/objects/obj-construction.dm @@ -0,0 +1,9 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Deconstruction *// + +/obj/drop_products(method, atom/where) + . = ..() + if(obj_storage?.drop_on_deconstruction_methods & method) + obj_storage.drop_everything_at(where) diff --git a/code/game/objects/obj-defense.dm b/code/game/objects/obj-defense.dm new file mode 100644 index 00000000000..4bbfcff3a17 --- /dev/null +++ b/code/game/objects/obj-defense.dm @@ -0,0 +1,118 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/obj/ex_act(power, dir, datum/automata/wave/explosion/E) + . = ..() + // todo: wave explosions + // no named arguments for speed reasons + run_damage_instance(power * (1 / 2.5) * (0.01 * rand(80, 120)), BRUTE, null, ARMOR_BOMB) + +/obj/legacy_ex_act(severity, target) + . = ..() + // todo: wave explosions + // no named arguments for speed reasons + run_damage_instance(global._legacy_ex_atom_damage[severity] * (0.01 * rand(80, 120)), BRUTE, null, ARMOR_BOMB) + +/obj/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain) + var/shieldcall_returns = atom_shieldcall_handle_item_melee(weapon, clickchain, FALSE, NONE) + if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return CLICKCHAIN_FULL_BLOCKED + // todo: maybe the item side should handle this? + run_damage_instance( + weapon.damage_force * (clickchain ? clickchain.damage_multiplier : 1), + weapon.damtype, + weapon.damage_tier, + weapon.damage_flag, + weapon.damage_mode, + ATTACK_TYPE_MELEE, + weapon, + NONE, + target_zone, + null, + null, + ) + return NONE + +/obj/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain) + var/shieldcall_returns = atom_shieldcall_handle_unarmed_melee(style, clickchain, FALSE, NONE) + if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return CLICKCHAIN_FULL_BLOCKED + // todo: maybe the unarmed_style side should handle this? + run_damage_instance( + style.get_unarmed_damage(attacker, src) * (clickchain ? clickchain.damage_multiplier : 1), + style.damage_type, + style.damage_tier, + style.damage_flag, + style.damage_mode, + ATTACK_TYPE_UNARMED, + style, + NONE, + target_zone, + null, + null, + ) + return NONE + +/obj/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(!(impact_flags & (PROJECTILE_IMPACT_BLOCKED | PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE))) + // todo: maybe the projectile side should handle this? + run_damage_instance( + proj.get_structure_damage() * bullet_act_args[BULLET_ACT_ARG_EFFICIENCY], + proj.damage_type, + proj.damage_tier, + proj.damage_flag, + proj.damage_mode, + ATTACK_TYPE_PROJECTILE, + proj, + NONE, + bullet_act_args[BULLET_ACT_ARG_ZONE], + null, + null, + ) + if(QDELETED(src)) + impact_flags |= PROJECTILE_IMPACT_TARGET_DELETED + return ..() + +/obj/throw_impacted(atom/movable/AM, datum/thrownthing/TT) + . = ..() + if(TT.throw_flags & THROW_AT_IS_GENTLE) + return + // todo: /atom/movable/proc/throw_impact_attack(atom/target) + if(temporary_legacy_dont_auto_handle_obj_damage_for_mechs) + return + if(isitem(AM)) + var/obj/item/I = AM + inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM) + else + inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), TT.get_damage_tier(src), ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM) + // if we got destroyed + if(QDELETED(src) && (obj_flags & OBJ_ALLOW_THROW_THROUGH)) + . |= COMPONENT_THROW_HIT_PIERCE + +/obj/blob_act(obj/structure/blob/blob) + . = ..() + inflict_atom_damage(100, damage_flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE) + +/obj/hitsound_melee(obj/item/I) + if(!isnull(material_primary)) + var/datum/material/primary = get_primary_material() + . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute + if(!isnull(.)) + return + return ..() + +/obj/hitsound_throwhit(obj/item/I) + if(!isnull(material_primary)) + var/datum/material/primary = get_primary_material() + . = I.damtype == BURN? primary.sound_melee_burn : primary.sound_melee_brute + if(!isnull(.)) + return + return ..() + +/obj/hitsound_unarmed(mob/M, datum/unarmed_attack/style) + if(!isnull(material_primary)) + var/datum/material/primary = get_primary_material() + . = style.damage_type == BURN? primary.sound_melee_burn : primary.sound_melee_brute + if(!isnull(.)) + return + return ..() diff --git a/code/game/objects/objs.dm b/code/game/objects/obj.dm similarity index 100% rename from code/game/objects/objs.dm rename to code/game/objects/obj.dm diff --git a/code/game/objects/random/mapping.dm b/code/game/objects/random/mapping.dm index d6e34c541f6..dfda3c019e9 100644 --- a/code/game/objects/random/mapping.dm +++ b/code/game/objects/random/mapping.dm @@ -405,11 +405,11 @@ // /obj/item/archaeological_find //), prob(1);list( - /obj/item/melee/energy/sword, - /obj/item/melee/energy/sword, - /obj/item/melee/energy/sword, - /obj/item/shield/energy, - /obj/item/shield/energy, + /obj/item/melee/transforming/energy/sword, + /obj/item/melee/transforming/energy/sword, + /obj/item/melee/transforming/energy/sword, + /obj/item/shield/transforming/energy, + /obj/item/shield/transforming/energy, /obj/structure/closet/crate/science ), prob(1);list( diff --git a/code/game/objects/random/misc.dm b/code/game/objects/random/misc.dm index 6a874a3abae..a7ec6ec77a9 100644 --- a/code/game/objects/random/misc.dm +++ b/code/game/objects/random/misc.dm @@ -811,7 +811,7 @@ prob(8);/obj/item/gun/energy/gun/eluger, prob(8);/obj/item/gun/energy/xray, prob(8);/obj/item/gun/ballistic/automatic/c20r, - prob(8);/obj/item/melee/energy/sword, + prob(8);/obj/item/melee/transforming/energy/sword, prob(8);/obj/item/gun/ballistic/derringer, prob(8);/obj/item/gun/ballistic/konigin, prob(8);/obj/item/gun/ballistic/revolver/lemat, @@ -835,7 +835,7 @@ prob(4);/obj/item/gun/ballistic/deagle, prob(4);/obj/item/gun/ballistic/deagle/taj, prob(4);/obj/item/material/knife/tacknife/combatknife, - prob(4);/obj/item/melee/energy/sword, + prob(4);/obj/item/melee/transforming/energy/sword, prob(2);/obj/item/gun/ballistic/automatic/mini_uzi, prob(2);/obj/item/gun/ballistic/automatic/mini_uzi/taj, prob(4);/obj/item/gun/ballistic/automatic/wt274, diff --git a/code/game/objects/structures/catwalk.dm b/code/game/objects/structures/catwalk.dm index e5cfe97fa40..f34b8a2dfba 100644 --- a/code/game/objects/structures/catwalk.dm +++ b/code/game/objects/structures/catwalk.dm @@ -194,7 +194,7 @@ if(6 to 50) inflict_atom_damage( rand(10, 20), - flag = ARMOR_MELEE, + damage_flag = ARMOR_MELEE, ) visible_message("The planks creak and groan as they're crossed.") if(51 to 100) diff --git a/code/game/objects/structures/cliff.dm b/code/game/objects/structures/cliff.dm index 8dae834f31c..f649caacdfc 100644 --- a/code/game/objects/structures/cliff.dm +++ b/code/game/objects/structures/cliff.dm @@ -141,13 +141,14 @@ two tiles on initialization, and which way a cliff is facing may change during m return ..() // Projectiles and objects flying 'upward' have a chance to hit the cliff instead, wasting the shot. - else if(istype(mover, /obj)) - var/obj/O = mover - if(check_shield_arc(src, dir, O)) // This is actually for mobs but it will work for our purposes as well. + else if(istype(mover, /obj/projectile) || mover.throwing) + if(get_dir(mover, src) & dir) if(prob(uphill_penalty / (1 + is_double_cliff) )) // Firing upwards facing NORTH means it will likely have to pass through two cliffs, so the chance is halved. return FALSE return TRUE + return ..() + /obj/structure/cliff/Bumped(atom/A) if(isliving(A)) var/mob/living/L = A diff --git a/code/game/objects/structures/crates_lockers/__closet.dm b/code/game/objects/structures/crates_lockers/__closet.dm index 979cae2af26..d4418893794 100644 --- a/code/game/objects/structures/crates_lockers/__closet.dm +++ b/code/game/objects/structures/crates_lockers/__closet.dm @@ -314,7 +314,7 @@ return if(!user.attempt_insert_item_for_installation(I, opened? loc : src)) return - else if(istype(I, /obj/item/melee/energy/blade)) + else if(istype(I, /obj/item/melee/ninja_energy_blade)) if(emag_act(INFINITY, user, "The locker has been sliced open by [user] with \an [I]!", "You hear metal being sliced and sparks flying.")) var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() spark_system.set_up(5, 0, loc) diff --git a/code/game/objects/structures/crates_lockers/closets/coffin.dm b/code/game/objects/structures/crates_lockers/closets/coffin.dm index 4c1e44438b8..fcf23cf8bde 100644 --- a/code/game/objects/structures/crates_lockers/closets/coffin.dm +++ b/code/game/objects/structures/crates_lockers/closets/coffin.dm @@ -32,6 +32,7 @@ opened = 1 color = "#c2b29f" use_old_icon_update = TRUE + obj_flags = OBJ_MELEE_TARGETABLE /obj/structure/closet/grave/attack_hand(mob/user, list/params) if(opened) @@ -146,9 +147,6 @@ .=..() alpha = 255 // Needed because of grave hiding -/obj/structure/closet/grave/bullet_act(var/obj/projectile/P) - return PROJECTILE_CONTINUE // It's a hole in the ground, doesn't usually stop or even care about bullets - /obj/structure/closet/grave/return_air_for_internal_lifeform(var/mob/living/L) var/gasid = GAS_ID_CARBON_DIOXIDE if(ishuman(L)) diff --git a/code/game/objects/structures/crates_lockers/closets/gimmick.dm b/code/game/objects/structures/crates_lockers/closets/gimmick.dm index ed1c5f42e03..3f3901f4a74 100644 --- a/code/game/objects/structures/crates_lockers/closets/gimmick.dm +++ b/code/game/objects/structures/crates_lockers/closets/gimmick.dm @@ -57,7 +57,7 @@ starts_with = list( /obj/item/clothing/suit/armor/tdome/red = 3, - /obj/item/melee/energy/sword = 3, + /obj/item/melee/transforming/energy/sword = 3, /obj/item/gun/energy/laser = 3, /obj/item/melee/baton = 3, /obj/item/storage/box/flashbangs = 3, @@ -71,7 +71,7 @@ starts_with = list( /obj/item/clothing/suit/armor/tdome/green = 3, - /obj/item/melee/energy/sword = 3, + /obj/item/melee/transforming/energy/sword = 3, /obj/item/gun/energy/laser = 3, /obj/item/melee/baton = 3, /obj/item/storage/box/flashbangs = 3, diff --git a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm index 24ea0ce03cf..ad445635d1b 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/personal.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/personal.dm @@ -50,7 +50,7 @@ update_icon() else to_chat(user, "Access Denied") - else if(istype(W, /obj/item/melee/energy/blade)) + else if(istype(W, /obj/item/melee/ninja_energy_blade)) if(emag_act(INFINITY, user, "The locker has been sliced open by [user] with \an [W]!", "You hear metal being sliced and sparks flying.")) var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() spark_system.set_up(5, 0, loc) diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index dfe4064a21e..1b53f9f2738 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -97,7 +97,7 @@ /obj/item/clothing/glasses/sunglasses/sechud, /obj/item/barrier_tape_roll/police, /obj/item/shield/riot, - /obj/item/shield/riot/tele, + /obj/item/shield/transforming/telescopic, /obj/item/storage/box/holobadge/hos, /obj/item/storage/box/firingpins, /obj/item/clothing/accessory/badge/holo/hos, @@ -173,7 +173,7 @@ /obj/item/radio/headset/heads/hos/alt, /obj/item/clothing/accessory/armor/helmetcamera/security, /obj/item/clothing/accessory/armor/helmetcamera/security/body, - /obj/item/shield/riot/tele, + /obj/item/shield/transforming/telescopic, /obj/item/storage/box/holobadge/hos, /obj/item/clothing/accessory/badge/holo/hos, /obj/item/reagent_containers/spray/pepper, @@ -500,7 +500,7 @@ GLOBAL_LIST_BOILERPLATE(all_brig_closets, /obj/structure/closet/secure_closet/br /obj/item/clothing/glasses/sunglasses/sechud, /obj/item/barrier_tape_roll/police, /obj/item/shield/riot, - /obj/item/shield/riot/tele, + /obj/item/shield/transforming/telescopic, /obj/item/storage/box/holobadge/hos, /obj/item/clothing/accessory/badge/holo/hos, /obj/item/reagent_containers/spray/pepper, diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm index e0b788d4508..513aeb888ce 100644 --- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm +++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm @@ -16,7 +16,7 @@ /obj/item/cell/high, /obj/item/card/id/syndicate, /obj/item/multitool, - /obj/item/shield/energy, + /obj/item/shield/transforming/energy, /obj/item/clothing/shoes/magboots) diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index 5f5d7baf031..04e971a25f0 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -211,7 +211,7 @@ /obj/structure/closet/crate/secure/attackby(obj/item/W as obj, mob/user as mob) if(is_type_in_list(W, list(/obj/item/packageWrap, /obj/item/stack/cable_coil, /obj/item/radio/electropack, /obj/item/tool/wirecutters))) return ..() - if(istype(W, /obj/item/melee/energy/blade)) + if(istype(W, /obj/item/melee/ninja_energy_blade)) emag_act(INFINITY, user) if(!opened) src.togglelock(user) @@ -261,17 +261,17 @@ req_access += pick(get_all_station_access()) ..() -/obj/structure/closet/crate/secure/bullet_act(var/obj/projectile/Proj) - if(!(Proj.damage_type == BRUTE || Proj.damage_type == BURN)) - return +/obj/structure/closet/crate/secure/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(!(proj.damage_type == BRUTE || proj.damage_type == BURN)) + return ..() - if(locked && tamper_proof && integrity <= Proj.damage) + if(locked && tamper_proof && integrity <= proj.damage) if(tamper_proof == 2) // Mainly used for events to prevent any chance of opening the box improperly. visible_message("The anti-tamper mechanism of [src] triggers an explosion!") var/turf/T = get_turf(src.loc) explosion(T, 0, 0, 0, 1) // Non-damaging, but it'll alert security. qdel(src) - return + return impact_flags var/open_chance = rand(1,5) switch(open_chance) if(1) @@ -287,12 +287,9 @@ qdel(src) if(5) visible_message("The anti-tamper mechanism of [src] fails!") - return - - ..() - - return + return impact_flags + return ..() /obj/structure/closet/crate/plastic name = "plastic crate" diff --git a/code/game/objects/structures/curtains.dm b/code/game/objects/structures/curtains.dm index 8dfecee5121..b3f82578d8a 100644 --- a/code/game/objects/structures/curtains.dm +++ b/code/game/objects/structures/curtains.dm @@ -7,6 +7,10 @@ opacity = 1 density = 0 anchored = TRUE + integrity = 40 + integrity_failure = 30 + integrity_max = 40 + var/obj/item/stack/mat = /obj/item/stack/material/plastic /obj/structure/curtain/open @@ -15,13 +19,6 @@ layer = 3.3 //3.3 so its above windows, not the same as them. anything below 3.3 puts the curtain beneath the window sprite in current build opacity = 0 -/obj/structure/curtain/bullet_act(obj/projectile/P, def_zone) - if(!P.nodamage) - visible_message("[P] tears [src] down!") - qdel(src) - else - ..(P, def_zone) - /obj/structure/curtain/attack_hand(mob/user, list/params) playsound(get_turf(loc), "rustle", 15, 1, -5) toggle() diff --git a/code/game/objects/structures/decorations.dm b/code/game/objects/structures/decorations.dm index f7c5260e74e..bcbf68b08f0 100644 --- a/code/game/objects/structures/decorations.dm +++ b/code/game/objects/structures/decorations.dm @@ -3,15 +3,10 @@ desc = "Ornately twisted rope holding up a religious seal." icon = 'icons/obj/decals.dmi' icon_state = "shrine_seal" - layer = 3.3 //3.3 so its above windows, not the same as them. anything below 3.3 puts the curtain beneath the window sprite in current build - opacity = 0 - -/obj/structure/shrine_seal/bullet_act(obj/projectile/P, def_zone) - if(!P.nodamage) - visible_message("[P] tears [src] down!") - qdel(src) - else - ..(P, def_zone) + layer = ABOVE_WINDOW_LAYER + integrity = 40 + integrity_max = 40 + integrity_failure = 30 /obj/structure/shrine_seal/attackby(obj/item/P, mob/user) if(P.is_wirecutter()) diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm index 147f5d25ff7..87795c2998e 100644 --- a/code/game/objects/structures/door_assembly.dm +++ b/code/game/objects/structures/door_assembly.dm @@ -324,7 +324,7 @@ // Airlock frames are indestructable, so bullets hitting them would always be stopped. // To fix this, airlock assemblies will sometimes let bullets pass through, since generally the sprite shows them partially open. -/obj/structure/door_assembly/bullet_act(var/obj/projectile/P) - if(prob(40)) // Chance for the frame to let the bullet keep going. - return PROJECTILE_CONTINUE +/obj/structure/door_assembly/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(prob(40)) + return PROJECTILE_IMPACT_PASSTHROUGH return ..() diff --git a/code/game/objects/structures/flora/rocks.dm b/code/game/objects/structures/flora/rocks.dm index 74528dfd6a4..63ec2bbd4ed 100644 --- a/code/game/objects/structures/flora/rocks.dm +++ b/code/game/objects/structures/flora/rocks.dm @@ -38,11 +38,12 @@ return . = ..() -/obj/structure/flora/rock/bullet_act(obj/projectile/P, def_zone) - if(P.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks. - GetDrilled() - return +/obj/structure/flora/rock/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT) + return + if(proj.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks. + GetDrilled() /obj/structure/flora/rock/proc/GetDrilled() new outcropdrop(get_turf(src),rand(mindrop,upperdrop)) diff --git a/code/game/objects/structures/flora/trees.dm b/code/game/objects/structures/flora/trees.dm index 3b6f73af5e2..75898d9168c 100644 --- a/code/game/objects/structures/flora/trees.dm +++ b/code/game/objects/structures/flora/trees.dm @@ -65,10 +65,10 @@ animate(src, transform=turn(M, shake_animation_degrees * shake_dir), pixel_x=init_px + 2*shake_dir, time=1) animate(transform=M, pixel_x=init_px, time=6, easing=ELASTIC_EASING) -/obj/structure/flora/tree/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual) +/obj/structure/flora/tree/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon) . = ..() // ruins some of the wood if you use high power modes or types - if(. > 5 && ((mode & (DAMAGE_MODE_ABLATING | DAMAGE_MODE_PIERCE | DAMAGE_MODE_SHRED)) || (flag == ARMOR_BOMB))) + if(. > 5 && ((damage_mode & (DAMAGE_MODE_ABLATING | DAMAGE_MODE_PIERCE | DAMAGE_MODE_SHRED)) || (damage_flag == ARMOR_BOMB))) product_amount -= round((. * 0.5) / integrity_max * initial(product_amount)) /obj/structure/flora/tree/atom_break() diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm index 4d97e12a8f6..58a1913f12f 100644 --- a/code/game/objects/structures/grille.dm +++ b/code/game/objects/structures/grille.dm @@ -43,36 +43,14 @@ return TRUE return ..() -/obj/structure/grille/bullet_act(var/obj/projectile/Proj) - //Flimsy grilles aren't so great at stopping projectiles. However they can absorb some of the impact - var/damage = Proj.get_structure_damage() - var/passthrough = 0 - - if(!damage) return - - //20% chance that the grille provides a bit more cover than usual. Support structure for example might take up 20% of the grille's area. - //If they click on the grille itself then we assume they are aiming at the grille itself and the extra cover behaviour is always used. - switch(Proj.damage_type) - if(BRUTE) - //bullets - if(Proj.original == src || prob(20)) - Proj.damage *= clamp( Proj.damage/60, 0, 0.5) - if(prob(max((damage-10)/25, 0))*100) - passthrough = 1 - else - Proj.damage *= clamp( Proj.damage/60, 0, 1) - passthrough = 1 - if(BURN) - //beams and other projectiles are either blocked completely by grilles or stop half the damage. - if(!(Proj.original == src || prob(20))) - Proj.damage *= 0.5 - passthrough = 1 - - if(passthrough) - . = PROJECTILE_CONTINUE - damage = between(0, (damage - Proj.damage)*(Proj.damage_type == BRUTE? 0.4 : 1), 10) //if the bullet passes through then the grille avoids most of the damage - - inflict_atom_damage(damage, Proj.damage_tier, Proj.damage_flag, Proj.damage_mode, ATTACK_TYPE_PROJECTILE, Proj) +/obj/structure/grille/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(proj.original_target == src) + impact_flags |= PROJECTILE_IMPACT_TRIVIAL + else + impact_flags |= PROJECTILE_IMPACT_TRIVIAL | PROJECTILE_IMPACT_PIERCE + . = ..() + if(impact_flags & PROJECTILE_IMPACT_PIERCE) + proj.dampen_on_pierce_experimental(src, 10, ARMOR_TIER_ABOVE) /obj/structure/grille/attackby(obj/item/W as obj, mob/user as mob) if(!istype(W)) @@ -120,12 +98,12 @@ WD.update_appearance() return ..() -/obj/structure/grille/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult) +/obj/structure/grille/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain) if(shock(attacker, 70)) return FALSE return ..() -/obj/structure/grille/melee_act(mob/user, obj/item/weapon, target_zone, mult) +/obj/structure/grille/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain) if(shock(user, 70, weapon)) return FALSE return ..() @@ -174,7 +152,7 @@ /obj/structure/grille/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) if(!destroyed) if(exposed_temperature > T0C + 1500) - inflict_atom_damage(1, flag = ARMOR_FIRE) + inflict_atom_damage(1, damage_flag = ARMOR_FIRE) ..() /obj/structure/grille/proc/is_on_frame() diff --git a/code/game/objects/structures/janicart.dm b/code/game/objects/structures/janicart.dm index a231a24ab6d..cd4858dd972 100644 --- a/code/game/objects/structures/janicart.dm +++ b/code/game/objects/structures/janicart.dm @@ -177,6 +177,7 @@ GLOBAL_LIST_BOILERPLATE(all_janitorial_carts, /obj/structure/janitorialcart) anchored = 1 density = 1 atom_flags = OPENCONTAINER + integrity_flags = INTEGRITY_INDESTRUCTIBLE //copypaste sorry var/amount_per_transfer_from_this = 5 //shit I dunno, adding this so syringes stop runtime erroring. --NeoFite var/obj/item/storage/bag/trash/mybag = null @@ -279,13 +280,10 @@ GLOBAL_LIST_BOILERPLATE(all_janitorial_carts, /obj/structure/janitorialcart) L.pixel_x = -13 L.pixel_y = 7 -/obj/structure/bed/chair/janicart/bullet_act(var/obj/projectile/Proj) - if(has_buckled_mobs()) - if(prob(85)) - var/mob/living/L = pick(buckled_mobs) - return L.bullet_act(Proj) - visible_message("[Proj] ricochets off the [callme]!") - +/obj/structure/bed/chair/janicart/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(has_buckled_mobs() && prob(85)) + return proj.impact_redirect(pick(buckled_mobs), args) + return ..() /obj/item/key name = "key" diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index df8f872b105..9e12dc21cec 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -39,14 +39,13 @@ desc = "Oh no, seven years of bad luck!" -/obj/structure/mirror/bullet_act(var/obj/projectile/Proj) - - if(prob(Proj.get_structure_damage() * 2)) +/obj/structure/mirror/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(prob(proj.get_structure_damage() * 2)) if(!shattered) shatter() else if(glass) playsound(src, 'sound/effects/hit_on_shattered_glass.ogg', 70, 1) - ..() /obj/structure/mirror/attackby(obj/item/I as obj, mob/user as mob) if(I.is_wrench()) diff --git a/code/game/objects/structures/props/beam_prism.dm b/code/game/objects/structures/props/beam_prism.dm index fad0ecd0328..8a2d8b095fd 100644 --- a/code/game/objects/structures/props/beam_prism.dm +++ b/code/game/objects/structures/props/beam_prism.dm @@ -1,5 +1,6 @@ //A series(?) of prisms for PoIs. The base one only works for beams. +// todo: refactor to /obj/structure/reflector /obj/structure/prop/prism name = "prismatic turret" desc = "A raised, externally powered 'turret'. It seems to have a massive crystal ring around its base." @@ -12,6 +13,12 @@ layer = 3.1 //Layer over projectiles. plane = -10 //Layer over projectiles. + /// projectile_type's to reflect + /// all of these must be on a projectile + var/projectile_type = PROJECTILE_TYPE_BEAM | PROJECTILE_TYPE_PHOTONIC + /// can't reflect these + var/projectile_type_cant = NONE + var/rotation_lock = 0 // Can you rotate the prism at all? var/free_rotate = 1 // Does the prism rotate in any direction, or only in the eight standard compass directions? var/external_control_lock = 0 // Does the prism only rotate from the controls of an external switch? @@ -19,8 +26,6 @@ var/compass_directions = list("North" = 0, "South" = 180, "East" = 90, "West" = 270, "Northwest" = 315, "Northeast" = 45, "Southeast" = 135, "Southwest" = 225) var/interaction_sound = 'sound/mecha/mechmove04.ogg' - var/redirect_type = /obj/projectile/beam - var/dialID = null var/obj/structure/prop/prismcontrol/remote_dial = null @@ -119,19 +124,17 @@ else animate(src, transform = turn(src.transform, rotate_degrees), time = 6) -/obj/structure/prop/prism/bullet_act(var/obj/projectile/Proj) - if(istype(Proj, redirect_type)) - if(!silent) - visible_message("\The [src] redirects \the [Proj]!") - flick("[initial(icon_state)]+glow", src) - - var/new_x = (1 * round(10 * cos(degrees_from_north - 90))) + x //Vectors vectors vectors. - var/new_y = (-1 * round(10 * sin(degrees_from_north - 90))) + y - var/turf/curloc = get_turf(src) - - Proj.penetrating += 1 // Needed for the beam to get out of the turret. - - Proj.redirect(new_x, new_y, curloc, null) +/obj/structure/prop/prism/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(((proj.projectile_type & projectile_type) != projectile_type) || (proj.projectile_type & projectile_type_cant)) + return ..() + if(!silent) + visible_message("\The [src] redirects \the [proj]!") + flick("[initial(icon_state)]+glow", src) + + // todo: this is not the right way; visuals are weird and the raycast is :/ + proj.physics_kick_forwards(16) + proj.set_angle(degrees_from_north) + return PROJECTILE_IMPACT_REFLECT /obj/structure/prop/prism/incremental free_rotate = 0 diff --git a/code/game/objects/structures/props/projectile_lock.dm b/code/game/objects/structures/props/projectile_lock.dm index 1bd5eaad95d..110d54c74a5 100644 --- a/code/game/objects/structures/props/projectile_lock.dm +++ b/code/game/objects/structures/props/projectile_lock.dm @@ -28,9 +28,11 @@ else icon_state = "[initial(icon_state)]" +// todo: this is shitcode rework this /obj/structure/prop/lock/projectile name = "beam lock" desc = "An esoteric object that responds to high intensity light." + integrity_flags = INTEGRITY_INDESTRUCTIBLE var/projectile_key = /obj/projectile/beam var/timed = 0 @@ -39,11 +41,14 @@ interaction_message = "The object remains inert to your touch." -/obj/structure/prop/lock/projectile/bullet_act(var/obj/projectile/Proj) - if(!istype(Proj, projectile_key) || timing) - return +/obj/structure/prop/lock/projectile/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(!istype(proj, projectile_key)) + return ..() - if(istype(Proj, /obj/projectile/beam/heavylaser/cannon) || istype(Proj, /obj/projectile/beam/emitter) || (Proj.damage >= 80 && Proj.damtype == BURN)) + if(timing) + return PROJECTILE_IMPACT_DELETE + + if(istype(proj, /obj/projectile/beam/heavylaser/cannon) || istype(proj, /obj/projectile/beam/emitter) || (proj.damage >= 80 && proj.damtype == BURN)) toggle_lock() visible_message("\The [src] [enabled ? "disengages" : "engages"] its locking mechanism.") @@ -51,3 +56,5 @@ timing = 1 spawn(time_limit) toggle_lock() + + return PROJECTILE_IMPACT_DELETE diff --git a/code/game/objects/structures/props/puzzledoor.dm b/code/game/objects/structures/props/puzzledoor.dm index 42416e90c3f..8f429ac12f7 100644 --- a/code/game/objects/structures/props/puzzledoor.dm +++ b/code/game/objects/structures/props/puzzledoor.dm @@ -26,10 +26,9 @@ return 0 return 1 -/obj/machinery/door/blast/puzzle/bullet_act(var/obj/projectile/Proj) - if(!istype(Proj, /obj/projectile/test)) - visible_message("\The [src] is completely unaffected by \the [Proj].") - qdel(Proj) //No piercing. No. +/obj/machinery/door/blast/puzzle/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + impact_flags &= ~PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH + return ..() /obj/machinery/door/blast/puzzle/legacy_ex_act(severity) visible_message("\The [src] is completely unaffected by the blast.") diff --git a/code/game/objects/structures/tables/interactions.dm b/code/game/objects/structures/tables/interactions.dm index 4f20ef5225d..6d48e296228 100644 --- a/code/game/objects/structures/tables/interactions.dm +++ b/code/game/objects/structures/tables/interactions.dm @@ -33,10 +33,10 @@ return 1 if (get_dist(P.starting, loc) <= 1) //Tables won't help you if people are THIS close return 1 - if (get_turf(P.original) == cover) + if (get_turf(P.original_target) == cover) var/chance = 20 - if (ismob(P.original)) - var/mob/M = P.original + if (ismob(P.original_target)) + var/mob/M = P.original_target if (M.lying) chance += 20 //Lying down lets you catch less bullets if(flipped==1) @@ -71,7 +71,7 @@ else playsound(loc, 'sound/weapons/tablehit1.ogg', 50, 1) var/turf/old_loc = loc - inflict_atom_damage(40, flag = ARMOR_MELEE) + inflict_atom_damage(40, damage_flag = ARMOR_MELEE) if(QDELETED(src)) // got broken visible_message(SPAN_DANGER("[src] shatters under the impact!")) diff --git a/code/game/objects/structures/target_stake.dm b/code/game/objects/structures/target_stake.dm index ab9122056f7..136ba2bc219 100644 --- a/code/game/objects/structures/target_stake.dm +++ b/code/game/objects/structures/target_stake.dm @@ -47,8 +47,7 @@ else return ..() -/obj/structure/target_stake/bullet_act(obj/projectile/P, def_zone) +/obj/structure/target_stake/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(pinned_target) - return pinned_target.bullet_act(P, def_zone) - else - return ..() + return proj.impact_redirect(pinned_target, args) + return ..() diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index b1f77bcbba0..4f5081b13d0 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -291,7 +291,7 @@ /obj/structure/window/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) . = ..() if (exposed_temperature > maximal_heat) - inflict_atom_damage(damage_per_fire_tick, flag = ARMOR_FIRE, gradual = TRUE) + inflict_atom_damage(damage_per_fire_tick, damage_flag = ARMOR_FIRE, damage_mode = DAMAGE_MODE_GRADUAL) /obj/structure/window/drop_products(method, atom/where) . = ..() diff --git a/code/game/turfs/defense.dm b/code/game/turfs/defense.dm deleted file mode 100644 index 34a90ac239a..00000000000 --- a/code/game/turfs/defense.dm +++ /dev/null @@ -1,2 +0,0 @@ -/turf/break_apart(method) - ScrapeAway(1, CHANGETURF_INHERIT_AIR) diff --git a/code/game/turfs/simulated/wall/defense.dm b/code/game/turfs/simulated/wall/defense.dm deleted file mode 100644 index 8811811f949..00000000000 --- a/code/game/turfs/simulated/wall/defense.dm +++ /dev/null @@ -1,48 +0,0 @@ -/turf/simulated/wall/throw_impacted(atom/movable/AM, datum/thrownthing/TT) - . = ..() - if(TT.throw_flags & THROW_AT_IS_GENTLE) - return - - // todo: /atom/movable/proc/throw_impact_attack(atom/target) - if(isitem(AM)) - var/obj/item/I = AM - inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), I.damage_tier, I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM) - else - inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), MELEE_TIER_LIGHT, ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM) - -/turf/simulated/wall/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, mult) - // todo: this should just be style.attack(attacker, src) - inflict_atom_damage(style.get_unarmed_damage(attacker, src), style.damage_tier, style.damage_flag, style.damage_mode, ATTACK_TYPE_UNARMED, attacker) - return NONE - -/turf/simulated/wall/melee_act(mob/user, obj/item/weapon, target_zone, mult) - inflict_atom_damage(weapon.damage_force, weapon.damage_tier, weapon.damage_flag, weapon.damage_mode, ATTACK_TYPE_MELEE, weapon) - return NONE - -/turf/simulated/wall/bullet_act(var/obj/projectile/Proj) - if(istype(Proj,/obj/projectile/beam)) - burn(2500) - else if(istype(Proj,/obj/projectile/ion)) - burn(500) - - if(Proj.damage_type == BURN && Proj.damage > 0) - if(thermite) - thermitemelt() - - if(Proj.ricochet_sounds && prob(15)) - playsound(src, pick(Proj.ricochet_sounds), 100, 1) - - inflict_atom_damage(Proj.get_structure_damage(), Proj.damage_tier, Proj.damage_flag, Proj.damage_mode, ATTACK_TYPE_PROJECTILE, Proj) - -/turf/simulated/wall/break_apart(method) - dismantle_wall() - -/turf/simulated/wall/damage_integrity(amount, gradual, do_not_break) - . = ..() - // todo: optimize - update_appearance() - -/turf/simulated/wall/heal_integrity(amount, gradual, do_not_fix) - . = ..() - // todo: optimize - update_appearance() diff --git a/code/game/turfs/simulated/wall/wall-construction.dm b/code/game/turfs/simulated/wall/wall-construction.dm new file mode 100644 index 00000000000..7633c8afaaf --- /dev/null +++ b/code/game/turfs/simulated/wall/wall-construction.dm @@ -0,0 +1,7 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Deconstruction *// + +/turf/simulated/wall/break_apart(method) + dismantle_wall() // this handles deletion too diff --git a/code/game/turfs/simulated/wall/wall-damage.dm b/code/game/turfs/simulated/wall/wall-damage.dm new file mode 100644 index 00000000000..e46661a6902 --- /dev/null +++ b/code/game/turfs/simulated/wall/wall-damage.dm @@ -0,0 +1,14 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Integrity - Direct Manipulation *// + +/turf/simulated/wall/damage_integrity(amount, gradual, do_not_break) + . = ..() + // todo: optimize + update_appearance() + +/turf/simulated/wall/heal_integrity(amount, gradual, do_not_fix) + . = ..() + // todo: optimize + update_appearance() diff --git a/code/game/turfs/simulated/wall/wall-defense.dm b/code/game/turfs/simulated/wall/wall-defense.dm new file mode 100644 index 00000000000..eeed876c8cc --- /dev/null +++ b/code/game/turfs/simulated/wall/wall-defense.dm @@ -0,0 +1,97 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +/turf/simulated/wall/throw_impacted(atom/movable/AM, datum/thrownthing/TT) + . = ..() + if(TT.throw_flags & THROW_AT_IS_GENTLE) + return + // todo: this method of detecting destruction is shitcode but turf refs don't change so qdeleted() won't work + var/old_type = type + // todo: /atom/movable/proc/throw_impact_attack(atom/target) + if(isitem(AM)) + var/obj/item/I = AM + inflict_atom_damage(I.throw_force * TT.get_damage_multiplier(src), I.damage_tier, I.damage_flag, I.damage_mode, ATTACK_TYPE_THROWN, AM) + else + inflict_atom_damage(AM.throw_force * TT.get_damage_multiplier(src), MELEE_TIER_LIGHT, ARMOR_MELEE, null, ATTACK_TYPE_THROWN, AM) + // turf refs don't change so while QDELETED() doesn't work this is a close approximate + // until we have a better system or we decide to pay some overhead to track with a number or something + if(old_type != type) + if(!density) + . |= COMPONENT_THROW_HIT_PIERCE // :trol: + return + +/turf/simulated/wall/unarmed_act(mob/attacker, datum/unarmed_attack/style, target_zone, datum/event_args/actor/clickchain/clickchain) + var/shieldcall_returns = atom_shieldcall_handle_unarmed_melee(style, clickchain, FALSE, NONE) + if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return CLICKCHAIN_FULL_BLOCKED + // todo: maybe the unarmed_style side should handle this? + run_damage_instance( + style.damage * (clickchain ? clickchain.damage_multiplier : 1), + style.damage_type, + style.damage_tier, + style.damage_flag, + style.damage_mode, + ATTACK_TYPE_UNARMED, + style, + NONE, + target_zone, + null, + null, + ) + return NONE + +/turf/simulated/wall/melee_act(mob/user, obj/item/weapon, target_zone, datum/event_args/actor/clickchain/clickchain) + var/shieldcall_returns = atom_shieldcall_handle_item_melee(weapon, clickchain, FALSE, NONE) + if(shieldcall_returns & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return CLICKCHAIN_FULL_BLOCKED + // todo: maybe the item side should handle this? + run_damage_instance( + weapon.damage_force * (clickchain ? clickchain.damage_multiplier : 1), + weapon.damtype, + weapon.damage_tier, + weapon.damage_flag, + weapon.damage_mode, + ATTACK_TYPE_MELEE, + weapon, + NONE, + target_zone, + null, + null, + ) + return NONE + +/turf/simulated/wall/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + // todo: this method of detecting destruction is shitcode but turf refs don't change so qdeleted() won't work + var/old_type = type + if(!(impact_flags & (PROJECTILE_IMPACT_BLOCKED | PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE))) + // todo: maybe the projectile side should handle this? + run_damage_instance( + proj.get_structure_damage() * bullet_act_args[BULLET_ACT_ARG_EFFICIENCY], + proj.damage_type, + proj.damage_tier, + proj.damage_flag, + proj.damage_mode, + ATTACK_TYPE_PROJECTILE, + proj, + NONE, + bullet_act_args[BULLET_ACT_ARG_ZONE], + null, + null, + ) + // turf refs don't change so while QDELETED() doesn't work this is a close approximate + // until we have a better system or we decide to pay some overhead to track with a number or something + if(old_type != type) + impact_flags |= PROJECTILE_IMPACT_TARGET_DELETED + return ..() + + //! legacy code handling + if((proj.projectile_type & (PROJECTILE_TYPE_ENERGY | PROJECTILE_TYPE_BEAM)) && !proj.nodamage && proj.damage) + burn(2500) + if(proj.damage_type == BURN && proj.damage && !proj.nodamage) + if(thermite) + thermitemelt() + if(proj.ricochet_sounds && prob(15)) + playsound(src, pick(proj.ricochet_sounds), 75, TRUE) + //! end + + return ..() diff --git a/code/game/turfs/simulated/wall/wall.dm b/code/game/turfs/simulated/wall/wall.dm index c0e7a557702..2112e988f2f 100644 --- a/code/game/turfs/simulated/wall/wall.dm +++ b/code/game/turfs/simulated/wall/wall.dm @@ -150,7 +150,7 @@ /turf/simulated/wall/adjacent_fire_act(turf/simulated/floor/adj_turf, datum/gas_mixture/adj_air, adj_temp, adj_volume) burn(adj_temp) if(adj_temp > material_outer.melting_point) - inflict_atom_damage(log(RAND_F(0.9, 1.1) * (adj_temp - material_outer.melting_point)), flag = ARMOR_FIRE, gradual = TRUE) + inflict_atom_damage(log(RAND_F(0.9, 1.1) * (adj_temp - material_outer.melting_point)), damage_flag = ARMOR_FIRE, damage_mode = DAMAGE_MODE_GRADUAL) return ..() @@ -182,11 +182,11 @@ ScrapeAway() if(2.0) if(prob(75)) - inflict_atom_damage(rand(150, 250), flag = ARMOR_BOMB) + inflict_atom_damage(rand(150, 250), damage_flag = ARMOR_BOMB) else dismantle_wall(1,1) if(3.0) - inflict_atom_damage(rand(0, 150), flag = ARMOR_BOMB) + inflict_atom_damage(rand(0, 150), damage_flag = ARMOR_BOMB) /turf/simulated/wall/proc/can_melt() return material_outer?.material_flags & MATERIAL_FLAG_UNMELTABLE diff --git a/code/game/turfs/simulated/wall/wall_attacks.dm b/code/game/turfs/simulated/wall/wall_attacks.dm index fcd2707cdfd..9737790e6ac 100644 --- a/code/game/turfs/simulated/wall/wall_attacks.dm +++ b/code/game/turfs/simulated/wall/wall_attacks.dm @@ -170,8 +170,8 @@ thermitemelt(user) return - else if( istype(I, /obj/item/melee/energy/blade) ) - var/obj/item/melee/energy/blade/EB = I + else if( istype(I, /obj/item/melee/ninja_energy_blade) ) + var/obj/item/melee/ninja_energy_blade/EB = I EB.spark_system.start() to_chat(user, "You slash \the [src] with \the [EB]; the thermite ignites!") @@ -222,7 +222,7 @@ dismantle_verb = "cutting" dismantle_sound = I.tool_sound // cut_delay *= 0.7 // Tools themselves now can shorten the time it takes. - else if(istype(I,/obj/item/melee/energy/blade)) + else if(istype(I,/obj/item/melee/ninja_energy_blade)) dismantle_sound = /datum/soundbyte/grouped/sparks dismantle_verb = "slicing" cut_delay *= 0.5 diff --git a/code/game/turfs/turf-construction.dm b/code/game/turfs/turf-construction.dm new file mode 100644 index 00000000000..d98289771d8 --- /dev/null +++ b/code/game/turfs/turf-construction.dm @@ -0,0 +1,7 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Deconstruction *// + +/turf/break_apart(method) + ScrapeAway(1, CHANGETURF_INHERIT_AIR) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index b5b07d730e7..1ad138f9fae 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1659,7 +1659,7 @@ if(!check_rights(R_FUN,0)) removed_paths += dirty_path continue - else if(ispath(path, /obj/item/melee/energy/blade))//Not an item one should be able to spawn./N + else if(ispath(path, /obj/item/melee/ninja_energy_blade))//Not an item one should be able to spawn./N if(!check_rights(R_FUN,0)) removed_paths += dirty_path continue diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm index b8c9b9ee87a..62daecd72cc 100644 --- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm +++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm @@ -1057,8 +1057,8 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null v = SSresearch if("SSprojectiles") v = SSprojectiles - if("SSfastprocess") - v = SSfastprocess + if("SSprocess_5fps") + v = SSprocess_5fps if("SSticker") v = SSticker if("SStimer") diff --git a/code/modules/admin/verbs/smite.dm b/code/modules/admin/verbs/smite.dm index 6430a74a451..6163ca1f500 100644 --- a/code/modules/admin/verbs/smite.dm +++ b/code/modules/admin/verbs/smite.dm @@ -130,13 +130,6 @@ to_chat(target,"You've been hit by bluespace artillery!") log_and_message_admins("[key_name(target)] has been hit by Bluespace Artillery fired by [key_name(user ? user : usr)]") - var/obj/effect/stop/S - S = new /obj/effect/stop - S.victim = target - S.loc = target.loc - spawn(20) - qdel(S) - var/turf/simulated/floor/T = get_turf(target) if(istype(T)) if(prob(80)) T.break_tile_to_plating() diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index b7520338a0e..ac03d8543b7 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -108,7 +108,7 @@ names = sortList(names) for(var/V in names) if(D.can_vv_get(V)) - variable_html += D.vv_get_var(V) + variable_html += D.vv_get_var(V, TRUE) if(VVING_A_LIST) var/list/L = D for(var/i in 1 to L.len) diff --git a/code/modules/assembly/infrared.dm b/code/modules/assembly/infrared.dm index 2f95cd1aadf..48e03c8cbd8 100644 --- a/code/modules/assembly/infrared.dm +++ b/code/modules/assembly/infrared.dm @@ -47,7 +47,7 @@ on = TRUE Rebuild() update_icon() - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) /obj/item/assembly/infra/proc/turn_off() if(!on) @@ -58,7 +58,7 @@ on = FALSE Rebuild() update_icon() - STOP_PROCESSING(SSfastprocess, src) + STOP_PROCESSING(SSprocess_5fps, src) /obj/item/assembly/infra/update_icon() cut_overlays() diff --git a/code/modules/atmospherics/environmental/zas/debug.dm b/code/modules/atmospherics/environmental/zas/debug.dm index 40ea525478b..c4c31b9f9d1 100644 --- a/code/modules/atmospherics/environmental/zas/debug.dm +++ b/code/modules/atmospherics/environmental/zas/debug.dm @@ -1,11 +1,11 @@ -var/image/assigned = image('icons/testing/Zone.dmi', icon_state = "assigned") -var/image/created = image('icons/testing/Zone.dmi', icon_state = "created") -var/image/merged = image('icons/testing/Zone.dmi', icon_state = "merged") -var/image/invalid_zone = image('icons/testing/Zone.dmi', icon_state = "invalid") -var/image/air_blocked = image('icons/testing/Zone.dmi', icon_state = "block") -var/image/zone_blocked = image('icons/testing/Zone.dmi', icon_state = "zoneblock") -var/image/blocked = image('icons/testing/Zone.dmi', icon_state = "fullblock") -var/image/mark = image('icons/testing/Zone.dmi', icon_state = "mark") +GLOBAL_DATUM_INIT(zas_debug_image_assigned, /image, image('icons/testing/Zone.dmi', icon_state = "assigned")) +GLOBAL_DATUM_INIT(zas_debug_image_created, /image, image('icons/testing/Zone.dmi', icon_state = "created")) +GLOBAL_DATUM_INIT(zas_debug_image_merged, /image, image('icons/testing/Zone.dmi', icon_state = "merged")) +GLOBAL_DATUM_INIT(zas_debug_image_invalid_zone, /image, image('icons/testing/Zone.dmi', icon_state = "invalid")) +GLOBAL_DATUM_INIT(zas_debug_image_air_blocked, /image, image('icons/testing/Zone.dmi', icon_state = "block")) +GLOBAL_DATUM_INIT(zas_debug_image_zone_blocked, /image, image('icons/testing/Zone.dmi', icon_state = "zoneblock")) +GLOBAL_DATUM_INIT(zas_debug_image_blocked, /image, image('icons/testing/Zone.dmi', icon_state = "fullblock")) +GLOBAL_DATUM_INIT(zas_debug_image_mark, /image, image('icons/testing/Zone.dmi', icon_state = "mark")) /datum/zas_edge/var/dbg_out = 0 diff --git a/code/modules/awaymissions/loot_vr.dm b/code/modules/awaymissions/loot_vr.dm index 504fd2d144d..7b50df091c3 100644 --- a/code/modules/awaymissions/loot_vr.dm +++ b/code/modules/awaymissions/loot_vr.dm @@ -128,7 +128,7 @@ prob(10);/obj/item/melee/baton,\ prob(10);/obj/item/melee/telebaton,\ prob(10);/obj/item/melee/classic_baton,\ - prob(10);/obj/item/melee/energy/sword,\ + prob(10);/obj/item/melee/transforming/energy/sword,\ prob(9);/obj/item/gun/ballistic/automatic/wt550/lethal,\ prob(9);/obj/item/gun/ballistic/automatic/pdw,\ prob(9);/obj/item/gun/ballistic/derringer,\ @@ -145,7 +145,7 @@ prob(8);/obj/item/gun/energy/xray,\ prob(8);/obj/item/gun/ballistic/automatic/c20r,\ prob(8);/obj/item/gun/ballistic/automatic/stg,\ - prob(8);/obj/item/melee/energy/sword,\ + prob(8);/obj/item/melee/transforming/energy/sword,\ /* prob(8);/obj/item/gun/ballistic/automatic/m41a,\ */ prob(7);/obj/item/gun/energy/captain,\ prob(7);/obj/item/gun/energy/sniperrifle,\ diff --git a/code/modules/blob2/blobs/base_blob.dm b/code/modules/blob2/blobs/base_blob.dm index 19e8ade9f76..6b44bb0e482 100644 --- a/code/modules/blob2/blobs/base_blob.dm +++ b/code/modules/blob2/blobs/base_blob.dm @@ -250,18 +250,17 @@ var/list/blobs = list() adjust_integrity_blob(-damage) return -/obj/structure/blob/bullet_act(var/obj/projectile/P) - if(!P) - return +/obj/structure/blob/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() - if(istype(P.firer) && P.firer.faction == "blob") + if(istype(proj.firer) && proj.firer.faction == "blob") return - var/damage = P.get_structure_damage() // So tasers don't hurt the blob. + var/damage = proj.get_structure_damage() // So tasers don't hurt the blob. if(!damage) return - switch(P.damage_type) + switch(proj.damage_type) if(BRUTE) if(overmind) damage *= overmind.blob_type.brute_multiplier @@ -270,12 +269,10 @@ var/list/blobs = list() damage *= overmind.blob_type.burn_multiplier if(overmind) - damage = overmind.blob_type.on_received_damage(src, damage, P.damage_type, P.firer) + damage = overmind.blob_type.on_received_damage(src, damage, proj.damage_type, proj.firer) adjust_integrity_blob(-damage) - return ..() - /obj/structure/blob/water_act(amount) if(overmind) overmind.blob_type.on_water(src, amount) @@ -304,4 +301,4 @@ var/list/blobs = list() qdel(src) /turf/simulated/wall/blob_act() - inflict_atom_damage(100, flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE) + inflict_atom_damage(100, damage_flag = ARMOR_MELEE, attack_type = ATTACK_TYPE_MELEE) diff --git a/code/modules/clothing/clothing_accessories.dm b/code/modules/clothing/clothing_accessories.dm index 40fc520530b..74d5a627e04 100644 --- a/code/modules/clothing/clothing_accessories.dm +++ b/code/modules/clothing/clothing_accessories.dm @@ -288,16 +288,6 @@ A.emp_act(severity) ..() -/obj/item/clothing/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - . = ..() - if((. == 0) && LAZYLEN(accessories)) - for(var/obj/item/I in accessories) - var/check = I.handle_shield(user, damage, damage_source, attacker, def_zone, attack_text) - - if(check != 0) // Projectiles sometimes use negatives IIRC, 0 is only returned if something is not blocked. - . = check - break - /obj/item/clothing/strip_menu_options(mob/user) . = ..() if(!length(accessories)) diff --git a/code/modules/clothing/gloves/arm_guards.dm b/code/modules/clothing/gloves/arm_guards.dm index 55642593b12..319237e3d25 100644 --- a/code/modules/clothing/gloves/arm_guards.dm +++ b/code/modules/clothing/gloves/arm_guards.dm @@ -28,14 +28,6 @@ return FALSE return TRUE -/obj/item/clothing/gloves/arm_guard/laserproof - name = "ablative arm guards" - desc = "These arm guards will protect your hands and arms from energy weapons." - icon_state = "arm_guards_laser" - item_state_slots = list(SLOT_ID_RIGHT_HAND = "swat", SLOT_ID_LEFT_HAND = "swat") - siemens_coefficient = 0.4 //This is worse than the other ablative pieces, to avoid this from becoming the poor warden's insulated gloves. - armor_type = /datum/armor/station/ablative - /obj/item/clothing/gloves/arm_guard/bulletproof name = "ballistic arm guards" desc = "These arm guards will protect your hands and arms from ballistic weapons." diff --git a/code/modules/clothing/sets/armor/ablative.dm b/code/modules/clothing/sets/armor/ablative.dm new file mode 100644 index 00000000000..f8c39360c51 --- /dev/null +++ b/code/modules/clothing/sets/armor/ablative.dm @@ -0,0 +1,64 @@ +// todo: this shouldn't be a signalled shieldcall, as +/obj/item/clothing/suit/armor/laserproof + name = "ablative armor vest" + desc = "A vest that excels in protecting the wearer against energy projectiles." + icon_state = "armor_reflec" + blood_overlay_type = "armor" + armor_type = /datum/armor/station/ablative + siemens_coefficient = 0.1 + +/obj/item/clothing/suit/armor/laserproof/equipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + // if you're reading this: this is not the right way to do shieldcalls + // this is just a lazy implementation + // signals have highest priority, this as a piece of armor shouldn't have that. + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall)) + +/obj/item/clothing/suit/armor/laserproof/unequipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL) + +/obj/item/clothing/suit/armor/laserproof/proc/shieldcall(mob/defending, list/shieldcall_args, fake_attack) + var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON] + var/def_zone = shieldcall_args[SHIELDCALL_ARG_HIT_ZONE] + if(istype(damage_source, /obj/projectile/energy) || istype(damage_source, /obj/projectile/beam)) + var/obj/projectile/P = damage_source + + if(P.reflected) // Can't reflect twice + return + + var/reflectchance = 50 - round(shieldcall_args[SHIELDCALL_ARG_DAMAGE]/3) + if(!(def_zone in list(BP_TORSO, BP_GROIN))) + reflectchance /= 2 + if(P.starting && prob(reflectchance)) + visible_message("\The [defending]'s [src.name] reflects [P]!") + + // Find a turf near or on the original location to bounce to + var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) + var/turf/curloc = get_turf(defending) + + // redirect the projectile + P.legacy_redirect(new_x, new_y, curloc, defending) + P.reflected = 1 + shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_PASSTHROUGH | SHIELDCALL_FLAG_ATTACK_REDIRECT | SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_TERMINATE + +/obj/item/clothing/gloves/arm_guard/laserproof + name = "ablative arm guards" + desc = "These arm guards will protect your hands and arms from energy weapons." + icon_state = "arm_guards_laser" + item_state_slots = list(SLOT_ID_RIGHT_HAND = "swat", SLOT_ID_LEFT_HAND = "swat") + siemens_coefficient = 0.4 //This is worse than the other ablative pieces, to avoid this from becoming the poor warden's insulated gloves. + armor_type = /datum/armor/station/ablative + +/obj/item/clothing/shoes/leg_guard/laserproof + name = "ablative leg guards" + desc = "These will protect your legs and feet from energy weapons." + icon_state = "leg_guards_laser" + item_state_slots = list(SLOT_ID_RIGHT_HAND = "jackboots", SLOT_ID_LEFT_HAND = "jackboots") + siemens_coefficient = 0.1 + armor_type = /datum/armor/station/ablative diff --git a/code/modules/clothing/shoes/leg_guards.dm b/code/modules/clothing/shoes/leg_guards.dm index b88fed8ce0a..e77aa2e7e41 100644 --- a/code/modules/clothing/shoes/leg_guards.dm +++ b/code/modules/clothing/shoes/leg_guards.dm @@ -31,14 +31,6 @@ return FALSE return TRUE -/obj/item/clothing/shoes/leg_guard/laserproof - name = "ablative leg guards" - desc = "These will protect your legs and feet from energy weapons." - icon_state = "leg_guards_laser" - item_state_slots = list(SLOT_ID_RIGHT_HAND = "jackboots", SLOT_ID_LEFT_HAND = "jackboots") - siemens_coefficient = 0.1 - armor_type = /datum/armor/station/ablative - /obj/item/clothing/shoes/leg_guard/bulletproof name = "ballistic leg guards" desc = "These will protect your legs and feet from ballistic weapons." diff --git a/code/modules/clothing/spacesuits/alien.dm b/code/modules/clothing/spacesuits/alien.dm index bf61251514e..6821a426cce 100644 --- a/code/modules/clothing/spacesuits/alien.dm +++ b/code/modules/clothing/spacesuits/alien.dm @@ -33,7 +33,7 @@ w_class = WEIGHT_CLASS_NORMAL atom_flags = PHORONGUARD clothing_flags = CLOTHING_THICK_MATERIAL | CLOTHING_INJECTION_PORT - allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs,/obj/item/tank) + allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs,/obj/item/tank) armor_type = /datum/armor/vox/space/armored siemens_coefficient = 0.2 heat_protection_cover = UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS diff --git a/code/modules/clothing/spacesuits/syndi.dm b/code/modules/clothing/spacesuits/syndi.dm index 51f0fdf7415..313e9fab76c 100644 --- a/code/modules/clothing/spacesuits/syndi.dm +++ b/code/modules/clothing/spacesuits/syndi.dm @@ -11,7 +11,7 @@ icon_state = "syndicate" desc = "A crimson spacesuit sporting clean lines and durable plating. Robust, reliable, and slightly suspicious." w_class = WEIGHT_CLASS_NORMAL - allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs,/obj/item/tank/emergency/oxygen) + allowed = list(/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs,/obj/item/tank/emergency/oxygen) armor_type = /datum/armor/agent/space siemens_coefficient = 0.6 diff --git a/code/modules/clothing/spacesuits/void/merc.dm b/code/modules/clothing/spacesuits/void/merc.dm index be3f3ab703d..b9252aa9e2f 100644 --- a/code/modules/clothing/spacesuits/void/merc.dm +++ b/code/modules/clothing/spacesuits/void/merc.dm @@ -20,7 +20,7 @@ weight = ITEM_WEIGHT_VOIDSUIT w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/merc/space - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 /obj/item/clothing/head/helmet/space/void/merc/fire @@ -38,7 +38,7 @@ desc = "A blackened suit that has had many of its protective plates coated in or replaced with high-grade thermal insulation, to protect against incineration. Property of Gorlex Marauders." armor_type = /datum/armor/merc/space/ghostrider max_heat_protection_temperature = FIRESUIT_MAX_HEAT_PROTECTION_TEMPERATURE - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs,/obj/item/material/twohanded/fireaxe,/obj/item/flamethrower) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs,/obj/item/material/twohanded/fireaxe,/obj/item/flamethrower) siemens_coefficient = 0.7 //Soviet Void Suit @@ -91,7 +91,7 @@ item_state_slots = list(SLOT_ID_RIGHT_HAND = "syndie_voidsuit", SLOT_ID_LEFT_HAND = "syndie_voidsuit") w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/merc/space/clown - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 //Four below avalible through cargo @@ -111,7 +111,7 @@ desc = "One of the few combat-grade suits avalible in the frontier, and the poster-child of Hephaestus Industries. Comes equipped with a wrist-bound oxygen timer." w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/station/secsuit - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 species_restricted = null helmet_type = /obj/item/clothing/head/helmet/space/void/odst @@ -131,7 +131,7 @@ desc = "A standard Icarus line suit that has been repourposed to protect from heavier biohazards." w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/exploration/space - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 species_restricted = null helmet_type = /obj/item/clothing/head/helmet/space/void/odst_med @@ -151,7 +151,7 @@ desc = "Favoured suit of deep-space engineers, comfortable and comparable to suits avalible to Nanotrasen Engineers. Comes equipped with a wrist-bound oxygen timer." w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/engineering/space - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 species_restricted = null helmet_type = /obj/item/clothing/head/helmet/space/void/odst_eng @@ -171,7 +171,7 @@ desc = "Cheaper version of the main Icarus line, often marketed to Frontier settlements. Perfect for Expeditions." w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/exploration/space - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 species_restricted = null helmet_type = /obj/item/clothing/head/helmet/space/void/odst_exp @@ -195,7 +195,7 @@ item_state_slots = list(SLOT_ID_RIGHT_HAND = "syndie_voidsuit", SLOT_ID_LEFT_HAND = "syndie_voidsuit") w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/merc/space - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 species_restricted = null helmet_type = /obj/item/clothing/head/helmet/space/void/odst_necro @@ -207,6 +207,6 @@ item_state_slots = list(SLOT_ID_RIGHT_HAND = "syndie_voidsuit", SLOT_ID_LEFT_HAND = "syndie_voidsuit") w_class = WEIGHT_CLASS_NORMAL armor_type = /datum/armor/merc/space - allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/energy/sword,/obj/item/handcuffs) + allowed = list(/obj/item/flashlight,/obj/item/tank,/obj/item/suit_cooling_unit,/obj/item/gun,/obj/item/ammo_magazine,/obj/item/ammo_casing,/obj/item/melee/baton,/obj/item/melee/transforming/energy/sword,/obj/item/handcuffs) siemens_coefficient = 0.6 species_restricted = null diff --git a/code/modules/clothing/spacesuits/void/xeno/tajara.dm b/code/modules/clothing/spacesuits/void/xeno/tajara.dm index 44e08971eed..2c1c2e0bced 100644 --- a/code/modules/clothing/spacesuits/void/xeno/tajara.dm +++ b/code/modules/clothing/spacesuits/void/xeno/tajara.dm @@ -15,7 +15,7 @@ /obj/item/ammo_magazine, /obj/item/ammo_casing, /obj/item/melee/baton, - /obj/item/melee/energy/sword, + /obj/item/melee/transforming/energy/sword, /obj/item/handcuffs ) species_restricted = list(SPECIES_TAJ) @@ -46,7 +46,7 @@ /obj/item/ammo_magazine, /obj/item/ammo_casing, /obj/item/melee/baton, - /obj/item/melee/energy/sword, + /obj/item/melee/transforming/energy/sword, /obj/item/handcuffs ) species_restricted = list(SPECIES_TAJ) diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 99728ffc838..5914082a641 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -79,37 +79,6 @@ item_state_slots = list(SLOT_ID_RIGHT_HAND = "bulletproof_new", SLOT_ID_LEFT_HAND = "bulletproof_new") blood_overlay_type = "armor" -/obj/item/clothing/suit/armor/laserproof - name = "ablative armor vest" - desc = "A vest that excels in protecting the wearer against energy projectiles." - icon_state = "armor_reflec" - blood_overlay_type = "armor" - armor_type = /datum/armor/station/ablative - siemens_coefficient = 0.1 - -/obj/item/clothing/suit/armor/laserproof/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(istype(damage_source, /obj/projectile/energy) || istype(damage_source, /obj/projectile/beam)) - var/obj/projectile/P = damage_source - - if(P.reflected) // Can't reflect twice - return ..() - - var/reflectchance = 40 - round(damage/3) - if(!(def_zone in list(BP_TORSO, BP_GROIN))) - reflectchance /= 2 - if(P.starting && prob(reflectchance)) - visible_message("\The [user]'s [src.name] reflects [attack_text]!") - - // Find a turf near or on the original location to bounce to - var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/curloc = get_turf(user) - - // redirect the projectile - P.redirect(new_x, new_y, curloc, user) - P.reflected = 1 - - return PROJECTILE_CONTINUE // complete projectile permutation /obj/item/clothing/suit/armor/combat name = "combat vest" @@ -193,7 +162,22 @@ blood_overlay_type = "armor" armor_type = /datum/armor/none -/obj/item/clothing/suit/armor/reactive/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") +/obj/item/clothing/suit/armor/reactive/equipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + // if you're reading this: this is not the right way to do shieldcalls + // this is just a lazy implementation + // signals have highest priority, this as a piece of armor shouldn't have that. + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall)) + +/obj/item/clothing/suit/armor/reactive/unequipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL) + +/obj/item/clothing/suit/armor/reactive/proc/shieldcall(mob/user, list/shieldcall_args, fake_attack) if(prob(50)) user.visible_message("The reactive teleport system flings [user] clear of the attack!") var/list/turfs = new/list() @@ -211,10 +195,8 @@ spark_system.set_up(5, 0, user.loc) spark_system.start() playsound(user.loc, /datum/soundbyte/grouped/sparks, 50, 1) - - user.loc = picked - return PROJECTILE_FORCE_MISS - return 0 + user.forceMove(picked) + shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAG_ATTACK_BLOCKED | SHIELDCALL_FLAG_ATTACK_PASSTHROUGH /obj/item/clothing/suit/armor/reactive/attack_self(mob/user) . = ..() @@ -250,6 +232,13 @@ valid_accessory_slots = null var/block_chance = 20 +/obj/item/clothing/suit/armor/alien/mob_armorcall(mob/defending, list/shieldcall_args, fake_attack) + if(prob(block_chance)) + defending.visible_message(SPAN_DANGER("[src] completely absorbs [RESOLVE_SHIELDCALL_ATTACK_TEXT(shieldcall_args)]!")) + shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAGS_FOR_COMPLETE_BLOCK + return + return ..() + /obj/item/clothing/suit/armor/alien/tank name = "alien protection suit" desc = "It's really resilient yet lightweight, so it's probably meant to be armor. Strangely enough it seems to have been designed for a humanoid shape." @@ -260,12 +249,6 @@ armor_type = /datum/armor/alien/heavy block_chance = 40 -/obj/item/clothing/suit/armor/alien/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(prob(block_chance)) - user.visible_message("\The [src] completely absorbs [attack_text]!") - return TRUE - return FALSE - //Non-hardsuit ERT armor. /obj/item/clothing/suit/armor/vest/ert name = "emergency response team armor" diff --git a/code/modules/clothing/under/accessories/armor.dm b/code/modules/clothing/under/accessories/armor.dm index 612445c6eb7..f42b06b1c0a 100644 --- a/code/modules/clothing/under/accessories/armor.dm +++ b/code/modules/clothing/under/accessories/armor.dm @@ -178,28 +178,44 @@ armor_type = /datum/armor/station/ablative siemens_coefficient = 0.2 -/obj/item/clothing/accessory/armor/armorplate/ablative/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") +/obj/item/clothing/accessory/armor/armorplate/ablative/equipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + // if you're reading this: this is not the right way to do shieldcalls + // this is just a lazy implementation + // signals have highest priority, this as a piece of armor shouldn't have that. + RegisterSignal(user, COMSIG_ATOM_SHIELDCALL, PROC_REF(shieldcall)) + +/obj/item/clothing/accessory/armor/armorplate/ablative/unequipped(mob/user, slot, flags) + . = ..() + if(slot == SLOT_ID_HANDS) + return + UnregisterSignal(user, COMSIG_ATOM_SHIELDCALL) + +/obj/item/clothing/accessory/armor/armorplate/ablative/proc/shieldcall(mob/defending, list/shieldcall_args, fake_attack) + var/damage_source = shieldcall_args[SHIELDCALL_ARG_WEAPON] + var/def_zone = shieldcall_args[SHIELDCALL_ARG_HIT_ZONE] if(istype(damage_source, /obj/projectile/energy) || istype(damage_source, /obj/projectile/beam)) var/obj/projectile/P = damage_source if(P.reflected) - return ..() + return - var/reflectchance = 20 - round(damage/3) + var/reflectchance = 20 - round(shieldcall_args[SHIELDCALL_ARG_DAMAGE]/3) if(!(def_zone in list(BP_TORSO, BP_GROIN))) reflectchance /= 2 if(P.starting && prob(reflectchance)) - visible_message("\The [user]'s [src.name] reflects [attack_text]!") + visible_message("\The [defending]'s [src.name] reflects [P]!") var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2) - var/turf/curloc = get_turf(user) + var/turf/curloc = get_turf(defending) - P.redirect(new_x, new_y, curloc, user) + P.legacy_redirect(new_x, new_y, curloc, defending) P.reflected = 1 - - return PROJECTILE_CONTINUE + shieldcall_args[SHIELDCALL_ARG_FLAGS] |= SHIELDCALL_FLAGS_FOR_PROJECTILE_DEFLECT ////////////// //Arm guards diff --git a/code/modules/clothing/under/accessories/holster.dm b/code/modules/clothing/under/accessories/holster.dm index 4107b17d545..20d169cc85e 100644 --- a/code/modules/clothing/under/accessories/holster.dm +++ b/code/modules/clothing/under/accessories/holster.dm @@ -157,7 +157,7 @@ desc = "A handsome synthetic leather scabbard with matching belt." icon_state = "holster_machete" concealed_holster = 0 - can_hold = list(/obj/item/material/knife/machete, /obj/item/melee/energy/hfmachete, /obj/item/reagent_containers/spray, /obj/item/soap, + can_hold = list(/obj/item/material/knife/machete, /obj/item/melee/transforming/hfmachete, /obj/item/reagent_containers/spray, /obj/item/soap, /obj/item/c_tube, /obj/item/bikehorn) cant_hold = list(/obj/item/material/knife/machete/armblade) sound_in = 'sound/effects/holster/sheathin.ogg' diff --git a/code/modules/examine/descriptions/weapons.dm b/code/modules/examine/descriptions/weapons.dm index 835d35882dc..1120bdcfb6c 100644 --- a/code/modules/examine/descriptions/weapons.dm +++ b/code/modules/examine/descriptions/weapons.dm @@ -66,7 +66,7 @@ set to 'harm', you will inflict damage when using it, regardless if it is on or not. Each stun reduces the baton's charge, which can be replenished by \ putting it inside a weapon recharger." -/obj/item/melee/energy/sword +/obj/item/melee/transforming/energy/sword description_antag = "The energy sword is a very strong melee weapon, capable of severing limbs easily, if they are targeted. It can also has a chance \ to block projectiles and melee attacks while it is on and being held. The sword can be toggled on or off by using it in your hand. While it is off, \ it can be concealed in your pocket or bag." diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index e553eba475a..93f79ff03da 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -328,7 +328,7 @@ proc/check_panel(mob/M) return GLOBAL_LIST_INIT(non_fakeattack_weapons, list(/obj/item/gun/ballistic, /obj/item/ammo_magazine/a357/speedloader,\ - /obj/item/gun/energy/crossbow, /obj/item/melee/energy/sword,\ + /obj/item/gun/energy/crossbow, /obj/item/melee/transforming/energy/sword,\ /obj/item/storage/box/syndicate, /obj/item/storage/box/emps,\ /obj/item/cartridge/syndicate, /obj/item/clothing/under/chameleon,\ /obj/item/clothing/shoes/syndigaloshes, /obj/item/card/id/syndicate,\ diff --git a/code/modules/food/drinks/bottle.dm b/code/modules/food/drinks/bottle.dm index ac9f648ac27..606531dcaec 100644 --- a/code/modules/food/drinks/bottle.dm +++ b/code/modules/food/drinks/bottle.dm @@ -162,7 +162,7 @@ if(target_zone == "head" && istype(L, /mob/living/carbon/)) user.visible_message("\The [user] smashes [src] over [L]'s head!") if(weaken_duration) - L.apply_effect(min(weaken_duration, 5), WEAKEN, blocked) // Never weaken more than a flash! + L.apply_effect(min(weaken_duration, 5), WEAKEN) // Never weaken more than a flash! else user.visible_message("\The [user] smashes [src] into [L]!") diff --git a/code/modules/gamemaster/actions/electrified_door.dm b/code/modules/gamemaster/actions/electrified_door.dm index e4ba9c2efb3..c1d4ff1ad91 100644 --- a/code/modules/gamemaster/actions/electrified_door.dm +++ b/code/modules/gamemaster/actions/electrified_door.dm @@ -63,7 +63,7 @@ if(!chosen_door || !chosen_door.arePowerSystemsOn()) return chosen_door.visible_message("\The [chosen_door]'s hydraulics detonate!") - chosen_door.fragmentate(get_turf(chosen_door), rand(5, 10), rand(3, 5), list(/obj/projectile/bullet/pellet/fragment/tank/small)) + chosen_door.shrapnel_explosion(rand(5, 10), rand(3, 5), /obj/projectile/bullet/pellet/fragment/tank/small) explosion(get_turf(chosen_door),-1,-1,2,3) chosen_door.lock() diff --git a/code/modules/ghostroles/spawnpoint.dm b/code/modules/ghostroles/spawnpoint.dm index 5d424b155bb..7439c71193e 100644 --- a/code/modules/ghostroles/spawnpoint.dm +++ b/code/modules/ghostroles/spawnpoint.dm @@ -20,7 +20,7 @@ GLOBAL_LIST_EMPTY(ghostrole_spawnpoints) var/spawntext /datum/component/ghostrole_spawnpoint/Initialize(role_type, allowed_spawns = INFINITY, list/params, datum/callback/proc_to_call_or_callback, notify_ghosts = TRUE, spawntext) - if((. = ..()) & COMPONENT_INCOMPATIBLE) + if((. = ..()) == COMPONENT_INCOMPATIBLE) return if(!isatom(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/modules/hardsuits/_rig.dm b/code/modules/hardsuits/_rig.dm index 44d584ac169..a0283f4234a 100644 --- a/code/modules/hardsuits/_rig.dm +++ b/code/modules/hardsuits/_rig.dm @@ -1136,11 +1136,6 @@ if(!CHECK_MOBILITY(user, MOBILITY_CAN_MOVE)) return - if(locate(/obj/effect/stop/, wearer.loc)) - for(var/obj/effect/stop/S in wearer.loc) - if(S.victim == wearer) - return - if(!wearer.lastarea) wearer.lastarea = get_area(wearer.loc) diff --git a/code/modules/hardsuits/modules/combat.dm b/code/modules/hardsuits/modules/combat.dm index dc2d4874ef6..66cdec37be3 100644 --- a/code/modules/hardsuits/modules/combat.dm +++ b/code/modules/hardsuits/modules/combat.dm @@ -202,7 +202,7 @@ /obj/item/hardsuit_module/mounted/energy_blade/process(delta_time) if(holder && holder.wearer) - if(!(locate(/obj/item/melee/energy/blade) in holder.wearer)) + if(!(locate(/obj/item/melee/ninja_energy_blade) in holder.wearer)) deactivate() return 0 @@ -219,7 +219,7 @@ deactivate() return - var/obj/item/melee/energy/blade/blade = new(M) + var/obj/item/melee/ninja_energy_blade/blade = new(M) blade.creator = M M.put_in_hands(blade) @@ -232,7 +232,7 @@ if(!M) return - for(var/obj/item/melee/energy/blade/blade in M.contents) + for(var/obj/item/melee/ninja_energy_blade/blade in M.contents) qdel(blade) /obj/item/hardsuit_module/fabricator diff --git a/code/modules/hardsuits/suits/merc.dm b/code/modules/hardsuits/suits/merc.dm index ce1877f6d35..956e6a901cc 100644 --- a/code/modules/hardsuits/suits/merc.dm +++ b/code/modules/hardsuits/suits/merc.dm @@ -31,7 +31,7 @@ /obj/item/ammo_magazine, /obj/item/ammo_casing, /obj/item/melee/baton, - /obj/item/melee/energy/sword, + /obj/item/melee/transforming/energy/sword, /obj/item/handcuffs, /obj/item/bluespace_radio, ) diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm index 0854a36c59c..0afe78ce86f 100644 --- a/code/modules/holodeck/HolodeckObjects.dm +++ b/code/modules/holodeck/HolodeckObjects.dm @@ -239,16 +239,7 @@ /obj/item/holo/esword/red lcolor = "#FF0000" -/obj/item/holo/esword/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(active && default_parry_check(user, attacker, damage_source) && prob(50)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - - var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() - spark_system.set_up(5, 0, user.loc) - spark_system.start() - playsound(user.loc, 'sound/weapons/blade1.ogg', 50, 1) - return TRUE - return FALSE +// todo: the parry system was removed from this because that sucks maybe readd it later lol /obj/item/holo/esword/attack_self(mob/user) . = ..() diff --git a/code/modules/holomap/holomap_datum.dm b/code/modules/holomap/holomap_datum.dm index f6d4f61bba5..df8f754888d 100644 --- a/code/modules/holomap/holomap_datum.dm +++ b/code/modules/holomap/holomap_datum.dm @@ -14,8 +14,8 @@ if(isAI) T = get_turf(user.client.eye) - cursor.pixel_x = (T.x - 6 + HOLOMAP_PIXEL_OFFSET_X(T.z)) * PIXEL_MULTIPLIER - cursor.pixel_y = (T.y - 6 + HOLOMAP_PIXEL_OFFSET_Y(T.z)) * PIXEL_MULTIPLIER + cursor.pixel_x = (T.x - 6 + HOLOMAP_PIXEL_OFFSET_X(T.z)) * (WORLD_ICON_SIZE / 32) + cursor.pixel_y = (T.y - 6 + HOLOMAP_PIXEL_OFFSET_Y(T.z)) * (WORLD_ICON_SIZE / 32) legend.pixel_x = HOLOMAP_LEGEND_X(T.z) legend.pixel_y = HOLOMAP_LEGEND_Y(T.z) diff --git a/code/modules/hydroponics/trays/tray.dm b/code/modules/hydroponics/trays/tray.dm index e1689c458cb..6003b3b9eab 100644 --- a/code/modules/hydroponics/trays/tray.dm +++ b/code/modules/hydroponics/trays/tray.dm @@ -201,26 +201,22 @@ check_health() update_icon() -/obj/machinery/portable_atmospherics/hydroponics/bullet_act(var/obj/projectile/Proj) - +/obj/machinery/portable_atmospherics/hydroponics/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() //Don't act on seeds like dionaea that shouldn't change. if(seed && seed.get_trait(TRAIT_IMMUTABLE) > 0) return //Override for somatoray projectiles. - if(istype(Proj ,/obj/projectile/energy/floramut)&& prob(20)) - if(istype(Proj, /obj/projectile/energy/floramut/gene)) - var/obj/projectile/energy/floramut/gene/G = Proj + if(istype(proj ,/obj/projectile/energy/floramut)&& prob(20)) + if(istype(proj, /obj/projectile/energy/floramut/gene)) + var/obj/projectile/energy/floramut/gene/G = proj if(seed) seed = seed.diverge_mutate_gene(G.gene, get_turf(loc)) //get_turf just in case it's not in a turf. else mutate(1) - return - else if(istype(Proj ,/obj/projectile/energy/florayield) && prob(20)) + else if(istype(proj ,/obj/projectile/energy/florayield) && prob(20)) yield_mod = min(10,yield_mod+rand(1,2)) - return - - ..() /obj/machinery/portable_atmospherics/hydroponics/proc/check_health() if(seed && !dead && health <= 0) diff --git a/code/modules/integrated_electronics/subtypes/time.dm b/code/modules/integrated_electronics/subtypes/time.dm index ffd8651f912..645bf29ae8e 100644 --- a/code/modules/integrated_electronics/subtypes/time.dm +++ b/code/modules/integrated_electronics/subtypes/time.dm @@ -85,7 +85,7 @@ /obj/item/integrated_circuit/time/ticker/Destroy() if(is_running) - STOP_PROCESSING(SSfastprocess, src) + STOP_PROCESSING(SSprocess_5fps, src) return ..() /obj/item/integrated_circuit/time/ticker/on_data_written() diff --git a/code/modules/loot/packs/weapons.dm b/code/modules/loot/packs/weapons.dm index 2443fa9c5f6..f6a9f723839 100644 --- a/code/modules/loot/packs/weapons.dm +++ b/code/modules/loot/packs/weapons.dm @@ -3,8 +3,8 @@ /datum/prototype/struct/loot_pack/weapons/melee1 some = list( - /obj/item/melee/energy/sword, - /obj/item/shield/energy, + /obj/item/melee/transforming/energy/sword, + /obj/item/shield/transforming/energy, /obj/item/melee/baton, /obj/item/melee/chainofcommand, /obj/item/melee/nanite_knife, diff --git a/code/modules/mapping/map_helpers/engine_loader.dm b/code/modules/mapping/map_helpers/engine_loader.dm index 7d0d8e97ea4..1dc4d35494f 100644 --- a/code/modules/mapping/map_helpers/engine_loader.dm +++ b/code/modules/mapping/map_helpers/engine_loader.dm @@ -36,7 +36,7 @@ their_for_map = initial(map_path.id) if(their_for_map != src.for_map) continue - var/name = initial(path.name) + var/name = lowertext(initial(path.name)) potential_filtered[path] = isnum(probabilities[name])? probabilities[name] : 1 var/picked_path = pickweightAllowZero(potential_filtered) diff --git a/code/modules/mining/kinetic_crusher.dm b/code/modules/mining/kinetic_crusher.dm index a6e7c3b6627..4bc04ca17d2 100644 --- a/code/modules/mining/kinetic_crusher.dm +++ b/code/modules/mining/kinetic_crusher.dm @@ -311,7 +311,7 @@ damage_type = BRUTE damage_flag = ARMOR_BOMB range = WORLD_ICON_SIZE * 6 - accuracy = INFINITY // NO. + accuracy_disabled = TRUE // log_override = TRUE var/obj/item/kinetic_crusher/hammer_synced @@ -319,7 +319,10 @@ hammer_synced = null return ..() -/obj/projectile/destabilizer/on_hit(atom/target, blocked = FALSE) +/obj/projectile/destabilizer/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(isliving(target)) var/mob/living/L = target if(hammer_synced.can_mark(L)) @@ -341,7 +344,6 @@ var/turf/simulated/mineral/M = target_turf new /obj/effect/temp_visual/kinetic_blast(M) M.GetDrilled(firer) - ..() /* //trophies diff --git a/code/modules/mining/machine_processing.dm b/code/modules/mining/machine_processing.dm index af02717eb6d..a3966837230 100644 --- a/code/modules/mining/machine_processing.dm +++ b/code/modules/mining/machine_processing.dm @@ -199,9 +199,9 @@ speed_process = !speed_process // switching gears if(speed_process) // high gear STOP_MACHINE_PROCESSING(src) - START_PROCESSING(SSfastprocess, src) + START_PROCESSING(SSprocess_5fps, src) else // low gear - STOP_PROCESSING(SSfastprocess, src) + STOP_PROCESSING(SSprocess_5fps, src) START_MACHINE_PROCESSING(src) /obj/machinery/mineral/processing_unit/process(delta_time) diff --git a/code/modules/mining/mine_outcrops.dm b/code/modules/mining/mine_outcrops.dm index d5c769d7747..03fb1c355fd 100644 --- a/code/modules/mining/mine_outcrops.dm +++ b/code/modules/mining/mine_outcrops.dm @@ -108,11 +108,12 @@ return . = ..() -/obj/structure/outcrop/bullet_act(obj/projectile/P, def_zone) - if(P.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks. - GetDrilled() - return +/obj/structure/outcrop/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT) + return + if(proj.damage_flag == ARMOR_BOMB) //Intended for kinetic accelerators/daggers to just get rid of this stuff quickly. They're rocks. + GetDrilled() /obj/structure/outcrop/proc/GetDrilled() new outcropdrop(get_turf(src), rand(mindrop,upperdrop)) diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index c8c81622e3e..adb4c316962 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -296,10 +296,10 @@ CREATE_STANDARD_TURFS(/turf/simulated/mineral/floor/ignore_cavegen) new oretype(src) resources[ore] = 0 -/turf/simulated/mineral/bullet_act(var/obj/projectile/Proj) // only emitters for now +/turf/simulated/mineral/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() - if(Proj.excavation_amount) - var/newDepth = excavation_level + Proj.excavation_amount // Used commonly below + if(proj.excavation_amount) + var/newDepth = excavation_level + proj.excavation_amount // Used commonly below if(newDepth >= 200) // first, if the turf is completely drilled then don't bother checking for finds and just drill it GetDrilled(0) return @@ -312,8 +312,8 @@ CREATE_STANDARD_TURFS(/turf/simulated/mineral/floor/ignore_cavegen) if(prob(50)) artifact_debris() - excavation_level += Proj.excavation_amount - update_archeo_overlays(Proj.excavation_amount) + excavation_level += proj.excavation_amount + update_archeo_overlays(proj.excavation_amount) /turf/simulated/mineral/Bumped(AM) diff --git a/code/modules/mob/defense.dm b/code/modules/mob/defense.dm deleted file mode 100644 index fae10ea95e3..00000000000 --- a/code/modules/mob/defense.dm +++ /dev/null @@ -1,83 +0,0 @@ -/** - * calculates the resulting damage from an attack, taking into account our armor and soak - * - * @params - * * damage - raw damage - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * * target_zone - where it's impacting - * - * @return args as list. - */ -/mob/proc/check_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - var/list/returned = check_armor(damage, tier, flag, mode, attack_type, weapon) - damage = returned[1] - mode = returned[4] - return args.Copy() - -/** - * runs armor against an incoming attack - * this proc can have side effects - * - * @params - * * damage - raw damage - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * * target_zone - where it's impacting - * - * @return args as list. - */ -/mob/proc/run_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - var/list/returned = run_armor(damage, tier, flag, mode, attack_type, weapon) - damage = returned[1] - mode = returned[4] - return args.Copy() - -/** - * check overall armor - * does not support modifying damage modes. - * - * @params - * * damage - raw damage - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * * target_zone - where it's impacting - * - * @return args as list. - */ -/mob/proc/check_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - var/list/returned = check_armor(damage, tier, flag, mode, attack_type, weapon) - damage = returned[1] - mode = returned[4] - return args.Copy() - -/** - * runs overall armor against an incoming attack - * this proc can have side effects - * does not support modifying damage modes. - * - * @params - * * damage - raw damage - * * tier - penetration / attack tier - * * flag - armor flag as seen in [code/__DEFINES/combat/armor.dm] - * * mode - damage_mode - * * attack_type - (optional) attack type flags from [code/__DEFINES/combat/attack_types.dm] - * * weapon - (optional) attacking /obj/item for melee or thrown, /obj/projectile for ranged, /mob for unarmed - * * target_zone - where it's impacting - * - * @return args as list. - */ -/mob/proc/run_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - var/list/returned = run_armor(damage, tier, flag, mode, attack_type, weapon) - damage = returned[1] - mode = returned[4] - return args.Copy() diff --git a/code/modules/mob/grab.dm b/code/modules/mob/grab.dm index aad94cc6f47..d42f8c6c55b 100644 --- a/code/modules/mob/grab.dm +++ b/code/modules/mob/grab.dm @@ -1,21 +1,39 @@ /** * returns everyone we're grabbing associated to state */ -/mob/proc/grabbing() +/mob/proc/get_grabbing() RETURN_TYPE(/list) . = list() for(var/obj/item/grab/G in get_held_items()) .[G.affecting] = G.state +/** + * returns everyone we're grabbing that are at least the given grab state + */ +/mob/proc/get_grabbing_of_state(state) + RETURN_TYPE(/list) + . = list() + for(var/obj/item/grab/G in get_held_items()) + if(G.state < state) + continue + . += G.affecting + /** * returns everyone we're grabbing, recursively; this can include ourselves! + * + * @return grabbed mobs associated to states */ -/mob/proc/grabbing_recursive(list/L = list()) +/mob/proc/get_grabbing_recursive(list/L = list(), safety = 15, list/processed = list()) RETURN_TYPE(/list) + if(processed[src]) + return + processed[src] = TRUE + if(safety <= 0) + CRASH("infinite loop guard tripped") . = L for(var/obj/item/grab/G in get_held_items()) .[G.affecting] = max(.[G.affecting], G.state) - grabbing_recursive(G.affecting) + G.affecting.get_grabbing_recursive(., --safety, processed) /** * check the grab state of us to someone @@ -242,7 +260,7 @@ /obj/item/grab/throw_resolve_override(atom/movable/resolved, mob/user) return TRUE -/obj/item/grab/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags, mult) +/obj/item/grab/melee_object_hit(atom/target, datum/event_args/actor/clickchain/clickchain, clickchain_flags) switch(state) if(GRAB_PASSIVE) clickchain.visible_feedback( diff --git a/code/modules/mob/inventory/items.dm b/code/modules/mob/inventory/items.dm index 59b2a1d438c..e0c62b3f61b 100644 --- a/code/modules/mob/inventory/items.dm +++ b/code/modules/mob/inventory/items.dm @@ -34,7 +34,7 @@ /obj/item/proc/equipped(mob/user, slot, flags) SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_EQUIPPED, user, slot, flags) - SEND_SIGNAL(user, COMSIG_MOB_ITEM_EQUIPPED, user, slot, flags) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_EQUIPPED, src, slot, flags) worn_slot = slot if(!(flags & INV_OP_IS_ACCESSORY)) // todo: shouldn't be in here @@ -68,7 +68,7 @@ /obj/item/proc/unequipped(mob/user, slot, flags) SHOULD_CALL_PARENT(TRUE) SEND_SIGNAL(src, COMSIG_ITEM_UNEQUIPPED, user, slot, flags) - SEND_SIGNAL(user, COMSIG_MOB_ITEM_UNEQUIPPED, user, slot, flags) + SEND_SIGNAL(user, COMSIG_MOB_ITEM_UNEQUIPPED, src, slot, flags) worn_slot = null if(!(flags & INV_OP_IS_ACCESSORY)) // todo: shouldn't be in here @@ -145,11 +145,20 @@ */ /obj/item/proc/pickup(mob/user, flags, atom/oldLoc) SHOULD_CALL_PARENT(TRUE) + + // we load the component here as it hooks equipped, + // so loading it here means it can still handle the equipped signal. + if(passive_parry) + LoadComponent(/datum/component/passive_parry, passive_parry) + SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user, flags, oldLoc) SEND_SIGNAL(user, COMSIG_MOB_ITEM_PICKUP, src, flags, oldLoc) + reset_pixel_offsets() hud_layerise() + item_flags |= ITEM_IN_INVENTORY + // todo: should this be here transform = null if(isturf(oldLoc) && !(flags & (INV_OP_SILENT | INV_OP_DIRECTLY_EQUIPPING))) @@ -374,6 +383,8 @@ * checks if we're held in hand * * if so, returns mob we're in + * + * @return the mob holding us */ /obj/item/proc/is_held() return (worn_slot == SLOT_ID_HANDS)? worn_mob() : null diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm index 182892376d3..48e5a56b50a 100644 --- a/code/modules/mob/living/bot/secbot.dm +++ b/code/modules/mob/living/bot/secbot.dm @@ -226,9 +226,9 @@ if(health < curhealth && on == TRUE) react_to_attack_polaris(user) -/mob/living/bot/secbot/bullet_act(var/obj/projectile/P) +/mob/living/bot/secbot/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) var/curhealth = health - var/mob/shooter = P.firer + var/mob/shooter = proj.firer . = ..() //if we already have a target just ignore to avoid lots of checking if(!target && health < curhealth && shooter && (shooter in view(world.view, src))) diff --git a/code/modules/mob/living/carbon/carbon-defense.dm b/code/modules/mob/living/carbon/carbon-defense.dm index 63a455454c0..8471cf172b6 100644 --- a/code/modules/mob/living/carbon/carbon-defense.dm +++ b/code/modules/mob/living/carbon/carbon-defense.dm @@ -1,5 +1,17 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 Citadel Station developers. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Projectile Handling *// + +/mob/living/carbon/process_bullet_miss(obj/projectile/proj, impact_flags, def_zone, efficiency) + . = ..() + if(!.) + return + // perform normal baymiss + . = get_zone_with_miss_chance(., src, -10, TRUE) + // check if we even have that organ; if not, they automatically miss + if(!get_organ(.)) + return null //* Misc Effects *// diff --git a/code/modules/mob/living/carbon/human/defense.dm b/code/modules/mob/living/carbon/human/defense.dm index c5b686488c2..f1035982f30 100644 --- a/code/modules/mob/living/carbon/human/defense.dm +++ b/code/modules/mob/living/carbon/human/defense.dm @@ -1,37 +1,20 @@ -/mob/living/carbon/human/check_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - var/obj/item/organ/external/part = get_organ(target_zone) - for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags)) - var/list/results = I.checking_mob_armor(arglist(args)) - damage = results[1] - mode = results[4] - return ..() +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// -/mob/living/carbon/human/run_mob_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - var/obj/item/organ/external/part = get_organ(target_zone) - for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags)) - var/list/results = I.running_mob_armor(arglist(args)) - damage = results[1] - mode = results[4] - return ..() +/mob/living/carbon/human/run_armorcalls(list/shieldcall_args, fake_attack, filter_zone) + ..() // perform default /mob level -/mob/living/carbon/human/check_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) - var/total = 0 - var/total_size = 0 - for(var/key in organs_by_name) - var/rel_size = organ_rel_size[key] - if(!rel_size) - continue - var/obj/item/organ/external/part = organs_by_name[key] - var/resultant = damage + if(filter_zone) + // just one zone + var/obj/item/organ/external/part = get_organ(filter_zone) for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags)) - var/list/results = I.checking_mob_armor(resultant, tier, flag, mode, attack_type, weapon, target_zone) - resultant = results[1] - total += resultant * rel_size - total_size += rel_size - damage = total / total_size - return ..() + I.mob_armorcall(src, shieldcall_args, fake_attack) + if(shieldcall_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_TERMINATE) + break + return -/mob/living/carbon/human/run_overall_armor(damage, tier, flag, mode, attack_type, datum/weapon, target_zone) + var/damage = shieldcall_args[SHIELDCALL_ARG_DAMAGE] + // all zones, uh oh, this is about to get very ugly var/total = 0 var/total_size = 0 for(var/key in organs_by_name) @@ -41,9 +24,10 @@ var/obj/item/organ/external/part = organs_by_name[key] var/resultant = damage for(var/obj/item/I as anything in inventory?.items_that_cover(part.body_part_flags)) - var/list/results = I.running_mob_armor(resultant, tier, flag, mode, attack_type, weapon, target_zone) - resultant = results[1] + var/list/copied_args = args.Copy() + copied_args[SHIELDCALL_ARG_DAMAGE] = resultant + I.mob_armorcall(src, copied_args, fake_attack) + resultant = copied_args[SHIELDCALL_ARG_DAMAGE] total += resultant * rel_size total_size += rel_size - damage = total / total_size - return ..() + shieldcall_args[SHIELDCALL_ARG_DAMAGE] = total / total_size diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human-damage-legacy.dm similarity index 98% rename from code/modules/mob/living/carbon/human/human_damage.dm rename to code/modules/mob/living/carbon/human/human-damage-legacy.dm index 212de7a6f40..f5a2e375af4 100644 --- a/code/modules/mob/living/carbon/human/human_damage.dm +++ b/code/modules/mob/living/carbon/human/human-damage-legacy.dm @@ -350,9 +350,6 @@ This function restores all organs. current_organ.rejuvenate_legacy(ignore_prosthetic_prefs) /mob/living/carbon/human/apply_damage(var/damage = 0, var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/soaked = 0, var/sharp = 0, var/edge = 0, var/obj/used_weapon = null) - if(GLOB.Debug2) - log_world("## DEBUG: human/apply_damage() was called on [src], with [damage] damage, an armor value of [blocked], and a soak value of [soaked].") - var/obj/item/organ/external/organ = null if(isorgan(def_zone)) organ = def_zone diff --git a/code/modules/mob/living/carbon/human/human-damage.dm b/code/modules/mob/living/carbon/human/human-damage.dm new file mode 100644 index 00000000000..1e411fffb29 --- /dev/null +++ b/code/modules/mob/living/carbon/human/human-damage.dm @@ -0,0 +1,4 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +// todo: start translating code over diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human-defense-legacy.dm similarity index 90% rename from code/modules/mob/living/carbon/human/human_defense.dm rename to code/modules/mob/living/carbon/human/human-defense-legacy.dm index c5ac3393588..663da91a067 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human-defense-legacy.dm @@ -1,44 +1,3 @@ -/* -Contains most of the procs that are called when a mob is attacked by something - -bullet_act -legacy_ex_act -meteor_act - -*/ - -/mob/living/carbon/human/bullet_act(var/obj/projectile/P, var/def_zone) - def_zone = check_zone(def_zone) - if(!has_organ(def_zone)) - return PROJECTILE_FORCE_MISS //if they don't have the organ in question then the projectile just passes by. - - var/obj/item/organ/external/organ = get_organ() - - //Shields - var/shield_check = check_shields(P.damage, P, null, def_zone, "the [P.name]") - if(shield_check) // If the block roll succeeded, this is true. - if(shield_check < 0) // The shield did something weird and the bullet needs to keep doing things (e.g. it was reflected). - return shield_check // Likely equal to PROJECTILE_FORCE_MISS or PROJECTILE_CONTINUE. - else // Otherwise we blocked normally and stopped all the damage. - return 0 - - if(!P.nodamage) - organ.add_autopsy_data("[P.name]", P.damage) - - //Shrapnel - if(P.can_embed()) - var/armor = getarmor_organ(organ, "bullet") - if(!prob(armor/2)) //Even if the armor doesn't stop the bullet from hurting you, it might stop it from embedding. - var/hit_embed_chance = P.embed_chance + (P.damage - armor) //More damage equals more chance to embed - if(prob(max(hit_embed_chance, 0))) - var/obj/item/material/shard/shrapnel/SP = new() - SP.name = (P.name != "shrapnel")? "[P.name] shrapnel" : "shrapnel" - SP.desc = "[SP.desc] It looks like it was fired from [P.shot_from]." - SP.loc = organ - organ.embed(SP) - - return ..() - /mob/living/carbon/human/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone) var/obj/item/organ/external/affected = get_organ(check_zone(def_zone)) var/siemens_coeff = get_siemens_coefficient_organ(affected) @@ -207,14 +166,8 @@ meteor_act return gear return null -/mob/living/carbon/human/proc/check_shields(var/damage = 0, var/atom/damage_source = null, var/mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - for(var/obj/item/shield in list(l_hand, r_hand, wear_suit)) - if(!shield) continue - . = shield.handle_shield(src, damage, damage_source, attacker, def_zone, attack_text) - if(.) return - return 0 - /mob/living/carbon/human/resolve_item_attack(obj/item/I, mob/living/user, var/target_zone) + SEND_SIGNAL(src, COMSIG_MOB_LEGACY_RESOLVE_ITEM_ATTACK, I, user, target_zone) if(check_neckgrab_attack(I, user, target_zone)) return null @@ -226,7 +179,9 @@ meteor_act if(!hit_zone) return null - if(check_shields(I.damage_force, I, user, target_zone, "the [I.name]")) + var/shieldcall_results = atom_shieldcall_handle_item_melee(I, new /datum/event_args/actor/clickchain(user), FALSE, NONE) + // todo: clickchain should be checked for damage mult + if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK) return var/obj/item/organ/external/affecting = get_organ(hit_zone) @@ -377,17 +332,29 @@ meteor_act miss_chance = max(5 * (distance - 2), 0) zone = get_zone_with_miss_chance(zone, src, miss_chance, ranged_attack=1) - if(zone && TT.thrower != src) - var/shield_check = check_shields(throw_damage, O, TT.thrower, zone, "[O]") - if(shield_check == PROJECTILE_FORCE_MISS) - zone = null - else if(shield_check) - return + var/force_pierce = FALSE + var/no_attack = FALSE + if(zone) + // perform shieldcall + // todo: reconcile all the way down to /atom, or at least a higher level than /human. + var/retval + for(var/datum/shieldcall/shieldcall as anything in shieldcalls) + retval |= shieldcall.handle_throw_impact(src, TT) + if(retval & SHIELDCALL_FLAGS_SHOULD_TERMINATE) + break + if(retval & SHIELDCALL_FLAGS_SHOULD_PROCESS) + if(retval & SHIELDCALL_FLAGS_PIERCE_ATTACK) + force_pierce = TRUE + if(retval & SHIELDCALL_FLAGS_BLOCK_ATTACK) + no_attack = TRUE if(!zone) visible_message("\The [O] misses [src] narrowly!") return COMPONENT_THROW_HIT_NEVERMIND | COMPONENT_THROW_HIT_PIERCE + if(no_attack) + return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE + var/obj/item/organ/external/affecting = get_organ(zone) var/hit_area = affecting.name @@ -406,7 +373,6 @@ meteor_act if(armor < 100) apply_damage(throw_damage, dtype, zone, armor, soaked, is_sharp(O), has_edge(O), O) - //thrown weapon embedded object code. if(dtype == BRUTE && istype(O,/obj/item)) var/obj/item/I = O @@ -450,6 +416,8 @@ meteor_act anchored = TRUE pinned += O + return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE + // This does a prob check to catch the thing flying at you, with a minimum of 1% /mob/living/carbon/human/proc/can_catch(var/obj/O) if(!get_active_held_item()) // If active hand is empty diff --git a/code/modules/mob/living/carbon/human/human-defense.dm b/code/modules/mob/living/carbon/human/human-defense.dm index 09f8eb24c17..4de1b17baac 100644 --- a/code/modules/mob/living/carbon/human/human-defense.dm +++ b/code/modules/mob/living/carbon/human/human-defense.dm @@ -1,6 +1,34 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2024 Citadel Station developers. *// +//* Projectile Handling *// + +/mob/living/carbon/human/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT) + return + + if(impact_flags & PROJECTILE_IMPACT_BLOCKED) + return + + // todo: this shit shouldn't be here + var/obj/item/organ/external/organ = get_organ() + + if(!proj.nodamage) + organ.add_autopsy_data("[proj.name]", proj.damage) + + //Shrapnel + if(proj.can_embed()) + var/armor = getarmor_organ(organ, "bullet") + if(!prob(armor/2)) //Even if the armor doesn't stop the bullet from hurting you, it might stop it from embedding. + var/hit_embed_chance = proj.embed_chance + (proj.damage - armor) //More damage equals more chance to embed + if(prob(max(hit_embed_chance, 0))) + var/obj/item/material/shard/shrapnel/SP = new() + SP.name = (proj.name != "shrapnel")? "[proj.name] shrapnel" : "shrapnel" + SP.desc = "[SP.desc] It looks like it was fired from [proj.shot_from]." + SP.loc = organ + organ.embed(SP) + //* Misc Effects *// /mob/living/carbon/human/slip_act(slip_class, source, hard_strength, soft_strength, suppressed) diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm index 56cf6cf52b3..0667987fb4f 100644 --- a/code/modules/mob/living/carbon/human/human_attackhand.dm +++ b/code/modules/mob/living/carbon/human/human_attackhand.dm @@ -53,9 +53,11 @@ visible_message("[H] reaches for [src], but misses!") return FALSE - if(H != src && check_shields(0, null, H, H.zone_sel.selecting, H.name)) - H.do_attack_animation(src) - return FALSE + if(user.a_intent != INTENT_HARM) + var/shieldcall_results = atom_shieldcall_handle_touch(new /datum/event_args/actor/clickchain(user)) + if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK) + H.do_attack_animation(src) + return FALSE if(istype(user,/mob/living/carbon)) var/mob/living/carbon/C = user @@ -195,6 +197,11 @@ if(!attack) return FALSE + var/shieldcall_results = atom_shieldcall_handle_unarmed_melee(attack, new /datum/event_args/actor/clickchain(user)) + if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK) + H.do_attack_animation(src) + return FALSE + if(attack.unarmed_override(H, src, hit_zone)) return FALSE diff --git a/code/modules/mob/living/carbon/human/traits/weaver_objs.dm b/code/modules/mob/living/carbon/human/traits/weaver_objs.dm index b08f5cd4bb2..45211ebf7d6 100644 --- a/code/modules/mob/living/carbon/human/traits/weaver_objs.dm +++ b/code/modules/mob/living/carbon/human/traits/weaver_objs.dm @@ -22,9 +22,9 @@ var/global/list/weavable_items = list() visible_message("\The [src] has been [W.get_attack_verb(src, user)] with \the [W][(user ? " by [user]." : ".")]") qdel(src) -/obj/effect/weaversilk/bullet_act(var/obj/projectile/Proj) - ..() - if(Proj.get_structure_damage()) +/obj/effect/weaversilk/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(proj.get_structure_damage()) qdel(src) /obj/effect/weaversilk/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) diff --git a/code/modules/mob/living/inventory.dm b/code/modules/mob/living/inventory.dm index 8f179980acf..c9db1ad494a 100644 --- a/code/modules/mob/living/inventory.dm +++ b/code/modules/mob/living/inventory.dm @@ -185,40 +185,6 @@ SLOT_ID_MASK ) -/mob/living/ret_grab(obj/effect/list_container/mobl/L as obj, flag) - if ((!( istype(l_hand, /obj/item/grab) ) && !( istype(r_hand, /obj/item/grab) ))) - if (!( L )) - return null - else - return L.container - else - if (!( L )) - L = new /obj/effect/list_container/mobl( null ) - L.container += src - L.master = src - if (istype(l_hand, /obj/item/grab)) - var/obj/item/grab/G = l_hand - if (!( L.container.Find(G.affecting) )) - L.container += G.affecting - if (G.affecting) - G.affecting.ret_grab(L, 1) - if (istype(r_hand, /obj/item/grab)) - var/obj/item/grab/G = r_hand - if (!( L.container.Find(G.affecting) )) - L.container += G.affecting - if (G.affecting) - G.affecting.ret_grab(L, 1) - if (!( flag )) - if (L.master == src) - var/list/temp = list( ) - temp += L.container - //L = null - qdel(L) - return temp - else - return L.container - return - /mob/living/abiotic(full_body) if(full_body) if(item_considered_abiotic(wear_mask)) diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/living-damage.dm similarity index 93% rename from code/modules/mob/living/damage_procs.dm rename to code/modules/mob/living/living-damage.dm index 379abddbf02..ac09b0d13bc 100644 --- a/code/modules/mob/living/damage_procs.dm +++ b/code/modules/mob/living/living-damage.dm @@ -206,3 +206,17 @@ return radiation = max(0, radiation - amt) +//* Damage Instance Handling *// + +/mob/living/inflict_damage_instance(SHIELDCALL_PROC_HEADER) + if(inflict_damage_type_special(args)) + return + + var/weapon_descriptor = RESOLVE_SHIELDCALL_WEAPON_DESCRIPTOR(args) + var/brute = damage_type == BRUTE? damage : 0 + var/burn = damage_type == BURN? damage : 0 + + if(hit_zone) + take_targeted_damage(brute, burn, damage_mode, hit_zone, weapon_descriptor) + else + take_overall_damage(brute, burn, damage_mode, weapon_descriptor) diff --git a/code/modules/mob/living/defense.dm b/code/modules/mob/living/living-defense-legacy.dm similarity index 86% rename from code/modules/mob/living/defense.dm rename to code/modules/mob/living/living-defense-legacy.dm index 1986b015bc0..6f88e783acd 100644 --- a/code/modules/mob/living/defense.dm +++ b/code/modules/mob/living/living-defense-legacy.dm @@ -96,6 +96,7 @@ . = ..() if(.) return + SEND_SIGNAL(src, COMSIG_MOB_LEGACY_ATTACK_HAND_INTERCEPT, user, params) var/mob/living/L = user if(!istype(L)) return @@ -110,65 +111,6 @@ else afflict_radiation(strength * RAD_MOB_ACT_COEFFICIENT - RAD_MOB_ACT_PROTECTION_PER_WAVE_SOURCE, TRUE) -/mob/living/bullet_act(var/obj/projectile/P, var/def_zone) - - //Being hit while using a deadman switch - if(istype(get_active_held_item(),/obj/item/assembly/signaler)) - var/obj/item/assembly/signaler/signaler = get_active_held_item() - if(signaler.deadman && prob(80)) - log_and_message_admins("has triggered a signaler deadman's switch") - src.visible_message("[src] triggers their deadman's switch!") - signaler.signal() - - if(ai_holder && P.firer) - ai_holder.react_to_attack_polaris(P.firer) - - //Armor - var/soaked = get_armor_soak(def_zone, P.damage_flag, P.armor_penetration) - var/absorb = run_armor_check(def_zone, P.damage_flag, P.armor_penetration) - var/proj_sharp = is_sharp(P) - var/proj_edge = has_edge(P) - var/final_damage = P.get_final_damage(src) - - if ((proj_sharp || proj_edge) && (soaked >= round(P.damage*0.8))) - proj_sharp = 0 - proj_edge = 0 - - if ((proj_sharp || proj_edge) && prob(legacy_mob_armor(def_zone, P.damage_flag))) - proj_sharp = 0 - proj_edge = 0 - - var/list/impact_sounds = islist(P.impact_sounds)? LAZYACCESS(P.impact_sounds, get_bullet_impact_effect_type(def_zone)) : P.impact_sounds - if(length(impact_sounds)) - playsound(src, pick(impact_sounds), 75) - else if(!isnull(impact_sounds)) - playsound(src, impact_sounds, 75) - - //Stun Beams - if(P.taser_effect) - stun_effect_act(0, P.agony, def_zone, P) - to_chat(src, "You have been hit by [P]!") - if(!P.nodamage) - apply_damage(final_damage, P.damage_type, def_zone, absorb, soaked, 0, P, sharp=proj_sharp, edge=proj_edge) - qdel(P) - return - - if(!P.nodamage) - apply_damage(final_damage, P.damage_type, def_zone, absorb, soaked, 0, P, sharp=proj_sharp, edge=proj_edge) - P.on_hit(src, absorb, soaked, def_zone) - - if(absorb == 100) - return 2 - else if (absorb >= 0) - return 1 - else - return 0 - -// return absorb - -/mob/living/get_bullet_impact_effect_type(var/def_zone) - return BULLET_IMPACT_MEAT - //Handles the effects of "stun" weapons /mob/living/proc/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) flash_pain() @@ -185,7 +127,7 @@ apply_effect(EYE_BLUR, agony_amount/10) /mob/living/proc/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null, var/stun = 1) - return 0 //only carbon liveforms have this proc + return 0 //only carbon liveforms have this proc /mob/living/emp_act(severity) var/list/L = src.get_equipped_items(TRUE, TRUE) @@ -231,6 +173,13 @@ apply_damage(damage, damage_type, def_zone, absorb, soaked) /mob/living/proc/resolve_item_attack(obj/item/I, mob/living/user, var/target_zone) + SEND_SIGNAL(src, COMSIG_MOB_LEGACY_RESOLVE_ITEM_ATTACK, I, user, target_zone) + + var/shieldcall_results = atom_shieldcall_handle_item_melee(I, new /datum/event_args/actor/clickchain(user), FALSE, NONE) + // todo: clickchain should be checked for damage mult + if(shieldcall_results & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return + return target_zone //Called when the mob is hit with an item in combat. Returns the blocked result @@ -284,6 +233,32 @@ visible_message("\The [O] misses [src] narrowly!") return COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND + var/force_pierce = FALSE + var/no_attack = FALSE + + var/zone + if (istype(TT.thrower, /mob/living)) + zone = check_zone(TT.target_zone) + else + zone = ran_zone(BP_TORSO,75) //Hits a random part of the body, geared towards the chest + + if(zone) + // perform shieldcall + // todo: reconcile all the way down to /atom, or at least a higher level than /human. + var/retval + for(var/datum/shieldcall/shieldcall as anything in shieldcalls) + retval |= shieldcall.handle_throw_impact(src, TT) + if(retval & SHIELDCALL_FLAGS_SHOULD_TERMINATE) + break + if(retval & SHIELDCALL_FLAGS_SHOULD_PROCESS) + if(retval & SHIELDCALL_FLAGS_PIERCE_ATTACK) + force_pierce = TRUE + if(retval & SHIELDCALL_FLAGS_BLOCK_ATTACK) + no_attack = TRUE + + if(no_attack) + return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE + src.visible_message("[src] has been hit by [O].") var/armor = run_armor_check(null, "melee") var/soaked = get_armor_soak(null, "melee") @@ -325,11 +300,13 @@ var/turf/T = near_wall(dir,2) if(T) - src.loc = T + src.forceMove(T) visible_message("[src] is pinned to the wall by [O]!","You are pinned to the wall by [O]!") src.anchored = 1 src.pinned += O + return force_pierce? COMPONENT_THROW_HIT_PIERCE | COMPONENT_THROW_HIT_NEVERMIND : NONE + /mob/living/proc/embed(var/obj/O, var/def_zone=null) O.loc = src src.embedded += O @@ -487,6 +464,7 @@ /mob/living/proc/reagent_permeability() return 1 +// todo: rework // Returns a number to determine if something is harder or easier to hit than normal. /mob/living/proc/get_evasion() var/result = evasion // First we get the 'base' evasion. Generally this is zero. @@ -495,14 +473,6 @@ result += M.evasion return result +// todo: rework /mob/living/proc/get_accuracy_penalty() - // Certain statuses make it harder to score a hit. - var/accuracy_penalty = 0 - if(has_status_effect(/datum/status_effect/sight/blindness)) - accuracy_penalty += 75 - if(eye_blurry) - accuracy_penalty += 30 - if(confused) - accuracy_penalty += 45 - - return accuracy_penalty + return 0 diff --git a/code/modules/mob/living/living-defense.dm b/code/modules/mob/living/living-defense.dm index 3bdb111c5bc..bce917a898b 100644 --- a/code/modules/mob/living/living-defense.dm +++ b/code/modules/mob/living/living-defense.dm @@ -1,5 +1,115 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 Citadel Station developers. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Projectile Handling *// + +/mob/living/bullet_act(obj/projectile/proj, impact_flags, def_zone, efficiency) + //! LEGACY + + // Using someone as a shield + // todo: need a counter to this.. + for(var/mob/living/victim in get_grabbing_of_state(GRAB_NECK)) + if(victim.stat == DEAD) + // small mobs are penalized; this is a holdover. + var/shield_chance = min(80, (30 * (mob_size / 10))) + if(prob(shield_chance)) + visible_message("\The [src] uses [victim] as a shield!") + if(!(proj.impact_redirect(victim, args) | (PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH | PROJECTILE_IMPACT_DUPLICATE))) + return + else + visible_message("\The [src] tries to use [victim] as a shield, but fails!") + else + visible_message("\The [src] uses [victim] as a shield!") + if(!(proj.impact_redirect(victim, args) | (PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH | PROJECTILE_IMPACT_DUPLICATE))) + return + // Process baymiss & zonemiss + def_zone = process_bullet_miss(proj, impact_flags, def_zone, efficiency) + def_zone = proj.process_zone_miss(src, def_zone, proj.distance_travelled, TRUE) + if(!def_zone) + if(!proj.silenced) + visible_message(SPAN_WARNING("\The [proj] misses [src] narrowly!")) + playsound(src, pick(proj.miss_sounds), 60, TRUE) + add_attack_logs( + proj.firer, + src, + "shot with [src] ([type]) (missed)", + ) + impact_flags |= PROJECTILE_IMPACT_PASSTHROUGH + return ..() + + //! END + + return ..() + +/mob/living/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + // todo: better logging + if(impact_flags & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT) + add_attack_logs( + proj.firer, + src, + "shot with [src] ([type]) (aborted)", + ) + return + add_attack_logs( + proj.firer, + src, + "shot with [src] ([type])[(impact_flags & PROJECTILE_IMPACT_BLOCKED)? " (blocked)" : ""]", + ) + // emit feedback + if(!(impact_flags & PROJECTILE_IMPACT_BLOCKED)) + if(proj.silenced) + to_chat(src, SPAN_DANGER("You've been hit in the [parse_zone(bullet_act_args[BULLET_ACT_ARG_ZONE])] with \the [proj]!")) + else + visible_message(SPAN_DANGER("\The [src] is hit by [proj] in the [parse_zone(bullet_act_args[BULLET_ACT_ARG_ZONE])]")) + + //! LEGACY + + //Being hit while using a deadman switch + for(var/obj/item/assembly/signaler/signaler in get_held_items()) + if(signaler.deadman && prob(80)) + log_and_message_admins("has triggered a signaler deadman's switch") + visible_message("[src] triggers their deadman's switch!") + signaler.signal() + + if(ai_holder && proj.firer) + ai_holder.react_to_attack_polaris(proj.firer) + + //! END + + if(!(impact_flags & (PROJECTILE_IMPACT_BLOCKED | PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE))) + // todo: this should just be in base projectile on_impact + impact_flags |= proj.inflict_impact_damage(src, bullet_act_args[BULLET_ACT_ARG_EFFICIENCY], impact_flags, bullet_act_args[BULLET_ACT_ARG_ZONE]) + return ..() + +/mob/living/get_bullet_impact_effect_type(var/def_zone) + return BULLET_IMPACT_MEAT + +/** + * @return zone to hit, or null to miss + */ +/mob/living/proc/process_bullet_miss(obj/projectile/proj, impact_flags, def_zone, efficiency) + var/hit_probability = process_baymiss(proj) + if(!prob(hit_probability)) + return null + return def_zone + +/** + * * our_opinion is intentionally mutable; it is however only mutable from before ..(), so call ..() after modifying for pre-modification + * * our_opinion and impact_check are defaulted in the base function; this means that if you need to use it before, default it yourself. + * + * todo: 0 to 100 for accuracy might not be amazing; maybe allow negative values evasion-style? + * todo: don't default our_opinion and impact_check so early wtf; BYOND proc structure disagrees with the design here. + * + * @params + * * proj - the projectile + * * our_opinion - base probability of hitting + * * impact_check - are we checking if we should impact at all? used by pellets. + * + * @return 0 to 100 % probability of hitting + */ +/mob/living/proc/process_baymiss(obj/projectile/proj, our_opinion = 100, impact_check = TRUE) + our_opinion = clamp(our_opinion - get_evasion(), 5, INFINITY) + return proj.process_accuracy(src, our_opinion, null, impact_check) //* Misc Effects *// @@ -20,3 +130,5 @@ */ /mob/living/proc/slip_act(slip_class, source, hard_strength, soft_strength, suppressed) return 1 +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station developers. *// diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 9c4ea9a0aec..d141d84f007 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -533,11 +533,13 @@ /mob/living/silicon/robot/restrained() return 0 -/mob/living/silicon/robot/bullet_act(var/obj/projectile/Proj) - ..(Proj) - if(prob(75) && Proj.damage > 0) +/mob/living/silicon/robot/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + // todo: why is this in bullet act and not where we take damage maybe? + if(prob(75) && proj.damage > 0) spark_system.start() - return 2 /mob/living/silicon/robot/attackby(obj/item/W as obj, mob/user as mob) if (istype(W, /obj/item/handcuffs)) // fuck i don't even know why isrobot() in handcuff code isn't working so this will have to do diff --git a/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm b/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm index 1d21b94abe8..bffbbf6c866 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/station/misc.dm @@ -37,7 +37,7 @@ /obj/item/robot_module/robot/standard/handle_special_module_init(mob/living/silicon/robot/R) . = ..() - src.emag = new /obj/item/melee/energy/sword(src) + src.emag = new /obj/item/melee/transforming/energy/sword(src) /obj/item/robot_module/robot/quad/basic name = "Standard Quadruped module" @@ -59,4 +59,4 @@ . = ..() // These get a larger water synth. synths_by_kind[MATSYN_WATER]:max_energy = 1000 - src.emag = new /obj/item/melee/energy/sword(src) + src.emag = new /obj/item/melee/transforming/energy/sword(src) diff --git a/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm b/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm index 543a007c2d6..5012c5031a3 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/swarm.dm @@ -27,4 +27,4 @@ /obj/item/robot_module/drone/swarm/melee/get_modules() . = ..() - . |= /obj/item/melee/energy/sword/ionic_rapier/lance + . |= /obj/item/melee/transforming/energy/sword/ionic_rapier/lance diff --git a/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm b/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm index 02c593c5337..fcb77a94c8f 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules/syndicate.dm @@ -35,7 +35,7 @@ . = ..() . |= list( /obj/item/pinpointer/shuttle/merc, - /obj/item/melee/energy/sword + /obj/item/melee/transforming/energy/sword ) /obj/item/robot_module/robot/syndicate/handle_special_module_init(mob/living/silicon/robot/R) @@ -90,7 +90,7 @@ /obj/item/multitool/ai_detector, /obj/item/pickaxe/plasmacutter, /obj/item/rcd/electric/mounted/borg/lesser, // Can't eat rwalls to prevent AI core cheese. - /obj/item/melee/energy/sword/ionic_rapier, + /obj/item/melee/transforming/energy/sword/ionic_rapier, // FBP repair. /obj/item/robotanalyzer, diff --git a/code/modules/mob/living/silicon/silicon-damage.dm b/code/modules/mob/living/silicon/silicon-damage.dm new file mode 100644 index 00000000000..d22faa57580 --- /dev/null +++ b/code/modules/mob/living/silicon/silicon-damage.dm @@ -0,0 +1,14 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Damage Instance Handling *// + +/mob/living/silicon/inflict_damage_instance(damage, damage_type, damage_tier, damage_flag, damage_mode, attack_type, datum/weapon, shieldcall_flags, hit_zone, list/additional, datum/event_args/actor/clickchain/clickchain) + if(inflict_damage_type_special(args)) + return + // we only care about those + switch(damage_type) + if(BRUTE) + adjustBruteLoss(damage) + if(BURN) + adjustBruteLoss(damage) diff --git a/code/modules/mob/living/silicon/silicon-defense-legacy.dm b/code/modules/mob/living/silicon/silicon-defense-legacy.dm new file mode 100644 index 00000000000..bf80122caf1 --- /dev/null +++ b/code/modules/mob/living/silicon/silicon-defense-legacy.dm @@ -0,0 +1,57 @@ +/mob/living/silicon/emp_act(severity) + switch(severity) + if(1) + src.take_random_targeted_damage(brute = 0, burn = 20, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") + Confuse(5) + if(2) + src.take_random_targeted_damage(brute = 0, burn = 15, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") + Confuse(4) + if(3) + src.take_random_targeted_damage(brute = 0, burn = 10, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") + Confuse(3) + if(4) + src.take_random_targeted_damage(brute = 0, burn = 5, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") + Confuse(2) + flash_eyes(affect_silicon = 1) + to_chat(src, "*BZZZT*") + to_chat(src, "Warning: Electromagnetic pulse detected.") + ..() + +/mob/living/silicon/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) + return //immune + +/mob/living/silicon/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null, var/stun = 1) + if(shock_damage > 0) + var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread + s.set_up(5, 1, loc) + s.start() + + shock_damage *= siemens_coeff //take reduced damage + take_overall_damage(0, shock_damage) + visible_message("[src] was shocked by \the [source]!", \ + "Energy pulse detected, system damaged!", \ + "You hear an electrical crack.") + if(prob(20)) + afflict_stun(20 * 2) + return + +/mob/living/silicon/legacy_ex_act(severity) + if(!has_status_effect(/datum/status_effect/sight/blindness)) + flash_eyes() + + switch(severity) + if(1.0) + if (stat != 2) + adjustBruteLoss(100) + adjustFireLoss(100) + if(!anchored) + gib() + if(2.0) + if (stat != 2) + adjustBruteLoss(60) + adjustFireLoss(60) + if(3.0) + if (stat != 2) + adjustBruteLoss(30) + + update_health() diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 479e26ccf90..0f0a42b4637 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -112,62 +112,9 @@ else src.bodytemp.icon_state = "temp-2" -/mob/living/silicon/emp_act(severity) - switch(severity) - if(1) - src.take_random_targeted_damage(brute = 0, burn = 20, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") - Confuse(5) - if(2) - src.take_random_targeted_damage(brute = 0, burn = 15, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") - Confuse(4) - if(3) - src.take_random_targeted_damage(brute = 0, burn = 10, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") - Confuse(3) - if(4) - src.take_random_targeted_damage(brute = 0, burn = 5, damage_mode = DAMAGE_MODE_INTERNAL, weapon_descriptor = "electromagnetic surge") - Confuse(2) - flash_eyes(affect_silicon = 1) - to_chat(src, "*BZZZT*") - to_chat(src, "Warning: Electromagnetic pulse detected.") - ..() - -/mob/living/silicon/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) - return //immune - -/mob/living/silicon/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null, var/stun = 1) - if(shock_damage > 0) - var/datum/effect_system/spark_spread/s = new /datum/effect_system/spark_spread - s.set_up(5, 1, loc) - s.start() - - shock_damage *= siemens_coeff //take reduced damage - take_overall_damage(0, shock_damage) - visible_message("[src] was shocked by \the [source]!", \ - "Energy pulse detected, system damaged!", \ - "You hear an electrical crack.") - if(prob(20)) - afflict_stun(20 * 2) - return - -/mob/living/silicon/proc/damage_mob(var/brute = 0, var/fire = 0, var/tox = 0) - return - /mob/living/silicon/IsAdvancedToolUser() return 1 -/mob/living/silicon/bullet_act(var/obj/projectile/Proj) - - if(!Proj.nodamage) - switch(Proj.damage_type) - if(BRUTE) - adjustBruteLoss(Proj.get_final_damage(src)) - if(BURN) - adjustFireLoss(Proj.get_final_damage(src)) - - Proj.on_hit(src,2) - update_health() - return 2 - /mob/living/silicon/apply_effect(var/effect = 0,var/effecttype = STUN, var/blocked = 0, var/check_protection = 1) return 0//The only effect that can hit them atm is flashes and they still directly edit so this works for now @@ -300,27 +247,6 @@ /mob/living/silicon/binarycheck() return 1 -/mob/living/silicon/legacy_ex_act(severity) - if(!has_status_effect(/datum/status_effect/sight/blindness)) - flash_eyes() - - switch(severity) - if(1.0) - if (stat != 2) - adjustBruteLoss(100) - adjustFireLoss(100) - if(!anchored) - gib() - if(2.0) - if (stat != 2) - adjustBruteLoss(60) - adjustFireLoss(60) - if(3.0) - if (stat != 2) - adjustBruteLoss(30) - - update_health() - /mob/living/silicon/proc/receive_alarm(var/datum/alarm_handler/alarm_handler, var/datum/alarm/alarm, was_raised) if(!next_alarm_notice) next_alarm_notice = world.time + SecondsToTicks(10) diff --git a/code/modules/mob/living/simple_animal/constructs/constructs.dm b/code/modules/mob/living/simple_animal/constructs/constructs.dm index 5f2b4382018..32e83317997 100644 --- a/code/modules/mob/living/simple_animal/constructs/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs/constructs.dm @@ -210,7 +210,7 @@ weakened = 0 ..() -/mob/living/simple_animal/construct/armoured/bullet_act(var/obj/projectile/P) +/mob/living/simple_animal/construct/armoured/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) // if(istype(P, /obj/projectile/energy) || istype(P, /obj/projectile/beam)) //If it's going to be slow, it's probably going to need every reflect it can get. var/reflectchance = 80 - round(P.damage/3) if(prob(reflectchance)) @@ -364,7 +364,7 @@ /spell/targeted/construct_advanced/slam ) -/mob/living/simple_animal/construct/behemoth/bullet_act(var/obj/projectile/P) +/mob/living/simple_animal/construct/behemoth/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) var/reflectchance = 80 - round(P.damage/3) if(prob(reflectchance)) visible_message("The [P.name] gets reflected by [src]'s shell!", \ diff --git a/code/modules/mob/living/simple_mob/combat.dm b/code/modules/mob/living/simple_mob/combat.dm index 9ac88e373ee..e226cbbfded 100644 --- a/code/modules/mob/living/simple_mob/combat.dm +++ b/code/modules/mob/living/simple_mob/combat.dm @@ -62,10 +62,10 @@ playsound(src, 'sound/weapons/punchmiss.ogg', 75, 1) return FALSE // We missed. - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(damage = damage_to_do, damage_source = src, attacker = src, def_zone = null, attack_text = "the attack")) - return FALSE // We were blocked. + var/datum/event_args/actor/clickchain/simulated_clickchain = new(src, target = L) + var/list/shieldcall_result = L.atom_shieldcall(damage_to_do, BRUTE, MELEE_TIER_MEDIUM, ARMOR_MELEE, NONE, ATTACK_TYPE_MELEE, clickchain = simulated_clickchain) + if(shieldcall_result[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return FALSE if(apply_attack(A, damage_to_do)) apply_melee_effects(A) @@ -138,7 +138,7 @@ // For some reason there isn't an argument for accuracy, so access the projectile directly instead. // Also, placing dispersion here instead of in forced_spread will randomize the chosen angle between dispersion and -dispersion in fire() instead of having to do that here. - P.accuracy += calculate_accuracy() + P.accuracy_overall_modify *= 1 + calculate_accuracy() / 100 P.dispersion += calculate_dispersion() P.launch_projectile(target = A, target_zone = null, user = src, params = null, angle_override = null, forced_spread = 0) diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm index 6161a011dda..9589a72493c 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/hunter.dm @@ -87,10 +87,9 @@ if(L == src) continue - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(damage = 0, damage_source = src, attacker = src, def_zone = null, attack_text = "the leap")) - continue // We were blocked. + var/list/shieldcall_result = L.atom_shieldcall(40, BRUTE, MELEE_TIER_MEDIUM, ARMOR_MELEE, NONE, ATTACK_TYPE_MELEE) + if(shieldcall_result[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK) + continue victim = L break diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm index 237bf4f729f..00cdeb79680 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/lurker.dm @@ -116,7 +116,7 @@ ..() // For the poison. // Force unstealthing if attacked. -/mob/living/simple_mob/animal/giant_spider/lurker/bullet_act(obj/projectile/P) +/mob/living/simple_mob/animal/giant_spider/lurker/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() break_cloak() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm b/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm index 98d4713d3de..baeb4b6a8c1 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/roach/roach.dm @@ -537,7 +537,7 @@ ..() // For the poison. // Force unstealthing if attacked. -/mob/living/simple_mob/animal/roach/zeitraum/bullet_act(obj/projectile/P) +/mob/living/simple_mob/animal/roach/zeitraum/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() break_cloak() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm b/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm index 57d8d7963a9..f76c3a8289a 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/space/mouse_army.dm @@ -417,8 +417,10 @@ ..() // For the poison. // Force unstealthing if attacked. -/mob/living/simple_mob/animal/space/mouse_army/stealth/bullet_act(obj/projectile/P) +/mob/living/simple_mob/animal/space/mouse_army/stealth/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return break_cloak() /mob/living/simple_mob/animal/space/mouse_army/stealth/hit_with_weapon(obj/item/O, mob/living/user, effective_force, hit_zone) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm index 592bba73f35..af439b298ee 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/Eddy.dm @@ -48,9 +48,9 @@ playsound(src, 'sound/h_sounds/headcrab.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/Eddy/bullet_act() +/mob/living/simple_mob/horror/Eddy/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/Eddy/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm index e17dcaf7b3c..d49a957e3d4 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/Master.dm @@ -49,9 +49,9 @@ playsound(src, 'sound/h_sounds/imbeciles.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/Master/bullet_act() +/mob/living/simple_mob/horror/Master/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/Master/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm index bd5b19c6b2e..c949dafb96e 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/Rickey.dm @@ -50,9 +50,9 @@ playsound(src, 'sound/h_sounds/headcrab.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/Rickey/bullet_act() +/mob/living/simple_mob/horror/Rickey/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/Rickey/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm index 4a33ac456be..c5f532332c8 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/Smiley.dm @@ -49,9 +49,9 @@ playsound(src, 'sound/h_sounds/lynx.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/Helix/bullet_act() +/mob/living/simple_mob/horror/Helix/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/Helix/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm index eb80a126aa9..507441749a7 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/Steve.dm @@ -54,9 +54,9 @@ playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/Steve/bullet_act() +/mob/living/simple_mob/horror/Steve/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/Steve/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm b/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm index 35fc24509ee..8e7fe09304d 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/Willy.dm @@ -50,9 +50,9 @@ playsound(src, 'sound/h_sounds/sampler.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/Willy/bullet_act() +/mob/living/simple_mob/horror/Willy/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/Willy/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm b/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm index 5dce24cb5fa..e10d0e55705 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/bradley.dm @@ -48,9 +48,9 @@ playsound(src, 'sound/h_sounds/mumble.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/bradley/bullet_act() +/mob/living/simple_mob/horror/bradley/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/bradley/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm b/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm index 0d4af8cfc58..b051923756d 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/sally.dm @@ -47,9 +47,9 @@ playsound(src, 'sound/h_sounds/lynx.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/Sally/bullet_act() +/mob/living/simple_mob/horror/Sally/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/Sally/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm b/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm index 8667b99e732..2c051b4d630 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/shittytim.dm @@ -48,9 +48,9 @@ playsound(src, 'sound/h_sounds/shitty_tim.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/BigTim/bullet_act() +/mob/living/simple_mob/horror/BigTim/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/BigTim/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm b/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm index f6c26f137cf..60fe08909b2 100644 --- a/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm +++ b/code/modules/mob/living/simple_mob/subtypes/horror/timling.dm @@ -48,9 +48,9 @@ playsound(src, 'sound/h_sounds/shitty_tim.ogg', 50, 1) ..() -/mob/living/simple_mob/horror/TinyTim/bullet_act() +/mob/living/simple_mob/horror/TinyTim/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) - ..() /mob/living/simple_mob/horror/TinyTim/attack_hand(mob/user, list/params) playsound(src, 'sound/h_sounds/holla.ogg', 50, 1) diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm index fd903d2671f..bf0caab9bcb 100644 --- a/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/cultist.dm @@ -508,17 +508,15 @@ to_chat(user, "This weapon is ineffective, it does no damage.") visible_message("\The [user] gently taps [src] with \the [O].") -/mob/living/simple_mob/humanoid/cultist/elite/bullet_act(var/obj/projectile/Proj) - if(!Proj) return +/mob/living/simple_mob/humanoid/cultist/elite/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(prob(50)) - visible_message("[Proj] disappears into the mirror world as it hits the shield.") - if(Proj.firer) + visible_message("[proj] disappears into the mirror world as it hits the shield.") + if(proj.firer) if(istype(src.ai_holder, /datum/ai_holder/polaris)) var/datum/ai_holder/polaris/ai_holder = src.ai_holder - ai_holder.react_to_attack_polaris(Proj.firer) - return - else - ..() + ai_holder.react_to_attack_polaris(proj.firer) + return PROJECTILE_IMPACT_DELETE + return ..() /mob/living/simple_mob/humanoid/cultist/elite/death() new /obj/effect/decal/remains/human (src.loc) diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm index 59acf04dd4d..344444ff822 100644 --- a/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/mercs/mercs.dm @@ -161,7 +161,7 @@ attack_edge = 1 attacktext = list("slashed") - loot_list = list(/obj/item/melee/energy/sword = 100, /obj/item/shield/energy = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword = 100, /obj/item/shield/transforming/energy = 100) // They have a shield, so they try to block /mob/living/simple_mob/humanoid/merc/melee/sword/attackby(var/obj/item/O as obj, var/mob/user as mob) @@ -177,16 +177,13 @@ to_chat(user, "This weapon is ineffective, it does no damage.") visible_message("\The [user] gently taps [src] with \the [O].") -/mob/living/simple_mob/humanoid/merc/melee/sword/bullet_act(var/obj/projectile/Proj) - if(!Proj) return +/mob/living/simple_mob/humanoid/merc/melee/sword/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(prob(35)) - visible_message("[src] blocks [Proj] with its shield!") - if(Proj.firer) - ai_holder.react_to_attack_polaris(Proj.firer) - return - else - ..() - + visible_message("[src] blocks [proj] with its shield!") + if(proj.firer) + ai_holder.react_to_attack_polaris(proj.firer) + return PROJECTILE_IMPACT_BLOCKED + return ..() //////////////////////////////// // Ranged @@ -553,15 +550,13 @@ else visible_message("\The [user] gently taps [src] with \the [O].") -/mob/living/simple_mob/humanoid/merc/ranged/space/suppressor/bullet_act(var/obj/projectile/Proj) - if(!Proj) return +/mob/living/simple_mob/humanoid/merc/ranged/space/suppressor/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(prob(50)) - visible_message("[src] blocks [Proj] with its shield!") - if(Proj.firer) - ai_holder.react_to_attack_polaris(Proj.firer) - return - else - ..() + visible_message("[src] blocks [proj] with its shield!") + if(proj.firer) + ai_holder.react_to_attack_polaris(proj.firer) + return PROJECTILE_IMPACT_BLOCKED + return ..() //////////////////////////////// // PoI Mercs @@ -683,7 +678,7 @@ ai_holder_type = /datum/ai_holder/polaris/simple_mob/melee/evasive corpse = /obj/spawner/corpse/vox/boarder_m - loot_list = list(/obj/item/melee/energy/sword = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword = 100) // They're good with the swords? I dunno. I like the idea they can deflect. /mob/living/simple_mob/humanoid/merc/voxpirate/boarder/attackby(var/obj/item/O, var/mob/user) @@ -699,15 +694,13 @@ to_chat(user, "This weapon is ineffective, it does no damage.") visible_message("\The [user] gently taps [src] with \the [O].") -/mob/living/simple_mob/humanoid/merc/voxpirate/boarder/bullet_act(var/obj/projectile/Proj) - if(!Proj) return +/mob/living/simple_mob/humanoid/merc/voxpirate/boarder/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(prob(35)) - visible_message("[src] blocks [Proj] with its sword!") - if(Proj.firer) - ai_holder.react_to_attack_polaris(Proj.firer) - return - else - ..() + visible_message("[src] blocks [proj] with its sword!") + if(proj.firer) + ai_holder.react_to_attack_polaris(proj.firer) + return PROJECTILE_IMPACT_BLOCKED + return ..() //////////////////////////////// // Vox Ranged diff --git a/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm b/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm index d7716b6f447..64056054591 100644 --- a/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm +++ b/code/modules/mob/living/simple_mob/subtypes/humanoid/pirates.dm @@ -122,7 +122,7 @@ attack_sound = 'sound/weapons/blade1.ogg' - loot_list = list(/obj/item/melee/energy/sword/pirate = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100) corpse = /obj/spawner/corpse/pirate/melee_energy @@ -134,7 +134,7 @@ icon_living = "piratemelee-las-armor" movement_cooldown = 4 armor_legacy_mob = list(melee = 30, bullet = 20, laser = 20, energy = 5, bomb = 5, bio = 100, rad = 100) - loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/accessory/armor/armorplate/stab = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/accessory/armor/armorplate/stab = 100) corpse = /obj/spawner/corpse/pirate/melee_energy_armor @@ -164,15 +164,13 @@ to_chat(user, "This weapon is ineffective, it does no damage.") visible_message("\The [user] gently taps [src] with \the [O].") -/mob/living/simple_mob/humanoid/merc/melee/sword/bullet_act(var/obj/projectile/Proj) - if(!Proj) return +/mob/living/simple_mob/humanoid/merc/melee/sword/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(prob(25)) - visible_message("[src] blocks [Proj] with its shield!") - if(Proj.firer) - ai_holder.react_to_attack_polaris(Proj.firer) - return - else - ..() + visible_message("[src] blocks [proj] with its shield!") + if(proj.firer) + ai_holder.react_to_attack_polaris(proj.firer) + return PROJECTILE_IMPACT_BLOCKED + return ..() // Armored Variant /mob/living/simple_mob/humanoid/pirate/shield/armored @@ -352,7 +350,7 @@ armor_legacy_mob = list(melee = 30, bullet = 20, laser = 20, energy = 5, bomb = 5, bio = 100, rad = 100) - loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/suit/armor/riot/alt = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/suit/armor/riot/alt = 100) corpse = /obj/spawner/corpse/pirate/mate @@ -526,13 +524,13 @@ icon_state = "old-piratemelee-las" icon_living = "old-piratemelee-las" icon_dead = "old-piratemelee_dead" - loot_list = list(/obj/item/melee/energy/sword/pirate = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100) //Armored Variant /mob/living/simple_mob/humanoid/pirate/las/armored/old icon_state = "old-piratemelee-las-armor" icon_living = "old-piratemelee-las-armor" - loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/suit/armor/material/makeshift = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/suit/armor/material/makeshift = 100) //Shield Pirate /mob/living/simple_mob/humanoid/pirate/shield/old @@ -653,7 +651,7 @@ armor_legacy_mob = list(melee = 30, bullet = 20, laser = 20, energy = 5, bomb = 5, bio = 100, rad = 100) - loot_list = list(/obj/item/melee/energy/sword/pirate = 100, /obj/item/clothing/suit/pirate = 100) + loot_list = list(/obj/item/melee/transforming/energy/sword/cutlass = 100, /obj/item/clothing/suit/pirate = 100) /////////////////////////////// diff --git a/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm index b724d80556b..9e81d5932b3 100644 --- a/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm +++ b/code/modules/mob/living/simple_mob/subtypes/illusion/illusion.dm @@ -45,14 +45,10 @@ return -/mob/living/simple_mob/illusion/bullet_act(obj/projectile/P) - if(!P) - return - +/mob/living/simple_mob/illusion/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(realistic) return ..() - - return PROJECTILE_FORCE_MISS + return PROJECTILE_IMPACT_PHASE /mob/living/simple_mob/illusion/attack_hand(mob/user, list/params) var/mob/living/M = user diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm index c61bba406f0..1f2841dc4cd 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/cyber_horror/cyber_horror.dm @@ -182,11 +182,9 @@ if(L == src) continue - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(damage = 0, damage_source = src, attacker = src, def_zone = null, attack_text = "the leap")) - // We were blocked. - continue + var/list/shieldcall_result = L.atom_shieldcall(40, BRUTE, MELEE_TIER_MEDIUM, ARMOR_MELEE, NONE, ATTACK_TYPE_MELEE) + if(shieldcall_result[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK) + continue victim = L break @@ -299,7 +297,7 @@ ..() // For the poison. // Force unstealthing if attacked. -/mob/living/simple_mob/mechanical/cyber_horror/tajaran/bullet_act(obj/projectile/P) +/mob/living/simple_mob/mechanical/cyber_horror/tajaran/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() break_cloak() @@ -435,7 +433,10 @@ damage = 15 damage_type = BRUTE -/obj/projectile/arc/blue_energy/priest/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/arc/blue_energy/priest/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ishuman(target)) var/mob/living/carbon/human/M = target M.Confuse(rand(3,5)) @@ -500,8 +501,11 @@ icon_state = "plasma3" rad_power = RAD_INTENSITY_PROJ_ARC_HORROR_PRIEST -/obj/projectile/arc/radioactive/priest/on_impact(turf/T) +/obj/projectile/arc/radioactive/priest/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(!isturf(target)) + return + var/turf/T = target new /obj/effect/explosion(T) explosion(T, 0, 1, 4) diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm index 0f3ce3a2319..ba2be85fb94 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm @@ -169,15 +169,23 @@ /obj/projectile/arc/emp_blast name = "emp blast" icon_state = "bluespace" - -/obj/projectile/arc/emp_blast/on_impact(turf/T) - empulse(T, 2, 4, 7, 10) // Normal EMP grenade. - return ..() - -/obj/projectile/arc/emp_blast/weak/on_impact(turf/T) - empulse(T, 1, 2, 3, 4) // Sec EMP grenade. - return ..() - + var/emp_dev = 2 + var/emp_heavy = 4 + var/emp_med = 7 + var/emp_light = 10 + +/obj/projectile/arc/emp_blast/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + empulse(target, emp_dev, emp_heavy, emp_med, emp_light) // Normal EMP grenade. + return . | PROJECTILE_IMPACT_DELETE + +/obj/projectile/arc/emp_blast/weak + emp_dev = 1 + emp_heavy = 2 + emp_med = 3 + emp_light = 4 // Fires shots that irradiate the tile hit. /mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/radiation diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm index 7c5cef5f8a1..478bc8fd872 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/adv_dark_gygax.dm @@ -227,7 +227,11 @@ name = "rocket" icon_state = "mortar" -/obj/projectile/arc/explosive_rocket/on_impact(turf/T) +/obj/projectile/arc/explosive_rocket/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!isturf(target)) + return + var/turf/T = target new /obj/effect/explosion(T) // Weak explosions don't produce this on their own, apparently. explosion(T, 0, 0, 2, adminlog = FALSE) @@ -244,10 +248,13 @@ name = "micro singularity" icon_state = "bluespace" -/obj/projectile/arc/microsingulo/on_impact(turf/T) +/obj/projectile/arc/microsingulo/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!isturf(target)) + return + var/turf/T = target new /obj/effect/temporary_effect/pulse/microsingulo(T) - /obj/effect/temporary_effect/pulse/microsingulo name = "micro singularity" desc = "It's sucking everything in!" diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm index 53e146c324f..3ec87c9c34f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/mecha.dm @@ -90,7 +90,7 @@ if(has_repair_droid) add_overlay(image(icon = 'icons/mecha/mecha_equipment.dmi', icon_state = "repair_droid")) -/mob/living/simple_mob/mechanical/mecha/bullet_act() +/mob/living/simple_mob/mechanical/mecha/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() sparks.start() @@ -111,11 +111,11 @@ return ..() */ -/mob/living/simple_mob/mechanical/mecha/bullet_act(obj/projectile/P) +/mob/living/simple_mob/mechanical/mecha/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(prob(deflect_chance)) - visible_message(SPAN_WARNING( "\The [P] is deflected by \the [src]'s armor!")) + visible_message(SPAN_WARNING( "\The [proj] is deflected by \the [src]'s armor!")) deflect_sprite() - return 0 + return PROJECTILE_IMPACT_BLOCKED return ..() /mob/living/simple_mob/mechanical/mecha/proc/deflect_sprite() diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm index bc6b49659e4..9781a461877 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mecha/odysseus.dm @@ -67,14 +67,13 @@ damage = 5 // Getting hit with a launched syringe probably hurts, and makes it at least slightly relevant against synthetics. var/piercing = FALSE // If true, ignores thick material. -/obj/projectile/fake_syringe/on_hit(atom/target, blocked = 0, def_zone = null) +/obj/projectile/fake_syringe/on_impact(atom/target, impact_flags, def_zone, efficiency) if(isliving(target)) var/mob/living/L = target if(!L.can_inject(null, null, def_zone, piercing)) - return FALSE + return impact_flags | PROJECTILE_IMPACT_BLOCKED L.custom_pain(SPAN_WARNING("You feel a tiny prick!"), 1, TRUE) - return ..() // This will add the modifier and return the correct value. - + return ..() // Fake syringe, which inflicts a long lasting modifier that slowly kills them. /obj/projectile/fake_syringe/poison diff --git a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm index 172854fd9d5..56ddbc516b3 100644 --- a/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm +++ b/code/modules/mob/living/simple_mob/subtypes/occult/constructs/juggernaut.dm @@ -63,46 +63,45 @@ . = ..() AddComponent(/datum/component/horror_aura/strong) -/mob/living/simple_mob/construct/juggernaut/bullet_act(var/obj/projectile/P) - var/reflectchance = 80 - round(P.damage/3) - if(prob(reflectchance)) +/mob/living/simple_mob/construct/juggernaut/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + var/reflectchance = 80 - round(proj.damage/3) + if(prob(reflectchance) && !istype(src, /mob/living/simple_mob/construct/juggernaut/behemoth)) var/damage_mod = rand(2,4) - var/projectile_dam_type = P.damage_type - var/incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3))) - var/armorcheck = run_armor_check(null, P.damage_flag) - var/soakedcheck = get_armor_soak(null, P.damage_flag) - if(!(istype(P, /obj/projectile/energy) || istype(P, /obj/projectile/beam))) - visible_message("The [P.name] bounces off of [src]'s shell!", \ - "The [P.name] bounces off of [src]'s shell!") + var/projectile_dam_type = proj.damage_type + var/incoming_damage = (round(proj.damage / damage_mod) - (round((proj.damage / damage_mod) * 0.3))) + var/armorcheck = run_armor_check(null, proj.damage_flag) + var/soakedcheck = get_armor_soak(null, proj.damage_flag) + if(!(istype(proj, /obj/projectile/energy) || istype(proj, /obj/projectile/beam))) + visible_message("The [proj.name] bounces off of [src]'s shell!", \ + "The [proj.name] bounces off of [src]'s shell!") new /obj/item/material/shard/shrapnel(src.loc) - if(!(P.damage_type == BRUTE || P.damage_type == BURN)) + if(!(proj.damage_type == BRUTE || proj.damage_type == BURN)) projectile_dam_type = BRUTE incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to brute for physical projectiles, though severely decreased. - apply_damage(incoming_damage, projectile_dam_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P) - return -1 //Doesn't reflect non-beams or non-energy projectiles. They just smack and drop with little to no effect. + apply_damage(incoming_damage, projectile_dam_type, null, armorcheck, soakedcheck, is_sharp(proj), has_edge(proj), proj) + return ..() else - visible_message("The [P.name] gets reflected by [src]'s shell!", \ - "The [P.name] gets reflected by [src]'s shell!") + visible_message("The [proj.name] gets reflected by [src]'s shell!", \ + "The [proj.name] gets reflected by [src]'s shell!") damage_mod = rand(3,5) - incoming_damage = (round(P.damage / damage_mod) - (round((P.damage / damage_mod) * 0.3))) - if(!(P.damage_type == BRUTE || P.damage_type == BURN)) + incoming_damage = (round(proj.damage / damage_mod) - (round((proj.damage / damage_mod) * 0.3))) + if(!(proj.damage_type == BRUTE || proj.damage_type == BURN)) projectile_dam_type = BURN incoming_damage = round(incoming_damage / 4) //Damage from strange sources is converted to burn for energy-type projectiles, though severely decreased. - apply_damage(incoming_damage, P.damage_type, null, armorcheck, soakedcheck, is_sharp(P), has_edge(P), P) + apply_damage(incoming_damage, proj.damage_type, null, armorcheck, soakedcheck, is_sharp(proj), has_edge(proj), proj) // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + if(proj.starting) + var/new_x = proj.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/new_y = proj.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) var/turf/curloc = get_turf(src) // redirect the projectile - P.redirect(new_x, new_y, curloc, src) - P.reflected = 1 - - return -1 // complete projectile permutation + proj.legacy_redirect(new_x, new_y, curloc, src) + proj.reflected = 1 - return (..(P)) + return PROJECTILE_IMPACT_REFLECT + return ..() /* * The Behemoth. Admin-allowance only, still try to keep it in some guideline of 'Balanced', even if it means Security has to be fully geared to be so. @@ -138,22 +137,21 @@ /spell/targeted/construct_advanced/slam ) -/mob/living/simple_mob/construct/juggernaut/behemoth/bullet_act(var/obj/projectile/P) - var/reflectchance = 80 - round(P.damage/3) +/mob/living/simple_mob/construct/juggernaut/behemoth/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + var/reflectchance = 80 - round(proj.damage/3) if(prob(reflectchance)) - visible_message("The [P.name] gets reflected by [src]'s shell!", \ - "The [P.name] gets reflected by [src]'s shell!") + visible_message("The [proj.name] gets reflected by [src]'s shell!", \ + "The [proj.name] gets reflected by [src]'s shell!") // Find a turf near or on the original location to bounce to - if(P.starting) - var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) - var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + if(proj.starting) + var/new_x = proj.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/new_y = proj.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) var/turf/curloc = get_turf(src) // redirect the projectile - P.redirect(new_x, new_y, curloc, src) - P.reflected = 1 + proj.legacy_redirect(new_x, new_y, curloc, src) + proj.reflected = 1 - return -1 // complete projectile permutation - - return (..(P)) + return PROJECTILE_IMPACT_REFLECT + return ..() diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm index 349fd1bfe42..6165ec87ae7 100644 --- a/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm +++ b/code/modules/mob/living/simple_mob/subtypes/slime/feral/feral.dm @@ -73,9 +73,11 @@ icon_scale_y = 2 sharp = TRUE -/obj/projectile/icicle/on_impact(atom/A) - playsound(get_turf(A), "shatter", 70, 1) - return ..() +/obj/projectile/icicle/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + playsound(get_turf(target), "shatter", 70, 1) /obj/projectile/icicle/get_structure_damage() return damage / 2 // They're really deadly against mobs, but less effective against solid things. diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm index 9bee1fc035f..8923e161059 100644 --- a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/subtypes.dm @@ -199,12 +199,11 @@ log_and_message_admins("[src] ignited due to exposure to fire.") ignite() -/mob/living/simple_mob/slime/xenobio/dark_purple/bullet_act(var/obj/projectile/P, var/def_zone) - if(P.damage_type && P.damage_type == BURN && P.damage) // Most bullets won't trigger the explosion, as a mercy towards Security. - log_and_message_admins("[src] ignited due to bring hit by a burning projectile[P.firer ? " by [key_name(P.firer)]" : ""].") +/mob/living/simple_mob/slime/xenobio/dark_purple/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(proj.damage_type && proj.damage_type == BURN && proj.damage) // Most bullets won't trigger the explosion, as a mercy towards Security. + log_and_message_admins("[src] ignited due to bring hit by a burning projectile[proj.firer ? " by [key_name(proj.firer)]" : ""].") ignite() - else - return ..() /mob/living/simple_mob/slime/xenobio/dark_purple/attackby(var/obj/item/W, var/mob/user) if(istype(W) && W.damage_force && W.damtype == BURN) @@ -285,21 +284,20 @@ /mob/living/simple_mob/slime/xenobio/amber ) -/mob/living/simple_mob/slime/xenobio/silver/bullet_act(var/obj/projectile/P, var/def_zone) - if(istype(P,/obj/projectile/beam) || istype(P, /obj/projectile/energy)) - visible_message(SPAN_DANGER("\The [src] reflects \the [P]!")) +/mob/living/simple_mob/slime/xenobio/silver/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(istype(proj,/obj/projectile/beam) || istype(proj, /obj/projectile/energy)) + visible_message(SPAN_DANGER("\The [src] reflects \the [proj]!")) // Find a turf near or on the original location to bounce to - var/new_x = P.starting.x + pick(0, 0, 0, -1, 1, -2, 2) - var/new_y = P.starting.y + pick(0, 0, 0, -1, 1, -2, 2) + var/new_x = proj.starting.x + pick(0, 0, 0, -1, 1, -2, 2) + var/new_y = proj.starting.y + pick(0, 0, 0, -1, 1, -2, 2) var/turf/curloc = get_turf(src) // redirect the projectile - P.redirect(new_x, new_y, curloc, src) - P.reflected = TRUE - return PROJECTILE_CONTINUE // complete projectile permutation - else - return ..() + proj.legacy_redirect(new_x, new_y, curloc, src) + proj.reflected = TRUE + impact_flags |= PROJECTILE_IMPACT_REFLECT + return ..() // Tier 3 @@ -654,12 +652,11 @@ log_and_message_admins("[src] exploded due to exposure to fire.") explode() -/mob/living/simple_mob/slime/xenobio/oil/bullet_act(obj/projectile/P, def_zone) - if(P.damage_type && P.damage_type == BURN && P.damage) // Most bullets won't trigger the explosion, as a mercy towards Security. - log_and_message_admins("[src] exploded due to bring hit by a burning projectile[P.firer ? " by [key_name(P.firer)]" : ""].") +/mob/living/simple_mob/slime/xenobio/oil/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(proj.damage_type && proj.damage_type == BURN && proj.damage) // Most bullets won't trigger the explosion, as a mercy towards Security. + log_and_message_admins("[src] exploded due to bring hit by a burning projectile[proj.firer ? " by [key_name(proj.firer)]" : ""].") explode() - else - return ..() /mob/living/simple_mob/slime/xenobio/oil/attackby(obj/item/W, mob/living/user) if(istype(W) && W.damage_force && W.damtype == BURN) diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm index e8bef7354c0..0313e5be44b 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/corrupt_hounds.dm @@ -136,16 +136,13 @@ to_chat(user, "This weapon is ineffective, it does no damage.") visible_message("\The [user] gently taps [src] with \the [O].") -/mob/living/simple_mob/vore/aggressive/corrupthound/sword/bullet_act(var/obj/projectile/Proj) - if(!Proj) return +/mob/living/simple_mob/vore/aggressive/corrupthound/sword/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(prob(35)) - visible_message("[src] deflects [Proj] with its sword tail!") - if(Proj.firer) - ai_holder.react_to_attack_polaris(Proj.firer) - return - else - ..() - + visible_message("[src] deflects [proj] with its sword tail!") + if(proj.firer) + ai_holder.react_to_attack_polaris(proj.firer) + return PROJECTILE_IMPACT_BLOCKED + return ..() /mob/living/simple_mob/vore/aggressive/corrupthound/isSynthetic() return TRUE diff --git a/code/modules/mob/mob-damage.dm b/code/modules/mob/mob-damage.dm new file mode 100644 index 00000000000..f797f95b870 --- /dev/null +++ b/code/modules/mob/mob-damage.dm @@ -0,0 +1,7 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Damage Instance Handling *// + +/mob/inflict_damage_instance(SHIELDCALL_PROC_HEADER) + return diff --git a/code/modules/mob/mob-defense.dm b/code/modules/mob/mob-defense.dm new file mode 100644 index 00000000000..632159f429c --- /dev/null +++ b/code/modules/mob/mob-defense.dm @@ -0,0 +1,121 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 silicons *// + +//* Armor Handling *// + +/** + * An override of /atom/proc/run_armorcalls(), with zone filter capability. + * + * This is what you should override, instead of the check/run procs. + * + * * At current moment, due to expensiveness reasons, not providing filter_zone will result in only SHIELDCALL_ARG_DAMAGE being modified. + * + * @params + * * shieldcall_args - passed in shieldcall args list to modify + * * fake_attack - are we just checking armor? + * * filter_zone - confine to a certain hit zone. this is **required** for full processing, otherwise we just check overall damage. + */ +/mob/run_armorcalls(list/shieldcall_args, fake_attack, filter_zone) + ..() // perform default /atom level + +/** + * Generic, low-level armor check for inbound attacks + * + * * This will filter armor by zone. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/check_mob_armor(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, TRUE, hit_zone) // by default, use atom/var/armor on ourselves + +/** + * Generic, low-level armor processing for inbound attacks + * + * * This will filter armor by zone. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/run_mob_armor(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, FALSE, hit_zone) // by default, use atom/var/armor on ourselves + +/** + * Checks the average armor for a full-body attack. + * + * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/check_mob_overall_armor(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, TRUE) // by default, use atom/var/armor on ourselves + +/** + * Checks the average armor for a full-body attack. + * + * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/run_mob_overall_armor(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, FALSE) // by default, use atom/var/armor on ourselves + +//* Defense Handling *// + +/** + * Generic, low-level defense check for inbound attacks + * + * * This will filter defense by zone. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/check_mob_defense(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, TRUE, hit_zone) // by default, use atom/var/armor on ourselves + run_shieldcalls(args, TRUE) + +/** + * Generic, low-level defense processing for inbound attacks + * + * * This will filter defense by zone. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/run_mob_defense(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, FALSE, hit_zone) // by default, use atom/var/armor on ourselves + run_shieldcalls(args, FALSE) + +/** + * Checks the average defense for a full-body attack. + * + * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/check_mob_overall_defense(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, TRUE) // by default, use atom/var/armor on ourselves + run_shieldcalls(args, TRUE) + +/** + * Checks the average defense for a full-body attack. + * + * * this is used for lazy-sim's like explosion where we're not simulating every limb's individual damage tick. + * * This operates like [atom_shieldcall()]. + * + * @return modified argument list, with SHIELDCALL_ARG_* defines as indices. + */ +/mob/proc/run_mob_overall_defense(SHIELDCALL_PROC_HEADER) + SHOULD_NOT_OVERRIDE(TRUE) + run_armorcalls(args, FALSE) // by default, use atom/var/armor on ourselves + run_shieldcalls(args, FALSE) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 74257bf1b8e..6c4eccc05ae 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -427,9 +427,6 @@ . += M . -= src -/mob/proc/ret_grab(obj/effect/list_container/mobl/L as obj, flag) - return - /** * Get the notes of this mob * diff --git a/code/modules/mob/movement.dm b/code/modules/mob/movement.dm index a42342538f4..c02dd0d4d7e 100644 --- a/code/modules/mob/movement.dm +++ b/code/modules/mob/movement.dm @@ -47,9 +47,6 @@ . = ..() if(.) return - if(istype(mover, /obj/projectile)) - var/obj/projectile/P = mover - return !P.can_hit_target(src, P.permutated, src == P.original, TRUE) // thrown things still hit us even when nondense if(can_cross_under(mover)) return TRUE @@ -61,8 +58,11 @@ return TRUE return ..() +/** + * Can something cross under us without being blocked by us? + */ /mob/proc/can_cross_under(atom/movable/mover) - return !mover.density && !mover.throwing + return !mover.density && !mover.throwing && !istype(mover, /obj/projectile) /** * Toggle the move intent of the mob @@ -284,7 +284,7 @@ //Something with pulling things if(locate(/obj/item/grab, mob)) add_delay_grab = 7 - var/list/grabbed = mob.ret_grab() + var/list/grabbed = mob.get_grabbing_recursive() + src // im fucking screaming; this is because old code always considers self as grabbed. if(grabbed) if(grabbed.len == 2) grabbed -= mob diff --git a/code/modules/modular_computers/computers/modular_computer/damage.dm b/code/modules/modular_computers/computers/modular_computer/damage.dm index 64d34486891..32da9c40a24 100644 --- a/code/modules/modular_computers/computers/modular_computer/damage.dm +++ b/code/modules/modular_computers/computers/modular_computer/damage.dm @@ -51,12 +51,12 @@ * "Burn" damage is equally strong against internal components and exterior casing * "Brute" damage mostly damages the casing. */ -/obj/item/modular_computer/bullet_act(obj/projectile/Proj) +/obj/item/modular_computer/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() - switch(Proj.damage_type) + switch(proj.damage_type) if(BRUTE) - take_damage_legacy(Proj.damage, Proj.damage / 2) + take_damage_legacy(proj.damage, proj.damage / 2) if(HALLOSS) - take_damage_legacy(Proj.damage, Proj.damage / 3, 0) + take_damage_legacy(proj.damage, proj.damage / 3, 0) if(BURN) - take_damage_legacy(Proj.damage, Proj.damage / 1.5) + take_damage_legacy(proj.damage, proj.damage / 1.5) diff --git a/code/modules/multiz/atoms.dm b/code/modules/multiz/atoms.dm index 9aa67f7f61c..5abbda59d9a 100644 --- a/code/modules/multiz/atoms.dm +++ b/code/modules/multiz/atoms.dm @@ -23,4 +23,4 @@ * new_loc - new turf */ /atom/proc/z_pass_out(atom/movable/AM, dir, turf/new_loc) - return !AM || Uncross(AM) + return !AM || Uncross(AM, new_loc) diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm index 650e4f85d0d..907f3d5a410 100644 --- a/code/modules/multiz/movement.dm +++ b/code/modules/multiz/movement.dm @@ -314,7 +314,7 @@ return 1 var/atom/A = find_fall_target(oldloc, landing) - if(special_fall_handle(A) || !A || !A.check_impact(src)) + if(special_fall_handle(A) || !A || !A.check_z_impact(src)) return var/mob/drop_mob = locate(/mob, landing) if(drop_mob && !(drop_mob == src) && ismob(drop_mob) && isliving(drop_mob)) //Shitload of checks. This is because the game finds various ways to screw me over. @@ -371,16 +371,14 @@ return TRUE return prevent_z_fall(falling_atom, 0, NONE) & (FALL_TERMINATED | FALL_BLOCKED) - /** * If you are hit: how is it handled. * Return TRUE if the generic fall_impact should be called. * Return FALSE if you handled it yourself or if there's no effect from hitting you. */ -/atom/proc/check_impact(atom/movable/falling_atom) +/atom/proc/check_z_impact(atom/movable/falling_atom) return TRUE - /** * Called by CheckFall when we actually hit something. Various Vars will be described below. * hit_atom is the thing we fall on. diff --git a/code/modules/multiz/turf.dm b/code/modules/multiz/turf.dm index a4261ffa6b4..e35652e4e2b 100644 --- a/code/modules/multiz/turf.dm +++ b/code/modules/multiz/turf.dm @@ -113,9 +113,6 @@ return TRUE // impact! return ..() -/turf/check_impact(atom/movable/falling_atom) - return TRUE - //* lookups /turf/proc/above() diff --git a/code/modules/organs/external/external.dm b/code/modules/organs/external/external.dm index a7861e3289a..ded3e53b109 100644 --- a/code/modules/organs/external/external.dm +++ b/code/modules/organs/external/external.dm @@ -1269,7 +1269,7 @@ Note that amputating the affected organ does in fact remove the infection from t /obj/item/organ/external/proc/embed(var/obj/item/W, var/silent = 0) if(!owner || loc != owner) return - if(owner.species.species_flags & IS_SLIME) + if(owner.species.reagent_tag == IS_SLIME) create_wound( CUT, 15 ) //fixes proms being bugged into paincrit;instead whatever would embed now just takes a chunk out src.visible_message("[owner] has been seriously wounded by [W]!") W.add_blood(owner) diff --git a/code/modules/organs/internal/augment/armmounted.dm b/code/modules/organs/internal/augment/armmounted.dm index b6e32224a72..c4b1c99dd71 100644 --- a/code/modules/organs/internal/augment/armmounted.dm +++ b/code/modules/organs/internal/augment/armmounted.dm @@ -70,7 +70,7 @@ /obj/item/organ/internal/augment/armmounted/hand/sword name = "energy blade implant" - integrated_object_type = /obj/item/melee/energy/sword + integrated_object_type = /obj/item/melee/transforming/energy/sword /* * Shoulder augment. diff --git a/code/modules/overmap/legacy/ships/computers/sensors.dm b/code/modules/overmap/legacy/ships/computers/sensors.dm index ad0e255d737..f142942fcc8 100644 --- a/code/modules/overmap/legacy/ships/computers/sensors.dm +++ b/code/modules/overmap/legacy/ships/computers/sensors.dm @@ -219,9 +219,9 @@ else if(health < max_health * 0.75) . += "It shows signs of damage!" -/obj/machinery/shipsensors/bullet_act(var/obj/projectile/Proj) - take_damage_legacy(Proj.get_structure_damage()) - ..() +/obj/machinery/shipsensors/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + take_damage_legacy(proj.get_structure_damage()) /obj/machinery/shipsensors/proc/toggle() if(!use_power && (health == 0 || !in_vacuum())) diff --git a/code/modules/power/antimatter/control.dm b/code/modules/power/antimatter/control.dm index 75ad385a600..f65362e5f7e 100644 --- a/code/modules/power/antimatter/control.dm +++ b/code/modules/power/antimatter/control.dm @@ -8,6 +8,7 @@ use_power = USE_POWER_IDLE idle_power_usage = 100 active_power_usage = 1000 + integrity_flags = INTEGRITY_INDESTRUCTIBLE var/list/obj/machinery/am_shielding/linked_shielding var/list/obj/machinery/am_shielding/linked_cores @@ -116,12 +117,10 @@ check_stability() return - -/obj/machinery/power/am_control_unit/bullet_act(var/obj/projectile/Proj) - if(Proj.damage_flag != "bullet") - stability -= Proj.damage - return 0 - +/obj/machinery/power/am_control_unit/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(proj.damage_flag != ARMOR_BULLET) + stability -= proj.damage + return ..() /obj/machinery/power/am_control_unit/power_change() ..() diff --git a/code/modules/power/antimatter/shielding.dm b/code/modules/power/antimatter/shielding.dm index 8a8784bc5ab..d7997462c6d 100644 --- a/code/modules/power/antimatter/shielding.dm +++ b/code/modules/power/antimatter/shielding.dm @@ -98,12 +98,11 @@ check_stability() return +/obj/machinery/am_shielding/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() -/obj/machinery/am_shielding/bullet_act(var/obj/projectile/Proj) - if(Proj.damage_flag != "bullet") - stability -= Proj.damage/2 - return 0 - + if(proj.damage_flag != "bullet") + stability -= proj.damage/2 /obj/machinery/am_shielding/update_icon() cut_overlays() diff --git a/code/modules/power/fusion/core/_core.dm b/code/modules/power/fusion/core/_core.dm index eff1ef5682b..2ff85f772b7 100644 --- a/code/modules/power/fusion/core/_core.dm +++ b/code/modules/power/fusion/core/_core.dm @@ -87,9 +87,10 @@ var/list/fusion_cores = list() owned_field.AddParticles(name, quantity) . = 1 -/obj/machinery/power/fusion_core/bullet_act(var/obj/projectile/Proj) +/obj/machinery/power/fusion_core/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(owned_field) - . = owned_field.bullet_act(Proj) + return proj.impact_redirect(owned_field, args) + return ..() /obj/machinery/power/fusion_core/proc/set_strength(var/value) value = clamp(value, MIN_FIELD_STR, MAX_FIELD_STR) diff --git a/code/modules/power/fusion/core/core_field.dm b/code/modules/power/fusion/core/core_field.dm index 35daf97aa66..ec9c2ab3410 100644 --- a/code/modules/power/fusion/core/core_field.dm +++ b/code/modules/power/fusion/core/core_field.dm @@ -26,7 +26,6 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY) var/obj/machinery/power/fusion_core/owned_core var/list/dormant_reactant_quantities = list() - var/list/particle_catchers = list() var/list/ignore_types @@ -60,67 +59,24 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY) if(!owned_core) qdel(src) id_tag = owned_core.id_tag - //create the gimmicky things to handle field collisions - var/obj/effect/fusion_particle_catcher/catcher - - catcher = new (locate(src.x,src.y,src.z)) - catcher.parent = src - catcher.SetSize(1) - particle_catchers.Add(catcher) - - catcher = new (locate(src.x-1,src.y,src.z)) - catcher.parent = src - catcher.SetSize(3) - particle_catchers.Add(catcher) - catcher = new (locate(src.x+1,src.y,src.z)) - catcher.parent = src - catcher.SetSize(3) - particle_catchers.Add(catcher) - catcher = new (locate(src.x,src.y+1,src.z)) - catcher.parent = src - catcher.SetSize(3) - particle_catchers.Add(catcher) - catcher = new (locate(src.x,src.y-1,src.z)) - catcher.parent = src - catcher.SetSize(3) - particle_catchers.Add(catcher) - - catcher = new (locate(src.x-2,src.y,src.z)) - catcher.parent = src - catcher.SetSize(5) - particle_catchers.Add(catcher) - catcher = new (locate(src.x+2,src.y,src.z)) - catcher.parent = src - catcher.SetSize(5) - particle_catchers.Add(catcher) - catcher = new (locate(src.x,src.y+2,src.z)) - catcher.parent = src - catcher.SetSize(5) - particle_catchers.Add(catcher) - catcher = new (locate(src.x,src.y-2,src.z)) - catcher.parent = src - catcher.SetSize(5) - particle_catchers.Add(catcher) - - catcher = new (locate(src.x-3,src.y,src.z)) - catcher.parent = src - catcher.SetSize(7) - particle_catchers.Add(catcher) - catcher = new (locate(src.x+3,src.y,src.z)) - catcher.parent = src - catcher.SetSize(7) - particle_catchers.Add(catcher) - catcher = new (locate(src.x,src.y+3,src.z)) - catcher.parent = src - catcher.SetSize(7) - particle_catchers.Add(catcher) - catcher = new (locate(src.x,src.y-3,src.z)) - catcher.parent = src - catcher.SetSize(7) - particle_catchers.Add(catcher) START_PROCESSING(SSobj, src) +/obj/effect/fusion_em_field/bullet_act(obj/projectile/proj, impact_flags, def_zone, efficiency) + if(!(proj.projectile_type & PROJECTILE_TYPE_BEAM)) + return ..() + AddEnergy(proj.damage) + update_icon() + impact_flags |= PROJECTILE_IMPACT_DELETE + return ..() + +/obj/effect/fusion_em_field/CanAllowThrough(atom/movable/mover, turf/target) + if(istype(mover, /obj/projectile)) + var/obj/projectile/proj = mover + if(proj.projectile_type & PROJECTILE_TYPE_BEAM) + return FALSE + return TRUE + /obj/effect/fusion_em_field/process(delta_time) //make sure the field generator is still intact if(!owned_core || QDELETED(owned_core)) @@ -340,8 +296,20 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY) tick_instability += rand(30,50) AM.emp_act(empsev) -/obj/effect/fusion_em_field/proc/change_size(var/newsize = 1) - var/changed = 0 +/** + * Immediately change the field's size. + * + * * This is the radius of the field. + * * This will change `locs` of this field, and its bounds! + */ +/obj/effect/fusion_em_field/proc/change_size(new_size = 1) + ASSERT(!((new_size - 1) % 2)) + ASSERT(new_size <= 13) + + if(new_size == size) + return FALSE + + //! LEGACY var/static/list/size_to_icon = list( "3" = 'icons/effects/96x96.dmi', "5" = 'icons/effects/160x160.dmi', @@ -351,19 +319,27 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY) "13" = 'icons/effects/416x416.dmi' ) - if( ((newsize-1)%2==0) && (newsize<=13) ) - icon = 'icons/obj/machines/power/fusion.dmi' - if(newsize>1) - icon = size_to_icon["[newsize]"] - icon_state = "emfield_s[newsize]" - pixel_x = ((newsize-1) * -16) * PIXEL_MULTIPLIER - pixel_y = ((newsize-1) * -16) * PIXEL_MULTIPLIER - size = newsize - changed = newsize + icon = 'icons/obj/machines/power/fusion.dmi' - for(var/obj/effect/fusion_particle_catcher/catcher in particle_catchers) - catcher.UpdateSize() - return changed + if(new_size>1) + icon = size_to_icon["[new_size]"] + icon_state = "emfield_s[new_size]" + //! END + + size = new_size + + // this will shift locs! + var/new_additional_radius = (new_size - 1) / 2 + bound_x = -new_additional_radius * WORLD_ICON_SIZE + bound_y = -new_additional_radius * WORLD_ICON_SIZE + bound_width = new_additional_radius * 2 * WORLD_ICON_SIZE + WORLD_ICON_SIZE + bound_height = new_additional_radius * 2 * WORLD_ICON_SIZE + WORLD_ICON_SIZE + + // shift visuals + pixel_x = -new_additional_radius * WORLD_ICON_SIZE + pixel_y = -new_additional_radius * WORLD_ICON_SIZE + + return TRUE //the !!fun!! part /obj/effect/fusion_em_field/proc/React() @@ -480,18 +456,12 @@ GLOBAL_VAR_INIT(max_fusion_air_heat, INFINITY) /obj/effect/fusion_em_field/Destroy() set_light(0) RadiateAll() - for(var/obj/effect/fusion_particle_catcher/catcher in particle_catchers) - qdel(catcher) if(owned_core) owned_core.owned_field = null owned_core = null STOP_PROCESSING(SSobj, src) . = ..() -/obj/effect/fusion_em_field/bullet_act(var/obj/projectile/Proj) - AddEnergy(Proj.damage) - update_icon() - return 0 //All procs below this point are called in _core.dm, starting at line 41. //Stability monitoring. Gives radio annoucements if field stability is below 80% /obj/effect/fusion_em_field/proc/stability_monitor() diff --git a/code/modules/power/fusion/fusion_particle_catcher.dm b/code/modules/power/fusion/fusion_particle_catcher.dm deleted file mode 100644 index ea8ae497ff5..00000000000 --- a/code/modules/power/fusion/fusion_particle_catcher.dm +++ /dev/null @@ -1,43 +0,0 @@ -/obj/effect/fusion_particle_catcher - icon = 'icons/effects/effects.dmi' - density = 1 - anchored = 1 - invisibility = 101 - var/obj/effect/fusion_em_field/parent - var/mysize = 0 - - light_color = COLOR_BLUE - -/obj/effect/fusion_particle_catcher/Destroy() - . =..() - parent.particle_catchers -= src - parent = null - -/obj/effect/fusion_particle_catcher/proc/SetSize(var/newsize) - name = "collector [newsize]" - mysize = newsize - UpdateSize() - -/obj/effect/fusion_particle_catcher/proc/AddParticles(var/name, var/quantity = 1) - if(parent && parent.size >= mysize) - parent.AddParticles(name, quantity) - return 1 - return 0 - -/obj/effect/fusion_particle_catcher/proc/UpdateSize() - if(parent.size >= mysize) - density = 1 - name = "collector [mysize] ON" - else - density = 0 - name = "collector [mysize] OFF" - -/obj/effect/fusion_particle_catcher/bullet_act(var/obj/projectile/Proj) - parent.AddEnergy(Proj.damage) - update_icon() - return 0 - -/obj/effect/fusion_particle_catcher/CanAllowThrough(atom/movable/mover, turf/target) - if(istype(mover, /obj/effect/accelerated_particle) || istype(mover, /obj/projectile/beam)) - return !density - return TRUE diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index c7474618077..fc548866d6b 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -607,10 +607,10 @@ tesla_zap(src, 5, power_gen * 50) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), get_turf(src), 2, 3, 4, 8), 100) // Not a normal explosion. -/obj/machinery/power/rtg/abductor/bullet_act(obj/projectile/Proj) +/obj/machinery/power/rtg/abductor/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) . = ..() - if(!going_kaboom && istype(Proj) && !Proj.nodamage && ((Proj.damage_type == BURN) || (Proj.damage_type == BRUTE))) - log_and_message_admins("[ADMIN_LOOKUPFLW(Proj.firer)] triggered an Abductor Core explosion at [x],[y],[z] via projectile.") + if(!going_kaboom && istype(proj) && !proj.nodamage && ((proj.damage_type == BURN) || (proj.damage_type == BRUTE))) + log_and_message_admins("[ADMIN_LOOKUPFLW(proj.firer)] triggered an Abductor Core explosion at [x],[y],[z] via projectile.") asplod() /obj/machinery/power/rtg/abductor/attack_hand(mob/user, list/params) diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/singularity/field_generator.dm index 2233e458f08..6a80497c66b 100644 --- a/code/modules/power/singularity/field_generator.dm +++ b/code/modules/power/singularity/field_generator.dm @@ -21,7 +21,7 @@ field_generator power level display anchored = 0 density = 1 use_power = USE_POWER_OFF - + armor = /datum/armor/object/heavy worth_intrinsic = 350 var/const/num_power_levels = 6 // Total number of power level icon has var/Varedit_start = 0 @@ -152,23 +152,20 @@ field_generator power level display ..() return - /obj/machinery/field_generator/emp_act() return 0 -/obj/machinery/field_generator/bullet_act(var/obj/projectile/Proj) - if(istype(Proj, /obj/projectile/beam)) - power += Proj.damage * EMITTER_DAMAGE_POWER_TRANSFER +/obj/machinery/field_generator/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(istype(proj, /obj/projectile/beam)) + power += proj.damage * EMITTER_DAMAGE_POWER_TRANSFER update_icon() - return 0 - + return PROJECTILE_IMPACT_DELETE + return ..() /obj/machinery/field_generator/Destroy() src.cleanup() . = ..() - - /obj/machinery/field_generator/proc/turn_off() active = 0 warming_up = 0 diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/singularity/particle_accelerator/particle.dm index 3c6aadbaf71..c19fd665569 100644 --- a/code/modules/power/singularity/particle_accelerator/particle.dm +++ b/code/modules/power/singularity/particle_accelerator/particle.dm @@ -5,8 +5,11 @@ desc = "Small things moving very fast." icon = 'icons/obj/machines/particle_accelerator2.dmi' icon_state = "particle1"//Need a new icon for this - anchored = 1 - density = 1 + anchored = TRUE + density = TRUE + generic_canpass = FALSE + plane = MOB_PLANE + layer = ABOVE_MOB_LAYER var/movement_range = 10 var/energy = 10 //energy in eV var/mega_energy = 0 //energy in MeV @@ -18,6 +21,10 @@ var/turf/source var/movetotarget = 1 +/obj/effect/accelerated_particle/CanPassThrough(atom/blocker, turf/target, blocker_opinion) + SHOULD_CALL_PARENT(FALSE) + return TRUE + /obj/effect/accelerated_particle/weak icon_state = "particle0" movement_range = 8 @@ -39,18 +46,44 @@ energy = -20 /obj/effect/accelerated_particle/Initialize(mapload, dir = SOUTH) + setDir(dir) . = ..() - src.loc = loc - src.setDir(dir) - INVOKE_ASYNC(src, PROC_REF(move), 1) + START_PROCESSING(SSprocess_5fps, src) + +/obj/effect/accelerated_particle/Destroy() + STOP_PROCESSING(SSprocess_5fps, src) + return ..() + +/obj/effect/accelerated_particle/process(delta_time) + var/old_z = z + var/turf/where_to = get_step(src, dir) + if(!where_to) + qdel(src) + return + if(!Move(where_to)) + if(!QDELETED(src)) + qdel(src) + return + else + return PROCESS_KILL + // being deleted changes Z so that's also an implicit qdeleted() check. + if(z != old_z) + if(!QDELETED(src)) + qdel(src) + return + else + return PROCESS_KILL /obj/effect/accelerated_particle/Moved() . = ..() if(!isturf(loc)) return - for(var/atom/movable/AM as anything in loc.contents) + for(var/atom/movable/AM as anything in loc) do_the_funny(AM) + if(QDELETED(src)) + return +// todo: particle_accelerator_act() or something /obj/effect/accelerated_particle/proc/do_the_funny(atom/A) if (A) if(ismob(A)) @@ -58,24 +91,14 @@ if((istype(A,/obj/machinery/the_singularitygen))||(istype(A,/obj/singularity/))||(istype(A, /obj/machinery/particle_smasher))) A:energy += energy //R-UST port - else if(istype(A,/obj/machinery/power/fusion_core)) - var/obj/machinery/power/fusion_core/collided_core = A - if(particle_type && particle_type != "neutron") - if(collided_core.AddParticles(particle_type, 1 + additional_particles)) - collided_core.owned_field.plasma_temperature += mega_energy - collided_core.owned_field.energy += energy - loc = null - else if(istype(A, /obj/effect/fusion_particle_catcher)) - var/obj/effect/fusion_particle_catcher/PC = A + if(istype(A, /obj/effect/fusion_em_field)) + var/obj/effect/fusion_em_field/field = A if(particle_type && particle_type != "neutron") - if(PC.parent.owned_core.AddParticles(particle_type, 1 + additional_particles)) - PC.parent.plasma_temperature += mega_energy - PC.parent.energy += energy - loc = null - - -/obj/effect/accelerated_particle/legacy_ex_act(severity) - qdel(src) + if(field.owned_core.AddParticles(particle_type, 1 + additional_particles)) + field.plasma_temperature += mega_energy + field.energy += energy + qdel(src) + return /obj/effect/accelerated_particle/singularity_act() return @@ -84,26 +107,3 @@ if(!istype(M)) return M.afflict_radiation(energy * 5, TRUE) - -/obj/effect/accelerated_particle/proc/move(var/lag) - var/turf/new_target - if(target) - if(movetotarget) - new_target = get_step_towards(src, target) - if(get_dist(src,new_target) < 1) - movetotarget = 0 - else - new_target = get_step_away(src, source) - else - new_target = get_step(src, dir) - if(new_target) - forceMove(new_target) - else - qdel(src) - return - movement_range-- - if(movement_range <= 0) - qdel(src) - else - sleep(lag) - move(lag) diff --git a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm index a98c670f46a..fb7500fcf42 100644 --- a/code/modules/power/singularity/particle_accelerator/particle_smasher.dm +++ b/code/modules/power/singularity/particle_accelerator/particle_smasher.dm @@ -10,6 +10,7 @@ anchored = 0 density = 1 use_power = USE_POWER_OFF + armor = /datum/armor/object/heavy var/image/material_layer // Holds the image used for the filled overlay. var/image/material_glow // Holds the image used for the glow overlay. @@ -127,11 +128,12 @@ else set_light(0, 0, "#FFFFFF") -/obj/machinery/particle_smasher/bullet_act(var/obj/projectile/Proj) - if(istype(Proj, /obj/projectile/beam)) - if(Proj.damage >= 50) +/obj/machinery/particle_smasher/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(istype(proj, /obj/projectile/beam)) + if(proj.damage >= 50) TryCraft() - return 0 + return PROJECTILE_IMPACT_DELETE + return ..() /obj/machinery/particle_smasher/process(delta_time) if(!src.anchored) // Rapidly loses focus. diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 0b12ef9396e..71ed027c49a 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -64,8 +64,8 @@ GLOBAL_LIST_BOILERPLATE(all_singularities, /obj/singularity) if(3) energy -= round(((energy+1)/4),1) -/obj/singularity/bullet_act(obj/projectile/P) - return 0 //Will there be an impact? Who knows. Will we see it? No. +/obj/singularity/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + return PROJECTILE_IMPACT_DELETE /obj/singularity/Bump(atom/A) consume(A) diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 599a8ed31d9..63c73826cfa 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -361,25 +361,26 @@ return 1 - -/obj/machinery/power/supermatter/bullet_act(var/obj/projectile/Proj) +/obj/machinery/power/supermatter/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) var/turf/L = loc - if(!istype(L)) // We don't run process() when we are in space - return 0 // This stops people from being able to really power up the supermatter - // Then bring it inside to explode instantly upon landing on a valid turf. + // We don't run process() when we are in space + // This stops people from being able to really power up the supermatter + // Then bring it inside to explode instantly upon landing on a valid turf. + if(!istype(L)) + return PROJECTILE_IMPACT_DELETE var/added_energy var/added_damage - var/proj_damage = Proj.get_structure_damage() - if(istype(Proj, /obj/projectile/beam)) + var/proj_damage = proj.get_structure_damage() + if(istype(proj, /obj/projectile/beam)) added_energy = proj_damage * config_bullet_energy * CHARGING_FACTOR / POWER_FACTOR power += added_energy else added_damage = proj_damage * config_bullet_energy damage += added_damage if(added_energy || added_damage) - investigate_log("Hit by \"[Proj.name]\". +[added_energy] Energy, +[added_damage] Damage.", INVESTIGATE_SUPERMATTER) - return 0 + investigate_log("Hit by \"[proj.name]\". +[added_energy] Energy, +[added_damage] Damage.", INVESTIGATE_SUPERMATTER) + return PROJECTILE_IMPACT_DELETE /obj/machinery/power/supermatter/attack_robot(mob/user as mob) if(Adjacent(user)) @@ -465,20 +466,25 @@ Consume(W) -/obj/machinery/power/supermatter/Bumped(atom/AM as mob|obj) - if(istype(AM, /obj/effect)) - return - if(istype(AM, /mob/living)) - var/mob/living/M = AM - var/datum/gender/T = GLOB.gender_datums[M.get_visible_gender()] - AM.visible_message("\The [AM] slams into \the [src] inducing a resonance... [T.his] body starts to glow and catch flame before flashing into ash.",\ - "You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\ - "You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat.") - else if(!grav_pulling) //To prevent spam, detonating supermatter does not indicate non-mobs being destroyed - AM.visible_message("\The [AM] smacks into \the [src] and rapidly flashes to ash.",\ - "You hear a loud crack as you are washed with a wave of heat.") - - Consume(AM) +/obj/machinery/power/supermatter/Bumped(atom/movable/bumped_atom) + . = ..() + var/their_loc = bumped_atom.loc + spawn(0) + if(their_loc != bumped_atom.loc) + return + if(istype(bumped_atom, /obj/effect)) + return + if(istype(bumped_atom, /mob/living)) + var/mob/living/M = bumped_atom + var/datum/gender/T = GLOB.gender_datums[M.get_visible_gender()] + bumped_atom.visible_message("\The [bumped_atom] slams into \the [src] inducing a resonance... [T.his] body starts to glow and catch flame before flashing into ash.",\ + "You slam into \the [src] as your ears are filled with unearthly ringing. Your last thought is \"Oh, fuck.\"",\ + "You hear an uneartly ringing, then what sounds like a shrilling kettle as you are washed with a wave of heat.") + else if(!grav_pulling) //To prevent spam, detonating supermatter does not indicate non-mobs being destroyed + bumped_atom.visible_message("\The [bumped_atom] smacks into \the [src] and rapidly flashes to ash.",\ + "You hear a loud crack as you are washed with a wave of heat.") + + Consume(bumped_atom) /obj/machinery/power/supermatter/proc/Consume(var/mob/living/user) // todo: rework the fucking supermatter so we don't need this diff --git a/code/modules/projectiles/README.md b/code/modules/projectiles/README.md new file mode 100644 index 00000000000..845e6a2075b --- /dev/null +++ b/code/modules/projectiles/README.md @@ -0,0 +1,8 @@ +# Projectiles + +The projectiles module contains the following and the systems that support them: + +* Guns +* Ammunition +* Magazines +* Projectiles diff --git a/code/modules/projectiles/ammunition/ammo_caliber.dm b/code/modules/projectiles/ammunition/ammo_caliber.dm index 4bc9635dff4..a16391347c3 100644 --- a/code/modules/projectiles/ammunition/ammo_caliber.dm +++ b/code/modules/projectiles/ammunition/ammo_caliber.dm @@ -1,5 +1,5 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 silicons *// +//* Copyright (c) 2024 Citadel Station Developers *// GLOBAL_LIST_INIT(calibers, init_calibers()) diff --git a/code/modules/projectiles/ammunition/calibers/special/dart.dm b/code/modules/projectiles/ammunition/calibers/special/dart.dm index 118dac8ddb3..a4772c10cd3 100644 --- a/code/modules/projectiles/ammunition/calibers/special/dart.dm +++ b/code/modules/projectiles/ammunition/calibers/special/dart.dm @@ -63,8 +63,13 @@ . = ..() create_reagents(reagent_amount) -/obj/projectile/bullet/chemdart/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) - if(blocked < 2 && isliving(target)) +/obj/projectile/bullet/chemdart/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if((. & PROJECTILE_IMPACT_BLOCKED) || (efficiency < 0.95)) + return + if(isliving(target)) var/mob/living/L = target if(L.can_inject(target_zone=def_zone)) reagents.trans_to_mob(L, reagent_amount, CHEM_INJECT) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 76512e49644..8fb6cf408a3 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -55,6 +55,15 @@ zoomdevicename = "scope" inhand_default_type = INHAND_DEFAULT_ICON_GUNS + //* Accuracy, Dispersion, Instability *// + + /// entirely disable baymiss on fired projectiles + /// + /// * this is a default value; set to null by default to have the projectile's say. + var/accuracy_disabled = null + + // legacy below // + var/burst = 1 var/fire_delay = 6 //delay after shooting before the gun can be used again var/burst_delay = 2 //delay between shots, if firing in bursts @@ -321,9 +330,10 @@ if(!istype(A)) return ..() if(user.a_intent == INTENT_HARM) //point blank shooting - if (A == user && user.zone_sel.selecting == O_MOUTH && !mouthshoot) - handle_suicide(user) - return + // todo: disabled for now + // if (A == user && user.zone_sel.selecting == O_MOUTH && !mouthshoot) + // handle_suicide(user) + // return var/mob/living/L = user if(user && user.client && istype(L) && L.aiming && L.aiming.active && L.aiming.aiming_at != A && A != user) PreFire(A,user) //They're using the new gun system, locate what they're aiming at. @@ -501,7 +511,7 @@ var/acc = burst_accuracy[min(i, burst_accuracy.len)] var/disp = dispersion[min(i, dispersion.len)] - P.accuracy = accuracy + acc + P.accuracy_overall_modify *= 1 + acc / 100 P.dispersion = disp P.shot_from = src.name @@ -662,22 +672,24 @@ disp_mod += one_handed_penalty*0.5 //dispersion per point of two-handedness //Accuracy modifiers - P.accuracy = accuracy + acc_mod - P.dispersion = disp_mod + if(!isnull(accuracy_disabled)) + P.accuracy_disabled = accuracy_disabled - P.accuracy -= user.get_accuracy_penalty() + P.accuracy_overall_modify *= 1 + (acc_mod / 100) + P.accuracy_overall_modify *= 1 - (user.get_accuracy_penalty() / 100) + P.dispersion = disp_mod //accuracy bonus from aiming if (aim_targets && (target in aim_targets)) //If you aim at someone beforehead, it'll hit more often. //Kinda balanced by fact you need like 2 seconds to aim //As opposed to no-delay pew pew - P.accuracy += 30 + P.accuracy_overall_modify *= 1.3 // Some modifiers make it harder or easier to hit things. for(var/datum/modifier/M in user.modifiers) if(!isnull(M.accuracy)) - P.accuracy += M.accuracy + P.accuracy_overall_modify += 1 + (M.accuracy / 100) if(!isnull(M.accuracy_dispersion)) P.dispersion = max(P.dispersion + M.accuracy_dispersion, 0) @@ -717,44 +729,45 @@ else playsound(src, shot_sound, 50, 1) +// todo: rework all this this is fucking dumb //Suicide handling. -/obj/item/gun/var/mouthshoot = 0 //To stop people from suiciding twice... >.> - -/obj/item/gun/proc/handle_suicide(mob/living/user) - if(!ishuman(user)) - return - var/mob/living/carbon/human/M = user - - mouthshoot = 1 - M.visible_message("[user] sticks their gun in their mouth, ready to pull the trigger...") - if(!do_after(user, 40)) - M.visible_message("[user] decided life was worth living") - mouthshoot = 0 - return - var/obj/projectile/in_chamber = consume_next_projectile() - if (istype(in_chamber)) - user.visible_message("[user] pulls the trigger.") - play_fire_sound(M, in_chamber) - if(istype(in_chamber, /obj/projectile/beam/lasertag)) - user.show_message("You feel rather silly, trying to commit suicide with a toy.") - mouthshoot = 0 - return - - in_chamber.on_hit(M) - if(in_chamber.damage_type != HALLOSS && !in_chamber.nodamage) - log_and_message_admins("[key_name(user)] commited suicide using \a [src]") - user.apply_damage(in_chamber.damage*2.5, in_chamber.damage_type, "head", used_weapon = "Point blank shot in the mouth with \a [in_chamber]", sharp=1) - user.death() - else if(in_chamber.damage_type == HALLOSS) - to_chat(user, "Ow...") - user.apply_effect(110,AGONY,0) - qdel(in_chamber) - mouthshoot = 0 - return - else - handle_click_empty(user) - mouthshoot = 0 - return +// /obj/item/gun/var/mouthshoot = 0 //To stop people from suiciding twice... >.> + +// /obj/item/gun/proc/handle_suicide(mob/living/user) +// if(!ishuman(user)) +// return +// var/mob/living/carbon/human/M = user + +// mouthshoot = 1 +// M.visible_message("[user] sticks their gun in their mouth, ready to pull the trigger...") +// if(!do_after(user, 40)) +// M.visible_message("[user] decided life was worth living") +// mouthshoot = 0 +// return +// var/obj/projectile/in_chamber = consume_next_projectile() +// if (istype(in_chamber)) +// user.visible_message("[user] pulls the trigger.") +// play_fire_sound(M, in_chamber) +// if(istype(in_chamber, /obj/projectile/beam/lasertag)) +// user.show_message("You feel rather silly, trying to commit suicide with a toy.") +// mouthshoot = 0 +// return + +// in_chamber.on_hit(M) +// if(in_chamber.damage_type != HALLOSS && !in_chamber.nodamage) +// log_and_message_admins("[key_name(user)] commited suicide using \a [src]") +// user.apply_damage(in_chamber.damage*2.5, in_chamber.damage_type, "head", used_weapon = "Point blank shot in the mouth with \a [in_chamber]", sharp=1) +// user.death() +// else if(in_chamber.damage_type == HALLOSS) +// to_chat(user, "Ow...") +// user.apply_effect(110,AGONY,0) +// qdel(in_chamber) +// mouthshoot = 0 +// return +// else +// handle_click_empty(user) +// mouthshoot = 0 +// return /obj/item/gun/proc/toggle_scope(var/zoom_amount=2.0) //looking through a scope limits your periphereal vision diff --git a/code/modules/projectiles/gun_item_renderer.dm b/code/modules/projectiles/gun_item_renderer.dm index 3ee4b565285..d7b57cc5415 100644 --- a/code/modules/projectiles/gun_item_renderer.dm +++ b/code/modules/projectiles/gun_item_renderer.dm @@ -1,6 +1,6 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 Citadel Station developers. *// +//* Copyright (c) 2024 Citadel Station Developers *// /** * gun render system diff --git a/code/modules/projectiles/gun_mob_renderer.dm b/code/modules/projectiles/gun_mob_renderer.dm index fb98d935d00..5bbce3dc96b 100644 --- a/code/modules/projectiles/gun_mob_renderer.dm +++ b/code/modules/projectiles/gun_mob_renderer.dm @@ -1,6 +1,6 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 Citadel Station developers. *// +//* Copyright (c) 2024 Citadel Station Developers *// /** * gun render system diff --git a/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm b/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm index 389ba521281..caeac99fe5e 100644 --- a/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm +++ b/code/modules/projectiles/guns/ballistic/microbattery/medigun_cells.dm @@ -20,7 +20,12 @@ tracer_type = /obj/effect/projectile/tracer/medigun impact_type = /obj/effect/projectile/impact/medigun -/obj/projectile/beam/medical_cell/on_hit(var/mob/living/carbon/human/target) //what does it do when it hits someone? +/obj/projectile/beam/medical_cell/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(ishuman(target)) + on_hit_legacy(target) + +/obj/projectile/beam/medical_cell/proc/on_hit_legacy(var/mob/living/carbon/human/target) //what does it do when it hits someone? return /obj/item/ammo_casing/microbattery/medical/brute @@ -29,7 +34,7 @@ type_name = "BRUTE" projectile_type = /obj/projectile/beam/medical_cell/brute -/obj/projectile/beam/medical_cell/brute/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/brute/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustBruteLoss(-10) else @@ -41,7 +46,7 @@ type_name = "BURN" projectile_type = /obj/projectile/beam/medical_cell/burn -/obj/projectile/beam/medical_cell/burn/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/burn/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustFireLoss(-10) else @@ -53,7 +58,7 @@ type_name = "STABILIZE" projectile_type = /obj/projectile/beam/medical_cell/stabilize -/obj/projectile/beam/medical_cell/stabilize/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/stabilize/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustOxyLoss(-30) for(var/name in list(BP_HEAD, BP_L_HAND, BP_R_HAND, BP_L_ARM, BP_R_ARM, BP_L_FOOT, BP_R_FOOT, BP_L_LEG, BP_R_LEG, BP_GROIN, BP_TORSO)) @@ -81,7 +86,7 @@ type_name = "TOXIN" projectile_type = /obj/projectile/beam/medical_cell/toxin -/obj/projectile/beam/medical_cell/toxin/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/toxin/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustToxLoss(-10) else @@ -93,7 +98,7 @@ type_name = "OMNI" projectile_type = /obj/projectile/beam/medical_cell/omni -/obj/projectile/beam/medical_cell/omni/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/omni/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustBruteLoss(-2.5) target.adjustFireLoss(-2.5) @@ -108,7 +113,7 @@ type_name = "ANTIRAD" projectile_type = /obj/projectile/beam/medical_cell/antirad -/obj/projectile/beam/medical_cell/antirad/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/antirad/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustToxLoss(-2.5) target.cure_radiation(RAD_MOB_CURE_STRENGTH_MEDIGUN) @@ -121,7 +126,7 @@ type_name = "BRUTE-II" projectile_type = /obj/projectile/beam/medical_cell/brute2 -/obj/projectile/beam/medical_cell/brute2/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/brute2/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustBruteLoss(-20) else @@ -133,7 +138,7 @@ type_name = "BURN-II" projectile_type = /obj/projectile/beam/medical_cell/burn2 -/obj/projectile/beam/medical_cell/burn2/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/burn2/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustFireLoss(-20) else @@ -145,7 +150,7 @@ type_name = "STABILIZE-II" projectile_type = /obj/projectile/beam/medical_cell/stabilize2 -/obj/projectile/beam/medical_cell/stabilize2/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/stabilize2/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustOxyLoss(-200) for(var/name in list(BP_HEAD, BP_L_HAND, BP_R_HAND, BP_L_ARM, BP_R_ARM, BP_L_FOOT, BP_R_FOOT, BP_L_LEG, BP_R_LEG, BP_GROIN, BP_TORSO)) @@ -168,7 +173,7 @@ type_name = "OMNI-II" projectile_type = /obj/projectile/beam/medical_cell/omni2 -/obj/projectile/beam/medical_cell/omni2/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/omni2/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustBruteLoss(-5) target.adjustFireLoss(-5) @@ -183,7 +188,7 @@ type_name = "TOXIN-II" projectile_type = /obj/projectile/beam/medical_cell/toxin2 -/obj/projectile/beam/medical_cell/toxin2/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/toxin2/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustToxLoss(-20) else @@ -195,7 +200,7 @@ type_name = "HASTE" projectile_type = /obj/projectile/beam/medical_cell/haste -/obj/projectile/beam/medical_cell/haste/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/haste/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.add_modifier(/datum/modifier/medigunhaste, 20 SECONDS) else @@ -215,7 +220,7 @@ type_name = "RESIST" projectile_type = /obj/projectile/beam/medical_cell/resist -/obj/projectile/beam/medical_cell/resist/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/resist/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.add_modifier(/datum/modifier/resistance, 20 SECONDS) else @@ -235,7 +240,7 @@ type_name = "CORPSE MEND" projectile_type = /obj/projectile/beam/medical_cell/corpse_mend -/obj/projectile/beam/medical_cell/corpse_mend/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/corpse_mend/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) if(target.stat == DEAD) target.adjustBruteLoss(-50) @@ -251,7 +256,7 @@ type_name = "BRUTE-III" projectile_type = /obj/projectile/beam/medical_cell/brute3 -/obj/projectile/beam/medical_cell/brute3/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/brute3/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustBruteLoss(-40) else @@ -263,7 +268,7 @@ type_name = "BURN-III" projectile_type = /obj/projectile/beam/medical_cell/burn3 -/obj/projectile/beam/medical_cell/burn3/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/burn3/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustFireLoss(-40) else @@ -275,7 +280,7 @@ type_name = "TOXIN-III" projectile_type = /obj/projectile/beam/medical_cell/toxin3 -/obj/projectile/beam/medical_cell/toxin3/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/toxin3/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustToxLoss(-40) else @@ -287,7 +292,7 @@ type_name = "OMNI-III" projectile_type = /obj/projectile/beam/medical_cell/omni3 -/obj/projectile/beam/medical_cell/omni3/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/omni3/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.adjustBruteLoss(-10) target.adjustFireLoss(-10) @@ -303,7 +308,7 @@ type_name = "SHRINK" projectile_type = /obj/projectile/beam/medical_cell/shrink -/obj/projectile/beam/medical_cell/shrink/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/shrink/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.resize(0.5) target.show_message("The beam fires into your body, changing your size!") @@ -317,7 +322,7 @@ type_name = "GROW" projectile_type = /obj/projectile/beam/medical_cell/grow -/obj/projectile/beam/medical_cell/grow/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/grow/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.resize(2.0) target.show_message("The beam fires into your body, changing your size!") @@ -331,7 +336,7 @@ type_name = "NORMALSIZE" projectile_type = /obj/projectile/beam/medical_cell/normalsize -/obj/projectile/beam/medical_cell/normalsize/on_hit(var/mob/living/carbon/human/target) +/obj/projectile/beam/medical_cell/normalsize/on_hit_legacy(var/mob/living/carbon/human/target) if(istype(target, /mob/living/carbon/human)) target.resize(1) target.show_message("The beam fires into your body, changing your size!") diff --git a/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm b/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm index c7ee2819ed9..e43caa1981c 100644 --- a/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm +++ b/code/modules/projectiles/guns/ballistic/microbattery/revolver_cells.dm @@ -40,9 +40,6 @@ damage = 2 agony = 20 pellets = 6 //number of pellets - range_step = 2 //projectile will lose a fragment each time it travels this distance. Can be a non-integer. - base_spread = 90 //lower means the pellets spread more across body parts. If zero then this is considered a shrapnel explosion instead of a shrapnel cone - spread_step = 10 embed_chance = 0 sharp = 0 damage_flag = ARMOR_MELEE @@ -67,14 +64,17 @@ sharp = 0 damage_flag = ARMOR_MELEE -/obj/projectile/bullet/stripper/on_hit(var/atom/stripped) - if(ishuman(stripped)) - var/mob/living/carbon/human/H = stripped +/obj/projectile/bullet/stripper/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + + if(ishuman(target)) + var/mob/living/carbon/human/H = target if(!H.permit_stripped) return H.drop_slots_to_ground(list(SLOT_ID_SUIT, SLOT_ID_UNIFORM, SLOT_ID_BACK, SLOT_ID_SHOES, SLOT_ID_GLOVES)) //Hats can stay! Most other things fall off with removing these. - ..() /obj/item/ammo_casing/microbattery/combat/final name = "\'Hydra\' microbattery - FINAL OPTION" @@ -94,7 +94,11 @@ tracer_type = /obj/effect/projectile/tracer/laser_omni impact_type = /obj/effect/projectile/impact/laser_omni -/obj/projectile/beam/final_option/on_hit(var/atom/impacted) +/obj/projectile/beam/final_option/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(isliving(impacted)) var/mob/living/L = impacted if(L.mind) @@ -104,5 +108,3 @@ nif = H.nif SStranscore.m_backup(L.mind,nif,one_time = TRUE) L.gib() - - ..() diff --git a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm index b8101a537ae..ef599f88a7c 100644 --- a/code/modules/projectiles/guns/energy/kinetic_accelerator.dm +++ b/code/modules/projectiles/guns/energy/kinetic_accelerator.dm @@ -223,7 +223,7 @@ kinetic_gun = null return ..() -/obj/projectile/kinetic/Bump(atom/target) +/obj/projectile/kinetic/pre_impact(atom/target, impact_flags, def_zone) if(kinetic_gun) var/list/mods = kinetic_gun.get_modkits() for(var/obj/item/ka_modkit/M in mods) @@ -234,20 +234,15 @@ pressure_decrease_active = TRUE return ..() -/obj/projectile/kinetic/projectile_attack_mob(mob/living/target_mob, distance, miss_modifier) - if(!pressure_decrease_active && !lavaland_environment_check(get_turf(src))) - name = "weakened [name]" - damage = damage * pressure_decrease - pressure_decrease_active = TRUE - return ..() - /obj/projectile/kinetic/legacy_on_range() strike_thing() ..() -/obj/projectile/kinetic/on_hit(atom/target) - strike_thing(target) +/obj/projectile/kinetic/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + strike_thing(target) /obj/projectile/kinetic/proc/strike_thing(atom/target) if(!pressure_decrease_active && !lavaland_environment_check(get_turf(src))) diff --git a/code/modules/projectiles/guns/energy/particle.dm b/code/modules/projectiles/guns/energy/particle.dm index 910bedde790..52184f2956c 100644 --- a/code/modules/projectiles/guns/energy/particle.dm +++ b/code/modules/projectiles/guns/energy/particle.dm @@ -187,9 +187,9 @@ light_power = 1 light_color = "#CCFFFF" -/turf/simulated/mineral/bullet_act(var/obj/projectile/Proj) - if(istype(Proj, /obj/projectile/bullet/particle)) - if(prob(Proj.damage)) +/turf/simulated/mineral/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(istype(proj, /obj/projectile/bullet/particle)) + if(prob(proj.damage)) GetDrilled() - ..() diff --git a/code/modules/projectiles/guns/energy/sizegun_vr.dm b/code/modules/projectiles/guns/energy/sizegun_vr.dm index a96008be2e0..73b690bbd4c 100644 --- a/code/modules/projectiles/guns/energy/sizegun_vr.dm +++ b/code/modules/projectiles/guns/energy/sizegun_vr.dm @@ -73,7 +73,11 @@ tracer_type = /obj/effect/projectile/tracer/xray impact_type = /obj/effect/projectile/impact/xray -/obj/projectile/beam/sizelaser/on_hit(var/atom/target) +/obj/projectile/beam/sizelaser/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + var/mob/living/M = target if(!M.permit_sizegun) M.visible_message("[src] has no effect on [M].") @@ -87,18 +91,13 @@ var/mob/living/H = M H.resize(set_size, TRUE) H.updateicon() - else - return 1 - /obj/projectile/beam/sizelaser/shrink set_size = 0.5 //50% of current size - /obj/projectile/beam/sizelaser/grow set_size = 2.0 //200% of current size - /obj/item/gun/energy/stripper//Because it can be fun name = "stripper gun" desc = "A gun designed to remove unnessary layers from people. For external use only!" diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm index 4286b0cd921..b117377439e 100644 --- a/code/modules/projectiles/guns/energy/special.dm +++ b/code/modules/projectiles/guns/energy/special.dm @@ -428,7 +428,7 @@ icon_state = "ermitter_gun" item_state = "pulse" projectile_type = /obj/projectile/beam/emitter - fire_delay = 10 + fire_delay = 2 SECONDS charge_cost = 900 cell_type = /obj/item/cell accept_cell_type = /obj/item/cell diff --git a/code/modules/projectiles/guns/launcher.dm b/code/modules/projectiles/guns/launcher.dm index f95cee55f07..a752f382bd7 100644 --- a/code/modules/projectiles/guns/launcher.dm +++ b/code/modules/projectiles/guns/launcher.dm @@ -14,11 +14,6 @@ /obj/item/gun/launcher/can_hit(var/mob/living/target as mob, var/mob/living/user as mob) return 1 -//Override this to avoid a runtime with suicide handling. -/obj/item/gun/launcher/handle_suicide(mob/living/user) - to_chat(user, "Shooting yourself with \a [src] is pretty tricky. You can't seem to manage it.") - return - /obj/item/gun/launcher/proc/update_release_force(obj/projectile) return 0 diff --git a/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm b/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm index e96a67f93eb..322951fb7dd 100644 --- a/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm +++ b/code/modules/projectiles/guns/legacy_vr_guns/crestrose.dm @@ -31,10 +31,10 @@ update_icon() update_held_icon() - -/obj/item/gun/ballistic/automatic/fluff/crestrose/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(default_parry_check(user, attacker, damage_source) && prob(50)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 +// todo: uhh we can fix it later maybe idfk +// /obj/item/gun/ballistic/automatic/fluff/crestrose/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") +// if(default_parry_check(user, attacker, damage_source) && prob(50)) +// user.visible_message("\The [user] parries [attack_text] with \the [src]!") +// playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) +// return 1 +// return 0 diff --git a/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm b/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm index 1c4283e0b10..85a97302a6f 100644 --- a/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm +++ b/code/modules/projectiles/guns/legacy_vr_guns/pummeler.dm @@ -36,13 +36,14 @@ vacuum_traversal = 0 range = WORLD_ICON_SIZE * 6 //Scary name, but just deletes the projectile after this range -/obj/projectile/pummel/on_hit(var/atom/movable/target, var/blocked = 0) +/obj/projectile/pummel/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(isliving(target)) var/mob/living/L = target var/throwdir = get_dir(firer,L) - if(prob(40) && !blocked) + if(prob(40) && (efficiency >= 0.9)) L.afflict_stun(20 * 1) L.Confuse(1) L.throw_at_old(get_edge_target_turf(L, throwdir), rand(3,6), 10) - - return 1 diff --git a/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm b/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm index efa27a57772..72b43ca83f1 100644 --- a/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm +++ b/code/modules/projectiles/guns/legacy_vr_guns/sickshot.dm @@ -34,7 +34,11 @@ vacuum_traversal = 0 range = WORLD_ICON_SIZE * 5 //Scary name, but just deletes the projectile after this range -/obj/projectile/sickshot/on_hit(var/atom/movable/target, var/blocked = 0) +/obj/projectile/sickshot/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(isliving(target)) var/mob/living/L = target if(prob(20)) @@ -44,5 +48,3 @@ var/mob/living/carbon/human/H = target H.vomit() H.Confuse(2) - - return 1 diff --git a/code/modules/projectiles/guns/magic/staff.dm b/code/modules/projectiles/guns/magic/staff.dm index 90a5b2d2af9..2cb005d1b73 100644 --- a/code/modules/projectiles/guns/magic/staff.dm +++ b/code/modules/projectiles/guns/magic/staff.dm @@ -27,9 +27,6 @@ icon_state = "staffofhealing" item_state = "staffofhealing" -/obj/item/gun/magic/staff/healing/handle_suicide() //Stops people trying to commit suicide to heal themselves - return - /* /obj/item/gun/magic/staff/chaos name = "staff of chaos" @@ -98,12 +95,12 @@ return ..() */ -/obj/item/gun/magic/staff/locker - name = "staff of the locker" - desc = "An artefact that expells encapsulating bolts, for incapacitating thy enemy." - fire_sound = 'sound/magic/staff_change.ogg' - ammo_type = /obj/item/ammo_casing/magic/locker - icon_state = "locker" - item_state = "locker" - max_charges = 6 - recharge_rate = 4 +// /obj/item/gun/magic/staff/locker +// name = "staff of the locker" +// desc = "An artefact that expells encapsulating bolts, for incapacitating thy enemy." +// fire_sound = 'sound/magic/staff_change.ogg' +// ammo_type = /obj/item/ammo_casing/magic/locker +// icon_state = "locker" +// item_state = "locker" +// max_charges = 6 +// recharge_rate = 4 diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm index e892f40b985..3e9afa3f3fe 100644 --- a/code/modules/projectiles/guns/projectile/automatic.dm +++ b/code/modules/projectiles/guns/projectile/automatic.dm @@ -735,10 +735,6 @@ . = ..() icon_state = (ammo_magazine)? "toy_smg" : "toy_smg-empty" -/obj/item/gun/ballistic/automatic/advanced_smg/foam/handle_suicide(mob/living/user) - user.show_message("You feel rather silly, trying to commit suicide with a toy.") - mouthshoot = 0 - /obj/item/gun/ballistic/automatic/advanced_smg/foam/blue icon_state = "toy_smg_blue" @@ -767,10 +763,6 @@ else icon_state = "toy_c20r" -/obj/item/gun/ballistic/automatic/c20r/foam/handle_suicide(mob/living/user) - user.show_message("You feel rather silly, trying to commit suicide with a toy.") - mouthshoot = 0 - //Foam LMG /obj/item/gun/ballistic/automatic/lmg/foam name = "toy light machine gun" @@ -795,8 +787,3 @@ /obj/item/gun/ballistic/automatic/lmg/foam/update_icon() . = ..() update_held_icon() - -/obj/item/gun/ballistic/automatic/lmg/foam/handle_suicide(mob/living/user) - user.show_message("You feel rather silly, trying to commit suicide with a toy.") - mouthshoot = 0 - return diff --git a/code/modules/projectiles/guns/projectile/boltaction.dm b/code/modules/projectiles/guns/projectile/boltaction.dm index afe3233f732..1106d7ea4a3 100644 --- a/code/modules/projectiles/guns/projectile/boltaction.dm +++ b/code/modules/projectiles/guns/projectile/boltaction.dm @@ -50,7 +50,7 @@ // Stole hacky terrible code from doublebarrel shotgun. -Spades /obj/item/gun/ballistic/shotgun/pump/rifle/ceremonial/attackby(var/obj/item/A as obj, mob/user as mob) - if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL) + if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/transforming/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL) to_chat(user, "You begin to shorten the barrel and stock of \the [src].") if(loaded.len) afterattack(user, user) @@ -90,7 +90,7 @@ holy = TRUE /obj/item/gun/ballistic/shotgun/pump/rifle/lever/attackby(var/obj/item/A as obj, mob/user as mob) - if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL) + if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/transforming/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL) to_chat(user, "You begin to shorten the barrel and stock of \the [src].") if(loaded.len) afterattack(user, user) @@ -134,7 +134,7 @@ holy = TRUE /obj/item/gun/ballistic/shotgun/pump/rifle/lever/vintage/attackby(var/obj/item/A as obj, mob/user as mob) - if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL) + if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/transforming/energy) || istype(A, /obj/item/pickaxe/plasmacutter) && w_class != WEIGHT_CLASS_NORMAL) to_chat(user, "You begin to shorten the barrel and stock of \the [src].") if(loaded.len) afterattack(user, user) diff --git a/code/modules/projectiles/guns/projectile/pistol.dm b/code/modules/projectiles/guns/projectile/pistol.dm index 08476e331c7..c188758bb3c 100644 --- a/code/modules/projectiles/guns/projectile/pistol.dm +++ b/code/modules/projectiles/guns/projectile/pistol.dm @@ -537,11 +537,6 @@ allowed_magazines = list(/obj/item/ammo_magazine/foam/pistol) fire_sound = 'sound/items/syringeproj.ogg' -/obj/item/gun/ballistic/pistol/foam/handle_suicide(mob/living/user) - user.show_message("You feel rather silly, trying to commit suicide with a toy.") - mouthshoot = 0 - return - /obj/item/gun/ballistic/pistol/foam/blue icon_state = "toy_pistol_blue" diff --git a/code/modules/projectiles/guns/projectile/shotgun.dm b/code/modules/projectiles/guns/projectile/shotgun.dm index 3aa3ab63401..96bd6b3fbd2 100644 --- a/code/modules/projectiles/guns/projectile/shotgun.dm +++ b/code/modules/projectiles/guns/projectile/shotgun.dm @@ -187,7 +187,7 @@ //this is largely hacky and bad :( -Pete /obj/item/gun/ballistic/shotgun/doublebarrel/attackby(var/obj/item/A as obj, mob/user as mob) - if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/energy) || istype(A, /obj/item/pickaxe/plasmacutter)) + if(istype(A, /obj/item/surgical/circular_saw) || istype(A, /obj/item/melee/transforming/energy) || istype(A, /obj/item/pickaxe/plasmacutter)) to_chat(user, "You begin to shorten the barrel of \the [src].") if(loaded.len) var/burstsetting = burst @@ -350,11 +350,6 @@ one_handed_penalty = 5 fire_sound = 'sound/items/syringeproj.ogg' -/obj/item/gun/ballistic/shotgun/pump/foam/handle_suicide(mob/living/user) - user.show_message("You feel rather silly, trying to commit suicide with a toy.") - mouthshoot = 0 - return - /obj/item/gun/ballistic/shotgun/pump/foam/pump(mob/M as mob) playsound(M, action_sound, 60, 1) diff --git a/code/modules/projectiles/guns/vox.dm b/code/modules/projectiles/guns/vox.dm index 53f3c262bc7..ca21ee38d08 100644 --- a/code/modules/projectiles/guns/vox.dm +++ b/code/modules/projectiles/guns/vox.dm @@ -151,8 +151,11 @@ /obj/projectile/sonic/strong damage = 45 -/obj/projectile/sonic/strong/on_hit(var/atom/movable/target, var/blocked = 0) +/obj/projectile/sonic/strong/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ismob(target)) + var/mob/M = target var/throwdir = get_dir(firer,target) - target.throw_at_old(get_edge_target_turf(target, throwdir), rand(1,6), 10) - return 1 + M.throw_at_old(get_edge_target_turf(target, throwdir), rand(1,6), 10) diff --git a/code/modules/projectiles/projectile-tracing.dm b/code/modules/projectiles/projectile-tracing.dm deleted file mode 100644 index dc6144a7735..00000000000 --- a/code/modules/projectiles/projectile-tracing.dm +++ /dev/null @@ -1,51 +0,0 @@ - -//* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 Citadel Station developers. *// - -/obj/projectile/trace - atom_flags = ATOM_ABSTRACT | ATOM_NONWORLD - invisibility = INVISIBILITY_ABSTRACT - hitscan = TRUE - has_tracer = FALSE - damage = 0 - nodamage = TRUE - /// did we manage to hit the given target? - var/could_hit_target = FALSE - /// delete on hitting target? - var/del_on_success = FALSE - /// do we check opacity? - var/check_opacity = FALSE - -/obj/projectile/trace/Bump(atom/A) - if(A == original) - could_hit_target = TRUE - if(del_on_success) - qdel(src) - return - return ..() - -/obj/projectile/trace/projectile_attack_mob() - return - -/obj/projectile/trace/Moved() - . = ..() - if(check_opacity && isturf(loc)) - // *sigh* // - var/turf/T = loc - if(T.has_opaque_atom) - qdel(src) - -/obj/projectile/trace/proc/prepare_trace(atom/target, pass_flags = ATOM_PASS_GLASS | ATOM_PASS_GRILLE | ATOM_PASS_TABLE, check_opacity) - src.pass_flags = pass_flags - src.original = target - src.check_opacity = check_opacity - src.range = max(src.range, (get_dist(src, target) + 1) * WORLD_ICON_SIZE) - -/** - * Simple trace to see if we could hit a given target - */ -/obj/projectile/trace/proc/simple_trace(atom/target, angle) - prepare_trace(target) - fire(angle) - del_on_success = TRUE - return could_hit_target diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm deleted file mode 100644 index ac7164dcc9d..00000000000 --- a/code/modules/projectiles/projectile.dm +++ /dev/null @@ -1,1320 +0,0 @@ -/** - * ## Physics Specifications - * - * We track physics as absolute pixel on a tile, not byond's pixel x/y - * thus the first pixel at bottom left of tile is 1, 1 - * and the last pixel at top right is 32, 32 (for a world icon size of 32 pixels) - * - * We cross over to the next tile at above 32, for up/right, - * and to the last tile at below 1, for bottom/left. - * - * The code might handle it based on how it's implemented, - * but as long as the error is 1 pixel or below, it's not a big deal. - * - * The reason we're so accurate (1 pixel/below is pretty insanely strict) is - * so players have the projectile act like what the screen says it should be like; - * hence why projectiles can realistically path across corners based on their 'hitbox center'. - */ -/obj/projectile - name = "projectile" - icon = 'icons/obj/projectiles.dmi' - icon_state = "bullet" - density = FALSE - anchored = TRUE - integrity_flags = INTEGRITY_INDESTRUCTIBLE | INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF - pass_flags = ATOM_PASS_TABLE - mouse_opacity = MOUSE_OPACITY_TRANSPARENT - depth_level = INFINITY // nothing should be passing over us from depth - - //* Physics - Configuration *// - - /// speed, in pixels per second - var/speed = 25 * WORLD_ICON_SIZE - /// are we a hitscan projectile? - var/hitscan = FALSE - /// angle, in degrees **clockwise of north** - var/angle - /// max distance in pixels - /// - /// * please set this to a multiple of [WORLD_ICON_SIZE] so we scale with tile size. - var/range = WORLD_ICON_SIZE * 50 - // todo: lifespan - - //* Physics - Homing *// - /// Are we homing in on something? - var/homing = FALSE - /// current homing target - var/atom/homing_target - /// angle per second - /// - /// * this is smoother the less time between SSprojectiles fires - var/homing_turn_speed = 100 - /// rand(min, max) for inaccuracy offsets - var/homing_inaccuracy_min = 0 - /// rand(min, max) for inaccuracy offsets - var/homing_inaccuracy_max = 0 - /// pixels; added to the real location of target so we're not exactly on-point - var/homing_offset_x = 0 - /// pixels; added to the real location of target so we're not exactly on-point - var/homing_offset_y = 0 - - //* Physics - Tracers *// - - /// tracer /datum/point's - var/list/tracer_vertices - /// first point is a muzzle effect - var/tracer_muzzle_flash - /// last point is an impact - var/tracer_impact_effect - /// default tracer duration - var/tracer_duration = 5 - - //* Physics - State *// - - /// paused? if so, we completely get passed over during processing - var/paused = FALSE - /// currently hitscanning - var/hitscanning = FALSE - /// a flag to prevent movement hooks from resetting our physics on a forced movement - var/trajectory_ignore_forcemove = FALSE - /// cached value: move this much x for this much distance - /// basically, dx / distance - var/calculated_dx - /// cached value: move this much y for this much distance - /// basically, dy / distance - var/calculated_dy - /// cached sign of dx; 1, -1, or 0 - var/calculated_sdx - /// cached sign of dy; 1, -1, or 0 - var/calculated_sdy - /// our current pixel location on turf - /// byond pixel_x rounds, and we don't want that - /// - /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf - var/current_px - /// our current pixel location on turf - /// byond pixel_y rounds, and we don't want that - /// - /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf - var/current_py - /// the pixel location we're moving to, or the [current_px] after this iteration step - /// - /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step. - var/next_px - /// the pixel location we're moving to, or the [current_px] after this iteration step - /// - /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step. - var/next_py - /// used to track if we got kicked forwards after calling Move() - var/trajectory_kick_forwards = 0 - /// to avoid going too fast when kicked forwards by a mirror, if we overshoot the pixels we're - /// supposed to move this gets set to penalize the next move with a weird algorithm - /// that i won't bother explaining - var/trajectory_penalty_applied = 0 - /// currently travelled distance in pixels - var/distance_travelled - /// if we get forcemoved, this gets reset to 0 as a trip - /// this way, we know exactly how far we moved - var/distance_travelled_this_iteration - /// where the physics loop and/or some other thing moving us is trying to move to - /// used to determine where to draw hitscan tracers - // todo: this being here is kinda a symptom that things are handled weirdly but whatever - // optimally physics loop should handle tracking for stuff like animations, not require on hit processing to check turfs - var/turf/trajectory_moving_to - - //Fired processing vars - var/fired = FALSE //Have we been fired yet - var/ignore_source_check = FALSE - - var/original_angle = 0 //Angle at firing - var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle - var/spread = 0 //amount (in degrees) of projectile spread - animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy - var/ricochets = 0 - var/ricochets_max = 2 - var/ricochet_chance = 30 - - //Hitscan - /// do we have a tracer? if not we completely ignore hitscan logic - var/has_tracer = TRUE - var/tracer_type - var/muzzle_type - var/impact_type - - var/miss_sounds - var/ricochet_sounds - var/list/impact_sounds //for different categories, IMPACT_MEAT etc - - //Fancy hitscan lighting effects! - var/hitscan_light_intensity = 1.5 - var/hitscan_light_range = 0.75 - var/hitscan_light_color_override - var/muzzle_flash_intensity = 3 - var/muzzle_flash_range = 1.5 - var/muzzle_flash_color_override - var/impact_light_intensity = 3 - var/impact_light_range = 2 - var/impact_light_color_override - - //Targetting - var/yo = null - var/xo = null - var/atom/original = null // the original target clicked - var/turf/starting = null // the projectile's starting turf - var/list/permutated = list() // we've passed through these atoms, don't try to hit them again - var/p_x = 16 - var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center - - var/def_zone = "" //Aiming at - var/mob/firer = null//Who shot it - var/silenced = 0 //Attack message - var/shot_from = "" // name of the object which shot us - - var/accuracy = 0 - var/dispersion = 0.0 - - // Sub-munitions. Basically, multi-projectile shotgun, rather than pellets. - var/use_submunitions = FALSE - var/only_submunitions = FALSE // Will the projectile delete itself after firing the submunitions? - var/list/submunitions = list() // Assoc list of the paths of any submunitions, and how many they are. [projectilepath] = [projectilecount]. - var/submunition_spread_max = 30 // Divided by 10 to get the percentile dispersion. - var/submunition_spread_min = 5 // Above. - /// randomize spread? if so, evenly space between 0 and max on each side. - var/submunition_constant_spread = FALSE - var/force_max_submunition_spread = FALSE // Do we just force the maximum? - var/spread_submunition_damage = FALSE // Do we assign damage to our sub projectiles based on our main projectile damage? - - //? Damage - default handling - /// damage amount - var/damage = 10 - /// damage tier - goes hand in hand with [damage_armor] - var/damage_tier = BULLET_TIER_DEFAULT - /// todo: legacy - BRUTE, BURN, TOX, OXY, CLONE, HALLOSS, ELECTROCUTE, BIOACID are the only things that should be in here - var/damage_type = BRUTE - /// armor flag for damage - goes hand in hand with [damage_tier] - var/damage_flag = ARMOR_BULLET - /// damage mode - see [code/__DEFINES/combat/damage.dm] - var/damage_mode = NONE - - var/SA_bonus_damage = 0 // Some bullets inflict extra damage on simple animals. - var/SA_vulnerability = null // What kind of simple animal the above bonus damage should be applied to. Set to null to apply to all SAs. - var/nodamage = 0 //Determines if the projectile will skip any damage inflictions - var/taser_effect = 0 //If set then the projectile will apply it's agony damage using stun_effect_act() to mobs it hits, and other damage will be ignored - var/projectile_type = /obj/projectile - var/penetrating = 0 //If greater than zero, the projectile will pass through dense objects as specified by on_penetrate() - //Effects - var/incendiary = 0 //1 for ignite on hit, 2 for trail of fire. 3 maybe later for burst of fire around the impact point. - Mech - var/flammability = 0 //Amount of fire stacks to add for the above. - var/combustion = TRUE //Does this set off flammable objects on fire/hit? - var/stun = 0 - var/weaken = 0 - var/paralyze = 0 - var/irradiate = 0 - var/stutter = 0 - var/eyeblur = 0 - var/drowsy = 0 - var/agony = 0 - var/reflected = 0 // This should be set to 1 if reflected by any means, to prevent infinite reflections. - var/modifier_type_to_apply = null // If set, will apply a modifier to mobs that are hit by this projectile. - var/modifier_duration = null // How long the above modifier should last for. Leave null to be permanent. - var/excavation_amount = 0 // How much, if anything, it drills from a mineral turf. - /// If this projectile is holy. Silver bullets, etc. Currently no effects. - var/holy = FALSE - - // Antimagic - /// Should we check for antimagic effects? - var/antimagic_check = FALSE - /// Antimagic charges to use, if any - var/antimagic_charges_used = 0 - /// Multiplier for damage if antimagic is on the target - var/antimagic_damage_factor = 0 - - var/embed_chance = 0 //Base chance for a projectile to embed - - var/fire_sound = 'sound/weapons/Gunshot_old.ogg' // Can be overriden in gun.dm's fire_sound var. It can also be null but I don't know why you'd ever want to do that. -Ace - - // todo: currently unimplemneted - var/vacuum_traversal = TRUE //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum. - - var/no_attack_log = FALSE - -/obj/projectile/Destroy() - // stop processing - STOP_PROCESSING(SSprojectiles, src) - // cleanup - cleanup_hitscan_tracers() - return ..() - -/obj/projectile/proc/legacy_on_range() //if we want there to be effects when they reach the end of their range - finalize_hitscan_tracers(impact_effect = FALSE, kick_forwards = 8) - qdel(src) - -/obj/projectile/Crossed(atom/movable/AM) //A mob moving on a tile with a projectile is hit by it. - if(AM.is_incorporeal()) - return - ..() - if(isliving(AM) && !check_pass_flags(ATOM_PASS_MOB)) - var/mob/living/L = AM - if(can_hit_target(L, permutated, (AM == original))) - Bump(AM) - -/obj/projectile/forceMove(atom/target) - var/is_a_jump = isturf(target) != isturf(loc) || target.z != z || !trajectory_ignore_forcemove - if(is_a_jump) - record_hitscan_end() - render_hitscan_tracers() - . = ..() - if(!.) - stack_trace("projectile forcemove failed; please do not try to forcemove projectiles to invalid locations!") - distance_travelled_this_iteration = 0 - if(!trajectory_ignore_forcemove) - reset_physics_to_turf() - if(is_a_jump) - record_hitscan_start() - -/obj/projectile/proc/fire(set_angle_to, atom/direct_target) - if(only_submunitions) // refactor projectiles whwen holy shit this is awful lmao - // todo: this should make a muzzle flash - qdel(src) - return - //If no angle needs to resolve it from xo/yo! - if(direct_target) - direct_target.bullet_act(src, def_zone) - // todo: this should make a muzzle flash - qdel(src) - return - if(isnum(set_angle_to)) - set_angle(set_angle_to) - - // setup physics - setup_physics() - - var/turf/starting = get_turf(src) - if(isnull(angle)) //Try to resolve through offsets if there's no angle set. - if(isnull(xo) || isnull(yo)) - stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!") - qdel(src) - return - var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z) - set_angle(get_visual_angle(src, target)) - if(dispersion) - set_angle(angle + rand(-dispersion, dispersion)) - original_angle = angle - forceMove(starting) - permutated = list() - fired = TRUE - // kickstart - if(hitscan) - physics_hitscan() - else - START_PROCESSING(SSprojectiles, src) - physics_iteration(WORLD_ICON_SIZE, SSprojectiles.wait) - -/obj/projectile/Move(atom/newloc, dir = NONE) - . = ..() - if(.) - if(fired && can_hit_target(original, permutated, TRUE)) - Bump(original) - -//Spread is FORCED! -/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0) - var/turf/curloc = get_turf(source) - var/turf/targloc = get_turf(target) - - if(istype(source, /atom/movable)) - var/atom/movable/MT = source - if(MT.locs && MT.locs.len) // Multi tile! - for(var/turf/T in MT.locs) - if(get_dist(T, target) < get_turf(curloc)) - curloc = get_turf(T) - - trajectory_ignore_forcemove = TRUE - forceMove(get_turf(source)) - trajectory_ignore_forcemove = FALSE - starting = curloc - original = target - if(targloc || !params) - yo = targloc.y - curloc.y - xo = targloc.x - curloc.x - set_angle(get_visual_angle(src, targloc) + spread) - - if(isliving(source) && params) - var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params) - p_x = calculated[2] - p_y = calculated[3] - - set_angle(calculated[1] + spread) - else if(targloc) - yo = targloc.y - curloc.y - xo = targloc.x - curloc.x - set_angle(get_visual_angle(src, targloc) + spread) - else - stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!") - qdel(src) - -/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params) - var/list/mouse_control = params2list(params) - var/p_x = 0 - var/p_y = 0 - var/angle = 0 - if(mouse_control["icon-x"]) - p_x = text2num(mouse_control["icon-x"]) - if(mouse_control["icon-y"]) - p_y = text2num(mouse_control["icon-y"]) - if(mouse_control["screen-loc"]) - //Split screen-loc up into X+Pixel_X and Y+Pixel_Y - var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") - - //Split X+Pixel_X up into list(X, Pixel_X) - var/list/screen_loc_X = splittext(screen_loc_params[1],":") - - //Split Y+Pixel_Y up into list(Y, Pixel_Y) - var/list/screen_loc_Y = splittext(screen_loc_params[2],":") - var/x = text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32 - var/y = text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32 - - //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. - var/screenviewX = user.client.current_viewport_width * world.icon_size - var/screenviewY = user.client.current_viewport_height * world.icon_size - - var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x - var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y - angle = arctan(y - oy, x - ox) - return list(angle, p_x, p_y) - -/obj/projectile/proc/redirect(x, y, starting, source) - old_style_target(locate(x, y, z), starting? get_turf(starting) : get_turf(source)) - -/obj/projectile/proc/old_style_target(atom/target, atom/source) - if(!source) - source = get_turf(src) - starting = get_turf(source) - original = target - set_angle(get_visual_angle(source, target)) - -/obj/projectile/proc/vol_by_damage() - if(damage) - return clamp((damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then clamp the value between 30 and 100 - else - return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume. - -//Returns true if the target atom is on our current turf and above the right layer -//If direct target is true it's the originally clicked target. -/obj/projectile/proc/can_hit_target(atom/target, list/passthrough, direct_target = FALSE, ignore_loc = FALSE) - if(QDELETED(target)) - return FALSE - if(!ignore_source_check && firer) - var/mob/M = firer - if((target == firer) || ((target == firer.loc) && istype(firer.loc, /obj/vehicle/sealed/mecha)) || (target in firer.buckled_mobs) || (istype(M) && (M.buckled == target))) - return FALSE - if(!ignore_loc && (loc != target.loc)) - return FALSE - if(target in passthrough) - return FALSE - if(target.density) //This thing blocks projectiles, hit it regardless of layer/mob stuns/etc. - return TRUE - if(!isliving(target)) - if(direct_target) - return TRUE - if(target.layer < PROJECTILE_HIT_THRESHOLD_LAYER) - return FALSE - else - var/mob/living/L = target - if(!direct_target) - if(!L.density) - return FALSE - return TRUE - -/obj/projectile/Bump(atom/A) - if(A in permutated) - trajectory_ignore_forcemove = TRUE - forceMove(get_turf(A)) - trajectory_ignore_forcemove = FALSE - return FALSE - if(firer && !reflected) - if(A == firer || (A == firer.loc && istype(A, /obj/vehicle/sealed/mecha))) //cannot shoot yourself or your mech - trajectory_ignore_forcemove = TRUE - forceMove(get_turf(A)) - trajectory_ignore_forcemove = FALSE - return FALSE - - var/distance = get_dist(starting, get_turf(src)) - var/turf/target_turf = get_turf(A) - var/passthrough = FALSE - - if(ismob(A)) - var/mob/M = A - if(istype(A, /mob/living)) - //if they have a neck grab on someone, that person gets hit instead - var/obj/item/grab/G = locate() in M - if(G && G.state >= GRAB_NECK) - if(G.affecting.stat == DEAD) - var/shield_chance = min(80, (30 * (M.mob_size / 10))) //Small mobs have a harder time keeping a dead body as a shield than a human-sized one. Unathi would have an easier job, if they are made to be SIZE_LARGE in the future. -Mech - if(prob(shield_chance)) - visible_message("\The [M] uses [G.affecting] as a shield!") - if(Bump(G.affecting)) - return - else - visible_message("\The [M] tries to use [G.affecting] as a shield, but fails!") - else - visible_message("\The [M] uses [G.affecting] as a shield!") - if(Bump(G.affecting)) - return //If Bump() returns 0 (keep going) then we continue on to attack M. - - passthrough = !projectile_attack_mob(M, distance) - else - passthrough = 1 //so ghosts don't stop bullets - else - passthrough = (A.bullet_act(src, def_zone) == PROJECTILE_CONTINUE) //backwards compatibility - if(isturf(A)) - for(var/obj/O in A) - O.bullet_act(src) - for(var/mob/living/M in A) - projectile_attack_mob(M, distance) - - //penetrating projectiles can pass through things that otherwise would not let them - if(!passthrough && penetrating > 0) - if(check_penetrate(A)) - passthrough = TRUE - penetrating-- - - if(passthrough) - trajectory_ignore_forcemove = TRUE - forceMove(target_turf) - permutated.Add(A) - trajectory_ignore_forcemove = FALSE - return FALSE - - if(A) - on_impact(A) - - if(hitscanning) - if(trajectory_moving_to) - // create tracers - var/datum/point/visual_impact_point = get_intersection_point(trajectory_moving_to) - if(visual_impact_point) - // kick it forwards a bit - visual_impact_point.shift_in_projectile_angle(angle, 2) - // draw - finalize_hitscan_tracers(visual_impact_point, impact_effect = TRUE) - else - finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32) - else - finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32) - - qdel(src) - return TRUE - -//TODO: make it so this is called more reliably, instead of sometimes by bullet_act() and sometimes not -/obj/projectile/proc/on_hit(atom/target, blocked = 0, def_zone) - if(blocked >= 100) - return 0//Full block - if(!isliving(target)) - return 0 -// if(isanimal(target)) return 0 - var/mob/living/L = target - L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY! - if(modifier_type_to_apply) - L.add_modifier(modifier_type_to_apply, modifier_duration) - return 1 - -//called when the projectile stops flying because it Bump'd with something -/obj/projectile/proc/on_impact(atom/A) - if(damage && damage_type == BURN) - var/turf/T = get_turf(A) - if(T) - T.hotspot_expose(700, 5) - -//Checks if the projectile is eligible for embedding. Not that it necessarily will. -/obj/projectile/proc/can_embed() - //embed must be enabled and damage type must be brute - if(embed_chance == 0 || damage_type != BRUTE) - return 0 - return 1 - -/obj/projectile/proc/get_structure_damage() - if(damage_type == BRUTE || damage_type == BURN) - return damage - return 0 - -//return 1 if the projectile should be allowed to pass through after all, 0 if not. -/obj/projectile/proc/check_penetrate(atom/A) - return 1 - -/obj/projectile/proc/check_fire(atom/target as mob, mob/living/user as mob) //Checks if you can hit them or not. - check_trajectory(target, user, pass_flags, atom_flags) - -/obj/projectile/CanAllowThrough() - . = ..() - return TRUE - -//Called when the projectile intercepts a mob. Returns 1 if the projectile hit the mob, 0 if it missed and should keep flying. -/obj/projectile/proc/projectile_attack_mob(mob/living/target_mob, distance, miss_modifier = 0) - if(!istype(target_mob)) - return - - //roll to-hit - miss_modifier = max(15*(distance-2) - accuracy + miss_modifier + target_mob.get_evasion(), 0) - var/hit_zone = get_zone_with_miss_chance(def_zone, target_mob, miss_modifier, ranged_attack=(distance > 1 || original != target_mob)) //if the projectile hits a target we weren't originally aiming at then retain the chance to miss - - var/result = PROJECTILE_FORCE_MISS - if(hit_zone) - def_zone = hit_zone //set def_zone, so if the projectile ends up hitting someone else later (to be implemented), it is more likely to hit the same part - result = target_mob.bullet_act(src, def_zone) - - if(result == PROJECTILE_FORCE_MISS) - if(!silenced) - visible_message("\The [src] misses [target_mob] narrowly!") - playsound(target_mob.loc, pick(miss_sounds), 60, 1) - return FALSE - - //hit messages - if(silenced) - to_chat(target_mob, "You've been hit in the [parse_zone(def_zone)] by \the [src]!") - else - visible_message("\The [target_mob] is hit by \the [src] in the [parse_zone(def_zone)]!")//X has fired Y is now given by the guns so you cant tell who shot you if you could not see the shooter - - //admin logs - if(!no_attack_log) - if(istype(firer, /mob) && istype(target_mob)) - add_attack_logs(firer,target_mob,"Shot with \a [src.type] projectile") - - //sometimes bullet_act() will want the projectile to continue flying - if (result == PROJECTILE_CONTINUE) - return FALSE - - return TRUE - -/** - * i hate everything - * - * todo: refactor guns - * projectiles - * and everything else - * - * i am losing my fucking mind - * this shouldn't have to fucking exist because the ammo casing and/or gun should be doing it - * and submunitions SHOULDNT BE HANDLED HERE!! - */ -/obj/projectile/proc/launch_projectile_common(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) - original = target - def_zone = check_zone(target_zone) - firer = user - - if(use_submunitions && submunitions.len) - var/temp_min_spread = 0 - if(force_max_submunition_spread) - temp_min_spread = submunition_spread_max - else - temp_min_spread = submunition_spread_min - - var/damage_override = null - - if(spread_submunition_damage) - damage_override = damage - if(nodamage) - damage_override = 0 - - var/projectile_count = 0 - - for(var/proj in submunitions) - projectile_count += submunitions[proj] - - damage_override = round(damage_override / max(1, projectile_count)) - - for(var/path in submunitions) - var/amt = submunitions[path] - for(var/count in 1 to amt) - var/obj/projectile/SM = new path(get_turf(loc)) - SM.shot_from = shot_from - SM.silenced = silenced - if(!isnull(damage_override)) - SM.damage = damage_override - if(submunition_constant_spread) - SM.dispersion = 0 - var/calculated = angle + round((count / amt - 0.5) * submunition_spread_max, 1) - SM.launch_projectile(target, target_zone, user, params, calculated) - else - SM.dispersion = rand(temp_min_spread, submunition_spread_max) / 10 - SM.launch_projectile(target, target_zone, user, params, angle_override) - -/obj/projectile/proc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) - var/direct_target - if(get_turf(target) == get_turf(src)) - direct_target = target - - preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread) - launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread) - return fire(angle_override, direct_target) - -//called to launch a projectile from a gun -/obj/projectile/proc/launch_from_gun(atom/target, target_zone, mob/user, params, angle_override, forced_spread, obj/item/gun/launcher) - - shot_from = launcher.name - silenced = launcher.silenced - - return launch_projectile(target, target_zone, user, params, angle_override, forced_spread) - -/obj/projectile/proc/launch_projectile_from_turf(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) - var/direct_target - if(get_turf(target) == get_turf(src)) - direct_target = target - - preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread) - launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread) - return fire(angle_override, direct_target) - -/** - * Standard proc to determine damage when impacting something. This does not affect the special damage variables/effect variables, only damage and damtype. - * May or may not be called before/after armor calculations. - * - * @params - * - target The atom hit - * - * @return Damage to apply to target. - */ -/obj/projectile/proc/run_damage_vulnerability(atom/target) - var/final_damage = damage - if(isliving(target)) - var/mob/living/L = target - if(issimple(target)) - var/mob/living/simple_mob/SM = L - if(SM.mob_class & SA_vulnerability) - final_damage += SA_bonus_damage - if(L.anti_magic_check(TRUE, TRUE, antimagic_charges_used, FALSE)) - final_damage *= antimagic_damage_factor - return final_damage - -/** - * Probably isn't needed but saves me the time and I can regex this later: - * Gets the final `damage` that should be used on something - */ -/obj/projectile/proc/get_final_damage(atom/target) - return run_damage_vulnerability(target) - -//* Hitscan Visuals *// - -/** - * returns a /datum/point based on where we currently are - */ -/obj/projectile/proc/get_tracer_point() - RETURN_TYPE(/datum/point) - var/datum/point/point = new - if(trajectory_moving_to) - // we're in move. use next px/py to respect 1. kick forwards 2. deflections - point.x = (trajectory_moving_to.x - 1) * WORLD_ICON_SIZE + next_px - point.y = (trajectory_moving_to.y - 1) * WORLD_ICON_SIZE + next_py - else - point.x = (x - 1) * WORLD_ICON_SIZE + current_px - point.y = (y - 1) * WORLD_ICON_SIZE + current_py - point.z = z - return point - -/** - * * returns a /datum/point based on where we'll be when we loosely intersect a tile - * * returns null if we'll never intersect it - * * returns our current point if we're already loosely intersecting it - * * loosely intersecting means that we are level with the tile in either x or y. - */ -/obj/projectile/proc/get_intersection_point(turf/colliding) - RETURN_TYPE(/datum/point) - ASSERT(!isnull(angle)) - - // calculate hwere we are - var/our_x = (x - 1) * WORLD_ICON_SIZE + current_px - var/our_y = (y - 1) * WORLD_ICON_SIZE + current_py - - // calculate how far we have to go to touch their closest x / y axis - var/d_to_reach_x - var/d_to_reach_y - - if(colliding.x != x) - switch(calculated_sdx) - if(0) - return - if(1) - if(colliding.x < x) - return - d_to_reach_x = (((colliding.x - 1) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx - if(-1) - if(colliding.x > x) - return - d_to_reach_x = (((colliding.x - 0) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx - else - d_to_reach_x = 0 - - if(colliding.y != y) - switch(calculated_sdy) - if(0) - return - if(1) - if(colliding.y < y) - return - d_to_reach_y = (((colliding.y - 1) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy - if(-1) - if(colliding.y > y) - return - d_to_reach_y = (((colliding.y - 0) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy - else - d_to_reach_y = 0 - - var/needed_distance = max(d_to_reach_x, d_to_reach_y) - - // calculate if we'll actually be touching the tile once we go that far - var/future_x = our_x + needed_distance * calculated_dx - var/future_y = our_y + needed_distance * calculated_dy - // let's be slightly lenient and do 1 instead of 0.5 - if(future_x < (colliding.x - 1) * WORLD_ICON_SIZE && future_x > (colliding.x) * WORLD_ICON_SIZE + 1 && \ - future_y < (colliding.y - 1) * WORLD_ICON_SIZE && future_y > (colliding.y) * WORLD_ICON_SIZE + 1) - return // not gonna happen - - // make the point based on how far we need to go - var/datum/point/point = new - point.x = future_x - point.y = future_y - point.z = z - return point - -/** - * records the start of a hitscan - * - * this can edit the point passed in! - */ -/obj/projectile/proc/record_hitscan_start(datum/point/point, muzzle_marker, kick_forwards) - if(!hitscanning) - return - if(isnull(point)) - point = get_tracer_point() - tracer_vertices = list(point) - tracer_muzzle_flash = muzzle_marker - - // kick forwards - point.shift_in_projectile_angle(angle, kick_forwards) - -/** - * ends the hitscan tracer - * - * this can edit the point passed in! - */ -/obj/projectile/proc/record_hitscan_end(datum/point/point, impact_marker, kick_forwards) - if(!hitscanning) - return - if(isnull(point)) - point = get_tracer_point() - tracer_vertices += point - tracer_impact_effect = impact_marker - - // kick forwards - point.shift_in_projectile_angle(angle, kick_forwards) - -/** - * records a deflection (change in angle, aka generate new tracer) - */ -/obj/projectile/proc/record_hitscan_deflection(datum/point/point) - if(!hitscanning) - return - if(isnull(point)) - point = get_tracer_point() - // there's no way you need more than 25 - // if this is hit, fix your shit, don't bump this up; there's absolutely no reason for example, - // to simulate reflectors working !!25!! times. - if(length(tracer_vertices) >= 25) - CRASH("tried to add more than 25 vertices to a hitscan tracer") - tracer_vertices += point - -/obj/projectile/proc/render_hitscan_tracers(duration = tracer_duration) - // don't stay too long - ASSERT(duration >= 0 && duration <= 10 SECONDS) - // check everything - if(!has_tracer || !duration || !length(tracer_vertices)) - return - var/list/atom/movable/beam_components = list() - - // muzzle - if(muzzle_type && tracer_muzzle_flash) - var/datum/point/starting = tracer_vertices[1] - var/atom/movable/muzzle_effect = starting.instantiate_movable_with_unmanaged_offsets(muzzle_type) - if(muzzle_effect) - // turn it - var/matrix/muzzle_transform = matrix() - muzzle_transform.Turn(original_angle) - muzzle_effect.transform = muzzle_transform - muzzle_effect.color = color - muzzle_effect.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color) - // add to list - beam_components += muzzle_effect - // impact - if(impact_type && tracer_impact_effect) - var/datum/point/starting = tracer_vertices[length(tracer_vertices)] - var/atom/movable/impact_effect = starting.instantiate_movable_with_unmanaged_offsets(impact_type) - if(impact_effect) - // turn it - var/matrix/impact_transform = matrix() - impact_transform.Turn(angle) - impact_effect.transform = impact_transform - impact_effect.color = color - impact_effect.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) - // add to list - beam_components += impact_effect - // path tracers - if(tracer_type) - var/tempref = "\ref[src]" - for(var/i in 1 to length(tracer_vertices) - 1) - var/j = i + 1 - var/datum/point/first_point = tracer_vertices[i] - var/datum/point/second_point = tracer_vertices[j] - generate_tracer_between_points(first_point, second_point, beam_components, tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) - - QDEL_LIST_IN(beam_components, duration) - - -/obj/projectile/proc/cleanup_hitscan_tracers() - tracer_vertices=null - -/obj/projectile/proc/finalize_hitscan_tracers(datum/point/end_point, impact_effect, kick_forwards) - // if end wasn't recorded yet and we're still on a turf, record end - if(isnull(tracer_impact_effect) && loc) - record_hitscan_end(end_point, impact_marker = impact_effect, kick_forwards = kick_forwards) - // render & cleanup - render_hitscan_tracers() - cleanup_hitscan_tracers() - -//* Physics - Configuration *// - -/** - * sets our angle - */ -/obj/projectile/proc/set_angle(new_angle) - angle = new_angle - - // update sprite - if(!nondirectional_sprite) - var/matrix/M = new - M.Turn(angle) - transform = M - - // update trajectory - calculated_dx = sin(new_angle) - calculated_dy = cos(new_angle) - calculated_sdx = calculated_dx == 0? 0 : (calculated_dx > 0? 1 : -1) - calculated_sdy = calculated_dy == 0? 0 : (calculated_dy > 0? 1 : -1) - - // record our tracer's change - if(hitscanning) - record_hitscan_deflection() - -/** - * sets our speed in pixels per decisecond - */ -/obj/projectile/proc/set_speed(new_speed) - speed = clamp(new_speed, 1, WORLD_ICON_SIZE * 100) - -/** - * sets our angle and speed - */ -/obj/projectile/proc/set_velocity(new_angle, new_speed) - // this is so this can be micro-optimized later but for once i'm not going to do it early for no reason - set_speed(new_speed) - set_angle(new_angle) - -/** - * todo: this is somewhat mildly terrible - */ -/obj/projectile/proc/set_homing_target(atom/A) - if(!A || (!isturf(A) && !isturf(A.loc))) - return FALSE - homing = TRUE - homing_target = A - homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max) - homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max) - if(prob(50)) - homing_offset_x = -homing_offset_x - if(prob(50)) - homing_offset_y = -homing_offset_y - -/** - * initializes physics vars - */ -/obj/projectile/proc/setup_physics() - distance_travelled = 0 - -/** - * called after an unhandled forcemove is detected, or other event - * that should reset our on-turf state - */ -/obj/projectile/proc/reset_physics_to_turf() - // we use this because we can center larger than 32x32 projectiles - // without disrupting physics this way - // - // we add by (WORLD_ICON_SIZE / 2) because - // pixel_x / pixel_y starts at center, - // - current_px = pixel_x - base_pixel_x + (WORLD_ICON_SIZE / 2) - current_py = pixel_y - base_pixel_y + (WORLD_ICON_SIZE / 2) - // interrupt active move logic - trajectory_moving_to = null - -//* Physics - Processing *// - -/obj/projectile/process(delta_time) - if(paused) - return - physics_iteration(min(10 * WORLD_ICON_SIZE, delta_time * speed * SSprojectiles.global_projectile_speed_multiplier), delta_time) - -/** - * immediately processes hitscan - */ -/obj/projectile/proc/physics_hitscan(safety = 250, resuming) - // setup - if(!resuming) - hitscanning = TRUE - record_hitscan_start(muzzle_marker = TRUE, kick_forwards = 16) - - // just move as many times as we can - while(!QDELETED(src) && loc) - // check safety - safety-- - if(safety <= 0) - // if you're here, you shouldn't be. do not bump safety up, fix whatever - // you're doing because no one should be making projectiles go more than 250 - // tiles in a single life. - stack_trace("projectile hit iteration limit for hitscan") - break - - // move forwards by 1 tile length - distance_travelled += physics_step(WORLD_ICON_SIZE) - // if we're being yanked, yield - if(movable_flags & MOVABLE_IN_MOVED_YANK) - spawn(0) - physics_hitscan(safety, TRUE) - return - - // see if we're done - if(distance_travelled >= range) - legacy_on_range() - break - - hitscanning = FALSE - -/** - * ticks forwards a number of pixels - * - * todo: potential lazy animate support for performance, as we honestly don't need to animate at full fps if the server's above 20fps - * - * * delta_tiem is in deciseconds, not seconds. - */ -/obj/projectile/proc/physics_iteration(pixels, delta_time, additional_animation_length) - // setup iteration - var/safety = 15 - var/pixels_remaining = pixels - distance_travelled_this_iteration = 0 - - // apply penalty - var/penalizing = clamp(trajectory_penalty_applied, 0, pixels_remaining) - pixels_remaining -= penalizing - trajectory_penalty_applied -= penalizing - - // clamp to max distance - pixels_remaining = min(pixels_remaining, range - distance_travelled) - - // move as many times as we need to - // - // * break if we're loc = null (by deletion or otherwise) - // * break if we get paused - while(pixels_remaining > 0) - // check safety - safety-- - if(safety <= 0) - CRASH("ran out of safety! what happened?") - - // move - var/pixels_moved = physics_step(pixels_remaining) - distance_travelled += pixels_moved - distance_travelled_this_iteration += pixels_moved - pixels_remaining -= pixels_moved - // we're being yanked, yield - if(movable_flags & MOVABLE_IN_MOVED_YANK) - spawn(0) - physics_iteration(pixels_remaining, delta_time, distance_travelled_this_iteration) - return - if(!loc || paused) - break - - // penalize next one if we were kicked forwards forcefully too far - trajectory_penalty_applied = max(0, -pixels_remaining) - - // if we don't have a loc anymore just bail - if(!loc) - return - - // if we're at max range - if(distance_travelled >= range) - // todo: egh - legacy_on_range() - if(QDELETED(src)) - return - - // process homing - physics_tick_homing(delta_time) - - // perform animations - // we assume at this point any deflections that should have happened, has happened, - // so we just do a naive animate based on our current loc and pixel x/y - // - // todo: animation needs to take into account angle changes, - // but that's expensive as shit so uh lol - // - // the reason we use distance_travelled_this_iteration is so if something disappears - // by forceMove or whatnot, - // we won't have it bounce from its previous location to the new one as it's not going - // to be accurate anymore - // - // so instead, as of right now, we backtrack via how much we know we moved. - var/final_px = base_pixel_x + current_px - (WORLD_ICON_SIZE / 2) - var/final_py = base_pixel_y + current_py - (WORLD_ICON_SIZE / 2) - var/anim_dist = distance_travelled_this_iteration + additional_animation_length - pixel_x = final_px - (anim_dist * sin(angle)) - pixel_y = final_py - (anim_dist * cos(angle)) - - animate( - src, - delta_time, - flags = ANIMATION_END_NOW, - pixel_x = final_px, - pixel_y = final_py, - ) - -/** - * based on but exactly http://www.cs.yorku.ca/~amana/research/grid.pdf - * - * move into the next tile, or the specified number of pixels, - * whichever is less pixels moved - * - * this will modify our current_px/current_py as necessary - * - * @return pixels moved - */ -/obj/projectile/proc/physics_step(limit) - // distance to move in our angle to get to next turf for horizontal and vertical - var/d_next_horizontal = \ - (calculated_sdx? ((calculated_sdx > 0? (WORLD_ICON_SIZE + 0.5) - current_px : -current_px + 0.5) / calculated_dx) : INFINITY) - var/d_next_vertical = \ - (calculated_sdy? ((calculated_sdy > 0? (WORLD_ICON_SIZE + 0.5) - current_py : -current_py + 0.5) / calculated_dy) : INFINITY) - var/turf/move_to_target - - /** - * explanation on why current and next are done: - * - * projectiles track their pixel x/y on turf, not absolute pixel x/y from edge of map - * this is done to make it simpler to reason about, but is not necessarily the most simple - * or efficient way to do things. - * - * part of the problems with this approach is that Move() is not infallible. the projectile can be blocked. - * if we immediately set current pixel x/y, if the projectile is intercepted by a Bump, we now dont' know the 'real' - * position of the projectile because it's out of sync with where it should be - * - * now, things that require math operations on it don't know the actual location of the projectile until this proc - * rolls it back - * - * so instead, we never touch current px/py until the move is known to be successful, then we set it - * to the stored next px/py - * - * this way, things accessing can mutate our state freely without worrying about needing to handle rollbacks - * - * this entire system however adds overhead - * if we want to not have overhead, we'll need to rewrite hit processing and have it so moves are fully illegal to fail - * but doing that is literally not possible because anything can reject a move for any reason whatsoever - * and we cannot control that, so, instead, we make projectiles track in absolute pixel x/y coordinates from edge of map - * - * that way, we don't even need to care about where the .loc is, we just know where the projectile is supposed to be by - * knowing where it isn't, and by taking the change in its pixels the projectile controller can tell the projectile - * where to go- - * - * (all shitposting aside, this is for future work; it works right now and we have an API to do set angle, kick forwards, etc) - * (so i'm not going to touch this more because it's 4 AM and honestly this entire raycaster is already far less overhead) - * (than the old system of a 16-loop of brute forced 2 pixel increments) - */ - - if(d_next_horizontal == d_next_vertical) - // we're diagonal - if(d_next_horizontal <= limit) - move_to_target = locate(x + calculated_sdx, y + calculated_sdy, z) - . = d_next_horizontal - if(!move_to_target) - // we hit the world edge and weren't transit; time to get deleted. - if(hitscanning) - finalize_hitscan_tracers(impact_effect = FALSE) - qdel(src) - return - next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) - next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) - else if(d_next_horizontal < d_next_vertical) - // closer is to move left/right - if(d_next_horizontal <= limit) - move_to_target = locate(x + calculated_sdx, y, z) - . = d_next_horizontal - if(!move_to_target) - // we hit the world edge and weren't transit; time to get deleted. - if(hitscanning) - finalize_hitscan_tracers(impact_effect = FALSE) - qdel(src) - return - next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) - next_py = current_py + d_next_horizontal * calculated_dy - else if(d_next_vertical < d_next_horizontal) - // closer is to move up/down - if(d_next_vertical <= limit) - move_to_target = locate(x, y + calculated_sdy, z) - . = d_next_vertical - if(!move_to_target) - // we hit the world edge and weren't transit; time to get deleted. - if(hitscanning) - finalize_hitscan_tracers(impact_effect = FALSE) - qdel(src) - return - next_px = current_px + d_next_vertical * calculated_dx - next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) - - // if we need to move - if(move_to_target) - var/atom/old_loc = loc - trajectory_moving_to = move_to_target - if(!Move(move_to_target) && ((loc != move_to_target) || !trajectory_moving_to)) - // if we don't successfully move, don't change anything, we didn't move. - . = 0 - if(loc == old_loc) - stack_trace("projectile failed to move, but is still on turf instead of deleted or relocated.") - qdel(src) // bye - else - // only do these if we successfully move, or somehow end up in that turf anyways - if(trajectory_kick_forwards) - . += trajectory_kick_forwards - trajectory_kick_forwards = 0 - current_px = next_px - current_py = next_py - #ifdef CF_PROJECTILE_RAYCAST_VISUALS - new /atom/movable/render/projectile_raycast(move_to_target, current_px, current_py, "#77ff77") - #endif - trajectory_moving_to = null - else - // not moving to another tile, so, just move on current tile - if(trajectory_kick_forwards) - trajectory_kick_forwards = 0 - stack_trace("how did something kick us forwards when we didn't even move?") - . = limit - current_px += limit * calculated_dx - current_py += limit * calculated_dy - next_px = current_px - next_py = current_py - #ifdef CF_PROJECTILE_RAYCAST_VISUALS - new /atom/movable/render/projectile_raycast(loc, current_px, current_py, "#ff3333") - #endif - -#ifdef CF_PROJECTILE_RAYCAST_VISUALS -GLOBAL_VAR_INIT(projectile_raycast_debug_visual_delay, 2 SECONDS) - -/atom/movable/render/projectile_raycast - plane = OBJ_PLANE - icon = 'icons/system/color_32x32.dmi' - icon_state = "white-pixel" - -/** - * px, py are absolute pixel coordinates on the tile, not pixel_x / pixel_y of this renderer! - */ -/atom/movable/render/projectile_raycast/Initialize(mapload, px, py, color) - src.pixel_x = px - 1 - src.pixel_y = py - 1 - src.color = color - . = ..() - QDEL_IN(src, GLOB.projectile_raycast_debug_visual_delay) -#endif - -/** - * immediately, without processing, kicks us forward a number of pixels - * - * since we immediately cross over into a turf when entering, - * things like mirrors/reflectors will immediately set angle - * - * it looks ugly and is pretty bad to just reflect off the edge of a turf so said things can - * call this proc to kick us forwards by a bit - */ -/obj/projectile/proc/physics_kick_forwards(pixels) - trajectory_kick_forwards += pixels - next_px += pixels * calculated_dx - next_py += pixels * calculated_dy - -/** - * only works during non-hitscan - * - * this is called once per tick - * homing is smoother the higher fps the server / SSprojectiles runs at - * - * todo: this is somewhat mildly terrible - * todo: this has absolutely no arc/animation support; this is bad - */ -/obj/projectile/proc/physics_tick_homing(delta_time) - if(!homing) - return FALSE - // checks if they're 1. on a turf, 2. on our z - // todo: should we add support for tracking something even if it leaves a turf? - if(homing_target?.z != z) - // bye bye! - return FALSE - // todo: this assumes single-tile objects. at some point, we should upgrade this to be unnecessarily expensive and always center-mass. - var/dx = (homing_target.x - src.x) * WORLD_ICON_SIZE + (0 - current_px) + homing_offset_x - var/dy = (homing_target.y - src.y) * WORLD_ICON_SIZE + (0 - current_py) + homing_offset_y - // say it with me, arctan() - // is CCW of east if (dx, dy) - // and CW of north if (dy, dx) - // where dx and dy is distance in x/y pixels from us to them. - - var/nudge_towards = closer_angle_difference(arctan(dy, dx)) - var/max_turn_speed = homing_turn_speed * delta_time - - set_angle(angle + clamp(nudge_towards, -max_turn_speed, max_turn_speed)) - -//* Physics - Querying *// - -/** - * predict what turf we'll be in after going forwards a certain amount of pixels - * - * doesn't actually sim; so this will go through walls/obstacles! - * - * * if we go out of bounds, we will return null; this doesn't level-wrap - */ -/obj/projectile/proc/physics_predicted_turf_after_iteration(pixels) - // -1 at the end if 0, because: - // - // -32 is go back 1 tile and be at the 1st pixel (as 0 is going back) - // 0 is go back 1 tile and be at the 32nd pixel. - var/incremented_px = (current_px + pixels * calculated_dx) || - 1 - var/incremented_py = (current_py + pixels * calculated_dy) || - 1 - - var/incremented_tx = floor(incremented_px / 32) - var/incremented_ty = floor(incremented_py / 32) - - return locate(x + incremented_tx, y + incremented_ty, z) - -/** - * predict what turfs we'll hit, excluding the current turf, after going forwards - * a certain amount of pixels - * - * doesn't actually sim; so this will go through walls/obstacles! - */ -/obj/projectile/proc/physics_predicted_turfs_during_iteration(pixels) - return pixel_physics_raycast(loc, current_px, current_py, angle, pixels) - -//* Targeting *// - -/** - * Checks if something is a valid target when directly clicked. - */ -/obj/projectile/proc/is_valid_target(atom/target) - if(isobj(target)) - var/obj/O = target - return O.obj_flags & OBJ_RANGE_TARGETABLE - else if(isliving(target)) - return TRUE - else if(isturf(target)) - return target.density - return FALSE diff --git a/code/modules/projectiles/projectile/README.md b/code/modules/projectiles/projectile/README.md new file mode 100644 index 00000000000..187e857cfe4 --- /dev/null +++ b/code/modules/projectiles/projectile/README.md @@ -0,0 +1,6 @@ +# /obj/projectile + +The base type of physics-simulated raycasting flying projectiles. + +Used for when you need precision, as throwing is very imprecise. + diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm index f0a0a86cbe8..8277bd2bfa5 100644 --- a/code/modules/projectiles/projectile/arc.dm +++ b/code/modules/projectiles/projectile/arc.dm @@ -10,6 +10,7 @@ name = "arcing shot" icon_state = "fireball" // WIP movement_type = MOVEMENT_UNSTOPPABLE + impact_ground_on_expiry = TRUE plane = ABOVE_PLANE // Since projectiles are 'in the air', they might visually overlap mobs while in flight, so the projectile needs to be above their plane. speed = 10 * WORLD_ICON_SIZE var/fired_dir = null // Which direction was the projectile fired towards. Needed to invert the projectile turning based on if facing left or right. @@ -17,8 +18,6 @@ var/visual_y_offset = -16 // Adjusts how high the projectile and its shadow start, visually. This is so the projectile and shadow align with the center of the tile. var/obj/effect/projectile_shadow/shadow = null // Visual indicator for the projectile's 'true' position. Needed due to being bound to two dimensions in reality. -/obj/projectile/arc/Bump() - return /obj/projectile/arc/Initialize(mapload) shadow = new(get_turf(src)) @@ -28,7 +27,6 @@ QDEL_NULL(shadow) return ..() - /obj/projectile/arc/proc/calculate_initial_pixel_distance(atom/user, atom/target) var/datum/point/A = new(user) var/datum/point/B = new(target) @@ -44,12 +42,6 @@ var/datum/point/starting_point = new(starting) return pixel_length_between_points(current_point, starting_point) -/obj/projectile/arc/legacy_on_range() - if(loc) - on_impact(loc) - return ..() - - /obj/projectile/arc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) fired_dir = get_dir(user, target) // Used to determine if the projectile should turn in the air. distance_to_fly = calculate_initial_pixel_distance(user, target) // Calculates how many pixels to travel before hitting the ground. @@ -93,7 +85,6 @@ shadow.pixel_x = pixel_x shadow.pixel_y = pixel_y + visual_y_offset - /obj/effect/projectile_shadow name = "shadow" desc = "You better avoid the thing coming down!" @@ -102,64 +93,13 @@ anchored = TRUE animate_movement = 0 // Just like the projectile it's following. -////////////// -// Subtypes -////////////// - -// This is a test projectile in the sense that its testing the code to make sure it works, -// as opposed to a 'can I hit this thing' projectile. -/obj/projectile/arc/test/on_impact(turf/T) - new /obj/effect/explosion(T) - T.color = "#FF0000" - -// Generic, Hivebot related -/obj/projectile/arc/blue_energy - name = "energy missile" - icon_state = "force_missile" - damage = 15 - damage_type = BURN - -/obj/projectile/arc/blue_energy/on_impact(turf/T) - for(var/mob/living/L in T) - projectile_attack_mob(L) // Everything on the turf it lands gets hit. - -// Fragmentation arc shot -/obj/projectile/arc/fragmentation - name = "fragmentation shot" - icon_state = "shell" - var/list/fragment_types = list( - /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment, \ - /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment/strong - ) - var/fragment_amount = 63 // Same as a grenade. - var/spread_range = 7 - -/obj/projectile/arc/fragmentation/on_impact(turf/T) - fragmentate(T, fragment_amount, spread_range, fragment_types) - -/obj/projectile/arc/fragmentation/mortar - icon_state = "mortar" - fragment_amount = 10 - spread_range = 3 - -// EMP arc shot -/obj/projectile/arc/emp_blast - name = "emp blast" - icon_state = "bluespace" - -/obj/projectile/arc/emp_blast/on_impact(turf/T) - empulse(T, 2, 4, 7, 10) // Normal EMP grenade. - -/obj/projectile/arc/emp_blast/weak/on_impact(turf/T) - empulse(T, 1, 2, 3, 4) // Sec EMP grenade. - -// Radiation arc shot -/obj/projectile/arc/radioactive - name = "radiation blast" - icon_state = "green_pellet" - icon_scale_x = 2 - icon_scale_y = 2 - var/rad_power = RAD_INTENSITY_PROJ_ARC - -/obj/projectile/arc/radioactive/on_impact(turf/T) - radiation_pulse(T, rad_power) +//* We do not hit normally. *// + +/obj/projectile/arc/scan_crossed_atom(atom/movable/target) + return + +/obj/projectile/arc/scan_moved_turf(turf/tile) + return + +/obj/projectile/arc/impact_loop(turf/was_moving_onto, atom/bumped) + return diff --git a/code/modules/projectiles/projectile/helpers.dm b/code/modules/projectiles/projectile/helpers.dm new file mode 100644 index 00000000000..3469a09b6a6 --- /dev/null +++ b/code/modules/projectiles/projectile/helpers.dm @@ -0,0 +1,68 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Creates a shrapnel explosion + * + * todo: should this be in a single file with procs like explosion(), emp_pulse(), and other blast-like effects? + * todo: /pellet shouldn't be /bullet/pellet + * todo: there has to be a better way to set vars on every cloud than passing them in as args + * + * @params + * * total_fragments - total pellets. this is divided by turfs within the given radius and rounded up. + * * radius - radius to target. fragments are spread into every turf in the range, making one pellet cloud projectile + * * fragment_types - either a type, or a list of types to pick from. weighted lists are not allowed for speed reasons. all types must be /obj/projectile/bullet/pellet. + * * source - (optional) actual source; used to detect if something is on the ground or not + * * shot_from_name - (legacy - pending reconsideration) what we were shot from + * * firer - (legacy - pending reconsideration) what to set firer to + */ +/turf/proc/shrapnel_explosion(total_fragments, radius, fragment_types = /obj/projectile/bullet/pellet/fragment, atom/movable/source, shot_from_name, firer) + SHOULD_NOT_OVERRIDE(TRUE) + shrapnel_explosion_impl(total_fragments, radius, fragment_types, source, shot_from_name, firer) + +/turf/proc/shrapnel_explosion_impl(total_fragments, radius, fragment_types, atom/movable/source, shot_from_name, firer) + SHOULD_NOT_OVERRIDE(TRUE) + if(radius > 8) + // remember that for radius 8 we're making 32 projectiles already + CRASH("attempted radius [radius]; this is too large.") + // todo: verify getcircle() behavior is what we want + // todo: the answer is probably no, as this doesn't work near map edges. + var/list/target_turfs = getcircle(src, radius) + // round up + var/fragments_per_projectile = ceil(total_fragments / length(target_turfs)) + // make shrapnel clouds + . = list() + for(var/turf/T in target_turfs) + var/fragment_type = islist(fragment_types) ? pick(fragment_types) : fragment_types + var/obj/projectile/bullet/pellet/pellet_cloud = new fragment_type(src) + pellet_cloud.pellets = fragments_per_projectile + pellet_cloud.shot_from = shot_from_name + pellet_cloud.firer = firer + pellet_cloud.fire(arctan(T.y - y, T.x - x), null, TRUE) + . += pellet_cloud + +/** + * make a shrapnel explosion + */ +/obj/proc/shrapnel_explosion(total_fragments, radius, fragment_types) + var/turf/turf = get_turf(src) + if(!turf) + return + var/list/obj/projectile/bullet/pellet/pellet_clouds = turf.shrapnel_explosion(total_fragments, radius, fragment_types, src, name, src) + // hit things in our turf + for(var/mob/living/victim in turf) + for(var/obj/projectile/bullet/pellet/pellet_cloud as anything in pellet_clouds) + if(QDELETED(pellet_cloud)) + continue + // they're laying on us, o h n o + if(victim.lying && (loc == turf)) + if(prob(90)) + pellet_cloud.impact(victim) + // they're not laying down but they're holding the source and standing + else if(!victim.lying && victim.is_holding(src)) + if(prob(25)) + pellet_cloud.impact(victim) + // they are either holding us while laying down or just on the turf without holding us + else + if(prob(15)) + pellet_cloud.impact(victim) diff --git a/code/modules/projectiles/projectile/pellets.dm b/code/modules/projectiles/projectile/pellets.dm deleted file mode 100644 index 61e9b45cda0..00000000000 --- a/code/modules/projectiles/projectile/pellets.dm +++ /dev/null @@ -1,118 +0,0 @@ - -//For projectiles that actually represent clouds of projectiles -/obj/projectile/bullet/pellet - name = "shrapnel" //'shrapnel' sounds more dangerous (i.e. cooler) than 'pellet' - damage = 20 - //icon_state = "bullet" //TODO: would be nice to have it's own icon state - var/pellets = 4 //number of pellets - var/range_step = 2 //projectile will lose a fragment each time it travels this distance. Can be a non-integer. - var/base_spread = 90 //lower means the pellets spread more across body parts. If zero then this is considered a shrapnel explosion instead of a shrapnel cone - var/spread_step = 10 //higher means the pellets spread more across body parts with distance - -/obj/projectile/bullet/pellet/proc/get_pellets(var/distance) - var/pellet_loss = round((distance - 1)/range_step) //pellets lost due to distance - return max(pellets - pellet_loss, 1) - -/obj/projectile/bullet/pellet/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier) - if (pellets < 0) return 1 - - var/total_pellets = get_pellets(distance) - var/spread = max(base_spread - (spread_step*distance), 0) - - //shrapnel explosions miss prone mobs with a chance that increases with distance - var/prone_chance = 0 - if(!base_spread) - prone_chance = max(spread_step*(distance - 2), 0) - - var/hits = 0 - for (var/i in 1 to total_pellets) - if(target_mob.lying && target_mob != original && prob(prone_chance)) - continue - - //pellet hits spread out across different zones, but 'aim at' the targeted zone with higher probability - //whether the pellet actually hits the def_zone or a different zone should still be determined by the parent using get_zone_with_miss_chance(). - var/old_zone = def_zone - def_zone = ran_zone(def_zone, spread) - if (..()) hits++ - def_zone = old_zone //restore the original zone the projectile was aimed at - - pellets -= hits //each hit reduces the number of pellets left - if (hits >= total_pellets || pellets <= 0) - return 1 - return 0 - -/obj/projectile/bullet/pellet/get_structure_damage() - var/distance = get_dist(loc, starting) - return ..() * get_pellets(distance) - -/obj/projectile/bullet/pellet/Move() - . = ..() - - //If this is a shrapnel explosion, allow mobs that are prone to get hit, too - if(. && !base_spread && isturf(loc)) - for(var/mob/living/M in loc) - if(M.lying || !M.CanPass(src, loc)) //Bump if lying or if we would normally Bump. - if(Bump(M)) //Bump will make sure we don't hit a mob multiple times - return - -//Explosive grenade projectile, borrowed from fragmentation grenade code. -/obj/projectile/bullet/pellet/fragment - damage = 10 - armor_penetration = 30 - range_step = 2 //projectiles lose a fragment each time they travel this distance. Can be a non-integer. - - base_spread = 0 //causes it to be treated as a shrapnel explosion instead of cone - spread_step = 20 - - silenced = 1 //embedding messages are still produced so it's kind of weird when enabled. - no_attack_log = 1 - muzzle_type = null - -/obj/projectile/bullet/pellet/fragment/strong - damage = 15 - armor_penetration = 20 - -/obj/projectile/bullet/pellet/fragment/weak - damage = 4 - armor_penetration = 40 - -/obj/projectile/bullet/pellet/fragment/rubber - name = "stingball shrapnel" - damage = 3 - agony = 8 - sharp = FALSE - edge = FALSE - damage_flag = ARMOR_MELEE - -/obj/projectile/bullet/pellet/fragment/rubber/strong - damage = 8 - agony = 16 - -// Tank rupture fragments -/obj/projectile/bullet/pellet/fragment/tank - name = "metal fragment" - damage = 9 //Big chunks flying off. - range_step = 2 //controls damage falloff with distance. projectiles lose a "pellet" each time they travel this distance. Can be a non-integer. - - base_spread = 0 //causes it to be treated as a shrapnel explosion instead of cone - spread_step = 20 - - armor_penetration = 20 - - silenced = 1 - no_attack_log = 1 - muzzle_type = null - pellets = 3 - -/obj/projectile/bullet/pellet/fragment/tank/small - name = "small metal fragment" - damage = 6 - armor_penetration = 5 - pellets = 5 - -/obj/projectile/bullet/pellet/fragment/tank/big - name = "large metal fragment" - damage = 17 - armor_penetration = 10 - range_step = 5 //controls damage falloff with distance. projectiles lose a "pellet" each time they travel this distance. Can be a non-integer. - pellets = 1 diff --git a/code/modules/projectiles/projectile/projectile-hitscan_visuals.dm b/code/modules/projectiles/projectile/projectile-hitscan_visuals.dm new file mode 100644 index 00000000000..f9a370eee63 --- /dev/null +++ b/code/modules/projectiles/projectile/projectile-hitscan_visuals.dm @@ -0,0 +1,195 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Hitscan Visuals *// + +/** + * returns a /datum/point based on where we currently are + */ +/obj/projectile/proc/get_tracer_point() + RETURN_TYPE(/datum/point) + var/datum/point/point = new + if(trajectory_moving_to) + // we're in move. use next px/py to respect 1. kick forwards 2. deflections + point.x = (trajectory_moving_to.x - 1) * WORLD_ICON_SIZE + next_px + point.y = (trajectory_moving_to.y - 1) * WORLD_ICON_SIZE + next_py + else + point.x = (x - 1) * WORLD_ICON_SIZE + current_px + point.y = (y - 1) * WORLD_ICON_SIZE + current_py + point.z = z + return point + +/** + * * returns a /datum/point based on where we'll be when we loosely intersect a tile + * * returns null if we'll never intersect it + * * returns our current point if we're already loosely intersecting it + * * loosely intersecting means that we are level with the tile in either x or y. + */ +/obj/projectile/proc/get_intersection_point(turf/colliding) + RETURN_TYPE(/datum/point) + ASSERT(!isnull(angle)) + + // calculate where we are + var/our_x + var/our_y + if(trajectory_moving_to) + // we're in move. use next px/py to respect 1. kick forwards 2. deflections + our_x = (trajectory_moving_to.x - 1) * WORLD_ICON_SIZE + next_px + our_y = (trajectory_moving_to.y - 1) * WORLD_ICON_SIZE + next_py + else + our_x = (x - 1) * WORLD_ICON_SIZE + current_px + our_y = (y - 1) * WORLD_ICON_SIZE + current_py + + // calculate how far we have to go to touch their closest x / y axis + var/d_to_reach_x + var/d_to_reach_y + + if(colliding.x != x) + switch(calculated_sdx) + if(0) + return + if(1) + if(colliding.x < x) + return + d_to_reach_x = (((colliding.x - 1) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx + if(-1) + if(colliding.x > x) + return + d_to_reach_x = (((colliding.x - 0) * WORLD_ICON_SIZE + 0.5) - our_x) / calculated_dx + else + d_to_reach_x = 0 + + if(colliding.y != y) + switch(calculated_sdy) + if(0) + return + if(1) + if(colliding.y < y) + return + d_to_reach_y = (((colliding.y - 1) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy + if(-1) + if(colliding.y > y) + return + d_to_reach_y = (((colliding.y - 0) * WORLD_ICON_SIZE + 0.5) - our_y) / calculated_dy + else + d_to_reach_y = 0 + + var/needed_distance = max(d_to_reach_x, d_to_reach_y) + + // calculate if we'll actually be touching the tile once we go that far + var/future_x = our_x + needed_distance * calculated_dx + var/future_y = our_y + needed_distance * calculated_dy + // let's be slightly lenient and do 1 instead of 0.5 + if(future_x < (colliding.x - 1) * WORLD_ICON_SIZE && future_x > (colliding.x) * WORLD_ICON_SIZE + 1 && \ + future_y < (colliding.y - 1) * WORLD_ICON_SIZE && future_y > (colliding.y) * WORLD_ICON_SIZE + 1) + return // not gonna happen + + // make the point based on how far we need to go + var/datum/point/point = new + point.x = future_x + point.y = future_y + point.z = z + return point + +/** + * records the start of a hitscan + * + * this can edit the point passed in! + */ +/obj/projectile/proc/record_hitscan_start(datum/point/point, muzzle_marker, kick_forwards) + if(!hitscanning) + return + if(isnull(point)) + point = get_tracer_point() + tracer_vertices = list(point) + tracer_muzzle_flash = muzzle_marker + + // kick forwards + point.shift_in_projectile_angle(angle, kick_forwards) + +/** + * ends the hitscan tracer + * + * this can edit the point passed in! + */ +/obj/projectile/proc/record_hitscan_end(datum/point/point, impact_marker, kick_forwards) + if(!hitscanning) + return + if(isnull(point)) + point = get_tracer_point() + tracer_vertices += point + tracer_impact_effect = impact_marker + + // kick forwards + point.shift_in_projectile_angle(angle, kick_forwards) + +/** + * records a deflection (change in angle, aka generate new tracer) + */ +/obj/projectile/proc/record_hitscan_deflection(datum/point/point) + if(!hitscanning) + return + if(isnull(point)) + point = get_tracer_point() + // there's no way you need more than 25 + // if this is hit, fix your shit, don't bump this up; there's absolutely no reason for example, + // to simulate reflectors working !!25!! times. + if(length(tracer_vertices) >= 25) + CRASH("tried to add more than 25 vertices to a hitscan tracer") + tracer_vertices += point + +/obj/projectile/proc/render_hitscan_tracers(duration = tracer_duration) + // don't stay too long + ASSERT(duration >= 0 && duration <= 10 SECONDS) + // check everything + if(!has_tracer || !duration || !length(tracer_vertices)) + return + var/list/atom/movable/beam_components = list() + + // muzzle + if(muzzle_type && tracer_muzzle_flash) + var/datum/point/starting = tracer_vertices[1] + var/atom/movable/muzzle_effect = starting.instantiate_movable_with_unmanaged_offsets(muzzle_type) + if(muzzle_effect) + // turn it + var/matrix/muzzle_transform = matrix() + muzzle_transform.Turn(original_angle) + muzzle_effect.transform = muzzle_transform + muzzle_effect.color = color + muzzle_effect.set_light(muzzle_flash_range, muzzle_flash_intensity, muzzle_flash_color_override? muzzle_flash_color_override : color) + // add to list + beam_components += muzzle_effect + // impact + if(impact_type && tracer_impact_effect) + var/datum/point/starting = tracer_vertices[length(tracer_vertices)] + var/atom/movable/impact_effect = starting.instantiate_movable_with_unmanaged_offsets(impact_type) + if(impact_effect) + // turn it + var/matrix/impact_transform = matrix() + impact_transform.Turn(angle) + impact_effect.transform = impact_transform + impact_effect.color = color + impact_effect.set_light(impact_light_range, impact_light_intensity, impact_light_color_override? impact_light_color_override : color) + // add to list + beam_components += impact_effect + // path tracers + if(tracer_type) + var/tempref = "\ref[src]" + for(var/i in 1 to length(tracer_vertices) - 1) + var/j = i + 1 + var/datum/point/first_point = tracer_vertices[i] + var/datum/point/second_point = tracer_vertices[j] + generate_tracer_between_points(first_point, second_point, beam_components, tracer_type, color, duration, hitscan_light_range, hitscan_light_color_override, hitscan_light_intensity, tempref) + + QDEL_LIST_IN(beam_components, duration) + +/obj/projectile/proc/cleanup_hitscan_tracers() + tracer_vertices = null + +/obj/projectile/proc/finalize_hitscan_tracers(datum/point/end_point, impact_effect, kick_forwards) + // if end wasn't recorded yet and we're still on a turf, record end + if(isnull(tracer_impact_effect) && loc) + record_hitscan_end(end_point, impact_marker = impact_effect, kick_forwards = kick_forwards) + // render & cleanup + render_hitscan_tracers() + cleanup_hitscan_tracers() diff --git a/code/modules/projectiles/projectile/projectile-physics.dm b/code/modules/projectiles/projectile/projectile-physics.dm new file mode 100644 index 00000000000..ed446daf0ff --- /dev/null +++ b/code/modules/projectiles/projectile/projectile-physics.dm @@ -0,0 +1,434 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +//* Physics - Configuration *// + +/** + * sets our angle + */ +/obj/projectile/proc/set_angle(new_angle) + angle = new_angle + + // update sprite + if(!nondirectional_sprite) + var/matrix/M = new + M.Turn(angle) + transform = M + + // update trajectory + calculated_dx = sin(new_angle) + calculated_dy = cos(new_angle) + calculated_sdx = calculated_dx == 0? 0 : (calculated_dx > 0? 1 : -1) + calculated_sdy = calculated_dy == 0? 0 : (calculated_dy > 0? 1 : -1) + + var/normalized_to_first_quadrant = MODULUS_F(new_angle, 90) + angle_chebyshev_divisor = cos(normalized_to_first_quadrant >= 45? (90 - normalized_to_first_quadrant) : normalized_to_first_quadrant) + + // record our tracer's change + if(hitscanning) + record_hitscan_deflection() + +/** + * sets our speed in pixels per decisecond + */ +/obj/projectile/proc/set_speed(new_speed) + speed = clamp(new_speed, 1, WORLD_ICON_SIZE * 100) + +/** + * sets our angle and speed + */ +/obj/projectile/proc/set_velocity(new_angle, new_speed) + // this is so this can be micro-optimized later but for once i'm not going to do it early for no reason + set_speed(new_speed) + set_angle(new_angle) + +/** + * todo: this is somewhat mildly terrible + */ +/obj/projectile/proc/set_homing_target(atom/A) + if(!A || (!isturf(A) && !isturf(A.loc))) + return FALSE + homing = TRUE + homing_target = A + homing_offset_x = rand(homing_inaccuracy_min, homing_inaccuracy_max) + homing_offset_y = rand(homing_inaccuracy_min, homing_inaccuracy_max) + if(prob(50)) + homing_offset_x = -homing_offset_x + if(prob(50)) + homing_offset_y = -homing_offset_y + +/** + * initializes physics vars + */ +/obj/projectile/proc/setup_physics() + distance_travelled = 0 + +/** + * called after an unhandled forcemove is detected, or other event + * that should reset our on-turf state + */ +/obj/projectile/proc/reset_physics_to_turf() + // we use this because we can center larger than 32x32 projectiles + // without disrupting physics this way + // + // we add by (WORLD_ICON_SIZE / 2) because + // pixel_x / pixel_y starts at center, + // + current_px = pixel_x - base_pixel_x + (WORLD_ICON_SIZE / 2) + current_py = pixel_y - base_pixel_y + (WORLD_ICON_SIZE / 2) + // interrupt active move logic + trajectory_moving_to = null + +//* Physics - Processing *// + +/obj/projectile/process(delta_time) + if(paused) + return + physics_iteration(min(10 * WORLD_ICON_SIZE, delta_time * speed * SSprojectiles.global_projectile_speed_multiplier), delta_time) + +/** + * immediately processes hitscan + */ +/obj/projectile/proc/physics_hitscan(safety = 250, resuming) + // setup + if(!resuming) + hitscanning = TRUE + record_hitscan_start(muzzle_marker = TRUE, kick_forwards = 16) + + // just move as many times as we can + while(!QDELETED(src) && loc) + // check safety + safety-- + if(safety <= 0) + // if you're here, you shouldn't be. do not bump safety up, fix whatever + // you're doing because no one should be making projectiles go more than 250 + // tiles in a single life. + stack_trace("projectile hit iteration limit for hitscan") + break + + // move forwards by 1 tile length + var/pixels_moved = physics_step(WORLD_ICON_SIZE) + distance_travelled += pixels_moved + // if we're being yanked, yield + if(movable_flags & MOVABLE_IN_MOVED_YANK) + spawn(0) + physics_hitscan(safety, TRUE) + return + + // see if we're done + if(distance_travelled >= range) + legacy_on_range() + break + + hitscanning = FALSE + +/** + * ticks forwards a number of pixels + * + * todo: potential lazy animate support for performance, as we honestly don't need to animate at full fps if the server's above 20fps + * + * * delta_tiem is in deciseconds, not seconds. + */ +/obj/projectile/proc/physics_iteration(pixels, delta_time, additional_animation_length) + // setup iteration + var/safety = 15 + var/pixels_remaining = pixels + distance_travelled_this_iteration = 0 + + // apply penalty + var/penalizing = clamp(trajectory_penalty_applied, 0, pixels_remaining) + pixels_remaining -= penalizing + trajectory_penalty_applied -= penalizing + + // clamp to max distance + pixels_remaining = min(pixels_remaining, range - distance_travelled) + + // move as many times as we need to + // + // * break if we're loc = null (by deletion or otherwise) + // * break if we get paused + while(pixels_remaining > 0) + // check safety + safety-- + if(safety <= 0) + CRASH("ran out of safety! what happened?") + + // move + var/pixels_moved = physics_step(pixels_remaining) + distance_travelled += pixels_moved + distance_travelled_this_iteration += pixels_moved + pixels_remaining -= pixels_moved + // we're being yanked, yield + if(movable_flags & MOVABLE_IN_MOVED_YANK) + spawn(0) + physics_iteration(pixels_remaining, delta_time, distance_travelled_this_iteration) + return + // this is also a catch-all for deletion + if(!loc || paused) + break + + // penalize next one if we were kicked forwards forcefully too far + trajectory_penalty_applied = max(0, -pixels_remaining) + + // if we don't have a loc anymore just bail + if(!loc) + return + + // if we're at max range + if(distance_travelled >= range) + // todo: egh + legacy_on_range() + if(QDELETED(src)) + return + + // process homing + physics_tick_homing(delta_time) + + // perform animations + // we assume at this point any deflections that should have happened, has happened, + // so we just do a naive animate based on our current loc and pixel x/y + // + // todo: animation needs to take into account angle changes, + // but that's expensive as shit so uh lol + // + // the reason we use distance_travelled_this_iteration is so if something disappears + // by forceMove or whatnot, + // we won't have it bounce from its previous location to the new one as it's not going + // to be accurate anymore + // + // so instead, as of right now, we backtrack via how much we know we moved. + var/final_px = base_pixel_x + current_px - (WORLD_ICON_SIZE / 2) + var/final_py = base_pixel_y + current_py - (WORLD_ICON_SIZE / 2) + var/anim_dist = distance_travelled_this_iteration + additional_animation_length + pixel_x = final_px - (anim_dist * sin(angle)) + pixel_y = final_py - (anim_dist * cos(angle)) + + animate( + src, + delta_time, + flags = ANIMATION_END_NOW, + pixel_x = final_px, + pixel_y = final_py, + ) + +/** + * based on but exactly http://www.cs.yorku.ca/~amana/research/grid.pdf + * + * move into the next tile, or the specified number of pixels, + * whichever is less pixels moved + * + * this will modify our current_px/current_py as necessary + * + * @return pixels moved + */ +/obj/projectile/proc/physics_step(limit) + // distance to move in our angle to get to next turf for horizontal and vertical + var/d_next_horizontal = \ + (calculated_sdx? ((calculated_sdx > 0? (WORLD_ICON_SIZE + 0.5) - current_px : -current_px + 0.5) / calculated_dx) : INFINITY) + var/d_next_vertical = \ + (calculated_sdy? ((calculated_sdy > 0? (WORLD_ICON_SIZE + 0.5) - current_py : -current_py + 0.5) / calculated_dy) : INFINITY) + var/turf/move_to_target + + /** + * explanation on why current and next are done: + * + * projectiles track their pixel x/y on turf, not absolute pixel x/y from edge of map + * this is done to make it simpler to reason about, but is not necessarily the most simple + * or efficient way to do things. + * + * part of the problems with this approach is that Move() is not infallible. the projectile can be blocked. + * if we immediately set current pixel x/y, if the projectile is intercepted by a Bump, we now dont' know the 'real' + * position of the projectile because it's out of sync with where it should be + * + * now, things that require math operations on it don't know the actual location of the projectile until this proc + * rolls it back + * + * so instead, we never touch current px/py until the move is known to be successful, then we set it + * to the stored next px/py + * + * this way, things accessing can mutate our state freely without worrying about needing to handle rollbacks + * + * this entire system however adds overhead + * if we want to not have overhead, we'll need to rewrite hit processing and have it so moves are fully illegal to fail + * but doing that is literally not possible because anything can reject a move for any reason whatsoever + * and we cannot control that, so, instead, we make projectiles track in absolute pixel x/y coordinates from edge of map + * + * that way, we don't even need to care about where the .loc is, we just know where the projectile is supposed to be by + * knowing where it isn't, and by taking the change in its pixels the projectile controller can tell the projectile + * where to go- + * + * (all shitposting aside, this is for future work; it works right now and we have an API to do set angle, kick forwards, etc) + * (so i'm not going to touch this more because it's 4 AM and honestly this entire raycaster is already far less overhead) + * (than the old system of a 16-loop of brute forced 2 pixel increments) + */ + + if(d_next_horizontal == d_next_vertical) + // we're diagonal + if(d_next_horizontal <= limit) + move_to_target = locate(x + calculated_sdx, y + calculated_sdy, z) + . = d_next_horizontal + if(!move_to_target) + // we hit the world edge and weren't transit; time to get deleted. + if(hitscanning) + finalize_hitscan_tracers(impact_effect = FALSE) + qdel(src) + return + next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + else if(d_next_horizontal < d_next_vertical) + // closer is to move left/right + if(d_next_horizontal <= limit) + move_to_target = locate(x + calculated_sdx, y, z) + . = d_next_horizontal + if(!move_to_target) + // we hit the world edge and weren't transit; time to get deleted. + if(hitscanning) + finalize_hitscan_tracers(impact_effect = FALSE) + qdel(src) + return + next_px = calculated_sdx > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + next_py = current_py + d_next_horizontal * calculated_dy + else if(d_next_vertical < d_next_horizontal) + // closer is to move up/down + if(d_next_vertical <= limit) + move_to_target = locate(x, y + calculated_sdy, z) + . = d_next_vertical + if(!move_to_target) + // we hit the world edge and weren't transit; time to get deleted. + if(hitscanning) + finalize_hitscan_tracers(impact_effect = FALSE) + qdel(src) + return + next_px = current_px + d_next_vertical * calculated_dx + next_py = calculated_sdy > 0? 0.5 : (WORLD_ICON_SIZE + 0.5) + + // if we need to move + if(move_to_target) + var/atom/old_loc = loc + trajectory_moving_to = move_to_target + // mark next distance so impact processing can work + next_distance = distance_travelled + . + if(!Move(move_to_target) && ((loc != move_to_target) || !trajectory_moving_to)) + // if we don't successfully move, don't change anything, we didn't move. + . = 0 + if(loc == old_loc) + stack_trace("projectile failed to move, but is still on turf instead of deleted or relocated.") + qdel(src) // bye + else + // only do these if we successfully move, or somehow end up in that turf anyways + if(trajectory_kick_forwards) + . += trajectory_kick_forwards + trajectory_kick_forwards = 0 + current_px = next_px + current_py = next_py + #ifdef CF_PROJECTILE_RAYCAST_VISUALS + new /atom/movable/render/projectile_raycast(move_to_target, current_px, current_py, "#77ff77") + #endif + trajectory_moving_to = null + else + // not moving to another tile, so, just move on current tile + if(trajectory_kick_forwards) + trajectory_kick_forwards = 0 + stack_trace("how did something kick us forwards when we didn't even move?") + . = limit + current_px += limit * calculated_dx + current_py += limit * calculated_dy + next_px = current_px + next_py = current_py + #ifdef CF_PROJECTILE_RAYCAST_VISUALS + new /atom/movable/render/projectile_raycast(loc, current_px, current_py, "#ff3333") + #endif + +#ifdef CF_PROJECTILE_RAYCAST_VISUALS +GLOBAL_VAR_INIT(projectile_raycast_debug_visual_delay, 2 SECONDS) + +/atom/movable/render/projectile_raycast + plane = OBJ_PLANE + icon = 'icons/system/color_32x32.dmi' + icon_state = "white-pixel" + +/** + * px, py are absolute pixel coordinates on the tile, not pixel_x / pixel_y of this renderer! + */ +/atom/movable/render/projectile_raycast/Initialize(mapload, px, py, color) + src.pixel_x = px - 1 + src.pixel_y = py - 1 + src.color = color + . = ..() + QDEL_IN(src, GLOB.projectile_raycast_debug_visual_delay) +#endif + +/** + * immediately, without processing, kicks us forward a number of pixels + * + * since we immediately cross over into a turf when entering, + * things like mirrors/reflectors will immediately set angle + * + * it looks ugly and is pretty bad to just reflect off the edge of a turf so said things can + * call this proc to kick us forwards by a bit + */ +/obj/projectile/proc/physics_kick_forwards(pixels) + trajectory_kick_forwards += pixels + next_px += pixels * calculated_dx + next_py += pixels * calculated_dy + +/** + * only works during non-hitscan + * + * this is called once per tick + * homing is smoother the higher fps the server / SSprojectiles runs at + * + * todo: this is somewhat mildly terrible + * todo: this has absolutely no arc/animation support; this is bad + */ +/obj/projectile/proc/physics_tick_homing(delta_time) + if(!homing) + return FALSE + // checks if they're 1. on a turf, 2. on our z + // todo: should we add support for tracking something even if it leaves a turf? + if(homing_target?.z != z) + // bye bye! + return FALSE + // todo: this assumes single-tile objects. at some point, we should upgrade this to be unnecessarily expensive and always center-mass. + var/dx = (homing_target.x - src.x) * WORLD_ICON_SIZE + (0 - current_px) + homing_offset_x + var/dy = (homing_target.y - src.y) * WORLD_ICON_SIZE + (0 - current_py) + homing_offset_y + // say it with me, arctan() + // is CCW of east if (dx, dy) + // and CW of north if (dy, dx) + // where dx and dy is distance in x/y pixels from us to them. + + var/nudge_towards = closer_angle_difference(arctan(dy, dx)) + var/max_turn_speed = homing_turn_speed * delta_time + + set_angle(angle + clamp(nudge_towards, -max_turn_speed, max_turn_speed)) + +//* Physics - Querying *// + +/** + * predict what turf we'll be in after going forwards a certain amount of pixels + * + * doesn't actually sim; so this will go through walls/obstacles! + * + * * if we go out of bounds, we will return null; this doesn't level-wrap + */ +/obj/projectile/proc/physics_predicted_turf_after_iteration(pixels) + // -1 at the end if 0, because: + // + // -32 is go back 1 tile and be at the 1st pixel (as 0 is going back) + // 0 is go back 1 tile and be at the 32nd pixel. + var/incremented_px = (current_px + pixels * calculated_dx) || - 1 + var/incremented_py = (current_py + pixels * calculated_dy) || - 1 + + var/incremented_tx = floor(incremented_px / 32) + var/incremented_ty = floor(incremented_py / 32) + + return locate(x + incremented_tx, y + incremented_ty, z) + +/** + * predict what turfs we'll hit, excluding the current turf, after going forwards + * a certain amount of pixels + * + * doesn't actually sim; so this will go through walls/obstacles! + */ +/obj/projectile/proc/physics_predicted_turfs_during_iteration(pixels) + return pixel_physics_raycast(loc, current_px, current_py, angle, pixels) diff --git a/code/modules/projectiles/projectile/projectile-tracing.dm b/code/modules/projectiles/projectile/projectile-tracing.dm new file mode 100644 index 00000000000..7c4b4d10014 --- /dev/null +++ b/code/modules/projectiles/projectile/projectile-tracing.dm @@ -0,0 +1,68 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station developers. *// + +/obj/projectile/trace + atom_flags = ATOM_ABSTRACT | ATOM_NONWORLD + invisibility = INVISIBILITY_ABSTRACT + hitscan = TRUE + has_tracer = FALSE + damage = 0 + nodamage = TRUE + projectile_type = PROJECTILE_TYPE_TRACE + + /// did we manage to hit the given target? + var/could_hit_target = FALSE + /// delete on hitting target? + var/del_on_success = TRUE + /// do we check opacity? + var/check_opacity = FALSE + /// do we only care about opacity, and not pass flags or anything else? + var/only_opacity = FALSE + /// do we only need to reach their turf? + var/require_turf_only = FALSE + /// target turf, if we only require reaching their turf + var/turf/require_turf_cached + +/obj/projectile/trace/CanPassThrough(atom/blocker, turf/target, blocker_opinion) + if(only_opacity && !blocker.opacity) + return TRUE + return ..() + +/obj/projectile/trace/pre_impact(atom/target, impact_flags, def_zone) + if(target == original_target) + could_hit_target = TRUE + if(del_on_success) + return PROJECTILE_IMPACT_DELETE + if(get_turf(target) == require_turf_cached) + could_hit_target = TRUE + if(del_on_success) + return PROJECTILE_IMPACT_DELETE + . = ..() + // tracers only count as 'can move across' if pre_impact() says we should phase/pierce. + if(. & PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH) + return PROJECTILE_IMPACT_PASSTHROUGH + else + return PROJECTILE_IMPACT_DELETE + +/obj/projectile/trace/Moved() + . = ..() + if(QDELETED(src)) + return + if(require_turf_cached == loc) + could_hit_target = TRUE + if(del_on_success) + qdel(src) + return + if(check_opacity && isturf(loc)) + // *sigh* // + var/turf/T = loc + if(T.has_opaque_atom) + qdel(src) + +/** + * always call this before firing. + */ +/obj/projectile/trace/proc/prepare_trace(atom/target) + src.original_target = target + if(require_turf_only) + src.require_turf_cached = get_turf(target) diff --git a/code/modules/projectiles/projectile/projectile.dm b/code/modules/projectiles/projectile/projectile.dm new file mode 100644 index 00000000000..dd6fc8e9bab --- /dev/null +++ b/code/modules/projectiles/projectile/projectile.dm @@ -0,0 +1,1180 @@ +/** + * ## Physics Specifications + * + * We track physics as absolute pixel on a tile, not byond's pixel x/y + * thus the first pixel at bottom left of tile is 1, 1 + * and the last pixel at top right is 32, 32 (for a world icon size of 32 pixels) + * + * We cross over to the next tile at above 32, for up/right, + * and to the last tile at below 1, for bottom/left. + * + * The code might handle it based on how it's implemented, + * but as long as the error is 1 pixel or below, it's not a big deal. + * + * The reason we're so accurate (1 pixel/below is pretty insanely strict) is + * so players have the projectile act like what the screen says it should be like; + * hence why projectiles can realistically path across corners based on their 'hitbox center'. + */ +/obj/projectile + name = "projectile" + icon = 'icons/obj/projectiles.dmi' + icon_state = "bullet" + density = FALSE + anchored = TRUE + integrity_flags = INTEGRITY_INDESTRUCTIBLE | INTEGRITY_ACIDPROOF | INTEGRITY_FIREPROOF | INTEGRITY_LAVAPROOF + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + depth_level = INFINITY // nothing should be passing over us from depth + + //* Collision Handling *// + + /** PROJECTILE PIERCING + * WARNING: + * Projectile piercing MUST be done using these variables. + * Ordinary passflags will result in can_hit_target being false unless directly clicked on - similar to pass_flags_phase but without even going to process_hit. + * The two flag variables below both use pass flags. + * In the context of ATOM_PASS_THROWN, it means the projectile will ignore things that are currently "in the air" from a throw. + * + * Also, projectiles sense hits using Bump(), and then pierce them if necessary. + * They simply do not follow conventional movement rules. + * NEVER flag a projectile as PHASING movement type. + * If you so badly need to make one go through *everything*, override check_pierce() for your projectile to always return PROJECTILE_PIERCE_PHASE/HIT. + */ + /// The "usual" flags of pass_flags is used in that can_hit_target ignores these unless they're specifically targeted/clicked on. This behavior entirely bypasses process_hit if triggered, rather than phasing which uses prehit_pierce() to check. + pass_flags = ATOM_PASS_TABLE + /// we handle our own go through + generic_canpass = FALSE + /// If FALSE, allow us to hit something directly targeted/clicked/whatnot even if we're able to phase through it + var/phases_through_direct_target = FALSE + /// anything with these pass flags are automatically pierced + var/pass_flags_pierce = NONE + /// anything with these pass flags are automatically phased through + var/pass_flags_phase = NONE + /// number of times we've pierced something. Incremented BEFORE bullet_act and similar procs run! + var/pierces = 0 + /// What we already hit + /// initialized on fire() + var/list/impacted + + //* -- Combat - Accuracy -- *// + //* These are applied in additional to mob + //* evasion and miss handling! Projectiles should *// + //* generally be pretty accurate for that reason. *// + //* *// + //* All accuracy ranges use **manhattan** *// + //* distance, not euclidean! *// + + /// if enabled, projectile-side baymiss is entirely disabled + /// + /// * the target can still forcefully miss us, unfortunately, if it doesn't use the standard API + /// * this might be violently fixed in the future + var/accuracy_disabled = FALSE + /// perfect accuracy below this range (in pixels) + /// + /// * this means the projectile doesn't enforce inaccuracy; not the target! + /// * remember that even if a projectile clips a single pixel on a target turf, it hits. + /// * right now, accuracy is slightly more than it should be due to distance being ticked up post-impact. + var/accuracy_perfect_range = WORLD_ICON_SIZE * 7 + /// linear - accuracy outside of perfect range + /// + /// * [0, 100] inclusive as % + var/accuracy_drop_start = 100 + /// linear - hit % falloff per pixel + var/accuracy_drop_slope = 5 / WORLD_ICON_SIZE + /// linear - lowest possible accuracy to drop to + var/accuracy_drop_end = 20 + /// alter end result hit probability by this value + /// + /// todo: is this really the best way? + /// + /// * this is a multiplier for hit chance if less than 1 + /// * this is a divisor for miss chance if more than 1 + /// * 0.5 will turn a 80% hit to a 40% + /// * 2 will turn a 80% hit to a 90% + /// * 2 will turn a 40% hit to a 70% + var/accuracy_overall_modify = 1 + + //* Combat - Effects *// + + /// projectile effects + /// + /// * this is a static typelist on this typepath + /// * do not under any circumstances edit this + /// * this is /tmp because this should never change on a typepath + VAR_PROTECTED/tmp/list/base_projectile_effects + /// projectile effects + /// + /// * this is configured at runtime and can be edited + /// * this is non /tmp because this is infact serializable + VAR_PROTECTED/list/additional_projectile_effects + + //* Configuration *// + + /// Projectile type bitfield; set all that is relevant + var/projectile_type = NONE + /// Impact ground on expiry (range, or lifetime) + var/impact_ground_on_expiry = FALSE + + //* Physics - Configuration *// + + /// speed, in pixels per second + var/speed = 25 * WORLD_ICON_SIZE + /// are we a hitscan projectile? + var/hitscan = FALSE + /// angle, in degrees **clockwise of north** + var/angle + /// multiplier to distance travelled at the **current** [angle] to get it to chebyshev dist + var/angle_chebyshev_divisor + /// max distance in pixels + /// + /// * please set this to a multiple of [WORLD_ICON_SIZE] so we scale with tile size. + var/range = WORLD_ICON_SIZE * 50 + // todo: lifespan + + //* Physics - Homing *// + /// Are we homing in on something? + var/homing = FALSE + /// current homing target + var/atom/homing_target + /// angle per second + /// + /// * this is smoother the less time between SSprojectiles fires + var/homing_turn_speed = 100 + /// rand(min, max) for inaccuracy offsets + var/homing_inaccuracy_min = 0 + /// rand(min, max) for inaccuracy offsets + var/homing_inaccuracy_max = 0 + /// pixels; added to the real location of target so we're not exactly on-point + var/homing_offset_x = 0 + /// pixels; added to the real location of target so we're not exactly on-point + var/homing_offset_y = 0 + + //* Physics - Tracers *// + + /// tracer /datum/point's + var/list/tracer_vertices + /// first point is a muzzle effect + var/tracer_muzzle_flash + /// last point is an impact + var/tracer_impact_effect + /// default tracer duration + var/tracer_duration = 5 + + //* Physics - State *// + + /// paused? if so, we completely get passed over during processing + var/paused = FALSE + /// currently hitscanning + var/hitscanning = FALSE + /// a flag to prevent movement hooks from resetting our physics on a forced movement + var/trajectory_ignore_forcemove = FALSE + /// cached value: move this much x for this much distance + /// basically, dx / distance + var/calculated_dx + /// cached value: move this much y for this much distance + /// basically, dy / distance + var/calculated_dy + /// cached sign of dx; 1, -1, or 0 + var/calculated_sdx + /// cached sign of dy; 1, -1, or 0 + var/calculated_sdy + /// our current pixel location on turf + /// byond pixel_x rounds, and we don't want that + /// + /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf + var/current_px + /// our current pixel location on turf + /// byond pixel_y rounds, and we don't want that + /// + /// * at below 0 or at equals to WORLD_ICON_SIZE, we move to the next turf + var/current_py + /// the pixel location we're moving to, or the [current_px] after this iteration step + /// + /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step. + /// * only valid if [trajectory_moving_to] is set + var/next_px + /// the pixel location we're moving to, or the [current_px] after this iteration step + /// + /// * used so stuff like hitscan deflections work based on the actual raycasted collision step, and not the prior step. + /// * only valid if [trajectory_moving_to] is set + var/next_py + /// the pixel distance we'll have travelled after the current Move() + /// + /// * use this during impact processing or you'll be off by anywhere from 1 to 32 pixels. + /// * only valid if [trajectory_moving_to] is set + var/next_distance + /// used to track if we got kicked forwards after calling Move() + var/trajectory_kick_forwards = 0 + /// to avoid going too fast when kicked forwards by a mirror, if we overshoot the pixels we're + /// supposed to move this gets set to penalize the next move with a weird algorithm + /// that i won't bother explaining + var/trajectory_penalty_applied = 0 + /// currently travelled distance in pixels + var/distance_travelled + /// if we get forcemoved, this gets reset to 0 as a trip + /// this way, we know exactly how far we moved + var/distance_travelled_this_iteration + /// where the physics loop and/or some other thing moving us is trying to move to + /// used to determine where to draw hitscan tracers + // todo: this being here is kinda a symptom that things are handled weirdly but whatever + // optimally physics loop should handle tracking for stuff like animations, not require on hit processing to check turfs + var/turf/trajectory_moving_to + + //* Targeting *// + + /// Originally clicked target + var/atom/original_target + + //* legacy below *// + + //Fired processing vars + var/fired = FALSE //Have we been fired yet + + var/original_angle = 0 //Angle at firing + var/nondirectional_sprite = FALSE //Set TRUE to prevent projectiles from having their sprites rotated based on firing angle + var/spread = 0 //amount (in degrees) of projectile spread + animate_movement = 0 //Use SLIDE_STEPS in conjunction with legacy + var/ricochets = 0 + var/ricochets_max = 2 + var/ricochet_chance = 30 + + //Hitscan + /// do we have a tracer? if not we completely ignore hitscan logic + var/has_tracer = TRUE + var/tracer_type + var/muzzle_type + var/impact_type + + var/miss_sounds + var/ricochet_sounds + var/list/impact_sounds //for different categories, IMPACT_MEAT etc + + //Fancy hitscan lighting effects! + var/hitscan_light_intensity = 1.5 + var/hitscan_light_range = 0.75 + var/hitscan_light_color_override + var/muzzle_flash_intensity = 3 + var/muzzle_flash_range = 1.5 + var/muzzle_flash_color_override + var/impact_light_intensity = 3 + var/impact_light_range = 2 + var/impact_light_color_override + + //Targetting + var/yo = null + var/xo = null + var/turf/starting = null // the projectile's starting turf + var/p_x = 16 + var/p_y = 16 // the pixel location of the tile that the player clicked. Default is the center + + var/def_zone = BP_TORSO + var/mob/firer = null//Who shot it + var/silenced = 0 //Attack message + var/shot_from = "" // name of the object which shot us + + var/dispersion = 0.0 + + // Sub-munitions. Basically, multi-projectile shotgun, rather than pellets. + var/use_submunitions = FALSE + var/only_submunitions = FALSE // Will the projectile delete itself after firing the submunitions? + var/list/submunitions = list() // Assoc list of the paths of any submunitions, and how many they are. [projectilepath] = [projectilecount]. + var/submunition_spread_max = 30 // Divided by 10 to get the percentile dispersion. + var/submunition_spread_min = 5 // Above. + /// randomize spread? if so, evenly space between 0 and max on each side. + var/submunition_constant_spread = FALSE + var/force_max_submunition_spread = FALSE // Do we just force the maximum? + var/spread_submunition_damage = FALSE // Do we assign damage to our sub projectiles based on our main projectile damage? + + //? Damage - default handling + /// damage amount + var/damage = 10 + /// damage tier - goes hand in hand with [damage_armor] + var/damage_tier = BULLET_TIER_DEFAULT + /// todo: legacy - BRUTE, BURN, TOX, OXY, CLONE, HALLOSS, ELECTROCUTE, BIOACID are the only things that should be in here + var/damage_type = BRUTE + /// armor flag for damage - goes hand in hand with [damage_tier] + var/damage_flag = ARMOR_BULLET + /// damage mode - see [code/__DEFINES/combat/damage.dm] + var/damage_mode = NONE + + var/SA_bonus_damage = 0 // Some bullets inflict extra damage on simple animals. + var/SA_vulnerability = null // What kind of simple animal the above bonus damage should be applied to. Set to null to apply to all SAs. + var/nodamage = 0 //Determines if the projectile will skip any damage inflictions + var/taser_effect = 0 //If set then the projectile will apply it's agony damage using stun_effect_act() to mobs it hits, and other damage will be ignored + var/legacy_penetrating = 0 //If greater than zero, the projectile will pass through dense objects as specified by on_penetrate() + //Effects + var/incendiary = 0 //1 for ignite on hit, 2 for trail of fire. 3 maybe later for burst of fire around the impact point. - Mech + var/flammability = 0 //Amount of fire stacks to add for the above. + var/combustion = TRUE //Does this set off flammable objects on fire/hit? + var/stun = 0 + var/weaken = 0 + var/paralyze = 0 + var/irradiate = 0 + var/stutter = 0 + var/eyeblur = 0 + var/drowsy = 0 + var/agony = 0 + var/reflected = 0 // This should be set to 1 if reflected by any means, to prevent infinite reflections. + var/modifier_type_to_apply = null // If set, will apply a modifier to mobs that are hit by this projectile. + var/modifier_duration = null // How long the above modifier should last for. Leave null to be permanent. + var/excavation_amount = 0 // How much, if anything, it drills from a mineral turf. + /// If this projectile is holy. Silver bullets, etc. Currently no effects. + var/holy = FALSE + + // Antimagic + /// Should we check for antimagic effects? + var/antimagic_check = FALSE + /// Antimagic charges to use, if any + var/antimagic_charges_used = 0 + /// Multiplier for damage if antimagic is on the target + var/antimagic_damage_factor = 0 + + var/embed_chance = 0 //Base chance for a projectile to embed + + var/fire_sound = 'sound/weapons/Gunshot_old.ogg' // Can be overriden in gun.dm's fire_sound var. It can also be null but I don't know why you'd ever want to do that. -Ace + + // todo: currently unimplemneted + var/vacuum_traversal = TRUE //Determines if the projectile can exist in vacuum, if false, the projectile will be deleted if it enters vacuum. + + var/no_attack_log = FALSE + +/obj/projectile/Initialize(mapload) + if(islist(base_projectile_effects)) + base_projectile_effects = typelist(NAMEOF(src, base_projectile_effects), base_projectile_effects) + return ..() + +/obj/projectile/Destroy() + // stop processing + STOP_PROCESSING(SSprojectiles, src) + // cleanup + cleanup_hitscan_tracers() + return ..() + +/obj/projectile/proc/process_legacy_penetration(atom/A) + return TRUE + +/obj/projectile/proc/legacy_on_range() //if we want there to be effects when they reach the end of their range + finalize_hitscan_tracers(impact_effect = FALSE, kick_forwards = 8) + + for(var/datum/projectile_effect/effect as anything in base_projectile_effects) + if(effect.hook_lifetime) + effect.on_lifetime(src, impact_ground_on_expiry) + for(var/datum/projectile_effect/effect as anything in additional_projectile_effects) + if(effect.hook_lifetime) + effect.on_lifetime(src, impact_ground_on_expiry) + + if(impact_ground_on_expiry) + impact(loc, PROJECTILE_IMPACT_IS_EXPIRING) + + expire() + +/obj/projectile/proc/fire(set_angle_to, atom/direct_target, no_source_check) + if(only_submunitions) // refactor projectiles whwen holy shit this is awful lmao + // todo: this should make a muzzle flash + qdel(src) + return + + // setup impact checking + impacted = list() + // make sure firer is in it + if(firer && !no_source_check) + impacted[firer] = TRUE + if(ismob(firer)) + var/atom/buckle_iterating = firer.buckled + while(buckle_iterating) + if(impacted[buckle_iterating]) + CRASH("how did we loop in buckle iteration check?") + impacted[buckle_iterating] = TRUE + if(ismob(buckle_iterating)) + var/mob/cast_for_next = buckle_iterating + buckle_iterating = cast_for_next.buckled + else + break + // set angle if needed + if(isnum(set_angle_to)) + set_angle(set_angle_to) + // handle direct hit + if(direct_target) + if(direct_target.bullet_act(src, PROJECTILE_IMPACT_POINT_BLANK, def_zone) & PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH) + impacted[direct_target] = TRUE + else + // todo: this should make a muzzle flash + qdel(src) + return + // setup physics + setup_physics() + + // legacy below + var/turf/starting = get_turf(src) + if(isnull(angle)) //Try to resolve through offsets if there's no angle set. + if(isnull(xo) || isnull(yo)) + stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!") + qdel(src) + return + var/turf/target = locate(clamp(starting + xo, 1, world.maxx), clamp(starting + yo, 1, world.maxy), starting.z) + set_angle(get_visual_angle(src, target)) + if(dispersion) + set_angle(angle + rand(-dispersion, dispersion)) + original_angle = angle + forceMove(starting) + fired = TRUE + // legacy aboev + + // start physics & kickstart movement + if(hitscan) + physics_hitscan() + else + START_PROCESSING(SSprojectiles, src) + physics_iteration(WORLD_ICON_SIZE, SSprojectiles.wait) + +//Spread is FORCED! +/obj/projectile/proc/preparePixelProjectile(atom/target, atom/source, params, spread = 0) + var/turf/curloc = get_turf(source) + var/turf/targloc = get_turf(target) + + if(istype(source, /atom/movable)) + var/atom/movable/MT = source + if(MT.locs && MT.locs.len) // Multi tile! + for(var/turf/T in MT.locs) + if(get_dist(T, target) < get_turf(curloc)) + curloc = get_turf(T) + + trajectory_ignore_forcemove = TRUE + forceMove(get_turf(source)) + trajectory_ignore_forcemove = FALSE + starting = curloc + original_target = target + if(targloc || !params) + yo = targloc.y - curloc.y + xo = targloc.x - curloc.x + set_angle(get_visual_angle(src, targloc) + spread) + + if(isliving(source) && params) + var/list/calculated = calculate_projectile_angle_and_pixel_offsets(source, params) + p_x = calculated[2] + p_y = calculated[3] + + set_angle(calculated[1] + spread) + else if(targloc) + yo = targloc.y - curloc.y + xo = targloc.x - curloc.x + set_angle(get_visual_angle(src, targloc) + spread) + else + stack_trace("WARNING: Projectile [type] fired without either mouse parameters, or a target atom to aim at!") + qdel(src) + +/proc/calculate_projectile_angle_and_pixel_offsets(mob/user, params) + var/list/mouse_control = params2list(params) + var/p_x = 0 + var/p_y = 0 + var/angle = 0 + if(mouse_control["icon-x"]) + p_x = text2num(mouse_control["icon-x"]) + if(mouse_control["icon-y"]) + p_y = text2num(mouse_control["icon-y"]) + if(mouse_control["screen-loc"]) + //Split screen-loc up into X+Pixel_X and Y+Pixel_Y + var/list/screen_loc_params = splittext(mouse_control["screen-loc"], ",") + + //Split X+Pixel_X up into list(X, Pixel_X) + var/list/screen_loc_X = splittext(screen_loc_params[1],":") + + //Split Y+Pixel_Y up into list(Y, Pixel_Y) + var/list/screen_loc_Y = splittext(screen_loc_params[2],":") + var/x = text2num(screen_loc_X[1]) * 32 + text2num(screen_loc_X[2]) - 32 + var/y = text2num(screen_loc_Y[1]) * 32 + text2num(screen_loc_Y[2]) - 32 + + //Calculate the "resolution" of screen based on client's view and world's icon size. This will work if the user can view more tiles than average. + var/screenviewX = user.client.current_viewport_width * world.icon_size + var/screenviewY = user.client.current_viewport_height * world.icon_size + + var/ox = round(screenviewX/2) - user.client.pixel_x //"origin" x + var/oy = round(screenviewY/2) - user.client.pixel_y //"origin" y + angle = arctan(y - oy, x - ox) + return list(angle, p_x, p_y) + +/obj/projectile/proc/legacy_redirect(x, y, starting, source) + reflected = TRUE + old_style_target(locate(x, y, z), starting? get_turf(starting) : get_turf(source)) + +/obj/projectile/proc/old_style_target(atom/target, atom/source) + if(!source) + source = get_turf(src) + starting = get_turf(source) + original_target = target + set_angle(get_visual_angle(source, target)) + +/obj/projectile/proc/vol_by_damage() + if(damage) + return clamp((damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then clamp the value between 30 and 100 + else + return 50 //if the projectile doesn't do damage, play its hitsound at 50% volume. + +//Checks if the projectile is eligible for embedding. Not that it necessarily will. +/obj/projectile/proc/can_embed() + //embed must be enabled and damage type must be brute + if(embed_chance == 0 || damage_type != BRUTE) + return 0 + return 1 + +/obj/projectile/proc/get_structure_damage() + if(damage_type == BRUTE || damage_type == BURN) + return damage + return 0 + +/obj/projectile/proc/check_fire(atom/target as mob, mob/living/user as mob) //Checks if you can hit them or not. + check_trajectory(target, user, pass_flags, atom_flags) + +/** + * i hate everything + * + * todo: refactor guns + * projectiles + * and everything else + * + * i am losing my fucking mind + * this shouldn't have to fucking exist because the ammo casing and/or gun should be doing it + * and submunitions SHOULDNT BE HANDLED HERE!! + */ +/obj/projectile/proc/launch_projectile_common(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) + original_target = target + def_zone = check_zone(target_zone) + firer = user + + if(use_submunitions && submunitions.len) + var/temp_min_spread = 0 + if(force_max_submunition_spread) + temp_min_spread = submunition_spread_max + else + temp_min_spread = submunition_spread_min + + var/damage_override = null + + if(spread_submunition_damage) + damage_override = damage + if(nodamage) + damage_override = 0 + + var/projectile_count = 0 + + for(var/proj in submunitions) + projectile_count += submunitions[proj] + + damage_override = round(damage_override / max(1, projectile_count)) + + for(var/path in submunitions) + var/amt = submunitions[path] + for(var/count in 1 to amt) + var/obj/projectile/SM = new path(get_turf(loc)) + SM.shot_from = shot_from + SM.silenced = silenced + if(!isnull(damage_override)) + SM.damage = damage_override + if(submunition_constant_spread) + SM.dispersion = 0 + var/calculated = angle + round((count / amt - 0.5) * submunition_spread_max, 1) + SM.launch_projectile(target, target_zone, user, params, calculated) + else + SM.dispersion = rand(temp_min_spread, submunition_spread_max) / 10 + SM.launch_projectile(target, target_zone, user, params, angle_override) + +/obj/projectile/proc/launch_projectile(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) + var/direct_target + if(get_turf(target) == get_turf(src)) + direct_target = target + + preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread) + launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread) + return fire(angle_override, direct_target) + +//called to launch a projectile from a gun +/obj/projectile/proc/launch_from_gun(atom/target, target_zone, mob/user, params, angle_override, forced_spread, obj/item/gun/launcher) + + shot_from = launcher.name + silenced = launcher.silenced + + return launch_projectile(target, target_zone, user, params, angle_override, forced_spread) + +/obj/projectile/proc/launch_projectile_from_turf(atom/target, target_zone, mob/user, params, angle_override, forced_spread = 0) + var/direct_target + if(get_turf(target) == get_turf(src)) + direct_target = target + + preparePixelProjectile(target, user? user : get_turf(src), params, forced_spread) + launch_projectile_common(target, target_zone, user, params, angle_override, forced_spread) + return fire(angle_override, direct_target) + +/** + * Standard proc to determine damage when impacting something. This does not affect the special damage variables/effect variables, only damage and damtype. + * May or may not be called before/after armor calculations. + * + * @params + * - target The atom hit + * + * @return Damage to apply to target. + */ +/obj/projectile/proc/run_damage_vulnerability(atom/target) + var/final_damage = damage + if(isliving(target)) + var/mob/living/L = target + if(issimple(target)) + var/mob/living/simple_mob/SM = L + if(SM.mob_class & SA_vulnerability) + final_damage += SA_bonus_damage + if(L.anti_magic_check(TRUE, TRUE, antimagic_charges_used, FALSE)) + final_damage *= antimagic_damage_factor + return final_damage + +/** + * Probably isn't needed but saves me the time and I can regex this later: + * Gets the final `damage` that should be used on something + */ +/obj/projectile/proc/get_final_damage(atom/target) + return run_damage_vulnerability(target) + +// !legacy code above! + +//* Collision Handling *// + +/obj/projectile/CanAllowThrough() + SHOULD_CALL_PARENT(FALSE) + return TRUE + +/obj/projectile/CanPassThrough(atom/blocker, turf/target, blocker_opinion) + // performance + SHOULD_CALL_PARENT(FALSE) + // always can go through already impacted things + if(impacted[blocker]) + return TRUE + return blocker_opinion + +/obj/projectile/Crossed(atom/movable/AM) + ..() + scan_crossed_atom(AM) + +// todo: should we inline this? +/obj/projectile/proc/scan_crossed_atom(atom/movable/target) + if(!should_impact(target)) + return + impact(target) + +/obj/projectile/Bump(atom/A) + . = ..() + impact_loop(get_turf(A), A) + +/obj/projectile/forceMove(atom/target) + var/is_a_jump = isturf(target) != isturf(loc) || target.z != z || !trajectory_ignore_forcemove + if(is_a_jump) + record_hitscan_end() + render_hitscan_tracers() + . = ..() + if(!.) + stack_trace("projectile forcemove failed; please do not try to forcemove projectiles to invalid locations!") + distance_travelled_this_iteration = 0 + if(!trajectory_ignore_forcemove) + reset_physics_to_turf() + if(is_a_jump) + record_hitscan_start() + +/obj/projectile/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + // if we're being yanked, we'll get Moved() again + if(movable_flags & MOVABLE_IN_MOVED_YANK) + return + // not even fired yet + if(!fired) + return + // scan the turf for anything we need to hit + scan_moved_turf(loc) + // trigger effects + for(var/datum/projectile_effect/effect as anything in base_projectile_effects) + if(effect.hook_moved) + effect.on_moved(src, old_loc) + for(var/datum/projectile_effect/effect as anything in additional_projectile_effects) + if(effect.hook_moved) + effect.on_moved(src, old_loc) + +// todo: should we inline this? +/obj/projectile/proc/scan_moved_turf(turf/tile) + if(original_target?.loc != tile) + return + if(!should_impact(original_target)) + return + impact(original_target) + +/** + * checks if we're valid to hit a target + * + * this is called 'should' because it's not called in impact() + * this checks if we should try to impact when processing collisions + * this doesn't actually prevent us from having an impact call + */ +/obj/projectile/proc/should_impact(atom/target, is_colliding_us) + // 1. emulate the usual physics / cross + // remember that impact_loop() scans all atoms, not just the hit one. + if(impacted[target]) + return FALSE + if(QDELETED(target)) + return FALSE + // 1.5: legacy bullshit + if(target.is_incorporeal()) + return FALSE + // 2. are they the thing blocking us? + if(is_colliding_us) + return TRUE + // 3. process projectile things + if(target == original_target) + return TRUE + else if(!target.density || (target.pass_flags_self & pass_flags)) + return FALSE + else if(target.layer < PROJECTILE_HIT_THRESHOLD_LAYER) + return FALSE + return TRUE + +/** + * this strangely named proc is basically the hit processing loop + * + * "now why the hells would you do this" + * + * well, you see, turf movement handling doesn't support what we need to do, + * and for good reason. + * + * most of the time, turf movement handling is more than enough for any game use case. + * it is not nearly accurate/comprehensive enough for projectiles + * and we're not going to make it that, because that's a ton of overhead for everything else + * + * so instead, projectiles handle it themselves on a Bump(). + * + * when this happens, the projectile should hit everything that's going to collide it anyways + * in the turf, not just one thing; this way, hits are instant for a given collision. + * + * @params + * * was_moving_onto - the turf we were moving to + * * bumped - what bumped us? + */ +/obj/projectile/proc/impact_loop(turf/was_moving_onto, atom/bumped) + var/impact_return + // so unfortunately, only one border object is considered here + // why? + // because you see, for speed reasons we're not going to iterate once just to gather border. + // so we assume that 'bumped' is border, or it just doesn't happen. + + // make sure we're not inf looping + ASSERT(!(impacted[bumped])) + // see if we should impact + impact_return = impact(bumped) + if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP)) + return + + // at this point we're technically safe to just move again because + // we processed bumped + // problem is, that's O(n^2) behavior + // we don't want to process just one bumped atom + // as turf/Enter() will loop through everything again + // + // we want to process all of them. + + // at this point you might ask + // why not use MOVEMENT_UNSTOPPABLE? + // if we did, we wouldn't have the border-prioritization we have + // that'd be bad as you'd be hit by a bullet behind a directional window + + // wow, projectiles are annoying to deal with + + // begin: main loop + + // first, targeted atom + if(original_target?.loc == was_moving_onto) + if(should_impact(original_target)) + impact_return = impact(bumped) + if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP)) + return + + // then, mobs + for(var/mob/mob_target in was_moving_onto) + if(!should_impact(mob_target)) + continue + impact_return = impact(mob_target) + if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP)) + return + + // then, objs + for(var/obj/obj_target in was_moving_onto) + if(!should_impact(obj_target)) + continue + impact_return = impact(obj_target) + if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP)) + return + + // then, the turf + if(should_impact(was_moving_onto)) + impact_return = impact(was_moving_onto) + if(!(impact_return & PROJECTILE_IMPACT_CONTINUE_LOOP)) + return + + // if we passed everything and we're still going, + // we can safely move onto their turf again, and this time we should succeed. + if(trajectory_moving_to) + Move(trajectory_moving_to) + +//* Lifetime & Deletion *// + +// todo: on_lifetime() --> expire() + +/** + * Called to delete if: + * + * * ran out of range + * * hit something and shouldn't pass through + * + * @params + * * impacting - we're deleting from impact, rather than range + */ +/obj/projectile/proc/expire(impacting) + qdel(src) + +//* Impact Processing *// + +/** + * Called to perform an impact + * + * @params + * * target - thing being hit + * * impact_flags - impact flags to feed in + * * def_zone - zone to hit; otherwise this'll be calculated. + * + * @return resultant impact_flags + */ +/obj/projectile/proc/impact(atom/target, impact_flags, def_zone = src.def_zone || BP_TORSO) + SHOULD_NOT_OVERRIDE(TRUE) + + if(impacted[target]) + return impact_flags | PROJECTILE_IMPACT_PASSTHROUGH | PROJECTILE_IMPACT_DUPLICATE | PROJECTILE_IMPACT_CONTINUE_LOOP + impacted[target] = TRUE + var/where_we_were = loc + impact_flags = pre_impact(target, impact_flags, def_zone) + var/keep_going + + // priority 1: delete? + if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE) + if(hitscanning) + finalize_hitscan_tracers() + qdel(src) + return impact_flags + // priority 2: should we hit? + if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT) + keep_going = TRUE + // phasing? + if(impact_flags & PROJECTILE_IMPACT_PHASE) + impact_flags = on_phase(target, impact_flags, def_zone) + // reflect? + else if(impact_flags & PROJECTILE_IMPACT_REFLECT) + impact_flags = on_reflect(target, impact_flags, def_zone) + // else, is passthrough. do nothing + else + impact_flags = target.bullet_act(src, impact_flags, def_zone, 1) + // did we pierce? + if(impact_flags & PROJECTILE_IMPACT_PIERCE) + keep_going = TRUE + impact_flags = on_pierce(target, impact_flags, def_zone) + // are we otherwise meant to keep going? + else if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_GO_THROUGH) + keep_going = TRUE + + // did anything triggered up above trigger a delete? + if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE) + if(hitscanning) + finalize_hitscan_tracers() + qdel(src) + return impact_flags + + // trigger projectile effects + if(base_projectile_effects || additional_projectile_effects) + // todo: can we move this to on_impact_new and have 'blocked' passed in? hrm. + for(var/datum/projectile_effect/effect as anything in base_projectile_effects) + if(effect.hook_impact) + impact_flags = effect.on_impact(src, target, impact_flags, def_zone) + for(var/datum/projectile_effect/effect as anything in additional_projectile_effects) + if(effect.hook_impact) + impact_flags = effect.on_impact(src, target, impact_flags, def_zone) + // did anything triggered up above trigger a delete? + if(impact_flags & PROJECTILE_IMPACT_FLAGS_SHOULD_DELETE) + if(hitscanning) + finalize_hitscan_tracers() + qdel(src) + return impact_flags + + // see if we should keep going or delete + if(keep_going) + if(loc == where_we_were) + // if we are supposed to keep going and we didn't get yanked, continue the impact loop. + impact_flags |= PROJECTILE_IMPACT_CONTINUE_LOOP + else + // or if we aren't supposed to keep going, delete. + if(hitscanning) + if(trajectory_moving_to) + // create tracers + var/datum/point/visual_impact_point = get_intersection_point(trajectory_moving_to) + // it's possible to not have an intersection point, if say, angle was being messed with mid-move + // this entire system is suboptimal design-wise but atleast it's fast. + if(visual_impact_point) + // kick it forwards a bit + visual_impact_point.shift_in_projectile_angle(angle, 2) + // draw + finalize_hitscan_tracers(visual_impact_point, impact_effect = TRUE) + else + finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32) + else + finalize_hitscan_tracers(impact_effect = TRUE, kick_forwards = 32) + expire(TRUE) + + return impact_flags + +/** + * Called at the start of impact. + * + * * Hooks to return flags / whatnot should happen here + * * You are allowed to edit the projectile here, but it is absolutely not recommended. + * + * @return new impact_flags + */ +/obj/projectile/proc/pre_impact(atom/target, impact_flags, def_zone) + if(target.pass_flags_self & pass_flags_phase) + return impact_flags | PROJECTILE_IMPACT_PHASE + if(target.pass_flags_self & pass_flags_pierce) + return impact_flags | PROJECTILE_IMPACT_PIERCE + return impact_flags + +/** + * Called after bullet_act() of the target. + * + * * Please take into account impact_flags. + * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact(). + * * please see [/atom/proc/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args)] + * * Args at this point are no longer mutable after the ..() call. + * + * Things to keep in mind, if you ignore the above and didn't read bullet_act(): + * * Args are changed directly and passed up, but not passed back down. This means setting efficiency at base of /on_impact doesn't change a subtype's call + * * This also means you need to default efficiency to 1 if you have things acting on it, as it won't be propagated for you. + * * 'efficiency' is extremely powerful + * * impact_flags having PROJECTILE_IMPACT_DELETE is a good sign to delete and do nothing else. + * + * todo: add PROJECTILE_IMPACT_DELETE_AFTER as opposed to DELETE? so rest of effects can still run + * + * @return new impact_flags; only PROJECTILE_IMPACT_DELETE is rechecked. + */ +/obj/projectile/proc/on_impact(atom/target, impact_flags, def_zone, efficiency = 1) + //! legacy shit + var/blocked = clamp((1 - efficiency) * 100, 0, 100) + if(damage && damage_type == BURN) + var/turf/T = get_turf(target) + if(T) + T.hotspot_expose(700, 5) + if(isliving(target)) + var/mob/living/L = target + L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) + if(modifier_type_to_apply) + L.add_modifier(modifier_type_to_apply, modifier_duration) + //! end + return impact_flags + +/** + * called in bullet_act() to redirect our impact to another atom + * + * use like this: + * + * `return proj.impact_redirect(target, args)` + * + * * You **must** use this, not just `return target.bullet_act(arglist(args))` + * * This does book-keeping like adding the target to permutated, ensure the target can't be hit multiple times in a row, and more. + * * Don't be too funny about bullet_act_args, it's a directly passed in args list. Don't be stupid. + */ +/obj/projectile/proc/impact_redirect(atom/target, list/bullet_act_args) + if(impacted[target]) + return bullet_act_args[BULLET_ACT_ARG_FLAGS] | PROJECTILE_IMPACT_DUPLICATE + bullet_act_args[BULLET_ACT_ARG_FLAGS] |= PROJECTILE_IMPACT_INDIRECTED + return target.bullet_act(arglist(bullet_act_args)) + +/** + * phasing through + * + * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact(). + * + * @return new impact flags; only PROJETILE_IMPACT_DELETE is rechecked. + */ +/obj/projectile/proc/on_phase(atom/target, impact_flags, def_zone) + return impact_flags + +/** + * reflected off of + * + * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact(). + * + * @return new impact flags; only PROJETILE_IMPACT_DELETE is rechecked. + */ +/obj/projectile/proc/on_reflect(atom/target, impact_flags, def_zone) + return impact_flags + +/** + * piercing through + * + * * Most impact flags returned are not re-checked for performance; pierce/phase calculations should be done in pre_impact(). + * + * @return new impact flags; only PROJETILE_IMPACT_DELETE is rechecked. + */ +/obj/projectile/proc/on_pierce(atom/target, impact_flags, def_zone) + return impact_flags + +//* Impact Processing - Combat *// + +/** + * processes default hit probability for baymiss + * + * * This is called by things like /mob/living as needed; there is no default baymiss handling on /projectile anymore. + * * More than 100 target_opinion means that much % more than 100 of *not missing*. + * * e.g. 200 target_opinion makes a 75% inherent hit chance (25% miss chance) to 87.5% hit cahnce (12.5% miss chance) + * + * todo: 0 to 100 for accuracy might not be amazing; maybe allow negative values evasion-style? + * + * @params + * * target - what we're hitting + * * target_opinion - the return from processing hit chance on their side + * * distance - distance in pixels + * * impact_check - are we checking for impact? this way things like pellets can do their own rolls after 100% hitting + * + * @return hit probability as % in [0, 100]; > 100 is allowed. + */ +/obj/projectile/proc/process_accuracy(atom/target, target_opinion = 100, distance, impact_check) + if(isnull(distance)) + distance = (trajectory_moving_to ? next_distance : distance_travelled) * angle_chebyshev_divisor + if(accuracy_disabled) + return 100 + . = 100 + // perform accuracy curving + if(distance > accuracy_perfect_range) + . = accuracy_drop_start + var/extra_distance = distance - accuracy_perfect_range + var/drop_percent = extra_distance * accuracy_drop_slope + . = clamp(. - drop_percent, ., accuracy_drop_end) + if(accuracy_overall_modify != 1) + if(accuracy_overall_modify < 1) + // below 1: multiplier for hit chance + . *= accuracy_overall_modify + else + // above 1: divisor for miss chance + . = 100 - ((100 - .) / accuracy_overall_modify) + if(target_opinion < 100) + . *= (target_opinion / 100) + else if(target_opinion > 100) + . = 100 - ((100 - .) / (target_opinion / 100)) + +/** + * processes zone accuracy + * + * * this is here to override 'special' baymiss, like 'don't even hit this zone' systems. + * + * @params + * * target - what we're hitting + * * target_opinion - the return from processing hit zone on their side + * * distance - distance in pixels + * * impact_check - are we checking for impact? this way things like pellets can do their own processing + */ +/obj/projectile/proc/process_zone_miss(atom/target, target_opinion, distance, impact_check) + return target_opinion + +/** + * Applies the standard damage instance to an entity. + * + * @params + * * target - thing being hit + * * efficiency - 0 to 1+ - efficiency of hit, where 0% is full block + * * impact_flags - impact flags passed in + * * hit_zone - zone to hit + * + * @return BULLET_ACT_* flags to append into the calling bullet_act(). + */ +/obj/projectile/proc/inflict_impact_damage(atom/target, efficiency, impact_flags, hit_zone) + . = NONE + + //! LEGACY COMBAT CODE + // SHIM!!! + var/list/shieldcall_modified_args = target.check_damage_instance(damage, damage_type, damage_tier, damage_flag, damage_mode, ATTACK_TYPE_PROJECTILE, src, SHIELDCALL_FLAG_SECOND_CALL, hit_zone) + // todo: this handling very obviously should not be here + // dear lord this code is a dumpster fire + if(shieldcall_modified_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_PIERCE_ATTACK) + . |= PROJECTILE_IMPACT_REFLECT + if(shieldcall_modified_args[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAGS_BLOCK_ATTACK) + return + // END + if(isliving(target)) + var/mob/living/L = target + //Armor + var/soaked = L.get_armor_soak(hit_zone, src.damage_flag, src.armor_penetration) + var/absorb = L.run_armor_check(hit_zone, src.damage_flag, src.armor_penetration) + var/proj_sharp = is_sharp(src) + var/proj_edge = has_edge(src) + var/final_damage = src.get_final_damage(target) * efficiency + + if ((proj_sharp || proj_edge) && (soaked >= round(src.damage*0.8))) + proj_sharp = 0 + proj_edge = 0 + + if ((proj_sharp || proj_edge) && prob(L.legacy_mob_armor(hit_zone, src.damage_flag))) + proj_sharp = 0 + proj_edge = 0 + + var/list/impact_sounds = islist(src.impact_sounds)? LAZYACCESS(src.impact_sounds, L.get_bullet_impact_effect_type(hit_zone)) : src.impact_sounds + if(length(impact_sounds)) + playsound(L, pick(impact_sounds), 75) + else if(!isnull(impact_sounds)) + playsound(L, impact_sounds, 75) + + //Stun Beams + if(src.taser_effect) + L.stun_effect_act(0, src.agony, hit_zone, src) + to_chat(L, "You have been hit by [src]!") + if(!src.nodamage) + L.apply_damage(final_damage, src.damage_type, hit_zone, absorb, soaked, 0, src, sharp=proj_sharp, edge=proj_edge) + return + + if(!src.nodamage) + L.apply_damage(final_damage, src.damage_type, hit_zone, absorb, soaked, 0, src, sharp=proj_sharp, edge=proj_edge) + //! END + + for(var/datum/projectile_effect/effect as anything in base_projectile_effects) + if(effect.hook_damage) + effect.on_damage(src, target, impact_flags, hit_zone, efficiency) + for(var/datum/projectile_effect/effect as anything in additional_projectile_effects) + if(effect.hook_damage) + effect.on_damage(src, target, impact_flags, hit_zone, efficiency) + + if(legacy_penetrating > 0) + if(process_legacy_penetration(target)) + . |= PROJECTILE_IMPACT_PIERCE | PROJECTILE_IMPACT_PASSTHROUGH + +/** + * wip algorithm to dampen a projectile when it pierces + * + * * entity - thing hit + * * force - nominal force to resist the damping; generally, projectiles at this lose a moderate chunk of energy, while 2x loses minimal, 0.5x loses a lot. + * * tier - effective armor tier of object; modulates actual energy lost + */ +/obj/projectile/proc/dampen_on_pierce_experimental(atom/entity, force, tier) + var/tdiff = damage_tier - tier + var/dmult = src.damage / force + var/malus = dmult >= 1 ? ((1 / dmult) ** tdiff * 10) : (10 * ((1 / dmult) / (1 + tdiff))) + src.damage = clamp(src.damage - malus, src.damage * 0.5, src.damage) + +//* Targeting *// + +/** + * Checks if something is a valid target when directly clicked. + */ +/obj/projectile/proc/is_valid_target(atom/target) + if(isobj(target)) + var/obj/O = target + return O.obj_flags & OBJ_RANGE_TARGETABLE + else if(isliving(target)) + return TRUE + else if(isturf(target)) + return target.density + return FALSE diff --git a/code/modules/projectiles/projectile/projectile_effect.dm b/code/modules/projectiles/projectile/projectile_effect.dm new file mode 100644 index 00000000000..cadfae340b9 --- /dev/null +++ b/code/modules/projectiles/projectile/projectile_effect.dm @@ -0,0 +1,51 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * projectile effects + * + * * can be anonymous type'd, so global cache exists for given projectile typepath, not here! + */ +/datum/projectile_effect + /// has impact effect + var/hook_impact = FALSE + /// has moved effect + var/hook_moved = FALSE + /// has on-range / lifetime expired effect + var/hook_lifetime = FALSE + /// has damage effect + var/hook_damage = FALSE + +/** + * * you'll note that [efficiency] is not a thing here + * * this is because this runs regardless of target's opinion + * * this means you should probably be careful and check impact_flags! + * + * @return new impact flags + */ +/datum/projectile_effect/proc/on_impact(obj/projectile/proj, atom/target, impact_flags, def_zone) + return impact_flags + +/** + * * you'll note that [efficiency] **is** a thing here + * * this only runs if we're able to damage the target + * + * @return new impact flags + */ +/datum/projectile_effect/proc/on_damage(obj/projectile/proj, atom/target, impact_flags, def_zone, efficiency) + return impact_flags + +/** + * Do not delete the projectile, the projectile does that. + * + * todo: add way to delete projectile so it doesn't drop stuff i guess for foam and whatnot + * + * @params + * * proj - the projectile + * * impact_ground_on_expiry - we should impact the ground on expiry; a projectile var, but relay'd in for override capability later + */ +/datum/projectile_effect/proc/on_lifetime(obj/projectile/proj, impact_ground_on_expiry) + return + +/datum/projectile_effect/proc/on_moved(obj/projectile/proj, atom/old_loc) + return diff --git a/code/modules/projectiles/projectile/animate.dm b/code/modules/projectiles/projectile/subtypes/animate.dm similarity index 100% rename from code/modules/projectiles/projectile/animate.dm rename to code/modules/projectiles/projectile/subtypes/animate.dm diff --git a/code/modules/projectiles/projectile/subtypes/arc.dm b/code/modules/projectiles/projectile/subtypes/arc.dm new file mode 100644 index 00000000000..7245b300111 --- /dev/null +++ b/code/modules/projectiles/projectile/subtypes/arc.dm @@ -0,0 +1,82 @@ + +////////////// +// Subtypes +////////////// + +// This is a test projectile in the sense that its testing the code to make sure it works, +// as opposed to a 'can I hit this thing' projectile. +/obj/projectile/arc/test/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!isturf(target)) + return + var/turf/T = target + new /obj/effect/explosion(T) + T.color = "#FF0000" + +// Generic, Hivebot related +/obj/projectile/arc/blue_energy + name = "energy missile" + icon_state = "force_missile" + damage = 15 + damage_type = BURN + +/obj/projectile/arc/blue_energy/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!isturf(target)) + return + var/turf/T = target + for(var/mob/living/L in T) + impact(L) + +// Fragmentation arc shot +/obj/projectile/arc/fragmentation + name = "fragmentation shot" + icon_state = "shell" + var/list/fragment_types = list( + /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment, \ + /obj/projectile/bullet/pellet/fragment, /obj/projectile/bullet/pellet/fragment/strong + ) + var/fragment_amount = 63 // Same as a grenade. + var/spread_range = 7 + +/obj/projectile/arc/fragmentation/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!isturf(target)) + return + var/turf/T = target + T.shrapnel_explosion(fragment_amount, spread_range, fragment_types, src, name, firer) + +/obj/projectile/arc/fragmentation/mortar + icon_state = "mortar" + fragment_amount = 10 + spread_range = 3 + +// EMP arc shot +/obj/projectile/arc/emp_blast + name = "emp blast" + icon_state = "bluespace" + +/obj/projectile/arc/emp_blast/on_impact(atom/target, impact_flags, def_zone, efficiency) + var/turf/T = target + empulse(T, 2, 4, 7, 10) // Normal EMP grenade. + return impact_flags + +/obj/projectile/arc/emp_blast/weak/on_impact(atom/target, impact_flags, def_zone, efficiency) + var/turf/T = target + empulse(T, 1, 2, 3, 4) // Sec EMP grenade. + return impact_flags + +// Radiation arc shot +/obj/projectile/arc/radioactive + name = "radiation blast" + icon_state = "green_pellet" + icon_scale_x = 2 + icon_scale_y = 2 + var/rad_power = RAD_INTENSITY_PROJ_ARC + +/obj/projectile/arc/radioactive/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!isturf(target)) + return + var/turf/T = target + radiation_pulse(T, rad_power) diff --git a/code/modules/projectiles/projectile/beam/beams.dm b/code/modules/projectiles/projectile/subtypes/beam/beams.dm similarity index 89% rename from code/modules/projectiles/projectile/beam/beams.dm rename to code/modules/projectiles/projectile/subtypes/beam/beams.dm index b90a9342d3a..617097a40de 100644 --- a/code/modules/projectiles/projectile/beam/beams.dm +++ b/code/modules/projectiles/projectile/subtypes/beam/beams.dm @@ -6,6 +6,7 @@ damage = 40 damage_type = BURN damage_flag = ARMOR_LASER + projectile_type = PROJECTILE_TYPE_BEAM | PROJECTILE_TYPE_PHOTONIC eyeblur = 4 var/frequency = 1 hitscan = TRUE @@ -76,14 +77,14 @@ /obj/projectile/beam/heavylaser/cannon damage = 80 - armor_penetration = 50 + armor_penetration = 45 light_color = "#FF0D00" /obj/projectile/beam/xray name = "xray beam" icon_state = "xray" fire_sound = 'sound/weapons/eluger.ogg' - damage = 25 + damage = 30 armor_penetration = 50 light_color = "#00CC33" @@ -131,7 +132,8 @@ name = "emitter beam" icon_state = "emitter" fire_sound = 'sound/weapons/emitter.ogg' - damage = 0 // The actual damage is computed in /code/modules/power/singularity/emitter.dm + damage = 40 + armor_penetration = 70 light_color = "#00CC33" excavation_amount = 70 // 3 shots to mine a turf @@ -143,7 +145,6 @@ name = "lasertag beam" damage = 0 eyeblur = 0 - no_attack_log = 1 damage_type = BURN damage_flag = ARMOR_LASER @@ -157,23 +158,27 @@ tracer_type = /obj/effect/projectile/tracer/laser_blue impact_type = /obj/effect/projectile/impact/laser_blue -/obj/projectile/beam/lasertag/blue/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/beam/lasertag/blue/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ishuman(target)) var/mob/living/carbon/human/M = target if(istype(M.wear_suit, /obj/item/clothing/suit/redtag)) - M.afflict_paralyze(20 * 5) - return 1 + M.afflict_paralyze(1.5 SECONDS) /obj/projectile/beam/lasertag/red icon_state = "laser" light_color = "#FF0D00" -/obj/projectile/beam/lasertag/red/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/beam/lasertag/red/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ishuman(target)) var/mob/living/carbon/human/M = target if(istype(M.wear_suit, /obj/item/clothing/suit/bluetag)) - M.afflict_paralyze(20 * 5) - return 1 + M.afflict_paralyze(1.5 SECONDS) /obj/projectile/beam/lasertag/omni//A laser tag bolt that stuns EVERYONE icon_state = "omnilaser" @@ -183,12 +188,14 @@ tracer_type = /obj/effect/projectile/tracer/laser_omni impact_type = /obj/effect/projectile/impact/laser_omni -/obj/projectile/beam/lasertag/omni/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/beam/lasertag/omni/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ishuman(target)) var/mob/living/carbon/human/M = target if((istype(M.wear_suit, /obj/item/clothing/suit/bluetag))||(istype(M.wear_suit, /obj/item/clothing/suit/redtag))) - M.afflict_paralyze(20 * 5) - return 1 + M.afflict_paralyze(1.5 SECONDS) /obj/projectile/beam/sniper name = "sniper beam" @@ -267,18 +274,18 @@ tracer_type = /obj/effect/projectile/tracer/laser_omni impact_type = /obj/effect/projectile/impact/laser_omni -/obj/projectile/beam/stun/disabler/on_hit(atom/target, blocked = 0, def_zone) - . = ..(target, blocked, def_zone) - - if(. && istype(target, /mob/living/silicon/robot) && prob(agony)) +/obj/projectile/beam/stun/disabler/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT)) + return + if(istype(target, /mob/living/silicon/robot) && prob(agony)) var/mob/living/silicon/robot/R = target var/drainamt = agony * (rand(5, 15) / 10) // 100 to 300 drain R.drain_energy(DYNAMIC_CELL_UNITS_TO_KJ(drainamt * 10)) if(istype(firer, /mob/living/silicon/robot)) // Mischevious sappers, the swarm drones are. var/mob/living/silicon/robot/A = firer - if(A.cell) - A.cell.give(drainamt * 2) + A.cell?.give(drainamt * 2) /obj/projectile/beam/shock name = "shock beam" diff --git a/code/modules/projectiles/projectile/beam/beams_vr.dm b/code/modules/projectiles/projectile/subtypes/beam/beams_vr.dm similarity index 84% rename from code/modules/projectiles/projectile/beam/beams_vr.dm rename to code/modules/projectiles/projectile/subtypes/beam/beams_vr.dm index 7e2e9e04484..7e21f84b61c 100644 --- a/code/modules/projectiles/projectile/beam/beams_vr.dm +++ b/code/modules/projectiles/projectile/subtypes/beam/beams_vr.dm @@ -26,9 +26,13 @@ tracer_type = /obj/effect/projectile/tracer/xray impact_type = /obj/effect/projectile/impact/xray -/obj/projectile/beam/energy_net/on_hit(var/atom/netted) - do_net(netted) - ..() +/obj/projectile/beam/energy_net/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(!ismob(target)) + return + do_net(target) /obj/projectile/beam/energy_net/proc/do_net(var/mob/M) var/obj/item/energy_net/net = new (get_turf(M)) @@ -47,7 +51,6 @@ icon_state = "healbeam" damage = 0 //stops it damaging walls nodamage = TRUE - no_attack_log = TRUE damage_type = BURN damage_flag = ARMOR_LASER light_color = "#80F5FF" @@ -58,7 +61,11 @@ tracer_type = /obj/effect/projectile/tracer/medigun impact_type = /obj/effect/projectile/impact/medigun -/obj/projectile/beam/medigun/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/beam/medigun/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(istype(target, /mob/living/carbon/human)) var/mob/living/carbon/human/M = target if(M.health < M.maxHealth) @@ -74,4 +81,3 @@ M.adjustFireLoss(-15) M.adjustToxLoss(-5) M.adjustOxyLoss(-5) - return 1 diff --git a/code/modules/projectiles/projectile/beam/blaster.dm b/code/modules/projectiles/projectile/subtypes/beam/blaster.dm similarity index 100% rename from code/modules/projectiles/projectile/beam/blaster.dm rename to code/modules/projectiles/projectile/subtypes/beam/blaster.dm diff --git a/code/modules/projectiles/projectile/blob.dm b/code/modules/projectiles/projectile/subtypes/blob.dm similarity index 92% rename from code/modules/projectiles/projectile/blob.dm rename to code/modules/projectiles/projectile/subtypes/blob.dm index 6b643c160f2..cbb0fae0657 100644 --- a/code/modules/projectiles/projectile/blob.dm +++ b/code/modules/projectiles/projectile/subtypes/blob.dm @@ -25,7 +25,11 @@ reagents = null ..() -/obj/projectile/energy/blob/on_impact(var/atom/A) +/obj/projectile/energy/blob/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(splatter) var/turf/location = get_turf(src) var/datum/effect_system/smoke_spread/chem/S = new /datum/effect_system/smoke_spread/chem @@ -34,7 +38,6 @@ playsound(location, 'sound/effects/slime_squish.ogg', 30, 1, -3) spawn(0) S.start() - ..() /obj/projectile/energy/blob/proc/ready_chemicals() if(reagents) diff --git a/code/modules/projectiles/projectile/bullets.dm b/code/modules/projectiles/projectile/subtypes/bullets.dm similarity index 84% rename from code/modules/projectiles/projectile/bullets.dm rename to code/modules/projectiles/projectile/subtypes/bullets.dm index c6d6b9ef896..5f5b6706580 100644 --- a/code/modules/projectiles/projectile/bullets.dm +++ b/code/modules/projectiles/projectile/subtypes/bullets.dm @@ -8,7 +8,7 @@ damage_flag = ARMOR_BULLET embed_chance = 20 //Modified in the actual embed process, but this should keep embed chance about the same sharp = 1 - var/mob_passthrough_check = 0 + projectile_type = PROJECTILE_TYPE_KINETIC muzzle_type = /obj/effect/projectile/muzzle/bullet miss_sounds = list('sound/weapons/guns/miss1.ogg','sound/weapons/guns/miss2.ogg','sound/weapons/guns/miss3.ogg','sound/weapons/guns/miss4.ogg') @@ -16,37 +16,16 @@ 'sound/weapons/guns/ricochet3.ogg', 'sound/weapons/guns/ricochet4.ogg') impact_sounds = list(BULLET_IMPACT_MEAT = SOUNDS_BULLET_MEAT, BULLET_IMPACT_METAL = SOUNDS_BULLET_METAL) -/obj/projectile/bullet/on_hit(var/atom/target, var/blocked = 0) - if (..(target, blocked)) - var/mob/living/L = target - shake_camera(L, 3, 2) - -/obj/projectile/bullet/projectile_attack_mob(var/mob/living/target_mob, var/distance, var/miss_modifier) - if(penetrating > 0 && damage > 20 && prob(damage)) - mob_passthrough_check = 1 - else - mob_passthrough_check = 0 - return ..() - -/obj/projectile/bullet/can_embed() - //prevent embedding if the projectile is passing through the mob - if(mob_passthrough_check) - return 0 - return ..() - -/obj/projectile/bullet/check_penetrate(var/atom/A) - if(!A || !A.density) return 1 //if whatever it was got destroyed when we hit it, then I guess we can just keep going - - if(istype(A, /obj/vehicle/sealed/mecha)) - return 1 //mecha have their own penetration handling - - if(ismob(A)) - if(!mob_passthrough_check) - return 0 - if(iscarbon(A)) - damage *= 0.7 //squishy mobs absorb KE - return 1 +/obj/projectile/bullet/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + var/mob/living/L = target + if(!istype(L)) + return + shake_camera(L, 3, 2) +/obj/projectile/bullet/process_legacy_penetration(atom/A) var/chance = damage if(istype(A, /turf/simulated/wall)) var/turf/simulated/wall/W = A @@ -57,14 +36,12 @@ if(D.glass) chance *= 2 else if(istype(A, /obj/structure/girder)) chance = 100 + else if(ismob(A)) + chance = damage >= 20 && prob(damage) - if(prob(chance)) - if(A.opacity) - //display a message so that people on the other side aren't so confused - A.visible_message("\The [src] pierces through \the [A]!") - return 1 - - return 0 + . = prob(chance) + if(.) + damage *= 0.7 /* short-casing projectiles, like the kind used in pistols or SMGs */ @@ -128,7 +105,7 @@ damage = 10 // high rof kinda fucked up lets be real agony = 10 // brute easily heals, agony not so much armor_penetration = 30 // reduces shield blockchance - accuracy = -20 // he do miss actually + accuracy_overall_modify = 0.8 // heehoo speed = 25 * WORLD_ICON_SIZE /obj/projectile/bullet/pistol/medium/ap/suppressor/turbo // spicy boys @@ -193,20 +170,15 @@ fire_sound = 'sound/weapons/weaponsounds_shotgunshot.ogg' damage = 13 pellets = 6 - range_step = 1 - spread_step = 10 + pellet_loss = 0.66 / WORLD_ICON_SIZE /obj/projectile/bullet/pellet/shotgun_improvised name = "shrapnel" - damage = 1 + damage = 4 pellets = 10 - range_step = 1 - spread_step = 10 /obj/projectile/bullet/pellet/shotgun/flak damage = 2 //The main weapon using these fires four at a time, usually with different destinations. Usually. - range_step = 2 - spread_step = 30 armor_penetration = 10 // This is my boomstick, @@ -218,8 +190,6 @@ SA_vulnerability = MOB_CLASS_DEMONIC | MOB_CLASS_ABERRATION embed_chance = -1 pellets = 6 - range_step = 1 - spread_step = 20 holy = TRUE /obj/projectile/bullet/shotgun/stake @@ -242,27 +212,31 @@ combustion = FALSE -/obj/projectile/bullet/shotgun/ion/on_hit(var/atom/target, var/blocked = 0) - ..() +/obj/projectile/bullet/shotgun/ion/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return empulse(target, 0, 0, 2, 0) //Only affects what it hits - return 1 + . |= PROJECTILE_IMPACT_DELETE //Frag shot /obj/projectile/bullet/shotgun/frag12 name ="frag12 slug" damage = 25 -/obj/projectile/bullet/shotgun/frag12/on_hit(atom/target, blocked = FALSE) - ..() +/obj/projectile/bullet/shotgun/frag12/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return explosion(target, -1, 0, 1) - return 1 + . |= PROJECTILE_IMPACT_DELETE /* "Rifle" rounds */ /obj/projectile/bullet/rifle fire_sound = 'sound/weapons/Gunshot_generic_rifle.ogg' armor_penetration = 15 - penetrating = 1 + legacy_penetrating = 1 /obj/projectile/bullet/rifle/a762 fire_sound = 'sound/weapons/weaponsounds_heavyrifleshot.ogg' @@ -286,7 +260,7 @@ /obj/projectile/bullet/rifle/a762/hp damage = 40 armor_penetration = -50 - penetrating = 0 + legacy_penetrating = 0 /obj/projectile/bullet/rifle/a762/hunter // Optimized for killing simple animals and not people, because Balance(tm) damage = 25 @@ -318,19 +292,19 @@ /obj/projectile/bullet/rifle/a556/hp damage = 35 armor_penetration = -50 - penetrating = 0 + legacy_penetrating = 0 /obj/projectile/bullet/rifle/a556/hunter damage = 15 SA_bonus_damage = 35 // 50 total on animals. SA_vulnerability = MOB_CLASS_ANIMAL -/obj/projectile/bullet/rifle/a12_7mm // 14.5×114mm is bigger than a .50 BMG round. +/obj/projectile/bullet/rifle/a12_7mm fire_sound = 'sound/weapons/Gunshot_cannon.ogg' // This is literally an anti-tank rifle caliber. It better sound like a fucking cannon. damage = 80 stun = 3 weaken = 3 - penetrating = 5 + legacy_penetrating = 5 armor_penetration = 80 hitscan = 1 //so the PTR isn't useless as a sniper weapon @@ -361,10 +335,12 @@ embed_chance = 0 edge = 1 -/obj/projectile/bullet/burstbullet/on_hit(var/atom/target, var/blocked = 0) - if(isturf(target)) - explosion(target, -1, 0, 2) - ..() +/obj/projectile/bullet/burstbullet/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + explosion(target, -1, 0, 2) + . |= PROJECTILE_IMPACT_DELETE /obj/projectile/bullet/burstbullet/service name = "charge bullet" @@ -376,10 +352,13 @@ SA_vulnerability = MOB_CLASS_DEMONIC | MOB_CLASS_ABERRATION holy = TRUE -/obj/projectile/bullet/burstbullet/service/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/bullet/burstbullet/service/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(isturf(target)) explosion(target, 0, 1, 2) - ..() + return . | PROJECTILE_IMPACT_DELETE /* Black Powder */ @@ -399,8 +378,7 @@ /obj/projectile/bullet/pellet/blunderbuss //More Damage at close range greater falloff damage = 10 pellets = 8 - range_step = 0.5 //Very quick falloff - spread_step = 30 + pellet_loss = 1.5 / WORLD_ICON_SIZE /obj/projectile/bullet/pellet/blunderbuss/silver damage = 5 @@ -418,8 +396,6 @@ /obj/projectile/bullet/pellet/heavy_shotgun //I want this to use similar calcuations to blunderbuss shot for falloff. damage = 3 //Fires five pellets at a time. - range_step = 0.75 - spread_step = 30 armor_penetration = 10 /obj/projectile/bullet/pellet/heavy_shotgun/silver @@ -432,32 +408,33 @@ /obj/projectile/bullet/heavy_shotgun/grit name = "custom heavy slug" -/obj/projectile/bullet/heavy_shotgun/grit/on_hit(var/atom/movable/target, var/blocked = 0) +/obj/projectile/bullet/heavy_shotgun/grit/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(isliving(target)) var/mob/living/L = target var/throwdir = get_dir(firer,L) - if(prob(10) && !blocked) + if(prob(10) && (efficiency < 0.95)) L.afflict_stun(20 * 1) L.Confuse(1) L.throw_at_old(get_edge_target_turf(L, throwdir), rand(3,6), 10) - return 1 - /obj/projectile/bullet/pellet/heavy_shotgun/grit name = "heavy buckshot" - range_step = 1 -/obj/projectile/bullet/pellet/heavy_shotgun/grit/on_hit(var/atom/movable/target, var/blocked = 0) +/obj/projectile/bullet/pellet/heavy_shotgun/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(isliving(target)) var/mob/living/L = target var/throwdir = get_dir(firer,L) - if(prob(10) && !blocked) - L.afflict_stun(20 * 1) + if(prob(10) && (efficiency >= 0.9)) + L.afflict_stun(2 SECONDS) L.Confuse(1) L.throw_at_old(get_edge_target_turf(L, throwdir), rand(3,6), 10) - return 1 - /* Incendiary */ /obj/projectile/bullet/incendiary @@ -503,10 +480,14 @@ incendiary = 1 flammability = 4 armor_penetration = 40 - penetrating = 5 + legacy_penetrating = 5 combustion = TRUE -/obj/projectile/bullet/incendiary/caseless/on_hit(var/atom/movable/target, var/blocked = 0) +/obj/projectile/bullet/incendiary/caseless/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + // todo: burn this to the ground if(isliving(target)) var/mob/living/L = target L.adjustFireLoss(10) @@ -519,7 +500,7 @@ damage_type = BRUTE incendiary = 1 flammability = 4 - penetrating = 1 + legacy_penetrating = 1 combustion = TRUE diff --git a/code/modules/projectiles/projectile/bullets_vr.dm b/code/modules/projectiles/projectile/subtypes/bullets_vr.dm similarity index 100% rename from code/modules/projectiles/projectile/bullets_vr.dm rename to code/modules/projectiles/projectile/subtypes/bullets_vr.dm diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/subtypes/change.dm similarity index 93% rename from code/modules/projectiles/projectile/change.dm rename to code/modules/projectiles/projectile/subtypes/change.dm index a8a60f70ed9..187c92e96e7 100644 --- a/code/modules/projectiles/projectile/change.dm +++ b/code/modules/projectiles/projectile/subtypes/change.dm @@ -8,8 +8,11 @@ combustion = FALSE -/obj/projectile/change/on_hit(var/atom/change) - wabbajack(change) +/obj/projectile/change/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + wabbajack(target) /obj/projectile/change/proc/wabbajack(var/mob/M) if(istype(M, /mob/living) && M.stat != DEAD) diff --git a/code/modules/projectiles/projectile/energy.dm b/code/modules/projectiles/projectile/subtypes/energy.dm similarity index 93% rename from code/modules/projectiles/projectile/energy.dm rename to code/modules/projectiles/projectile/subtypes/energy.dm index cc3b78328ff..48717e3024a 100644 --- a/code/modules/projectiles/projectile/energy.dm +++ b/code/modules/projectiles/projectile/subtypes/energy.dm @@ -4,6 +4,7 @@ damage = 0 damage_type = BURN damage_flag = ARMOR_ENERGY + projectile_type = PROJECTILE_TYPE_ENERGY var/flash_strength = 10 //releases a burst of light on impact or after travelling a distance @@ -17,10 +18,11 @@ var/brightness = 7 var/light_colour = "#ffffff" -/obj/projectile/energy/flash/on_impact(var/atom/A) - var/turf/T = flash_range? src.loc : get_turf(A) - if(!istype(T)) return - +/obj/projectile/energy/flash/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(!isturf(target)) + return + var/turf/T = get_turf(target) //blind adjacent people for (var/mob/living/carbon/M in viewers(T, flash_range)) if(M.eyecheck() < 1) @@ -56,11 +58,9 @@ incendiary = 1 flammability = 2 -/obj/projectile/energy/flash/flare/on_impact(var/atom/A) +/obj/projectile/energy/flash/flare/on_impact(atom/target, impact_flags, def_zone, efficiency) light_colour = pick("#e58775", "#ffffff", "#90ff90", "#a09030") - - ..() //initial flash - + . = ..() //residual illumination new /obj/effect/particle_effect/smoke/illumination(src.loc, rand(190,240) SECONDS, 8, 3, light_colour) //same lighting power as flare @@ -206,9 +206,10 @@ to_chat(M, "Your ears start to ring!") M.update_icons() //Just to apply matrix transform for laying asap -/obj/projectile/energy/plasmastun/on_hit(var/atom/target) - bang(target) +/obj/projectile/energy/plasmastun/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + bang(target) + return . | PROJECTILE_IMPACT_DELETE /obj/projectile/energy/blue_pellet name = "suppressive pellet" diff --git a/code/modules/projectiles/projectile/energy_vr.dm b/code/modules/projectiles/projectile/subtypes/energy_vr.dm similarity index 100% rename from code/modules/projectiles/projectile/energy_vr.dm rename to code/modules/projectiles/projectile/subtypes/energy_vr.dm diff --git a/code/modules/projectiles/projectile/explosive.dm b/code/modules/projectiles/projectile/subtypes/explosive.dm similarity index 66% rename from code/modules/projectiles/projectile/explosive.dm rename to code/modules/projectiles/projectile/subtypes/explosive.dm index 05d9d178681..8139b108fe4 100644 --- a/code/modules/projectiles/projectile/explosive.dm +++ b/code/modules/projectiles/projectile/subtypes/explosive.dm @@ -8,26 +8,16 @@ damage = 30 //Meaty whack. *Chuckles* movable_flags = MOVABLE_NO_THROW_SPIN | MOVABLE_NO_THROW_DAMAGE_SCALING | MOVABLE_NO_THROW_SPEED_SCALING -/obj/projectile/bullet/srmrocket/on_hit(atom/target, blocked=0) - ..() +/obj/projectile/bullet/srmrocket/on_impact(atom/target, impact_flags, def_zone, efficiency) if(!isliving(target)) //if the target isn't alive, so is a wall or something explosion(target, 0, 1, 2, 4) else explosion(target, 0, 0, 2, 4) - return 1 - + return PROJECTILE_IMPACT_DELETE /obj/projectile/bullet/srmrocket/weak //Used in the jury rigged one. damage = 10 -/obj/projectile/bullet/srmrocket/weak/on_hit(atom/target, blocked=0) - ..() +/obj/projectile/bullet/srmrocket/weak/on_impact(atom/target, impact_flags, def_zone, efficiency) explosion(target, 0, 0, 2, 4)//No need to have a question. - return 1 - -/*Old vars here for reference. - var/devastation = 0 - var/heavy_blast = 1 - var/light_blast = 2 - var/flash_blast = 4 -*/ + return PROJECTILE_IMPACT_DELETE diff --git a/code/modules/projectiles/projectile/force.dm b/code/modules/projectiles/projectile/subtypes/force.dm similarity index 70% rename from code/modules/projectiles/projectile/force.dm rename to code/modules/projectiles/projectile/subtypes/force.dm index a95cefff563..05e11a848f6 100644 --- a/code/modules/projectiles/projectile/force.dm +++ b/code/modules/projectiles/projectile/subtypes/force.dm @@ -10,11 +10,12 @@ /obj/projectile/forcebolt/strong name = "force bolt" -/obj/projectile/forcebolt/on_hit(var/atom/movable/target, var/blocked = 0) - if(istype(target)) +/obj/projectile/forcebolt/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(ismovable(target)) + var/atom/movable/movable_target = target var/throwdir = get_dir(firer,target) - target.throw_at_old(get_edge_target_turf(target, throwdir),10,10) - return 1 + movable_target.throw_at_old(get_edge_target_turf(target, throwdir),10,10) /* /obj/projectile/forcebolt/strong/on_hit(var/atom/target, var/blocked = 0) diff --git a/code/modules/projectiles/projectile/hook.dm b/code/modules/projectiles/projectile/subtypes/hook.dm similarity index 95% rename from code/modules/projectiles/projectile/hook.dm rename to code/modules/projectiles/projectile/subtypes/hook.dm index 797063ec8ab..bcf0837c688 100644 --- a/code/modules/projectiles/projectile/hook.dm +++ b/code/modules/projectiles/projectile/subtypes/hook.dm @@ -58,12 +58,11 @@ ..() // Does the regular launching stuff. -/obj/projectile/energy/hook/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) - if(..()) - perform_intent_unique(target) - -/obj/projectile/energy/hook/on_impact(var/atom/A) - perform_intent_unique(get_turf(A)) +/obj/projectile/energy/hook/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + perform_intent_unique(target) /obj/projectile/energy/hook/proc/ranged_disarm(var/mob/living/carbon/human/H) if(istype(H)) @@ -116,8 +115,8 @@ else if(firer) var/obj/T - if(original in target.contents && istype(original, /obj)) - T = original + if(original_target.loc == target && istype(original_target, /obj)) + T = original_target var/list/possible_targets = list() for(var/obj/item/I in target.contents) diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/subtypes/magic.dm similarity index 94% rename from code/modules/projectiles/projectile/magic.dm rename to code/modules/projectiles/projectile/subtypes/magic.dm index 507121f0248..7a1ae4faa0d 100644 --- a/code/modules/projectiles/projectile/magic.dm +++ b/code/modules/projectiles/projectile/subtypes/magic.dm @@ -36,8 +36,8 @@ /obj/item/ammo_casing/magic/honk projectile_type = /obj/projectile/bullet/honker -/obj/item/ammo_casing/magic/locker - projectile_type = /obj/projectile/magic/locker +// /obj/item/ammo_casing/magic/locker + // projectile_type = /obj/projectile/magic/locker //Spell book ammo casing /obj/item/ammo_casing/magic/book diff --git a/code/modules/projectiles/projectile/magnetic.dm b/code/modules/projectiles/projectile/subtypes/magnetic.dm similarity index 72% rename from code/modules/projectiles/projectile/magnetic.dm rename to code/modules/projectiles/projectile/subtypes/magnetic.dm index 1705a9c1c65..0133907721b 100644 --- a/code/modules/projectiles/projectile/magnetic.dm +++ b/code/modules/projectiles/projectile/subtypes/magnetic.dm @@ -6,7 +6,7 @@ damage = 65 stun = 1 weaken = 1 - penetrating = 5 + legacy_penetrating = 5 armor_penetration = 70 /obj/projectile/bullet/magnetic/slug @@ -53,7 +53,7 @@ agony = 50 incendiary = 1 flammability = 0 //Deuterium and Tritium are both held in water, but the object moving so quickly will ignite the target. - penetrating = 2 + legacy_penetrating = 2 embed_chance = 0 armor_penetration = 40 range = WORLD_ICON_SIZE * 20 @@ -63,7 +63,13 @@ var/detonate_mob = 0 //Will this fuelrod explode when it hits a mob? var/energetic_impact = 0 //Does this fuelrod cause a bright flash on impact with a mob? -/obj/projectile/bullet/magnetic/fuelrod/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) //Future-proofing. Special effects for impact. +/obj/projectile/bullet/magnetic/fuelrod/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(searing) + efficiency = max(efficiency, 1) + if(. & PROJECTILE_IMPACT_BLOCKED) + return + if(istype(target,/mob/living)) var/mob/living/V = target if(detonate_mob) @@ -81,28 +87,21 @@ M.afflict_stun(20 * 2) M.afflict_paralyze(20 * 10) - if(searing) - if(blocked) - blocked = 0 - - return ..(target, blocked, def_zone) - -/obj/projectile/bullet/magnetic/fuelrod/on_impact(var/atom/A) //Future-proofing, again. In the event new fuel rods are introduced, and have special effects for when they stop flying. + // what the fuck?? if(src.loc) if(detonate_travel && detonate_mob) visible_message("\The [src] shatters in a violent explosion!") - explosion(src.loc, 1, 1, 3, 4) + explosion(src.loc, 0, 1, 3, 4) else if(detonate_travel) visible_message("\The [src] explodes in a shower of embers!") - explosion(src.loc, -1, 1, 2, 3) - ..(A) + explosion(src.loc, 0, 1, 2, 3) /obj/projectile/bullet/magnetic/fuelrod/tritium icon_state = "fuel-tritium" damage = 100 //Much harder to get than tritium - needs mhydrogen flammability = -1 armor_penetration = 50 - penetrating = 3 + legacy_penetrating = 3 /obj/projectile/bullet/magnetic/fuelrod/phoron name = "blazing fuel rod" @@ -111,7 +110,7 @@ incendiary = 2 flammability = 2 armor_penetration = 60 - penetrating = 5 + legacy_penetrating = 5 irradiate = 20 detonate_mob = 1 @@ -123,7 +122,7 @@ flammability = 4 weaken = 2 armor_penetration = 100 - penetrating = 100 //Theoretically, this shouldn't stop flying for a while, unless someone lines it up with a wall or fires it into a mountain. + legacy_penetrating = 100 //Theoretically, this shouldn't stop flying for a while, unless someone lines it up with a wall or fires it into a mountain. irradiate = 120 range = WORLD_ICON_SIZE * 75 searing = 1 @@ -131,14 +130,16 @@ detonate_mob = 1 energetic_impact = 1 -/obj/projectile/bullet/magnetic/fuelrod/supermatter/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) //You cannot touch the supermatter without disentigrating. Assumedly, this is true for condensed rods of it flying at relativistic speeds. +/obj/projectile/bullet/magnetic/fuelrod/supermatter/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(istype(target,/turf/simulated/wall) || istype(target,/mob/living)) target.visible_message("The [src] burns a perfect hole through \the [target] with a blinding flash!") playsound(target.loc, 'sound/effects/teleport.ogg', 40, 0) - return ..(target, blocked, def_zone) -/obj/projectile/bullet/magnetic/fuelrod/supermatter/check_penetrate() - return 1 +/obj/projectile/bullet/magnetic/fuelrod/supermatter/pre_impact(atom/target, impact_flags, def_zone) + return ..() | PROJECTILE_IMPACT_PIERCE /obj/projectile/bullet/magnetic/bore name = "phorogenic blast" @@ -146,24 +147,26 @@ damage = 20 incendiary = 1 armor_penetration = 20 - penetrating = 0 + legacy_penetrating = 0 damage_flag = ARMOR_MELEE irradiate = 20 range = WORLD_ICON_SIZE * 6 -/obj/projectile/bullet/magnetic/bore/Bump(atom/A, forced=0) - if(istype(A, /turf/simulated/mineral)) - var/turf/simulated/mineral/MI = A - loc = get_turf(A) // Careful. - permutated.Add(A) +/obj/projectile/bullet/magnetic/bore/pre_impact(atom/target, impact_flags, def_zone) + if(istype(target, /turf/simulated/mineral)) + return PROJECTILE_IMPACT_PIERCE | impact_flags + return ..() + +/obj/projectile/bullet/magnetic/bore/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(istype(target, /turf/simulated/mineral)) + var/turf/simulated/mineral/MI = target MI.GetDrilled(TRUE) - return 0 - else if(istype(A, /turf/simulated/wall) || istype(A, /turf/simulated/shuttle/wall)) // Cause a loud, but relatively minor explosion on the wall it hits. - explosion(A, -1, -1, 1, 3) - qdel(src) - return 1 - else - ..() + else if(istype(target, /turf/simulated/wall) || istype(target, /turf/simulated/shuttle/wall)) + explosion(target, 0, 0, 1, 3) + . |= PROJECTILE_IMPACT_DELETE /obj/projectile/bullet/magnetic/bore/powerful name = "energetic phorogenic blast" @@ -171,7 +174,7 @@ damage = 30 incendiary = 2 armor_penetration = 20 - penetrating = 0 + legacy_penetrating = 0 damage_flag = ARMOR_MELEE irradiate = 20 range = WORLD_ICON_SIZE * 12 diff --git a/code/modules/projectiles/projectile/subtypes/pellet.dm b/code/modules/projectiles/projectile/subtypes/pellet.dm new file mode 100644 index 00000000000..cd9891e7f3c --- /dev/null +++ b/code/modules/projectiles/projectile/subtypes/pellet.dm @@ -0,0 +1,153 @@ +// For projectiles that actually represent clouds of projectiles +// todo: message handling for this type is all over the place +// todo: target still makes message of being hit even if they weren't +// todo: just handle message in this file lmao +/obj/projectile/bullet/pellet + name = "shrapnel" //'shrapnel' sounds more dangerous (i.e. cooler) than 'pellet' + + damage = 20 + + /// number of pelelts + var/pellets = 4 + /// distance before pellets start falling off + var/pellet_loss_start = WORLD_ICON_SIZE * 2 + /// pellets lost per pixel moved + var/pellet_loss = 0.5 / WORLD_ICON_SIZE + /// last distance travelled + var/pellet_loss_last_distance = 0 + /// base spread chance to not hit center mass / the target limb + /// + /// * in 0 to 100 prob() value + var/pellet_zone_spread = 10 + /// min distance before spread + var/pellet_zone_spread_gain_threshold = 2 * WORLD_ICON_SIZE + /// spread chance per pixel to not hit center mass / target limb + /// + /// * in 0 to 100 prob() value + var/pellet_zone_spread_gain = 9 / WORLD_ICON_SIZE // complete spread after 2+10 tiles + +/obj/projectile/bullet/pellet/scan_moved_turf(turf/tile) + ..() + if(QDELETED(src)) + return + for(var/mob/victim in tile) + if(victim.atom_flags & (ATOM_NONWORLD | ATOM_ABSTRACT)) + continue + if(impacted[victim]) + continue + impact(victim) + +/obj/projectile/bullet/pellet/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + var/travelled = distance_travelled - pellet_loss_last_distance + pellet_loss_last_distance = distance_travelled + if(pellet_loss_start > 0) + var/reduction = min(pellet_loss_start, travelled) + travelled -= reduction + pellet_loss_start -= reduction + if(travelled <= 0) + return + pellets -= pellet_loss * travelled + +/obj/projectile/bullet/pellet/process_accuracy(atom/target, target_opinion, distance, impact_check) + if(impact_check) + return 100 + return ..() + +/obj/projectile/bullet/pellet/process_zone_miss(atom/target, target_opinion, distance, impact_check) + if(impact_check) + // if being hit, always direct to our def_zone + return def_zone + return ..() + +// todo: "you are hit by [number of] pellets in the chest" support. +/obj/projectile/bullet/pellet/inflict_impact_damage(atom/target, efficiency, impact_flags, hit_zone) + /** + * What's going on here: + * + * It's too expensive to simulate too many bullet_act()'s for no reason, but pellets + * aren't meant to hit one body part or even do one instance to every body part hit. + * + * Thus, we force process_damage_instance to do your dirty work as it's cheaper to run + * lower level shieldcalls/armorcalls than to simulate high level bullet hits. + */ + var/original_hit_zone = hit_zone + var/distance_penalty = distance_travelled > pellet_zone_spread_gain_threshold ? (distance_travelled * pellet_zone_spread_gain) : 0 + var/zone_true_chance = 100 - (pellet_zone_spread + distance_penalty) + var/pellet_hit_chance = 100 + . = NONE + if(isliving(target)) + var/mob/living/living_target = target + pellet_hit_chance = living_target.process_baymiss(src, impact_check = FALSE) + if(!target.density && (target != original_target)) + pellet_hit_chance *= 0.2 + var/hit_pellets = 0 + for(var/i in 1 to ceil(pellets)) + if(!prob(pellet_hit_chance)) + continue + // args is just a list ref, so we can do this (directly modify args) + hit_zone = ran_zone(def_zone, zone_true_chance) + . |= ..() + ++hit_pellets + pellets -= hit_pellets + hit_zone = original_hit_zone + if(pellets <= 0) + . |= PROJECTILE_IMPACT_DELETE + else + . |= PROJECTILE_IMPACT_PIERCE + +// todo: this is only needed because process_damage_instance isn't used for structured right now! +/obj/projectile/bullet/pellet/get_structure_damage() + return ..() * pellets + +//Explosive grenade projectile, borrowed from fragmentation grenade code. +/obj/projectile/bullet/pellet/fragment + damage = 10 + armor_penetration = 30 + + silenced = 1 //embedding messages are still produced so it's kind of weird when enabled. + muzzle_type = null + +/obj/projectile/bullet/pellet/fragment/strong + damage = 15 + armor_penetration = 20 + +/obj/projectile/bullet/pellet/fragment/weak + damage = 4 + armor_penetration = 40 + +/obj/projectile/bullet/pellet/fragment/rubber + name = "stingball shrapnel" + damage = 3 + agony = 8 + sharp = FALSE + edge = FALSE + damage_flag = ARMOR_MELEE + +/obj/projectile/bullet/pellet/fragment/rubber/strong + damage = 8 + agony = 16 + +// Tank rupture fragments +/obj/projectile/bullet/pellet/fragment/tank + name = "metal fragment" + damage = 9 //Big chunks flying off. + + armor_penetration = 20 + + silenced = 1 + muzzle_type = null + pellets = 3 + +/obj/projectile/bullet/pellet/fragment/tank/small + name = "small metal fragment" + damage = 6 + armor_penetration = 5 + pellets = 5 + +/obj/projectile/bullet/pellet/fragment/tank/big + name = "large metal fragment" + damage = 17 + armor_penetration = 10 + pellet_loss = 0.2 / WORLD_ICON_SIZE + pellets = 1 diff --git a/code/modules/projectiles/projectile/reusable.dm b/code/modules/projectiles/projectile/subtypes/reusable.dm similarity index 73% rename from code/modules/projectiles/projectile/reusable.dm rename to code/modules/projectiles/projectile/subtypes/reusable.dm index a2f5fabbb96..4c2f5904ff8 100644 --- a/code/modules/projectiles/projectile/reusable.dm +++ b/code/modules/projectiles/projectile/subtypes/reusable.dm @@ -4,54 +4,19 @@ name = "reusable bullet" desc = "How do you even reuse a bullet?" var/ammo_type = /obj/item/ammo_casing/arrow - var/dropped = FALSE //var/fragile = FALSE //var/durable = FALSE //var/shattered = 0 //var/broken_type = null -/obj/projectile/bullet/reusable/on_hit(atom/target, blocked = FALSE) - . = ..() - handle_drop() - //handle_shatter() - -/obj/projectile/bullet/reusable/legacy_on_range() +/obj/projectile/bullet/reusable/expire(impacting) handle_drop() - ..() + return ..() /obj/projectile/bullet/reusable/proc/handle_drop() - if(!dropped) - var/turf/T = get_turf(src) - new ammo_type(T) - dropped = TRUE -/* - else - var/turf/T = get_turf(src) - new broken_type(T) - dropped = TRUE - -/obj/projectile/bullet/reusable/proc/handle_shatter() - if(fragile) - switch(rand(1,100)) - if(1 to 50) - src.shattered = 1 - if(31 to 100) - return - if(durable) - switch(rand(1,100)) - if(1 to 5) - src.shattered = 1 - if(6 to 100) - return - else - switch(rand(1,100)) - if(1 to 25) - src.shattered = 1 - if(16 to 100) - return - return -*/ + var/turf/T = get_turf(src) + new ammo_type(T) //Arrows /obj/projectile/bullet/reusable/arrow @@ -97,14 +62,20 @@ icon_state = "plunger" ammo_type = /obj/item/ammo_casing/arrow/plunger -/obj/projectile/bullet/reusable/plunger/on_hit(atom/hit_atom) +/obj/projectile/bullet/reusable/plunger/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() - var/mob/living/carbon/H = hit_atom + // use target abort as this is a target effect. + if(. & PROJECTILE_IMPACT_FLAGS_TARGET_ABORT) + return + var/mob/living/carbon/H = target + if(!istype(H)) + return var/obj/item/plunger/P if(!H.wear_mask) H.equip_to_slot_if_possible(P, SLOT_MASK) else handle_drop() + return . | PROJECTILE_IMPACT_DELETE //Foam Darts /obj/projectile/bullet/reusable/foam diff --git a/code/modules/projectiles/projectile/scatter.dm b/code/modules/projectiles/projectile/subtypes/scatter.dm similarity index 100% rename from code/modules/projectiles/projectile/scatter.dm rename to code/modules/projectiles/projectile/subtypes/scatter.dm diff --git a/code/modules/projectiles/projectile/special.dm b/code/modules/projectiles/projectile/subtypes/special.dm similarity index 80% rename from code/modules/projectiles/projectile/special.dm rename to code/modules/projectiles/projectile/subtypes/special.dm index deadb73495d..1846df5ce77 100644 --- a/code/modules/projectiles/projectile/special.dm +++ b/code/modules/projectiles/projectile/subtypes/special.dm @@ -17,9 +17,12 @@ var/sev3_range = 1 var/sev4_range = 1 -/obj/projectile/ion/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/ion/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return empulse(target, sev1_range, sev2_range, sev3_range, sev4_range) - return 1 + return . | PROJECTILE_IMPACT_DELETE /obj/projectile/ion/small sev1_range = -1 @@ -41,9 +44,12 @@ sharp = 1 edge = 1 -/obj/projectile/bullet/gyro/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/bullet/gyro/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return explosion(target, -1, 0, 2) - ..() + return . | PROJECTILE_IMPACT_DELETE /obj/projectile/temp name = "freeze beam" @@ -61,8 +67,10 @@ combustion = FALSE -/obj/projectile/temp/on_hit(atom/target, blocked = FALSE) - ..() +/obj/projectile/temp/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(isliving(target)) var/mob/living/L = target @@ -84,8 +92,6 @@ new_temperature = round(new_temperature * temp_factor) L.bodytemperature = new_temperature - return 1 - /obj/projectile/temp/hot name = "heat beam" target_temperature = 1000 @@ -133,7 +139,11 @@ combustion = FALSE -/obj/projectile/energy/floramut/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/energy/floramut/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + var/mob/living/M = target if(ishuman(target)) var/mob/living/carbon/human/H = M @@ -163,8 +173,6 @@ // for (var/mob/V in viewers(src)) // V.show_message("The radiation beam dissipates harmlessly through [M]", 3) M.show_message("The radiation beam dissipates harmlessly through your body.") - else - return 1 /obj/projectile/energy/floramut/gene name = "gamma somatoray" @@ -188,7 +196,10 @@ light_power = 0.5 light_color = "#FFFFFF" -/obj/projectile/energy/florayield/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/energy/florayield/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return var/mob/M = target if(ishuman(target)) //These rays make plantmen fat. var/mob/living/carbon/human/H = M @@ -196,16 +207,15 @@ M.nutrition += 30 else if (istype(target, /mob/living/carbon/)) M.show_message("The radiation beam dissipates harmlessly through your body.") - else - return 1 - /obj/projectile/beam/mindflayer name = "flayer ray" - combustion = FALSE -/obj/projectile/beam/mindflayer/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/beam/mindflayer/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ishuman(target)) var/mob/living/carbon/human/M = target M.Confuse(rand(5,8)) @@ -229,14 +239,16 @@ combustion = FALSE -/obj/projectile/bola/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/bola/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(ishuman(target)) var/mob/living/carbon/human/M = target var/obj/item/handcuffs/legcuffs/bola/B = new(src.loc) if(!B.place_legcuffs(M,firer)) - if(B) - qdel(B) - ..() + qdel(B) /obj/projectile/webball name = "ball of web" @@ -248,13 +260,16 @@ combustion = FALSE -/obj/projectile/webball/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/webball/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(isturf(target.loc)) var/obj/effect/spider/stickyweb/W = locate() in get_turf(target) if(!W && prob(75)) visible_message("\The [src] splatters a layer of web on \the [target]!") new /obj/effect/spider/stickyweb(target.loc) - ..() /obj/projectile/beam/tungsten name = "core of molten tungsten" @@ -272,7 +287,10 @@ tracer_type = /obj/effect/projectile/tungsten/tracer impact_type = /obj/effect/projectile/tungsten/impact -/obj/projectile/beam/tungsten/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/beam/tungsten/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_BLOCKED) + return if(isliving(target)) var/mob/living/L = target L.add_modifier(/datum/modifier/grievous_wounds, 30 SECONDS) @@ -316,17 +334,13 @@ else if(armor_special) target.visible_message("\The [src] slams into \the [target]'s [target_limb] with a low rumble!") - ..() - -/obj/projectile/beam/tungsten/on_impact(var/atom/A) - if(istype(A,/turf/simulated/shuttle/wall) || istype(A,/turf/simulated/wall) || (istype(A,/turf/simulated/mineral) && A.density) || istype(A,/obj/vehicle/sealed/mecha) || istype(A,/obj/machinery/door)) + if(istype(target,/turf/simulated/shuttle/wall) || istype(target,/turf/simulated/wall) || (istype(target,/turf/simulated/mineral) && target.density) || istype(target,/obj/vehicle/sealed/mecha) || istype(target,/obj/machinery/door)) var/blast_dir = src.dir - A.visible_message("\The [A] begins to glow!") + target.visible_message("\The [target] begins to glow!") spawn(2 SECONDS) - var/blastloc = get_step(A, blast_dir) + var/blastloc = get_step(target, blast_dir) if(blastloc) explosion(blastloc, -1, -1, 2, 3) - ..() /obj/projectile/bullet/honker damage = 0 @@ -384,19 +398,18 @@ light_range = 4 light_power = 3 light_color = "#00ccff" + var/heavy = FALSE -/obj/projectile/plasma/on_hit(var/atom/target, var/blocked = 0) - explosion(target, -1, 0, 1, 2) - ..() +/obj/projectile/plasma/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return -/obj/projectile/plasma/on_impact(var/atom/A) - if(istype(A,/turf/simulated/shuttle/wall) || istype(A,/turf/simulated/wall) || (istype(A,/turf/simulated/mineral) && A.density) || istype(A,/obj/vehicle/sealed/mecha) || istype(A,/obj/machinery/door)) - var/blast_dir = src.dir - A.visible_message("\The [A] is engulfed in roiling plasma!") - var/blastloc = get_step(A, blast_dir) - if(blastloc) - explosion(blastloc, -1, 0, 1, 2) - ..() + var/blast_dir = src.dir + target.visible_message("\The [target] is engulfed in roiling plasma!") + var/blastloc = get_step(target, blast_dir) + if(blastloc) + explosion(blastloc, -1, 0, heavy? 2 : 1, heavy? 3 : 2) /obj/projectile/plasma/hot name ="heavy plasma bolt" @@ -404,16 +417,4 @@ light_range = 5 light_power = 4 light_color = "#00ccff" - -/obj/projectile/plasma/hot/on_hit(var/atom/target, var/blocked = 0) - explosion(target, -1, 0, 2, 3) - ..() - -/obj/projectile/plasma/hot/on_impact(var/atom/A) - if(istype(A,/turf/simulated/shuttle/wall) || istype(A,/turf/simulated/wall) || (istype(A,/turf/simulated/mineral) && A.density) || istype(A,/obj/vehicle/sealed/mecha) || istype(A,/obj/machinery/door)) - var/blast_dir = src.dir - A.visible_message("\The [A] is engulfed in roiling plasma!") - var/blastloc = get_step(A, blast_dir) - if(blastloc) - explosion(blastloc, -1, 0, 2, 3) - ..() + heavy = TRUE diff --git a/code/modules/projectiles/projectile/trace.dm b/code/modules/projectiles/projectile/subtypes/trace.dm similarity index 94% rename from code/modules/projectiles/projectile/trace.dm rename to code/modules/projectiles/projectile/subtypes/trace.dm index a01e1574e40..9c825bc3f75 100644 --- a/code/modules/projectiles/projectile/trace.dm +++ b/code/modules/projectiles/projectile/subtypes/trace.dm @@ -20,6 +20,7 @@ nodamage = TRUE damage = 0 has_tracer = FALSE + projectile_type = PROJECTILE_TYPE_TRACE var/list/hit = list() /obj/projectile/test/fire(angle, atom/direct_target) @@ -30,6 +31,3 @@ if(A != src) hit |= A return ..() - -/obj/projectile/test/projectile_attack_mob() - return diff --git a/code/modules/projectiles/unsorted/magic.dm b/code/modules/projectiles/unsorted/magic.dm index aa8735a1f02..690cd6a04e2 100644 --- a/code/modules/projectiles/unsorted/magic.dm +++ b/code/modules/projectiles/unsorted/magic.dm @@ -12,14 +12,17 @@ name = "bolt of death" icon_state = "pulse1_bl" -/obj/projectile/magic/death/on_hit(target, var/mob/living/L) +/obj/projectile/magic/death/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() - if(ismob(target)) - var/mob/M = target + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(isliving(target)) + var/mob/living/L = target if(L.anti_magic_check()) - M.visible_message("[src] vanishes on contact with [target]!") - return blocked - M.death(0) + L.visible_message("[src] vanishes on contact with [target]!") + . |= PROJECTILE_IMPACT_DELETE + return + L.death(0) /obj/projectile/magic/resurrection name = "bolt of resurrection" @@ -28,17 +31,21 @@ damage_type = OXY nodamage = 1 -/obj/projectile/magic/resurrection/on_hit(mob/living/carbon/target) +/obj/projectile/magic/resurrection/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(isliving(target)) - if(target.anti_magic_check()) - target.visible_message("[src] vanishes on contact with [target]!") - return blocked - if(target.revive(full_heal = TRUE)) - to_chat(target, "You rise with a start, you're alive!!!") - else if(target.stat != DEAD) - to_chat(target, "You feel great!") - target.rejuvenate(fix_missing = TRUE) + var/mob/living/L = target + if(L.anti_magic_check()) + L.visible_message("[src] vanishes on contact with [L]!") + . |= PROJECTILE_IMPACT_DELETE + return + if(L.revive(full_heal = TRUE)) + to_chat(L, "You rise with a start, you're alive!!!") + else if(L.stat != DEAD) + to_chat(L, "You feel great!") + L.rejuvenate(fix_missing = TRUE) /obj/projectile/magic/teleport name = "bolt of teleportation" @@ -49,13 +56,16 @@ var/inner_tele_radius = 0 var/outer_tele_radius = 6 -/obj/projectile/magic/teleport/on_hit(mob/target, var/mob/living/L) +/obj/projectile/magic/teleport/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ismob(target)) - var/mob/M = target + var/mob/L = target if(L.anti_magic_check()) - M.visible_message("[src] fizzles on contact with [target]!") - return blocked + L.visible_message("[src] fizzles on contact with [target]!") + . |= PROJECTILE_IMPACT_DELETE + return var/teleammount = 0 var/teleloc = target if(!isturf(target)) @@ -76,8 +86,10 @@ nodamage = 1 var/list/door_types = list(/obj/structure/simple_door/wood, /obj/structure/simple_door/iron, /obj/structure/simple_door/silver, /obj/structure/simple_door/gold, /obj/structure/simple_door/uranium, /obj/structure/simple_door/sandstone, /obj/structure/simple_door/phoron, /obj/structure/simple_door/diamond) -/obj/projectile/magic/door/on_hit(atom/target) +/obj/projectile/magic/door/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(istype(target, /obj/machinery/door)) OpenDoor(target) else @@ -291,6 +303,7 @@ if(owner) C.ChangeOwner(owner) */ + /obj/projectile/magic/spellblade name = "blade energy" icon_state = "lavastaff" @@ -299,14 +312,17 @@ sharp = TRUE magic = TRUE -/obj/projectile/magic/spellblade/on_hit(target, var/mob/living/L) +/obj/projectile/magic/spellblade/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(ismob(target)) - var/mob/M = target + var/mob/L = target if(L.anti_magic_check()) - M.visible_message("[src] vanishes on contact with [target]!") + L.visible_message("[src] vanishes on contact with [target]!") qdel(src) return - . = ..() /obj/projectile/magic/arcane_barrage name = "arcane bolt" @@ -318,16 +334,19 @@ magic = TRUE impact_sounds = 'sound/weapons/barragespellhit.ogg' -/obj/projectile/magic/arcane_barrage/on_hit(target, var/mob/living/L) +/obj/projectile/magic/arcane_barrage/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(ismob(target)) - var/mob/M = target + var/mob/L = target if(L.anti_magic_check()) - M.visible_message("[src] vanishes on contact with [target]!") + L.visible_message("[src] vanishes on contact with [target]!") qdel(src) return - . = ..() - +/* needs more work /obj/projectile/magic/locker name = "locker bolt" icon_state = "locker" @@ -352,21 +371,24 @@ return ..() */ -/obj/projectile/magic/locker/on_hit(target) - if(created) - return ..() - var/obj/structure/closet/decay/C = new(get_turf(src)) - if(LAZYLEN(contents)) - for(var/atom/movable/AM in contents) +/obj/projectile/magic/locker/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(!created) + var/obj/structure/closet/decay/C = new(src) C.update_icon() - created = TRUE - return ..() + if(locker_suck && !(target.atom_flags & (ATOM_ABSTRACT | ATOM_NONWORLD))) + if(ismovable(target)) + var/atom/movable/AM = target + AM.forceMove(src) /obj/projectile/magic/locker/Destroy() locker_suck = FALSE for(var/atom/movable/AM in contents) AM.forceMove(get_turf(src)) . = ..() +*/ /obj/structure/closet/decay breakout_time = 600 @@ -428,16 +450,17 @@ chain = caster.Beam(src, icon_state = "lightning[rand(1, 12)]", time = INFINITY, maxdistance = INFINITY) ..() -/obj/projectile/magic/aoe/lightning/on_hit(target, var/mob/living/L) +/obj/projectile/magic/aoe/lightning/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ismob(target)) - var/mob/M = target + var/mob/L = target if(L.anti_magic_check()) - M.visible_message("[src] fizzles on contact with [target]!") - qdel(src) - return blocked + L.visible_message("[src] fizzles on contact with [target]!") + return . | PROJECTILE_IMPACT_DELETE tesla_zap(src, zap_range, zap_power) - qdel(src) + return . | PROJECTILE_IMPACT_DELETE /obj/projectile/magic/aoe/lightning/Destroy() qdel(chain) @@ -456,17 +479,21 @@ var/exp_flash = 3 var/exp_fire = 2 -/obj/projectile/magic/aoe/fireball/on_hit(target) +/obj/projectile/magic/aoe/fireball/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(ismob(target)) var/mob/living/M = target if(M.anti_magic_check()) visible_message("[src] vanishes into smoke on contact with [target]!") - return + return . | PROJECTILE_IMPACT_DELETE M.take_overall_damage(0,10) //between this 10 burn, the 10 brute, the explosion brute, and the onfire burn, your at about 65 damage if you stop drop and roll immediately var/turf/T = get_turf(target) explosion(T, -1, exp_heavy, exp_light, exp_flash, 0)//, flame_range = exp_fire) + return . | PROJECTILE_IMPACT_DELETE +/* requires IMPACT_DELETE_AFTER /obj/projectile/magic/aoe/fireball/infernal name = "infernal fireball" exp_heavy = -1 @@ -474,15 +501,14 @@ exp_flash = 4 exp_fire= 5 -/obj/projectile/magic/aoe/fireball/infernal/on_hit(target) +/obj/projectile/magic/aoe/fireball/infernal/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() - if(ismob(target)) - var/mob/living/M = target - if(M.anti_magic_check()) - return + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return var/turf/T = get_turf(target) for(var/i=0, i<50, i+=10) addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(explosion), T, -1, exp_heavy, exp_light, exp_flash, FALSE, FALSE, exp_fire), i) +*/ /obj/projectile/magic/nuclear name = "\proper blazing manliness" @@ -491,7 +517,10 @@ var/mob/living/victim = null var/used = 0 -/obj/projectile/magic/nuclear/on_hit(target) +/obj/projectile/magic/nuclear/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(used) return if(ismob(target)) @@ -500,16 +529,12 @@ used = 1 visible_message("[victim] slams into [target] with explosive force!") explosion(src, 2, 3, 4, -1, TRUE, FALSE, 5) + return PROJECTILE_IMPACT_DELETE else used = 1 victim.take_overall_damage(30,30) explosion(src, -1, -1, -1, -1, FALSE, FALSE, 5) - return - -/obj/projectile/magic/nuclear/Destroy() - for(var/atom/movable/AM in contents) - AM.forceMove(get_turf(src)) - . = ..() + return PROJECTILE_IMPACT_DELETE //Spellcards @@ -533,8 +558,10 @@ damage = 4 var/fire_stacks = 4 -/obj/projectile/magic/spellcard/book/spark/on_hit(atom/target, blocked = FALSE) +/obj/projectile/magic/spellcard/book/spark/on_impact(atom/target, impact_flags, def_zone, efficiency) . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return var/mob/living/carbon/M = target if(ismob(target)) if(M.anti_magic_check()) diff --git a/code/modules/random_map/drop/drop_types.dm b/code/modules/random_map/drop/drop_types.dm index 9bd0d665fd5..c39f4ae18cc 100644 --- a/code/modules/random_map/drop/drop_types.dm +++ b/code/modules/random_map/drop/drop_types.dm @@ -37,7 +37,7 @@ var/global/list/datum/supply_drop_loot/supply_drop /obj/item/storage/belt/security/tactical/bandolier, /obj/item/clothing/accessory/storage/black_drop_pouches, /obj/item/storage/backpack/dufflebag/sec, - /obj/item/shield/energy, + /obj/item/shield/transforming/energy, /obj/item/gun/energy/ionrifle, /obj/item/gun/energy/xray, /obj/item/storage/box/emps, @@ -58,7 +58,7 @@ var/global/list/datum/supply_drop_loot/supply_drop /obj/item/storage/belt/security/tactical/bandolier, /obj/item/clothing/accessory/storage/black_drop_pouches, /obj/item/storage/backpack/dufflebag/sec, - /obj/item/shield/riot/tele, + /obj/item/shield/transforming/telescopic, /obj/item/storage/box/emps, /obj/item/storage/box/flashbangs, /obj/item/gun/ballistic/automatic/sts35, @@ -85,7 +85,7 @@ var/global/list/datum/supply_drop_loot/supply_drop /obj/item/gun/ballistic/automatic/bullpup, /obj/item/ammo_magazine/a7_62mm/ap, /obj/item/ammo_magazine/a7_62mm, - /obj/item/shield/energy, + /obj/item/shield/transforming/energy, /obj/item/grenade/explosive/frag, /obj/item/grenade/explosive/frag, /obj/item/grenade/smokebomb, @@ -107,7 +107,7 @@ var/global/list/datum/supply_drop_loot/supply_drop /obj/item/clothing/suit/armor/riot, /obj/item/clothing/gloves/arm_guard/riot, /obj/item/clothing/shoes/leg_guard/riot, - /obj/item/shield/riot/tele, + /obj/item/shield/transforming/telescopic, /obj/item/storage/box/flashbangs, /obj/item/storage/box/handcuffs, /obj/item/melee/baton, diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm index 9855c529c98..70a9ac9731a 100644 --- a/code/modules/reagents/chemistry/holder.dm +++ b/code/modules/reagents/chemistry/holder.dm @@ -402,7 +402,7 @@ var/mob/living/L = target if(ishuman(L)) var/mob/living/carbon/human/H = L - if(H.check_shields(0, null, null, null, "the spray") == 1) //If they block the spray, it does nothing. + if(H.atom_shieldcall_handle_touch(null, SHIELDCALL_CONTACT_FLAG_NEUTRAL, SHIELDCALL_CONTACT_SPECIFIC_CHEMICAL_SPRAY) & SHIELDCALL_FLAGS_BLOCK_ATTACK) amount = 0 perm = L.reagent_permeability() return trans_to_mob(target, amount, CHEM_TOUCH, perm, copy) diff --git a/code/modules/reagents/machinery/reagent_dispenser/fuel.dm b/code/modules/reagents/machinery/reagent_dispenser/fuel.dm index f02c4724a39..e7e9faab413 100644 --- a/code/modules/reagents/machinery/reagent_dispenser/fuel.dm +++ b/code/modules/reagents/machinery/reagent_dispenser/fuel.dm @@ -84,13 +84,14 @@ return ..() -/obj/structure/reagent_dispensers/fueltank/bullet_act(var/obj/projectile/Proj) - if(Proj.get_structure_damage()) - if(istype(Proj.firer)) - message_admins("[key_name_admin(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) (JMP).") - log_game("[key_name(Proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]).") +/obj/structure/reagent_dispensers/fueltank/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(proj.get_structure_damage()) + if(istype(proj.firer)) + message_admins("[key_name_admin(proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) (JMP).") + log_game("[key_name(proj.firer)] shot fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]).") - if(!istype(Proj ,/obj/projectile/beam/lasertag) && !istype(Proj ,/obj/projectile/beam/practice) ) + if(!istype(proj ,/obj/projectile/beam/lasertag) && !istype(proj ,/obj/projectile/beam/practice) ) explode() /obj/structure/reagent_dispensers/fueltank/legacy_ex_act() diff --git a/code/modules/reagents/machinery/reagent_dispenser/oil.dm b/code/modules/reagents/machinery/reagent_dispenser/oil.dm index 0ee657bc919..e28b3074f4e 100644 --- a/code/modules/reagents/machinery/reagent_dispenser/oil.dm +++ b/code/modules/reagents/machinery/reagent_dispenser/oil.dm @@ -21,8 +21,9 @@ ) starting_capacity = 5000 -/obj/structure/reagent_dispensers/cookingoil/bullet_act(var/obj/projectile/Proj) - if(Proj.get_structure_damage()) +/obj/structure/reagent_dispensers/cookingoil/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(proj.get_structure_damage()) explode() /obj/structure/reagent_dispensers/cookingoil/legacy_ex_act() diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm index 575fcd64276..586eea6f7c4 100644 --- a/code/modules/reagents/reagent_containers/syringes.dm +++ b/code/modules/reagents/reagent_containers/syringes.dm @@ -246,8 +246,10 @@ var/hit_area = affecting.name - if((user != target) && H.check_shields(7, src, user, "\the [src]")) - return + if(user != target) + var/list/shieldcall_results = target.run_mob_defense(7, attack_type = ATTACK_TYPE_MELEE, weapon = src, hit_zone = hit_area, clickchain = new /datum/event_args/actor/clickchain(user)) + if(shieldcall_results[SHIELDCALL_ARG_FLAGS] & SHIELDCALL_FLAG_ATTACK_BLOCKED) + return if (target != user && H.legacy_mob_armor(target_zone, "melee") > 5 && prob(50)) for(var/mob/O in viewers(world.view, user)) diff --git a/code/modules/research/designs/weapons.dm b/code/modules/research/designs/weapons.dm index 350c6ea1ae3..e966586ce3a 100644 --- a/code/modules/research/designs/weapons.dm +++ b/code/modules/research/designs/weapons.dm @@ -331,14 +331,14 @@ id = "chargesword" req_tech = list(TECH_COMBAT = 6, TECH_MAGNET = 4, TECH_ENGINEERING = 5, TECH_ILLEGAL = 4, TECH_ARCANE = 1) materials_base = list(MAT_PLASTEEL = 3500, MAT_GLASS = 1000, MAT_LEAD = 2250, MAT_METALHYDROGEN = 500) - build_path = /obj/item/melee/energy/sword/charge + build_path = /obj/item/melee/transforming/energy/sword/charge /datum/design/science/weapon/melee/eaxe design_name = "Energy Axe" id = "chargeaxe" req_tech = list(TECH_COMBAT = 6, TECH_MAGNET = 5, TECH_ENGINEERING = 4, TECH_ILLEGAL = 4) materials_base = list(MAT_PLASTEEL = 3500, MAT_OSMIUM = 2000, MAT_LEAD = 2000, MAT_METALHYDROGEN = 500) - build_path = /obj/item/melee/energy/axe/charge + build_path = /obj/item/melee/transforming/energy/axe/charge /datum/design/science/weapon/grenade abstract_type = /datum/design/science/weapon/grenade diff --git a/code/modules/shieldgen/energy_field.dm b/code/modules/shieldgen/energy_field.dm index 8d195c0f97f..666014f6393 100644 --- a/code/modules/shieldgen/energy_field.dm +++ b/code/modules/shieldgen/energy_field.dm @@ -58,7 +58,7 @@ user.do_attack_animation(src) user.setClickCooldown(user.get_attack_speed()) -/obj/effect/energy_field/inflict_atom_damage(damage, tier, flag, mode, attack_type, datum/weapon, gradual) +/obj/effect/energy_field/inflict_atom_damage(damage, damage_type, damage_tier, damage_flag, damage_mode, hit_zone, attack_type, datum/weapon) adjust_strength(damage / 20) return damage diff --git a/code/modules/shieldgen/energy_shield.dm b/code/modules/shieldgen/energy_shield.dm index 4f3d459109f..434a484ad33 100644 --- a/code/modules/shieldgen/energy_shield.dm +++ b/code/modules/shieldgen/energy_shield.dm @@ -231,15 +231,15 @@ if(!disabled_for) take_damage_legacy(rand(10,15) / severity, SHIELD_DAMTYPE_PHYSICAL) - // Fire /obj/effect/shield/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) if(!disabled_for) take_damage_legacy(rand(5,10), SHIELD_DAMTYPE_HEAT) - // Projectiles -/obj/effect/shield/bullet_act(var/obj/projectile/proj) +/obj/effect/shield/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + impact_flags &= ~PROJECTILE_IMPACT_FLAGS_SHOULD_NOT_HIT + . = ..() if(proj.damage_type == BURN) take_damage_legacy(proj.get_structure_damage(), SHIELD_DAMTYPE_HEAT) else if (proj.damage_type == BRUTE) @@ -247,7 +247,6 @@ else //TODO - This will never happen because of get_structure_damage() only returning values for BRUTE and BURN damage types take_damage_legacy(proj.get_structure_damage(), SHIELD_DAMTYPE_EM) - // Attacks with hand tools. Blocked by Hyperkinetic flag. /obj/effect/shield/attackby(var/obj/item/I as obj, var/mob/user as mob) user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN) diff --git a/code/modules/shieldgen/sheldwallgen.dm b/code/modules/shieldgen/sheldwallgen.dm index d5cbc172a19..7220b26436b 100644 --- a/code/modules/shieldgen/sheldwallgen.dm +++ b/code/modules/shieldgen/sheldwallgen.dm @@ -213,11 +213,9 @@ src.cleanup(8) ..() -/obj/machinery/shieldwallgen/bullet_act(var/obj/projectile/Proj) - storedpower -= 400 * Proj.get_structure_damage() - ..() - return - +/obj/machinery/shieldwallgen/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + storedpower -= 400 * proj.get_structure_damage() //////////////Containment Field START /obj/machinery/shieldwall @@ -277,18 +275,15 @@ else gen_secondary.storedpower -= power_usage - -/obj/machinery/shieldwall/bullet_act(var/obj/projectile/Proj) +/obj/machinery/shieldwall/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() if(needs_power) var/obj/machinery/shieldwallgen/G if(prob(50)) G = gen_primary else G = gen_secondary - G.storedpower -= 400 * Proj.get_structure_damage() - ..() - return - + G.storedpower -= 400 * proj.get_structure_damage() /obj/machinery/shieldwall/legacy_ex_act(severity) if(needs_power) diff --git a/code/modules/shuttles/shuttle_console.dm b/code/modules/shuttles/shuttle_console.dm index 9459cc25fba..9a639edbff8 100644 --- a/code/modules/shuttles/shuttle_console.dm +++ b/code/modules/shuttles/shuttle_console.dm @@ -136,16 +136,6 @@ to_chat(user, "You short out the console's ID checking system. It's now available to everyone!") return 1 -/obj/machinery/computer/shuttle_control/bullet_act(var/obj/projectile/Proj) - visible_message("\The [Proj] ricochets off \the [src]!") - -/obj/machinery/computer/shuttle_control/legacy_ex_act() - return - -/obj/machinery/computer/shuttle_control/emp_act() - return - - GLOBAL_LIST_BOILERPLATE(papers_dockingcode, /obj/item/paper/dockingcodes) /hook/roundstart/proc/populate_dockingcodes() for(var/paper in GLOB.papers_dockingcode) diff --git a/code/modules/species/promethean/promethean_blob.dm b/code/modules/species/promethean/promethean_blob.dm index 510e0780792..835fe946bce 100644 --- a/code/modules/species/promethean/promethean_blob.dm +++ b/code/modules/species/promethean/promethean_blob.dm @@ -195,11 +195,10 @@ set_light(max(1,min(5,rad_glow/15)), max(1,min(10,rad_glow/25)), color) update_icon() -/mob/living/simple_mob/slime/promethean/bullet_act(obj/projectile/P) +/mob/living/simple_mob/slime/promethean/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(humanform) - return humanform.bullet_act(P) - else - return ..() + return proj.impact_redirect(humanform, args) + return ..() /mob/living/simple_mob/slime/promethean/death(gibbed, deathmessage = "rapidly loses cohesion, splattering across the ground...") if(humanform) diff --git a/code/modules/species/xenomorphs/alien_facehugger.dm b/code/modules/species/xenomorphs/alien_facehugger.dm index 6d3c82a512c..159d148bed6 100644 --- a/code/modules/species/xenomorphs/alien_facehugger.dm +++ b/code/modules/species/xenomorphs/alien_facehugger.dm @@ -66,9 +66,9 @@ var/const/MAX_ACTIVE_TIME = 400 Die() return -/obj/item/clothing/mask/facehugger/bullet_act() +/obj/item/clothing/mask/facehugger/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() Die() - return /obj/item/clothing/mask/facehugger/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume) if(exposed_temperature > T0C+80) @@ -351,7 +351,7 @@ var/const/MAX_ACTIVE_TIME = 400 Die() return -/mob/living/simple_mob/animal/space/alien/facehugger/bullet_act() +/mob/living/simple_mob/animal/space/alien/facehugger/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) Die() return diff --git a/code/modules/spells/spell_projectile.dm b/code/modules/spells/spell_projectile.dm index 60538b4ba7b..8b3e0be5ea1 100644 --- a/code/modules/spells/spell_projectile.dm +++ b/code/modules/spells/spell_projectile.dm @@ -6,7 +6,6 @@ var/spell/targeted/projectile/carried - penetrating = 0 range = WORLD_ICON_SIZE * 10 //set by the duration of the spell var/proj_trail = 0 //if it leaves a trail @@ -47,10 +46,12 @@ prox_cast(carried.choose_prox_targets(user = carried.holder, spell_holder = src)) return 1 -/obj/projectile/spell_projectile/on_impact() +/obj/projectile/spell_projectile/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return if(loc && carried) prox_cast(carried.choose_prox_targets(user = carried.holder, spell_holder = src)) - return 1 /obj/projectile/spell_projectile/seeking name = "seeking spell" diff --git a/code/modules/spells/targeted/ethereal_jaunt.dm b/code/modules/spells/targeted/ethereal_jaunt.dm index 078cc2ac2ae..d1cb091cd77 100644 --- a/code/modules/spells/targeted/ethereal_jaunt.dm +++ b/code/modules/spells/targeted/ethereal_jaunt.dm @@ -105,5 +105,3 @@ /obj/effect/dummy/spell_jaunt/legacy_ex_act(blah) return -/obj/effect/dummy/spell_jaunt/bullet_act(blah) - return diff --git a/code/modules/surgery/generic.dm b/code/modules/surgery/generic.dm index 55b3c517a8d..4624eae98cc 100644 --- a/code/modules/surgery/generic.dm +++ b/code/modules/surgery/generic.dm @@ -76,7 +76,7 @@ /obj/item/surgical/scalpel/laser3 = 95, \ /obj/item/surgical/scalpel/laser2 = 85, \ /obj/item/surgical/scalpel/laser1 = 75, \ - /obj/item/melee/energy/sword = 5 + /obj/item/melee/transforming/energy/sword = 5 ) priority = 2 req_open = 0 diff --git a/code/modules/vehicles/sealed.dm b/code/modules/vehicles/sealed.dm index 27b14622f94..deaf039bbe4 100644 --- a/code/modules/vehicles/sealed.dm +++ b/code/modules/vehicles/sealed.dm @@ -176,7 +176,7 @@ * * silent - suppress user messages * * suppressed - suppress external messages */ -/obj/vehicle/sealed/proc/mob_exit(mob/exiting, datum/event_args/actor/actor, atom/new_loc, silent, suppressed) +/obj/vehicle/sealed/proc/mob_exit(mob/exiting, datum/event_args/actor/actor, atom/new_loc = drop_location(), silent, suppressed) var/old_control_flags = occupants[exiting] remove_occupant(exiting) exiting.forceMove(new_loc) diff --git a/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm b/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm index c575a01e3aa..b2de5f21e69 100644 --- a/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm +++ b/code/modules/vehicles/sealed/mecha/equipment/weapons/energy/pulse.dm @@ -11,11 +11,3 @@ /obj/projectile/beam/pulse/heavy name = "heavy pulse laser" icon_state = "pulse1_bl" - var/life = 20 - -/obj/projectile/beam/pulse/heavy/Bump(atom/A) - A.bullet_act(src, def_zone) - src.life -= 10 - if(life <= 0) - qdel(src) - return diff --git a/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm b/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm index 90966f17133..6e9b11c3884 100644 --- a/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm +++ b/code/modules/vehicles/sealed/mecha/equipment/weapons/weapons.dm @@ -83,11 +83,11 @@ if(!istype(P)) return - P.accuracy -= user.get_accuracy_penalty() + P.accuracy_overall_modify *= 1 - (user.get_accuracy_penalty() / 100) // Some modifiers make it harder or easier to hit things. for(var/datum/modifier/M in user.modifiers) if(!isnull(M.accuracy)) - P.accuracy += M.accuracy + P.accuracy_overall_modify *= 1 + (M.accuracy / 100) if(!isnull(M.accuracy_dispersion)) P.dispersion = max(P.dispersion + M.accuracy_dispersion, 0) diff --git a/code/modules/vehicles/sealed/mecha/mecha.dm b/code/modules/vehicles/sealed/mecha/mecha.dm index e06cb7c084a..f5e86771f09 100644 --- a/code/modules/vehicles/sealed/mecha/mecha.dm +++ b/code/modules/vehicles/sealed/mecha/mecha.dm @@ -304,16 +304,16 @@ src.legacy_eject_occupant() for(var/mob/M in src) //Be Extra Sure M.forceMove(get_turf(src)) - M.loc.Entered(M) if(M != src.occupant_legacy) step_rand(M) + for(var/atom/movable/A in src.cargo) A.forceMove(get_turf(src)) var/turf/T = get_turf(A) if(T) T.Entered(A) step_rand(A) - + cargo = list() if(prob(30)) explosion(get_turf(loc), 0, 0, 1, 3) @@ -918,6 +918,7 @@ /////////////////////////////////// //ATM, the ignore_threshold is literally only used for the pulse rifles beams used mostly by deathsquads. +// todo: this is uh, not a check, this is a **roll**. /obj/vehicle/sealed/mecha/proc/check_for_internal_damage(var/list/possible_int_damage,var/ignore_threshold=null) if(!islist(possible_int_damage) || !length(possible_int_damage)) return if(prob(30)) @@ -960,16 +961,6 @@ occupant_message("Internal fire extinquished.") if(MECHA_INT_TANK_BREACH) occupant_message("Damaged internal tank has been sealed.") - return - - -//////////////////////////////////////// -//////// Health related procs //////// -//////////////////////////////////////// - -/obj/vehicle/sealed/mecha/bullet_act(obj/projectile/Proj) - . = ..() - /obj/vehicle/sealed/mecha/proc/take_damage_legacy(amount, type="brute") update_damage_alerts() @@ -1158,17 +1149,16 @@ return -// todo: MAKE INFLICT_DAMAGE_INSTANCE() A THING ON HIT HANDLING PR!! -/obj/vehicle/sealed/mecha/bullet_act(var/obj/projectile/Proj) //wrapper - if(istype(Proj, /obj/projectile/test)) - var/obj/projectile/test/Test = Proj - Test.hit |= occupant_legacy // Register a hit on the occupant_legacy, for things like turrets, or in simple-mob cases stopping friendly fire in firing line mode. - return +/obj/vehicle/sealed/mecha/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + if(istype(proj, /obj/projectile/test)) + var/obj/projectile/test/Test = proj + Test.hit |= occupant_legacy // Register a hit on the occupant, for things like turrets, or in simple-mob cases stopping friendly fire in firing line mode. + return ..() - src.log_message("Hit by projectile. Type: [Proj.name]([Proj.damage_flag]).",1) - call((proc_res["dynbulletdamage"]||src), "dynbulletdamage")(Proj) //calls equipment - ..() - return + src.log_message("Hit by projectile. Type: [proj.name]([proj.damage_flag]).",1) + impact_flags |= call((proc_res["dynbulletdamage"]||src), "dynbulletdamage")(proj) //calls equipment + impact_flags |= PROJECTILE_IMPACT_SKIP_STANDARD_DAMAGE + return ..() /obj/vehicle/sealed/mecha/proc/dynbulletdamage(var/obj/projectile/Proj) var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] @@ -1210,7 +1200,7 @@ if(pass_damage < temp_damage_minimum)//too pathetic to really damage you. src.occupant_message("The armor deflects incoming projectile.") src.visible_message("The [src.name] armor deflects\the [Proj]") - return + return PROJECTILE_IMPACT_BLOCKED else if(Proj.armor_penetration < temp_minimum_penetration) //If you don't have enough pen, you won't do full damage src.occupant_message("\The [Proj] struggles to pierce \the [src] armor.") @@ -1230,25 +1220,21 @@ src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),ignore_threshold) //AP projectiles have a chance to cause additional damage - if(Proj.penetrating) - var/distance = get_dist(Proj.starting, get_turf(loc)) - var/hit_occupant = 1 //only allow the occupant_legacy to be hit once - for(var/i in 1 to min(Proj.penetrating, round(Proj.damage/15))) + if(Proj.legacy_penetrating) + var/hit_occupant = 1 //only allow the occupant to be hit once + for(var/i in 1 to min(Proj.legacy_penetrating, round(Proj.damage/15))) if(src.occupant_legacy && hit_occupant && prob(20)) - Proj.projectile_attack_mob(src.occupant_legacy, distance) + Proj.impact(occupant_legacy) hit_occupant = 0 else if(pass_damage > internal_damage_minimum) //Only decently painful attacks trigger a chance of mech damage. src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT), 1) - Proj.penetrating-- + Proj.legacy_penetrating-- if(prob(15)) break //give a chance to exit early - Proj.on_hit(src) //on_hit just returns if it's argument is not a living mob so does this actually do anything? - return - //This refer to whenever you are caught in an explosion. /obj/vehicle/sealed/mecha/legacy_ex_act(severity) var/obj/item/mecha_parts/component/armor/ArmC = internal_components[MECH_ARMOR] @@ -1267,19 +1253,13 @@ src.log_append_to_last("Armor saved, changing severity to [severity].") switch(severity) if(1.0) - src.take_damage_legacy(initial(src.integrity), "bomb") + src.take_damage_legacy(initial(src.integrity)/1.25, "bomb") if(2.0) - if (prob(30)) - src.take_damage_legacy(initial(src.integrity), "bomb") - else - src.take_damage_legacy(initial(src.integrity)/2, "bomb") - src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1) + src.take_damage_legacy(initial(src.integrity)/2.5, "bomb") + src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1) if(3.0) - if (prob(5)) - qdel(src) - else - src.take_damage_legacy(initial(src.integrity)/5, "bomb") - src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1) + src.take_damage_legacy(initial(src.integrity)/8, "bomb") + src.check_for_internal_damage(list(MECHA_INT_FIRE,MECHA_INT_TEMP_CONTROL,MECHA_INT_TANK_BREACH,MECHA_INT_CONTROL_LOST,MECHA_INT_SHORT_CIRCUIT),1) return /*Will fix later -Sieve @@ -2724,7 +2704,6 @@ // removing.mobility_flags = NONE removing.clear_alert("charge") removing.clear_alert("mech damage") - removing.reset_perspective() if(occupant_legacy == removing) occupant_legacy = null diff --git a/code/modules/vehicles/sealed/mecha/mecha_actions.dm b/code/modules/vehicles/sealed/mecha/mecha_actions.dm index 188179b6e54..a0ec46bdf31 100644 --- a/code/modules/vehicles/sealed/mecha/mecha_actions.dm +++ b/code/modules/vehicles/sealed/mecha/mecha_actions.dm @@ -68,7 +68,7 @@ return var/obj/vehicle/sealed/mecha/chassis = target chassis.lights() - button_icon_state = "mech_lights_[chassis.lights ? "off" : "on"]" + button_icon_state = "mech_lights_[chassis.lights ? "on" : "off"]" update_buttons() /datum/action/mecha/mech_toggle_internals @@ -80,7 +80,7 @@ if(.) return var/obj/vehicle/sealed/mecha/chassis = target - button_icon_state = "mech_internals_[chassis.use_internal_tank ? "off" : "on"]" + button_icon_state = "mech_internals_[chassis.use_internal_tank ? "on" : "off"]" update_buttons() chassis.internal_tank() @@ -105,7 +105,7 @@ return var/obj/vehicle/sealed/mecha/chassis = target chassis.strafing() - button_icon_state = "mech_strafe_[chassis.strafing ? "off" : "on"]" + button_icon_state = "mech_strafe_[chassis.strafing ? "on" : "off"]" update_buttons() /datum/action/mecha/mech_defence_mode @@ -118,7 +118,7 @@ return var/obj/vehicle/sealed/mecha/chassis = target chassis.defence_mode() - button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "off" : "on"]" + button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "on" : "off"]" update_buttons() /datum/action/mecha/mech_overload_mode @@ -131,7 +131,7 @@ return var/obj/vehicle/sealed/mecha/chassis = target chassis.overload() - button_icon_state = "mech_overload_[chassis.overload ? "off" : "on"]" + button_icon_state = "mech_overload_[chassis.overload ? "on" : "off"]" update_buttons() /datum/action/mecha/mech_smoke @@ -155,7 +155,7 @@ return var/obj/vehicle/sealed/mecha/chassis = target chassis.zoom() - button_icon_state = "mech_zoom_[chassis.zoom ? "off" : "on"]" + button_icon_state = "mech_zoom_[chassis.zoom ? "on" : "off"]" update_buttons() /datum/action/mecha/mech_toggle_thrusters @@ -168,7 +168,7 @@ return var/obj/vehicle/sealed/mecha/chassis = target chassis.thrusters() - button_icon_state = "mech_thrusters_[chassis.thrusters ? "off" : "on"]" + button_icon_state = "mech_thrusters_[chassis.thrusters ? "on" : "off"]" update_buttons() /datum/action/mecha/mech_cycle_equip //I'll be honest, i don't understand this part, buuuuuut it works! @@ -241,7 +241,7 @@ if(.) return var/obj/vehicle/sealed/mecha/chassis = target - button_icon_state = "mech_phasing_[chassis.phasing ? "off" : "on"]" + button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]" update_buttons() chassis.phasing() @@ -254,7 +254,7 @@ if(.) return var/obj/vehicle/sealed/mecha/chassis = target - button_icon_state = "mech_phasing_[chassis.cloaked ? "off" : "on"]" + button_icon_state = "mech_phasing_[chassis.cloaked ? "on" : "off"]" update_buttons() chassis.toggle_cloaking() diff --git a/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm b/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm index 210ec44013f..c7f2b5ee239 100644 --- a/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm +++ b/code/modules/vehicles/sealed/mecha/mecha_wreckage.dm @@ -20,16 +20,6 @@ crowbar_salvage = new return -/obj/effect/decal/mecha_wreckage/legacy_ex_act(severity) - if(severity < 2) - spawn - qdel(src) - return - -/obj/effect/decal/mecha_wreckage/bullet_act(var/obj/projectile/Proj) - return - - /obj/effect/decal/mecha_wreckage/attackby(obj/item/W as obj, mob/user as mob) if(istype(W, /obj/item/weldingtool)) var/obj/item/weldingtool/WT = W diff --git a/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm b/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm index 49381b86016..10e46a3ccd1 100644 --- a/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm +++ b/code/modules/vehicles/sealed/mecha/subtypes/combat/gorilla.dm @@ -128,9 +128,11 @@ icon_state = "shell" damage = 1000 // In order to 1-hit any other mech and royally fuck anyone unfortunate enough to get in the way. -/obj/projectile/bullet/cannon/on_hit(var/atom/target, var/blocked = 0) +/obj/projectile/bullet/cannon/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return explosion(target, 0, 0, 2, 4) - return 1 /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/cannon/weak name = "8.8 cm KwK 36" diff --git a/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm b/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm index abbbddf2bf1..4565776996a 100644 --- a/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm +++ b/code/modules/vehicles/sealed/mecha/subtypes/working/ripley.dm @@ -27,13 +27,6 @@ icon_scale_x = 1.2 icon_scale_y = 1.2 -/obj/vehicle/sealed/mecha/working/ripley/Destroy() - for(var/atom/movable/A in src.cargo) - A.forceMove(loc) - step_rand(A) - cargo.Cut() - ..() - /obj/vehicle/sealed/mecha/working/ripley/firefighter desc = "Standard APLU chassis was refitted with additional thermal protection and cistern." name = "APLU \"Firefighter\"" diff --git a/code/modules/vehicles_legacy/Securitrain_vr.dm b/code/modules/vehicles_legacy/Securitrain_vr.dm index 005f7ac2b57..81be49d1a6b 100644 --- a/code/modules/vehicles_legacy/Securitrain_vr.dm +++ b/code/modules/vehicles_legacy/Securitrain_vr.dm @@ -101,12 +101,11 @@ ..() //cargo trains are open topped, so there is a chance the projectile will hit the mob ridding the train instead -/obj/vehicle_old/train/security/bullet_act(var/obj/projectile/Proj) +/obj/vehicle_old/train/security/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(has_buckled_mobs() && prob(70)) - var/mob/living/M = pick(buckled_mobs) - M.bullet_act(Proj) - return - ..() + var/mob/buckled = pick(buckled_mobs) + return proj.impact_redirect(buckled, args) + return ..() /obj/vehicle_old/train/security/update_icon() if(open) diff --git a/code/modules/vehicles_legacy/bike.dm b/code/modules/vehicles_legacy/bike.dm index 121c533d38f..9e5ee11e95c 100644 --- a/code/modules/vehicles_legacy/bike.dm +++ b/code/modules/vehicles_legacy/bike.dm @@ -174,12 +174,11 @@ ..() -/obj/vehicle_old/bike/bullet_act(var/obj/projectile/Proj) +/obj/vehicle_old/bike/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(has_buckled_mobs() && prob(protection_percent)) var/mob/living/L = pick(buckled_mobs) - L.bullet_act(Proj) - return - ..() + return L.bullet_act(arglist(args)) + return ..() /obj/vehicle_old/bike/update_icon() cut_overlays() diff --git a/code/modules/vehicles_legacy/cargo_train.dm b/code/modules/vehicles_legacy/cargo_train.dm index 0f5c981c533..08464071630 100644 --- a/code/modules/vehicles_legacy/cargo_train.dm +++ b/code/modules/vehicles_legacy/cargo_train.dm @@ -86,7 +86,7 @@ /* //cargo trains are open topped, so there is a chance the projectile will hit the mob ridding the train instead -/obj/vehicle_old/train/cargo/bullet_act(var/obj/projectile/Proj) +/obj/vehicle_old/train/cargo/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(has_buckled_mobs() && prob(70)) var/mob/living/L = pick(buckled_mobs) L.bullet_act(Proj) diff --git a/code/modules/vehicles_legacy/rover_vr.dm b/code/modules/vehicles_legacy/rover_vr.dm index cbc180b2767..ef0305f9abb 100644 --- a/code/modules/vehicles_legacy/rover_vr.dm +++ b/code/modules/vehicles_legacy/rover_vr.dm @@ -102,12 +102,11 @@ ..() //cargo trains are open topped, so there is a chance the projectile will hit the mob ridding the train instead -/obj/vehicle_old/train/rover/bullet_act(var/obj/projectile/Proj) +/obj/vehicle_old/train/rover/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(has_buckled_mobs() && prob(70)) - var/mob/living/M = pick(buckled_mobs) - M.bullet_act(Proj) - return - ..() + var/mob/buckled = pick(buckled_mobs) + return proj.impact_redirect(buckled, args) + return ..() /obj/vehicle_old/train/rover/update_icon() if(open) diff --git a/code/modules/vehicles_legacy/train.dm b/code/modules/vehicles_legacy/train.dm index 1cd8e8bab08..32f45a77dab 100644 --- a/code/modules/vehicles_legacy/train.dm +++ b/code/modules/vehicles_legacy/train.dm @@ -55,12 +55,11 @@ add_attack_logs(D,M,"Ran over with [src.name]") //trains are commonly open topped, so there is a chance the projectile will hit the mob riding the train instead -/obj/vehicle_old/train/bullet_act(var/obj/projectile/Proj) +/obj/vehicle_old/train/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(has_buckled_mobs() && prob(70)) - var/mob/living/L = pick(buckled_mobs) - L.bullet_act(Proj) - return - ..() + var/mob/buckled = pick(buckled_mobs) + return proj.impact_redirect(buckled, args) + return ..() /obj/vehicle_old/train/update_icon() if(open) diff --git a/code/modules/vehicles_legacy/vehicle.dm b/code/modules/vehicles_legacy/vehicle.dm index 8e5b2915207..80de4b28517 100644 --- a/code/modules/vehicles_legacy/vehicle.dm +++ b/code/modules/vehicles_legacy/vehicle.dm @@ -13,6 +13,8 @@ anchored = 1 animate_movement=1 light_range = 3 + // todo: uses old integrity for now + integrity_flags = INTEGRITY_INDESTRUCTIBLE buckle_allowed = TRUE buckle_flags = BUCKLING_PASS_PROJECTILES_UPWARDS @@ -121,9 +123,9 @@ else ..() -/obj/vehicle_old/bullet_act(var/obj/projectile/Proj) - health -= Proj.get_structure_damage() - ..() +/obj/vehicle_old/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + health -= proj.get_structure_damage() healthcheck() /obj/vehicle_old/proc/adjust_health(amount) diff --git a/code/modules/vore/fluffstuff/custom_items.dm b/code/modules/vore/fluffstuff/custom_items.dm index 0ac75f8d292..531e04b41d1 100644 --- a/code/modules/vore/fluffstuff/custom_items.dm +++ b/code/modules/vore/fluffstuff/custom_items.dm @@ -66,50 +66,6 @@ if(!parts) qdel(src) -/* -//JoanRisu:Joan Risu -/obj/item/flame/lighter/zippo/fluff/joan - name = "Federation Zippo Lighter" - desc = "A red zippo lighter with the United Federation Logo on it." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "joanzip" - -//JoanRisu:Joan Risu -/obj/item/sword/fluff/joanaria - name = "Aria" - desc = "A beautifully crafted rapier owned by Joan Risu. It has a thin blade and is used for quick attacks." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "joanaria" - icon_override = 'icons/vore/custom_items_vr.dmi' - item_state = "joanariamob" - origin_tech = "materials=7" - damage_force = 15 - sharp = 1 - edge = 1 - attack_sound = 'sound/weapons/bladeslice.ogg' - - -/obj/item/sword/fluff/joanaria/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - - if(default_parry_check(user, attacker, damage_source) && prob(75)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 - -//joanrisu:Katarina Eine -/obj/item/material/knife/tacknife/combatknife/fluff/katarina - name = "tactical Knife" - desc = "A tactical knife with a small butterly engraved on the blade." -*/ - -/obj/item/material/knife/tacknife/combatknife/fluff/katarina/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - - if(default_parry_check(user, attacker, damage_source) && prob(75)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 //For General use /obj/item/sword/fluff/joanaria/scisword @@ -119,87 +75,6 @@ icon_state = "scisword" origin_tech = "materials=7" -/* -//john.wayne9392:Harmony Prechtl -/obj/item/twohanded/fireaxe/fluff/mjollnir - name = "Mjollnir" - desc = "Large hammer that looks like it can do a great deal of damage if properly used." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "harmonymjollnir" - origin_tech = "materials=7" - attack_verb = list("attacked", "hammered", "smashed", "slammed", "crushed") - -//JoanRisu:Joan Risu -/obj/item/card/id/centcom/station/fluff/joanbadge - name = "Faded Badge" - desc = "A faded badge, backed with leather, that reads 'NT Security Force' across the front." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "joanbadge" - registered_name = "Joan Risu" - assignment = "Centcom Officer" - - -/obj/item/card/id/centcom/station/fluff/joanbadge/attack_self(mob/user) - . = ..() - if(.) - return - if(isliving(user)) - user.visible_message("[user] flashes their golden security badge.\nIt reads:NT Security.","You display the faded badge.\nIt reads: NT Security.") - -/obj/item/card/id/centcom/station/fluff/joanbadge/attack(mob/living/carbon/human/M, mob/living/user) - if(isliving(user)) - user.visible_message("[user] invades [M]'s personal space, thrusting [src] into their face insistently.","You invade [M]'s personal space, thrusting [src] into their face insistently.") - -//JoanRisu:Joan Risu -/obj/item/pda/heads/hos/joanpda - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "pda-joan" - -//Vorrarkul:Lucina Dakarim -/obj/item/pda/heads/cmo/fluff/lucinapda - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "pda-lucina" - -//john.wayne9392:Harmony Prechtl -/obj/item/modkit_conversion/fluff/harmonyspace - name = "Harmony's captain space suit modkit" - desc = "A kit containing all the needed tools and parts to modify a Captain's hardsuit. It has green and yellow parts inside." - - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "harmony_kit" - - from_helmet = /obj/item/clothing/head/helmet/space/capspace - from_suit = /obj/item/clothing/suit/armor/captain - to_helmet = /obj/item/clothing/head/helmet/space/capspace/fluff/harmhelm - to_suit = /obj/item/clothing/suit/armor/captain/fluff/harmsuit - -//john.wayne9392:Harmony Prechtl -/obj/item/modkit_conversion/fluff/harmonysuit - name = "Harmony's captain suit modkit" - desc = "A sewing kit containing all the needed tools and fabric to modify a Captain's suit and hat. It has green and yellow fabrics inside." - - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "harmony_kit" - - from_helmet = /obj/item/clothing/head/caphat - from_suit = /obj/item/clothing/under/rank/captain - to_helmet = /obj/item/clothing/head/centhat/fluff/harmhat - to_suit = /obj/item/clothing/under/rank/captain/fluff/harmuniform - -//scree:Scree -/obj/item/modkit_conversion/fluff/screekit - name = "Scree's hardsuit modification kit" - desc = "A kit containing all the needed tools and parts to modify a hardsuit for a specific user. This one looks like it's fitted for a winged creature." - - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "modkit" - - from_helmet = /obj/item/clothing/head/helmet/space/void - from_suit = /obj/item/clothing/suit/space/void - to_helmet = /obj/item/clothing/head/helmet/space/void/engineering/hazmat/fluff/screehelm - to_suit = /obj/item/clothing/suit/space/void/engineering/hazmat/fluff/screespess -*/ - //General Use /obj/item/flag name = "Nanotrasen Banner" @@ -1254,6 +1129,7 @@ ..() //jacknoir413:Areax Third +// todo: check sprite, if it matches citmain just integrate this to citrp proper. /obj/item/melee/baton/fluff/stunstaff name = "Electrostaff" desc = "Six-foot long staff from dull, rugged metal, with two thin spikes protruding from each end. Small etching near to the middle of it reads 'Children Of Nyx Facilities: Product No. 12'." @@ -1270,6 +1146,10 @@ attack_verb = list("beaten") lightcolor = "#CC33FF" + passive_parry = /datum/passive_parry/melee{ + parry_chance_melee = 30; + } + //Two Handed var/wielded = 0 var/base_name = "stunstaff" @@ -1294,13 +1174,6 @@ update_icon() ..() -/obj/item/melee/baton/fluff/stunstaff/handle_shield(mob/user, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack") - if(wielded && default_parry_check(user, attacker, damage_source) && prob(30)) - user.visible_message("\The [user] parries [attack_text] with \the [src]!") - playsound(user.loc, 'sound/weapons/punchmiss.ogg', 50, 1) - return 1 - return 0 - /obj/item/melee/baton/fluff/stunstaff/update_icon() icon_state = "[base_icon][wielded][status]" item_state = icon_state diff --git a/code/modules/xenoarcheaology/artifacts/artifact.dm b/code/modules/xenoarcheaology/artifacts/artifact.dm index b29df379625..a55ec3da15e 100644 --- a/code/modules/xenoarcheaology/artifacts/artifact.dm +++ b/code/modules/xenoarcheaology/artifacts/artifact.dm @@ -4,6 +4,7 @@ icon = 'icons/obj/xenoarchaeology.dmi' icon_state = "ano00" var/icon_num = 0 + integrity_flags = INTEGRITY_INDESTRUCTIBLE density = TRUE var/datum/artifact_effect/my_effect var/datum/artifact_effect/secondary_effect @@ -223,7 +224,7 @@ if(secondary_effect && secondary_effect.trigger == TRIGGER_TOXIN && prob(25)) secondary_effect.ToggleActivate(0) else if(istype(W,/obj/item/melee/baton) && W:status ||\ - istype(W,/obj/item/melee/energy) ||\ + istype(W,/obj/item/melee/transforming/energy) ||\ istype(W,/obj/item/melee/cultblade) ||\ istype(W,/obj/item/card/emag) ||\ istype(W,/obj/item/multitool)) @@ -274,16 +275,17 @@ to_chat(M, "You accidentally touch [src].") ..() -/obj/machinery/artifact/bullet_act(var/obj/projectile/P) - if(istype(P,/obj/projectile/bullet)) +/obj/machinery/artifact/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(istype(proj,/obj/projectile/bullet)) if(my_effect.trigger == TRIGGER_FORCE) my_effect.ToggleActivate() if(secondary_effect && secondary_effect.trigger == TRIGGER_FORCE && prob(25)) secondary_effect.ToggleActivate(0) - else if(istype(P,/obj/projectile/beam) ||\ - istype(P,/obj/projectile/ion) ||\ - istype(P,/obj/projectile/energy)) + else if(istype(proj,/obj/projectile/beam) ||\ + istype(proj,/obj/projectile/ion) ||\ + istype(proj,/obj/projectile/energy)) if(my_effect.trigger == TRIGGER_ENERGY) my_effect.ToggleActivate() if(secondary_effect && secondary_effect.trigger == TRIGGER_ENERGY && prob(25)) diff --git a/code/modules/xenoarcheaology/tools/coolant_tank.dm b/code/modules/xenoarcheaology/tools/coolant_tank.dm index ba9ac92b9f4..feeeadb1fca 100644 --- a/code/modules/xenoarcheaology/tools/coolant_tank.dm +++ b/code/modules/xenoarcheaology/tools/coolant_tank.dm @@ -9,9 +9,10 @@ . = ..() reagents.add_reagent("coolant", 1000) -/obj/structure/reagent_dispensers/coolanttank/bullet_act(var/obj/projectile/Proj) - if(Proj.get_structure_damage()) - if(!istype(Proj ,/obj/projectile/beam/lasertag) && !istype(Proj ,/obj/projectile/beam/practice) ) // TODO: make this not terrible +/obj/structure/reagent_dispensers/coolanttank/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) + . = ..() + if(proj.get_structure_damage()) + if(!istype(proj ,/obj/projectile/beam/lasertag) && !istype(proj ,/obj/projectile/beam/practice) ) // TODO: make this not terrible explode() /obj/structure/reagent_dispensers/coolanttank/legacy_ex_act() diff --git a/code/modules/xenobio/items/weapons.dm b/code/modules/xenobio/items/weapons.dm index 243054319ca..e3a2c4b73cd 100644 --- a/code/modules/xenobio/items/weapons.dm +++ b/code/modules/xenobio/items/weapons.dm @@ -91,7 +91,11 @@ /obj/projectile/beam/stun/xeno/weak //Weaker variant for non-research equipment, turrets, or rapid fire types. agony = 3 -/obj/projectile/beam/stun/xeno/on_hit(var/atom/target, var/blocked = 0, var/def_zone = null) +/obj/projectile/beam/stun/xeno/on_impact(atom/target, impact_flags, def_zone, efficiency) + . = ..() + if(. & PROJECTILE_IMPACT_FLAGS_UNCONDITIONAL_ABORT) + return + if(istype(target, /mob/living)) var/mob/living/L = target if(L.mob_class & MOB_CLASS_SLIME) @@ -106,5 +110,3 @@ if(H.species && H.species.get_species_id() == SPECIES_ID_PROMETHEAN) if(agony == initial(agony)) // ?????? agony = round((14 * agony) - agony) //60-4 = 56, 56 / 4 = 14. Prior was flat 60 - agony of the beam to equate to 60. - - ..() diff --git a/code/modules/xenobio2/mob/xeno procs.dm b/code/modules/xenobio2/mob/xeno procs.dm index ad6846e281e..99c7e66db44 100644 --- a/code/modules/xenobio2/mob/xeno procs.dm +++ b/code/modules/xenobio2/mob/xeno procs.dm @@ -143,7 +143,7 @@ Divergence proc, used in mutation to make unique datums. /mob/living/simple_mob/xeno/proc/BuildReagentLists() return -/mob/living/simple_mob/xeno/bullet_act(var/obj/projectile/P) +/mob/living/simple_mob/xeno/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) //Shamelessly stolen from ablative armor. if((traitdat.traits[TRAIT_XENO_CHROMATIC]) && istype(P, /obj/projectile/beam)) visible_message(")\The beam reflects off of the [src]!") diff --git a/code/modules/xenobio2/mob/xeno.dm b/code/modules/xenobio2/mob/xeno.dm index 4f801e75df5..6b9cef60afb 100644 --- a/code/modules/xenobio2/mob/xeno.dm +++ b/code/modules/xenobio2/mob/xeno.dm @@ -92,7 +92,7 @@ Also includes Life and New if(!health) set_stat(DEAD) -/mob/living/simple_mob/xeno/bullet_act(var/obj/projectile/Proj) +/mob/living/simple_mob/xeno/on_bullet_act(obj/projectile/proj, impact_flags, list/bullet_act_args) if(istype(Proj, /obj/projectile/beam/stun/xeno)) var/obj/projectile/beam/stun/xeno/hit = Proj stasis += hit.stasisforce diff --git a/config/entries/game.txt b/config/entries/game.txt index 8f155dab9f7..b83f9f2e7cd 100644 --- a/config/entries/game.txt +++ b/config/entries/game.txt @@ -11,15 +11,9 @@ STARLIGHT ## Set to 0 to disable holidays (you monster) ALLOW_HOLIDAYS -## Engine submap probabilities -## Supermatter -ENGINE_SUBMAP EngineSubmap_SM 40 -## R-UST Fusion -ENGINE_SUBMAP EngineSubmap_RUST 40 -## Tesla -ENGINE_SUBMAP EngineSubmap_Tesla 20 -## Singulo -ENGINE_SUBMAP EngineSubmap_Singulo 20 +## Engine submap probabilities by name +# todo: add the rest once names are standardized +# ENGINE_SUBMAP ProcEngine_Triumph_RUST ## Alert Levels # ALERT_DESC_GREEN TEXT HERE diff --git a/icons/effects/defensive/README.md b/icons/effects/defensive/README.md new file mode 100644 index 00000000000..750cab6abc4 --- /dev/null +++ b/icons/effects/defensive/README.md @@ -0,0 +1,3 @@ +# Effects - Defensive + +Basically shields, parries, etc. diff --git a/icons/effects/defensive/main_parry.dmi b/icons/effects/defensive/main_parry.dmi new file mode 100644 index 0000000000000000000000000000000000000000..307ef5b4a7e86889f4f87450fd53f9f4c026e26a GIT binary patch literal 37674 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 literal 0 HcmV?d00001 diff --git a/icons/items/melee/basic.dmi b/icons/items/melee/basic.dmi new file mode 100644 index 0000000000000000000000000000000000000000..c1207c837d185f0effe348531348af796ba41079 GIT binary patch literal 2957 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 PzXs5D$B&fS23-3G=HY_0 literal 0 HcmV?d00001 diff --git a/icons/items/melee/transforming.dmi b/icons/items/melee/transforming.dmi new file mode 100644 index 0000000000000000000000000000000000000000..0a1c652185cff5dbea8a30e712722c21e177e2e6 GIT binary patch literal 30606 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%0AP7KYJRsvFrKYm2HT*q7D<4mcfj6hhu=^-YySR zWbMLCoiWh5iOG7OMD`ScoUaf>>fj@K-4>z)(+ucnH$Z9q@safGfoR{?Xyb3)*X6Lu zk9+zb4e)`E{-_H4E18)))(mv!=${w((@ST>Lw8`4L&Wz2ax0gAd?@rSA^!Z=j#Xk6 zEQc_|(m zf;!ZhX$In6lCFG&v&o$BL2#z5_{=akH0C>D!QdU=`tjv$3Xy&DQnC7?pglhvPDk6m z>6!2BCeocK-c`B%{TWT=-^(4+mw8G4D35dFgW5yApW~%k2M3)4#{Z?(T%{;KUj@$4 zoz4a4ec@fx9NJ=VXce2wJK}*j-5l43Scd|iPVDd%RZZUDjhnDyDk_ibd zOG_UT4_|vkt*FpkpRT;qMCn@gsD@|yy`+f!gI&fHpEg|OhjwD}g3<@|97eOd4)4yv z*kD4s+=#i9v}3*;2V1%A4Y*WG)*00pcrm_h_WVvTKK=~h3%-xSvk@o$GBKbyNqrnR zangH8%|zmwqCX2J)2J_t{eTL0Hrae?tp>zscv(R%Lrjh5wRt87eGFz571LFFI##;l zXx0ksSwzkz#IKhsnbs!K|1SL%g@=tL`vaMlY#6DRly(dkZy&vQ8(b+iKVC>bgb)0# zdMgX^znYC}qHpP32q?B9uu(V+{OSt}1ulRFT z4_}6{ALQZEc%c3-zm_dx7t+1_oPVgD`%GwB6N)TWPqWp4+*@-fL@l2YOx)@S=;ZuW zcXH5~>ILJE_VcS2rN0tg{*GlX4wKy5cu9Q*C#w}GSibfrZ%Kq!m5lxP1A^J3R7@^t z*cUcQ6^m&n76%jVu;sriVKE)V1U_gd13kG-JR9^c^Gt7Vum8ixm!Ss@$zeWHM4w7Y zX{|3c$l5zo3%ka`lmfvw-Voxq)_MsFGvl=pfptuYM*iS$2L+n(+Jz;8_|m#}G*MFk zk~7LETTteuxW*2fTv|~3ibx|w|lLTv@3}HuLClm?)e_yV`K|hy&8<-?d3HE z-^=D(76pH{bBey4mArQrYIMx;gPuF03OIqqwyzdhCBIqd3QF|Ji^L$W4>UCSb#xW+ z1ET?A4+~(iaj+a>=RI~5sid+I4n7d)LY#|ECDJ)pDN#HONlaRN-8+@=V>ZqoV%`~pgmj^FLM8U8Ap~lYXhVXPDJbtE76>j}!|U9is!?b}z_R`?7r_>E(rtGg*NSSb^Qy}%qp*MR7v-IO~$oinY=LsxoGMzs_)YnAUkN9!* zm>3j}z(SA*$cAWc{sS7^8sEhdF%BFv4I2CFuIDm(3h!7VjFjuASLjh>|!0?rw@t_ zfKKj{P>R9T)IOOTsv|jJ$YQFqf97+378x@|JT*OGg2`FmB?KJ1562saPhLZCACp8+ zZ8!VMnG|y^CH}C+@h6dJCJBi>gyJ(Zkr$kV36;=;ULaL=DLt4gc2v_D*1H{RR@?d4 z@mx!@S~?+AAUVj)hljB@CkCnSr_s`n^@`K3m+Ju%+<4qkUKkFD~rloa)-K9T=)J#oF z8Rpjm*lRLWT(oJ~SJwCz<|jo|A98wO&XP?y&)#qPAFZge`&T0}a_Rg|){5B4Op~{; zc%dWK6)WfV&|yLIqtH={%H+WL2r|JnN@S-@0*#n2?QV6sBr?Qrtb zb%%xS9IRVn@83!H<9Gg(6k!980mcdp){;K^cUdQge{Nj!CSm@4Gj$>kSRj{j0Rv%X zy5=UfWYo&Jl`UzS2m&az$N@A|b~ iT^8B9x(>8A0}`VOE@7K){B;sM3^O;gG%Yo8yY)ZC^ASk^ literal 0 HcmV?d00001 diff --git a/icons/items/shields/basic.dmi b/icons/items/shields/basic.dmi new file mode 100644 index 0000000000000000000000000000000000000000..6afb78610041dea16c2a12482441071043ec5926 GIT binary patch literal 22474 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 literal 0 HcmV?d00001 diff --git a/icons/items/shields/transforming.dmi b/icons/items/shields/transforming.dmi new file mode 100644 index 0000000000000000000000000000000000000000..56d1329a34866ee38c08682ec3bb9cfd8f1e9fbf GIT binary patch literal 7584 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ΝdlZ0*vPWOq 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 z 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 diff --git a/icons/mob/items/righthand_melee.dmi b/icons/mob/items/righthand_melee.dmi index 9609132d0a7b2fa74468f0fc9699ae2aa260b0c4..1cfcd5f622c5f677652858b824db674b1afdd3f1 100644 GIT binary patch literal 46536 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 z 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 literal 75923 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 diff --git a/icons/obj/weapons.dmi b/icons/obj/weapons.dmi index 1d4e812f670817423f1165e2d3f438784abea680..cda3aaf191cc0bc746c3dd31d5f05fb8a172c8c1 100644 GIT binary patch literal 69684 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 literal 81769 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 XqB8R5=wyvN00000NkvXXu0mjf7I(1& literal 5045 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 diff --git a/maps/away_missions/140x140/zoo.dmm b/maps/away_missions/140x140/zoo.dmm index 84e18f701d0..e87147a3208 100644 --- a/maps/away_missions/140x140/zoo.dmm +++ b/maps/away_missions/140x140/zoo.dmm @@ -73,7 +73,7 @@ /obj/item/stolenpackage, /obj/item/plastique, /obj/item/plastique, -/obj/item/melee/energy/sword/pirate, +/obj/item/melee/transforming/energy/sword/cutlass, /turf/space, /area/awaymission/zoo) "aj" = ( diff --git a/maps/away_missions/archive/spacebattle.dmm b/maps/away_missions/archive/spacebattle.dmm index 48106f306f1..0c972979b97 100644 --- a/maps/away_missions/archive/spacebattle.dmm +++ b/maps/away_missions/archive/spacebattle.dmm @@ -270,7 +270,7 @@ /area/awaymission/spacebattle/syndicate3) "bw" = ( /obj/structure/table/reinforced, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/simulated/floor, /area/awaymission/spacebattle/syndicate1) "bx" = ( @@ -888,7 +888,7 @@ /turf/simulated/floor, /area/awaymission/spacebattle/cruiser) "eR" = ( -/obj/item/shield/energy, +/obj/item/shield/transforming/energy, /turf/simulated/floor, /area/awaymission/spacebattle/cruiser) "eT" = ( @@ -929,7 +929,7 @@ /area/awaymission/spacebattle/cruiser) "fa" = ( /obj/spawner/corpse/syndicatesoldier, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /obj/effect/debris/cleanable/blood, /turf/simulated/floor, /area/awaymission/spacebattle/cruiser) diff --git a/maps/away_missions/archive/stationCollision.dmm b/maps/away_missions/archive/stationCollision.dmm index 74a28eeb496..94520c133f6 100644 --- a/maps/away_missions/archive/stationCollision.dmm +++ b/maps/away_missions/archive/stationCollision.dmm @@ -2777,7 +2777,7 @@ /turf/simulated/floor, /area/awaymission/arrivalblock) "kN" = ( -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /obj/item/clothing/shoes/syndigaloshes, /obj/item/clothing/under/syndicate, /obj/effect/decal/remains/human{ diff --git a/maps/rift/levels/rift-11-orbital.dmm b/maps/rift/levels/rift-11-orbital.dmm index 90f246634f8..fc113d7d0de 100644 --- a/maps/rift/levels/rift-11-orbital.dmm +++ b/maps/rift/levels/rift-11-orbital.dmm @@ -6284,7 +6284,7 @@ /obj/item/clothing/suit/armor/tdome/red, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor/dark, /area/tdome/tdome1) "tj" = ( @@ -6447,7 +6447,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/green, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor/dark, /area/tdome/tdome1) "vT" = ( @@ -6570,7 +6570,7 @@ /obj/item/clothing/suit/armor/tdome/green, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor/dark, /area/tdome/tdome1) "xw" = ( @@ -9484,7 +9484,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/red, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor/dark, /area/tdome/tdome1) "Uk" = ( diff --git a/maps/sectors/piratebase_192/levels/piratebase_192.dmm b/maps/sectors/piratebase_192/levels/piratebase_192.dmm index adb8f29930a..59da0041ba6 100644 --- a/maps/sectors/piratebase_192/levels/piratebase_192.dmm +++ b/maps/sectors/piratebase_192/levels/piratebase_192.dmm @@ -5314,14 +5314,14 @@ /area/piratebase/captain) "Wc" = ( /obj/structure/table/rack/shelf/steel, -/obj/item/melee/energy/sword/pirate{ +/obj/item/melee/transforming/energy/sword/cutlass{ pixel_x = 5 }, -/obj/item/melee/energy/sword/pirate{ +/obj/item/melee/transforming/energy/sword/cutlass{ pixel_x = 5; pixel_y = 5 }, -/obj/item/melee/energy/sword/pirate{ +/obj/item/melee/transforming/energy/sword/cutlass{ pixel_x = 5; pixel_y = 10 }, diff --git a/maps/sectors/tradeport_140/levels/tradeport_140.dmm b/maps/sectors/tradeport_140/levels/tradeport_140.dmm index 2684410d215..04dd8491891 100644 --- a/maps/sectors/tradeport_140/levels/tradeport_140.dmm +++ b/maps/sectors/tradeport_140/levels/tradeport_140.dmm @@ -471,7 +471,7 @@ req_access = list(160) }, /obj/structure/table/marble, -/obj/item/melee/energy/hfmachete, +/obj/item/melee/transforming/hfmachete, /turf/simulated/floor/carpet/bcarpet, /area/tradeport/cyndishow) "bU" = ( diff --git a/maps/sectors/tradeport_192/levels/tradeport_192.dmm b/maps/sectors/tradeport_192/levels/tradeport_192.dmm index 596ac6f8df3..53e25968b66 100644 --- a/maps/sectors/tradeport_192/levels/tradeport_192.dmm +++ b/maps/sectors/tradeport_192/levels/tradeport_192.dmm @@ -3061,7 +3061,7 @@ req_access = list(160) }, /obj/structure/table/marble, -/obj/item/melee/energy/hfmachete, +/obj/item/melee/transforming/hfmachete, /turf/simulated/floor/carpet/bcarpet, /area/tradeport/cyndishow) "kD" = ( diff --git a/maps/templates/admin/dhael_centcom.dmm b/maps/templates/admin/dhael_centcom.dmm index 247c12e9fe5..91ea585fafb 100644 --- a/maps/templates/admin/dhael_centcom.dmm +++ b/maps/templates/admin/dhael_centcom.dmm @@ -229,17 +229,17 @@ /area/centcom/specops) "ay" = ( /obj/structure/table/rack, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, /obj/effect/floor_decal/industrial/outline/yellow, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, /obj/effect/floor_decal/industrial/outline/yellow, /obj/structure/window/reinforced{ dir = 1 @@ -10688,17 +10688,17 @@ /area/centcom/specops) "GH" = ( /obj/structure/table/rack, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, /obj/effect/floor_decal/industrial/outline/yellow, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -11332,7 +11332,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/red, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -12059,7 +12059,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/green, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -12076,7 +12076,7 @@ /obj/item/clothing/suit/armor/tdome/green, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -13315,7 +13315,7 @@ /obj/item/clothing/suit/armor/tdome/red, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark" }, diff --git a/maps/templates/admin/ert_base.dmm b/maps/templates/admin/ert_base.dmm index 96879d65dce..5844bc5022b 100644 --- a/maps/templates/admin/ert_base.dmm +++ b/maps/templates/admin/ert_base.dmm @@ -1357,16 +1357,16 @@ /area/shuttle/specops/centcom) "cL" = ( /obj/structure/table/rack/steel, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, /turf/simulated/shuttle/floor/black, /area/shuttle/specops/centcom) "cM" = ( diff --git a/maps/templates/admin/kk_mercship.dmm b/maps/templates/admin/kk_mercship.dmm index 16b08549308..f889b1fa0f6 100644 --- a/maps/templates/admin/kk_mercship.dmm +++ b/maps/templates/admin/kk_mercship.dmm @@ -9345,18 +9345,18 @@ /turf/simulated/floor/tiled/techmaint, /area/ship/manta/bridge) "VF" = ( -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, /obj/structure/table/steel_reinforced, /obj/machinery/atmospherics/component/unary/vent_scrubber/on{ dir = 4 diff --git a/maps/templates/admin/mercbase.dmm b/maps/templates/admin/mercbase.dmm index 59eb33bbafb..bc8c94c333a 100644 --- a/maps/templates/admin/mercbase.dmm +++ b/maps/templates/admin/mercbase.dmm @@ -489,18 +489,18 @@ /area/antag/antag_base) "aP" = ( /obj/structure/table/rack, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, /obj/machinery/recharger/wallcharger{ pixel_x = 5; pixel_y = 32 diff --git a/maps/templates/admin/skipjack.dmm b/maps/templates/admin/skipjack.dmm index caad2f92559..e757e7953c5 100644 --- a/maps/templates/admin/skipjack.dmm +++ b/maps/templates/admin/skipjack.dmm @@ -1099,7 +1099,7 @@ /area/shuttle/skipjack) "cK" = ( /obj/structure/table/rack, -/obj/item/melee/energy/sword/pirate, +/obj/item/melee/transforming/energy/sword/cutlass, /obj/item/clothing/suit/space/pirate, /obj/item/clothing/suit/space/pirate, /obj/item/tank/oxygen, diff --git a/maps/templates/admin/thunderdome.dmm b/maps/templates/admin/thunderdome.dmm index 31f85fdaa41..8cb99cbba95 100644 --- a/maps/templates/admin/thunderdome.dmm +++ b/maps/templates/admin/thunderdome.dmm @@ -26,7 +26,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/red, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor{ icon_state = "dark"; dir = 5 @@ -43,7 +43,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/green, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor{ icon_state = "dark"; dir = 5 @@ -77,7 +77,7 @@ /obj/item/clothing/suit/armor/tdome/red, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark"; dir = 5 @@ -128,7 +128,7 @@ /obj/item/clothing/suit/armor/tdome/green, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark"; dir = 5 diff --git a/maps/templates/shuttles/overmaps/generic/cruiser.dmm b/maps/templates/shuttles/overmaps/generic/cruiser.dmm index 5472b593c6c..071f0063aa0 100644 --- a/maps/templates/shuttles/overmaps/generic/cruiser.dmm +++ b/maps/templates/shuttles/overmaps/generic/cruiser.dmm @@ -5404,16 +5404,16 @@ /obj/item/melee/baton/loaded, /obj/item/melee/baton/loaded, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, /obj/item/material/knife/tacknife/survival, /obj/item/material/knife/tacknife/survival, /obj/item/material/knife/tacknife/survival, diff --git a/maps/templates/shuttles/overmaps/generic/shelter_6.dmm b/maps/templates/shuttles/overmaps/generic/shelter_6.dmm index 242fc42c382..e0a277ddfda 100644 --- a/maps/templates/shuttles/overmaps/generic/shelter_6.dmm +++ b/maps/templates/shuttles/overmaps/generic/shelter_6.dmm @@ -65,14 +65,14 @@ /obj/item/material/knife/machete, /obj/item/material/knife/machete, /obj/item/material/knife/machete, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, /obj/item/melee/baton/loaded, /obj/item/melee/baton/loaded, /obj/item/melee/baton/loaded, diff --git a/maps/triumph/levels/flagship.dmm b/maps/triumph/levels/flagship.dmm index e79f15c0204..9f62a245cd1 100644 --- a/maps/triumph/levels/flagship.dmm +++ b/maps/triumph/levels/flagship.dmm @@ -1399,7 +1399,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/red, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -1851,17 +1851,17 @@ /area/centcom/specops) "fK" = ( /obj/structure/table/rack, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, /obj/effect/floor_decal/industrial/outline/yellow, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, /obj/effect/floor_decal/industrial/outline/yellow, /obj/structure/window/reinforced{ dir = 1 @@ -4455,17 +4455,17 @@ /area/centcom/security) "nz" = ( /obj/structure/table/rack, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, -/obj/item/shield/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, +/obj/item/shield/transforming/energy, /obj/effect/floor_decal/industrial/outline/yellow, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -7634,7 +7634,7 @@ /obj/item/clothing/suit/armor/tdome/red, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -8273,7 +8273,7 @@ /obj/structure/table/rack, /obj/item/clothing/under/color/green, /obj/item/clothing/shoes/brown, -/obj/item/melee/energy/axe, +/obj/item/melee/transforming/energy/axe, /turf/unsimulated/floor{ icon_state = "dark" }, @@ -10908,7 +10908,7 @@ /obj/item/clothing/suit/armor/tdome/green, /obj/item/clothing/head/helmet/thunderdome, /obj/item/melee/baton/loaded, -/obj/item/melee/energy/sword, +/obj/item/melee/transforming/energy/sword, /turf/unsimulated/floor{ icon_state = "dark" }, diff --git a/sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg b/sound/soundbytes/effects/combat/block-metal-on-metal-1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..10ed875b8d0f210b0b4081a595bff20b1ffaa552 GIT binary patch literal 10382 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 CYTPCO literal 0 HcmV?d00001 diff --git a/sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg b/sound/soundbytes/effects/combat/block-metal-on-metal-2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b7ae0e247d013778098b5b246fbc7769d03eea51 GIT binary patch literal 12604 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 Be3SqH literal 0 HcmV?d00001 diff --git a/sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg b/sound/soundbytes/effects/combat/block-metal-on-wood-2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..25694ec5578570fe1311c7556dd548ecb46269ea GIT binary patch literal 6631 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 literal 0 HcmV?d00001 diff --git a/sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg b/sound/soundbytes/effects/combat/block-wood-on-wood-1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d2829db00c372e256c1cbcff5eb6a9ea4b06ae3b GIT binary patch literal 12163 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 literal 0 HcmV?d00001 diff --git a/sound/soundbytes/effects/combat/parry-wood1.ogg b/sound/soundbytes/effects/combat/parry-wood1.ogg new file mode 100644 index 0000000000000000000000000000000000000000..a01f3dbe0161d2c7d859aeeb860b43cb8aabbe61 GIT binary patch literal 10321 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 T=}*8Qh*Kb(>RUpW5%7NiMs8yq literal 0 HcmV?d00001 diff --git a/sound/soundbytes/effects/combat/parry-wood2.ogg b/sound/soundbytes/effects/combat/parry-wood2.ogg new file mode 100644 index 0000000000000000000000000000000000000000..398a59e24863d4c3b872300dc91b86cab33d0798 GIT binary patch literal 11514 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 z Date: Mon, 2 Sep 2024 11:48:42 -0700 Subject: [PATCH 2/5] shrapnel is no longer rng + u can pick what to grab (#6712) ## About The Pull Request shrapnel is no longer rng + u can pick what to grab. theres also now a proper proc for removing something surgically. ## Why It's Good For The Game no reason for rng in ideal circumstances. u already get penalized for improper tools so like why have more rng. also a needed proc for slightly less shitcodedness. ## Changelog :cl: add: shrapnel / etc removal now goes through a surgically_remove proc. qol: shrapnel removal is no longer rng qol: shrapnel removal now lets u pick what item to remove /:cl: --- .../objects/items/weapons/implants/implant.dm | 4 ++ .../simple_mob/subtypes/animal/borer/borer.dm | 7 ++- code/modules/resleeving/mirror.dm | 4 ++ code/modules/surgery/implant.dm | 61 ++++++------------- 4 files changed, 34 insertions(+), 42 deletions(-) diff --git a/code/game/objects/items/weapons/implants/implant.dm b/code/game/objects/items/weapons/implants/implant.dm index 0bfe624f50f..92769892f31 100644 --- a/code/game/objects/items/weapons/implants/implant.dm +++ b/code/game/objects/items/weapons/implants/implant.dm @@ -92,6 +92,10 @@ else ..() +/obj/item/implant/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from) + . = ..() + imp_in = null + implanted = 0 ////////////////////////////// // Tracking Implant ////////////////////////////// diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm index 0944f22c199..1aa2a52144f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/borer/borer.dm @@ -51,7 +51,6 @@ var/roundstart = FALSE // If true, spawning won't try to pull a ghost. var/used_dominate // world.time when the dominate power was last used. - /mob/living/simple_mob/animal/borer/roundstart roundstart = TRUE @@ -267,3 +266,9 @@ continue else if(M.stat == DEAD && M.get_preference_toggle(/datum/game_preference_toggle/observer/ghost_ears)) to_chat(M, "[src.true_name] whispers to [host], \"[message]\"") + +/mob/living/simple_mob/animal/borer/proc/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from) + if(controlling) + target.release_control() + detatch() + leave_host() diff --git a/code/modules/resleeving/mirror.dm b/code/modules/resleeving/mirror.dm index b68cb679199..92bebfefa35 100644 --- a/code/modules/resleeving/mirror.dm +++ b/code/modules/resleeving/mirror.dm @@ -81,6 +81,10 @@ forceMove(MT) MT.imp = src +/obj/item/implant/mirror/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from) + . = ..() + target.mirror = null + /obj/item/implant/mirror/positronic name = "Synthetic Mirror" desc = "An altered form of the common mirror designed to work with synthetic brains." diff --git a/code/modules/surgery/implant.dm b/code/modules/surgery/implant.dm index 4f5bff25520..e0f11fce24b 100644 --- a/code/modules/surgery/implant.dm +++ b/code/modules/surgery/implant.dm @@ -158,6 +158,15 @@ // IMPLANT/ITEM REMOVAL SURGERY ////////////////////////////////////////////////////////////////// +/obj/item/proc/surgically_remove(mob/living/carbon/human/target, obj/item/organ/external/chest/removing_from) + removing_from.implants -= src + + target.update_hud_sec_implants() + + loc = get_turf(target) + add_blood(target) + update_icon() + /datum/surgery_step/cavity/implant_removal allowed_tools = list( /obj/item/surgical/hemostat = 100, \ @@ -190,49 +199,19 @@ /datum/surgery_step/cavity/implant_removal/end_step(mob/living/user, mob/living/carbon/human/target, target_zone, obj/item/tool) var/obj/item/organ/external/chest/affected = target.get_organ(target_zone) - var/find_prob = 0 - if (affected.implants.len) - var/obj/item/obj = pick(affected.implants) - - if(istype(obj,/obj/item/implant)) - var/obj/item/implant/imp = obj - if (imp.islegal()) - find_prob +=60 - else - find_prob +=40 - else - find_prob +=50 - - if (prob(find_prob)) - user.visible_message("[user] takes something out of incision on [target]'s [affected.name] with \the [tool]!", \ - "You take [obj] out of incision on [target]'s [affected.name]s with \the [tool]!" ) - affected.implants -= obj - - target.update_hud_sec_implants() - - //Handle possessive brain borers. - if(istype(obj,/mob/living/simple_mob/animal/borer)) - var/mob/living/simple_mob/animal/borer/worm = obj - if(worm.controlling) - target.release_control() - worm.detatch() - worm.leave_host() - else - obj.loc = get_turf(target) - obj.add_blood(target) - obj.update_icon() - if(istype(obj,/obj/item/implant)) - var/obj/item/implant/imp = obj - imp.imp_in = null - imp.implanted = 0 - if(istype(obj, /obj/item/implant/mirror)) - target.mirror = null - else if(istype(tool,/obj/item/nif)){var/obj/item/nif/N = tool;N.unimplant(target)} - else - user.visible_message("[user] removes \the [tool] from [target]'s [affected.name].", \ - "There's something inside [target]'s [affected.name], but you just missed it this time." ) + var/obj/item/obj = input("What do you want to extract?") in affected.implants + + user.visible_message("[user] takes something out of incision on [target]'s [affected.name] with \the [tool]!", \ + "You take [obj] out of incision on [target]'s [affected.name]s with \the [tool]!" ) + + obj.surgically_remove(target, affected) + + if(istype(tool, /obj/item/nif)) + var/obj/item/nif/N = tool + N.unimplant(target) + else user.visible_message("[user] could not find anything inside [target]'s [affected.name], and pulls \the [tool] out.", \ "You could not find anything inside [target]'s [affected.name]." ) From 50b7af466c41d7a44563bbc06a8b69a188a32ff3 Mon Sep 17 00:00:00 2001 From: Niezan Date: Mon, 2 Sep 2024 11:49:34 -0700 Subject: [PATCH 3/5] fixs sign language circuit (#6722) ## About The Pull Request fixes sign language circuit to hear sign language ## Why It's Good For The Game fix bug ## Changelog :cl: fix: sign language circuit now picks up sign language /:cl: --- code/modules/integrated_electronics/subtypes/input.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm index 28db56a4ff6..94a68fb24cb 100644 --- a/code/modules/integrated_electronics/subtypes/input.dm +++ b/code/modules/integrated_electronics/subtypes/input.dm @@ -1080,7 +1080,7 @@ GLOBAL_DATUM_INIT(circuit_translation_context, /datum/translation_context/simple if(signlang) activate_pin(2) -/obj/item/integrated_circuit/input/microphone/sign/hear_signlang(text, verb, datum/language/speaking, mob/M as mob) +/obj/item/integrated_circuit/input/microphone/sign/hear_signlang(mob/M as mob, text, verb, datum/language/speaking) hear_talk(M, text, verb, speaking) return From 9e30d9e47fa985e98e13950ea4f4b99ddb090f77 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:50:01 -0400 Subject: [PATCH 4/5] more loadout slots (#6715) @AlphaM01 asked --- code/__DEFINES/loadout.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/__DEFINES/loadout.dm b/code/__DEFINES/loadout.dm index 97541ba9432..e8fcbfcf7f4 100644 --- a/code/__DEFINES/loadout.dm +++ b/code/__DEFINES/loadout.dm @@ -33,9 +33,9 @@ DEFINE_BITFIELD(loadout_customize_flags, list( )) // *DO NOT RAISE THIS. This is for performance reasons.* // -#define LOADOUT_MAX_SLOTS 10 +#define LOADOUT_MAX_SLOTS 16 #define LOADOUT_SLOT_NAME_LENGTH 32 -#define LOADOUT_MAX_ITEMS 30 +#define LOADOUT_MAX_ITEMS 32 /// Used in chargen for accessory loadout limit. #define LOADOUT_MAX_COST 20 /// Used in chargen for accessory loadout limit on holidays. From e6e8ce042efc3f9b7e474db45109e40e4095b154 Mon Sep 17 00:00:00 2001 From: Timothy Teakettle <59849408+timothyteakettle@users.noreply.github.com> Date: Mon, 2 Sep 2024 20:05:33 +0100 Subject: [PATCH 5/5] snowflakes tts more so it works with headsets (#6686) ## About The Pull Request title ## Why It's Good For The Game you cant use tts with headsets right now ## Changelog :cl: fix: snowflakes tts more so it works with headsets /:cl: --------- Co-authored-by: BlueWildrose <57083662+BlueWildrose@users.noreply.github.com> --- code/game/objects/items/devices/text_to_speech.dm | 14 +++++++++++++- code/modules/mob/living/say.dm | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/code/game/objects/items/devices/text_to_speech.dm b/code/game/objects/items/devices/text_to_speech.dm index 71c444de8c5..07aa8e7721a 100644 --- a/code/game/objects/items/devices/text_to_speech.dm +++ b/code/game/objects/items/devices/text_to_speech.dm @@ -51,8 +51,20 @@ if(activated) var/message = message_args["message"] var/whispering = message_args["whispering"] + var/message_mode = message_args["message_mode"] + var/speech_verb = whispering ? "quietly states" : "states" message_args["cancelled"] = TRUE - audible_message("[icon2html(thing = src, target = world)] \The [name] [whispering ? "quietly states" : "states"], \"[message]\"", null, whispering ? 2 : world.view) + audible_message("[icon2html(thing = src, target = world)] \The [name] [speech_verb], \"[message]\"", null, whispering ? 2 : world.view) if(!whispering) linked_user.say_overhead(message, FALSE, MESSAGE_RANGE_COMBAT_LOUD) + + if(ishuman(source) && message_mode != null) + var/mob/living/carbon/human/H = source + var/obj/item/radio/headset/left_radio = H.l_ear + var/obj/item/radio/headset/right_radio = H.r_ear + if(istype(left_radio)) + left_radio.talk_into(source, message, message_mode, speech_verb, null) + if(istype(right_radio)) + right_radio.talk_into(source, message, message_mode, speech_verb, null) + playsound(src, 'sound/items/tts/stopped_type.ogg', 25, TRUE) diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 8b7ac8908cc..cac79cb47be 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -236,7 +236,7 @@ var/list/channel_to_radio_key = new verb = speaking.speech_verb w_not_heard = "[speaking.speech_verb] something [w_adverb]" - var/list/message_args = list("message" = message, "whispering" = whispering, "cancelled" = FALSE) + var/list/message_args = list("message" = message, "whispering" = whispering, "cancelled" = FALSE, "message_mode" = message_mode) SEND_SIGNAL(src, COMSIG_MOB_SAY, message_args)