diff --git a/_maps/shuttles/emergency_luxury.dmm b/_maps/shuttles/emergency_luxury.dmm deleted file mode 100644 index 042202d52b57..000000000000 --- a/_maps/shuttles/emergency_luxury.dmm +++ /dev/null @@ -1,1324 +0,0 @@ -//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE -"aa" = ( -/turf/template_noop, -/area/template_noop) -"ab" = ( -/turf/closed/wall/mineral/titanium, -/area/shuttle/escape/luxury) -"ac" = ( -/obj/machinery/door/airlock/external/ruin{ - name = "Economy-Class" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ad" = ( -/obj/machinery/scanner_gate/luxury_shuttle{ - layer = 2.6 - }, -/obj/machinery/door/airlock/silver{ - name = "First Class" - }, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"ae" = ( -/obj/docking_port/mobile/emergency{ - dir = 2; - dwidth = 5; - height = 14; - name = "Luxurious Emergency Shuttle"; - width = 25 - }, -/obj/machinery/scanner_gate/luxury_shuttle{ - layer = 2.6 - }, -/obj/machinery/door/airlock/silver{ - name = "First Class" - }, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"af" = ( -/obj/machinery/door/airlock/security/glass{ - name = "Escape Shuttle Cell" - }, -/obj/effect/mapping_helpers/airlock/access/all/security/brig, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"ag" = ( -/turf/closed/indestructible/riveted/plastinum, -/area/shuttle/escape/luxury) -"ah" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/ore_box, -/obj/effect/decal/cleanable/cobweb, -/obj/machinery/light/small/directional/north, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"aj" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/large, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ak" = ( -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"al" = ( -/turf/closed/wall/mineral/plastitanium/nodiagonal, -/area/shuttle/escape/luxury) -"ao" = ( -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"aq" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/reagent_dispensers/fueltank, -/obj/structure/window{ - dir = 4 - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ar" = ( -/obj/structure/shuttle/engine/propulsion{ - dir = 8 - }, -/turf/open/floor/plating/airless, -/area/shuttle/escape/luxury) -"as" = ( -/obj/structure/shuttle/engine/heater{ - dir = 8 - }, -/obj/structure/window/reinforced{ - dir = 4 - }, -/turf/open/floor/plating/airless, -/area/shuttle/escape/luxury) -"at" = ( -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"au" = ( -/obj/effect/spawner/structure/window, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"av" = ( -/obj/machinery/door/poddoor/shutters/indestructible{ - id = "ohnopoors" - }, -/turf/closed/indestructible/opsglass{ - desc = "A durable looking window made of an alloy of of plasma and titanium."; - name = "plastitanium window" - }, -/area/shuttle/escape/luxury) -"aw" = ( -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"ax" = ( -/obj/effect/decal/cleanable/cobweb/cobweb2, -/obj/machinery/light/small/directional/east, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ay" = ( -/turf/closed/indestructible/syndicate, -/area/shuttle/escape/luxury) -"az" = ( -/obj/machinery/computer/communications{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"aA" = ( -/obj/machinery/button/door{ - id = "ohnopoors"; - name = "window shutters"; - pixel_y = 26 - }, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"aB" = ( -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"aC" = ( -/obj/item/food/enchiladas{ - pixel_y = 3 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aD" = ( -/obj/item/food/candiedapple{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aE" = ( -/obj/item/food/benedict{ - pixel_y = 1 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aF" = ( -/obj/item/food/cakeslice/chocolate{ - pixel_y = 1 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aG" = ( -/obj/item/food/eggplantparm{ - pixel_y = 3 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aH" = ( -/obj/item/food/spaghetti/copypasta{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aI" = ( -/obj/item/food/spaghetti/boiledspaghetti{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aJ" = ( -/obj/item/food/cherrycupcake{ - pixel_y = 2 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aK" = ( -/obj/item/food/spaghetti/meatballspaghetti{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aL" = ( -/obj/item/food/notasandwich{ - pixel_y = 11 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aM" = ( -/obj/structure/chair/comfy/teal{ - dir = 4 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"aN" = ( -/obj/item/food/burger/baconburger{ - pixel_y = 2 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aO" = ( -/obj/item/food/melonfruitbowl{ - pixel_y = 4 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aP" = ( -/obj/item/food/khachapuri{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aQ" = ( -/obj/item/food/bearsteak{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aR" = ( -/obj/structure/mirror/directional/west, -/obj/structure/sink/greyscale{ - dir = 4; - pixel_x = -12 - }, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"aS" = ( -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"aT" = ( -/obj/item/food/spaghetti/pastatomato{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aU" = ( -/obj/item/food/kebab/tofu{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aV" = ( -/obj/item/food/honkdae{ - pixel_y = 8 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aW" = ( -/obj/item/food/spaghetti/chowmein{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aX" = ( -/obj/item/food/grilled_cheese_sandwich{ - pixel_y = 11 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aY" = ( -/obj/item/food/jelliedtoast/cherry{ - pixel_y = 6 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"aZ" = ( -/obj/item/food/honeybun{ - pixel_y = 1 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"ba" = ( -/obj/item/food/pizza/dank{ - pixel_y = 5 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/red, -/area/shuttle/escape/luxury) -"bb" = ( -/obj/machinery/door/airlock/silver{ - name = "Restroom" - }, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"bc" = ( -/turf/closed/indestructible/opsglass{ - desc = "A durable looking window made of an alloy of of plasma and titanium."; - name = "plastitanium window" - }, -/area/shuttle/escape/luxury) -"be" = ( -/obj/structure/girder, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bf" = ( -/obj/structure/closet/crate/trashcart, -/obj/effect/turf_decal/loading_area, -/obj/effect/spawner/random/maintenance/six, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bg" = ( -/obj/structure/closet/crate/trashcart, -/obj/effect/turf_decal/loading_area, -/obj/effect/spawner/random/maintenance/three, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bi" = ( -/obj/machinery/computer/emergency_shuttle{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"bj" = ( -/obj/structure/chair/comfy/shuttle, -/obj/machinery/light/directional/north, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"bk" = ( -/obj/machinery/light/directional/north, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"bl" = ( -/obj/machinery/computer/station_alert{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"bm" = ( -/obj/structure/table/wood/fancy/black, -/obj/item/storage/fancy/cigarettes/cigars/havana{ - pixel_y = 7 - }, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"bn" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/engineering/electrical, -/obj/effect/decal/cleanable/robot_debris, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bo" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/engineering, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bp" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/window{ - dir = 4 - }, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bq" = ( -/obj/item/stack/rods/ten, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"br" = ( -/obj/effect/decal/cleanable/robot_debris, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bs" = ( -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bt" = ( -/obj/structure/window/reinforced{ - dir = 1; - pixel_y = 2 - }, -/obj/item/clothing/head/collectable/paper, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bu" = ( -/obj/structure/window/reinforced{ - dir = 1; - pixel_y = 2 - }, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bv" = ( -/obj/machinery/computer/crew{ - dir = 8 - }, -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"bw" = ( -/obj/structure/window/reinforced{ - dir = 1; - pixel_y = 2 - }, -/obj/structure/window/reinforced{ - dir = 4; - layer = 2.9 - }, -/turf/open/floor/holofloor/beach/coast_t{ - dir = 8 - }, -/area/shuttle/escape/luxury) -"bx" = ( -/obj/machinery/door/airlock/silver{ - name = "Flight Control" - }, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"by" = ( -/obj/structure/closet/crate/large, -/obj/effect/turf_decal/delivery, -/obj/effect/decal/cleanable/robot_debris, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bz" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bA" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/window{ - dir = 4 - }, -/obj/structure/closet/crate{ - icon_state = "crateopen" - }, -/obj/effect/decal/cleanable/oil, -/obj/effect/spawner/random/maintenance/two, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bB" = ( -/obj/item/stack/ore/glass, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bC" = ( -/obj/structure/chair/comfy/shuttle{ - dir = 1 - }, -/obj/effect/turf_decal/stripes/line, -/obj/structure/window, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bD" = ( -/obj/structure/chair/comfy/shuttle{ - dir = 1 - }, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) -"bE" = ( -/obj/machinery/light/directional/north, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"bF" = ( -/obj/item/bikehorn/rubberducky, -/obj/item/bikehorn/rubberducky, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bG" = ( -/obj/machinery/door/window/left/directional/east, -/turf/open/floor/holofloor/beach/coast_t{ - dir = 8 - }, -/area/shuttle/escape/luxury) -"bH" = ( -/obj/structure/door_assembly, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bI" = ( -/obj/item/stack/tile/iron/base, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bJ" = ( -/obj/structure/chair/comfy/shuttle, -/obj/effect/turf_decal/stripes/line{ - dir = 1 - }, -/obj/structure/window{ - dir = 1 - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bK" = ( -/obj/structure/chair/comfy/shuttle, -/obj/effect/turf_decal/stripes/line{ - dir = 1 - }, -/obj/structure/window{ - dir = 1 - }, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bL" = ( -/obj/structure/window/reinforced, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bM" = ( -/obj/structure/window/reinforced, -/obj/machinery/door/window/right/directional/east, -/turf/open/floor/holofloor/beach/coast_t{ - dir = 8 - }, -/area/shuttle/escape/luxury) -"bN" = ( -/obj/machinery/chem_dispenser/drinks/beer{ - name = "aperitif fountain"; - pixel_y = 8 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bO" = ( -/obj/machinery/chem_dispenser/drinks{ - name = "soda fountain"; - pixel_y = 8 - }, -/obj/structure/table/wood/fancy/black, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bP" = ( -/obj/structure/table/wood/fancy/black, -/obj/machinery/recharger, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"bQ" = ( -/obj/machinery/light/directional/west, -/turf/open/floor/holofloor/beach/water, -/area/shuttle/escape/luxury) -"bR" = ( -/obj/effect/decal/cleanable/glass, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bS" = ( -/obj/item/stack/tile/iron/base, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bT" = ( -/obj/effect/decal/cleanable/oil, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bU" = ( -/obj/effect/decal/cleanable/generic, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"bV" = ( -/obj/structure/chair/comfy/brown, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"bW" = ( -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bX" = ( -/obj/structure/chair/comfy/shuttle{ - dir = 1 - }, -/obj/effect/turf_decal/stripes/line, -/obj/structure/window, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"bY" = ( -/obj/structure/table/wood/fancy/black, -/obj/item/reagent_containers/food/drinks/bottle/champagne{ - pixel_x = 7; - pixel_y = 11 - }, -/obj/item/reagent_containers/food/drinks/shaker{ - pixel_x = -7; - pixel_y = 11 - }, -/obj/item/reagent_containers/glass/rag{ - pixel_x = -6; - pixel_y = 4 - }, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"bZ" = ( -/obj/machinery/vending/boozeomat, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"cb" = ( -/obj/machinery/stasis, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cc" = ( -/obj/effect/turf_decal/delivery, -/obj/structure/closet/crate/large, -/obj/effect/decal/cleanable/robot_debris, -/obj/effect/spawner/random/maintenance/eight, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cd" = ( -/obj/machinery/door/airlock/public/glass{ - name = "Emergency Shuttle Infirmary" - }, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cf" = ( -/obj/effect/decal/cleanable/vomit/old, -/obj/item/stack/ore/glass, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cg" = ( -/obj/effect/decal/cleanable/glass, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ch" = ( -/obj/structure/chair/comfy/brown{ - dir = 1 - }, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"ci" = ( -/obj/structure/musician/piano{ - icon_state = "piano" - }, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"cj" = ( -/obj/structure/closet/crate/freezer, -/obj/item/reagent_containers/blood, -/obj/item/reagent_containers/blood, -/obj/item/reagent_containers/blood/a_minus, -/obj/item/reagent_containers/blood/b_minus{ - pixel_x = -4; - pixel_y = 4 - }, -/obj/item/reagent_containers/blood/b_plus{ - pixel_x = 1; - pixel_y = 2 - }, -/obj/item/reagent_containers/blood/o_minus, -/obj/item/reagent_containers/blood/o_plus{ - pixel_x = -2; - pixel_y = -1 - }, -/obj/item/reagent_containers/blood/random, -/obj/item/reagent_containers/blood/random, -/obj/item/reagent_containers/blood/a_plus, -/obj/item/reagent_containers/blood/random, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"ck" = ( -/obj/item/stack/tile/iron/base, -/obj/machinery/light/small/directional/west, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cl" = ( -/mob/living/simple_animal/bot/medbot{ - name = "\improper emergency medibot"; - pixel_x = -3; - pixel_y = 2 - }, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cm" = ( -/obj/structure/table, -/obj/item/scalpel{ - pixel_y = 12 - }, -/obj/item/circular_saw, -/obj/item/retractor{ - pixel_x = 4 - }, -/obj/item/hemostat{ - pixel_x = -4 - }, -/obj/item/clothing/gloves/color/latex, -/obj/item/clothing/mask/surgical, -/obj/item/surgicaldrill, -/obj/item/cautery, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cn" = ( -/obj/machinery/door/airlock{ - name = "Economy Medical" - }, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"co" = ( -/obj/item/shard, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cp" = ( -/obj/structure/closet/crate/freezer, -/obj/effect/decal/cleanable/blood/old, -/obj/item/bot_assembly/medbot, -/obj/item/stack/gauze, -/obj/item/reagent_containers/food/drinks/bottle/whiskey, -/obj/item/reagent_containers/glass/bottle/ethanol, -/obj/item/organ/stomach, -/obj/item/clothing/mask/surgical, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cq" = ( -/obj/effect/decal/cleanable/chem_pile, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cr" = ( -/obj/structure/frame/machine, -/obj/effect/decal/cleanable/cobweb/cobweb2, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cs" = ( -/obj/effect/decal/cleanable/robot_debris, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"ct" = ( -/obj/machinery/shower{ - dir = 8 - }, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"cu" = ( -/obj/structure/toilet{ - pixel_y = 8 - }, -/obj/machinery/light/directional/east, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"cv" = ( -/obj/structure/table, -/obj/item/reagent_containers/pill/maintenance{ - pixel_x = -6; - pixel_y = -3 - }, -/obj/item/reagent_containers/pill/maintenance{ - pixel_x = 8; - pixel_y = -3 - }, -/obj/item/reagent_containers/pill/maintenance, -/obj/item/healthanalyzer{ - pixel_y = 9 - }, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cw" = ( -/obj/structure/bed, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cx" = ( -/obj/effect/decal/cleanable/vomit/old, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cy" = ( -/obj/item/shard, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cz" = ( -/obj/machinery/light/directional/south, -/turf/open/floor/mineral/titanium/blue, -/area/shuttle/escape/luxury) -"cA" = ( -/obj/effect/decal/cleanable/oil, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cB" = ( -/obj/structure/bed, -/obj/effect/decal/cleanable/blood/old, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cC" = ( -/obj/structure/grille/broken, -/obj/effect/spawner/random/maintenance/two, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cD" = ( -/obj/structure/frame/machine, -/obj/effect/turf_decal/delivery, -/obj/effect/decal/cleanable/cobweb, -/obj/machinery/light/small/directional/west, -/turf/open/floor/iron, -/area/shuttle/escape/luxury) -"cE" = ( -/obj/structure/chair/comfy/brown, -/obj/machinery/light/directional/north, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"cF" = ( -/obj/machinery/light/directional/east, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"cG" = ( -/obj/structure/table, -/obj/item/storage/medkit/advanced, -/obj/item/storage/medkit/regular, -/obj/item/storage/medkit/fire, -/obj/item/reagent_containers/glass/bottle/epinephrine{ - pixel_x = 6 - }, -/obj/item/reagent_containers/glass/bottle/dylovene, -/obj/item/reagent_containers/glass/bottle/epinephrine{ - pixel_x = -3; - pixel_y = 8 - }, -/obj/item/reagent_containers/glass/bottle/dylovene, -/obj/item/reagent_containers/syringe/epinephrine{ - pixel_x = 3; - pixel_y = -2 - }, -/obj/item/reagent_containers/syringe/epinephrine{ - pixel_x = 3; - pixel_y = -2 - }, -/obj/item/reagent_containers/syringe/epinephrine{ - pixel_x = 3; - pixel_y = -2 - }, -/obj/machinery/light/directional/north, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cH" = ( -/obj/machinery/light/directional/south, -/obj/structure/table/optable, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"cI" = ( -/obj/machinery/light/directional/east, -/turf/open/floor/carpet/blue, -/area/shuttle/escape/luxury) -"cJ" = ( -/obj/item/stack/tile/iron/base, -/obj/machinery/light/small/directional/west, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cK" = ( -/obj/structure/table, -/obj/item/wirecutters{ - pixel_y = 6 - }, -/obj/item/hatchet, -/obj/item/knife/kitchen, -/obj/machinery/light/small/directional/west, -/turf/open/floor/plating, -/area/shuttle/escape/luxury) -"cL" = ( -/obj/structure/chair/comfy/brown{ - dir = 1 - }, -/obj/machinery/light/directional/south, -/turf/open/floor/carpet/orange, -/area/shuttle/escape/luxury) -"cM" = ( -/obj/machinery/light/directional/south, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"cN" = ( -/obj/structure/chair/wood{ - dir = 8 - }, -/turf/open/floor/carpet/green, -/area/shuttle/escape/luxury) -"qa" = ( -/obj/structure/toilet{ - pixel_y = 8 - }, -/obj/machinery/light/directional/west, -/turf/open/floor/mineral/titanium/white, -/area/shuttle/escape/luxury) -"zh" = ( -/turf/open/floor/mineral/diamond, -/area/shuttle/escape/luxury) -"Eg" = ( -/obj/machinery/vending/wallmed/directional/north, -/obj/machinery/iv_drip, -/turf/open/floor/carpet/cyan, -/area/shuttle/escape/luxury) -"Lt" = ( -/obj/machinery/door/airlock/security/glass{ - name = "Escape Shuttle Cell" - }, -/obj/machinery/scanner_gate/luxury_shuttle, -/obj/effect/mapping_helpers/airlock/access/all/security/brig, -/turf/open/floor/mineral/plastitanium/red/brig, -/area/shuttle/escape/brig) - -(1,1,1) = {" -aa -al -ar -ar -al -ar -ar -ar -ar -al -ar -ar -al -aa -"} -(2,1,1) = {" -ab -al -as -as -al -as -as -as -as -al -as -as -al -ab -"} -(3,1,1) = {" -ab -ah -bn -by -ab -cD -aj -cc -aj -ab -cp -cv -cK -ab -"} -(4,1,1) = {" -ab -aj -bo -bz -bH -bR -bC -bJ -ak -cn -cq -bs -cA -ab -"} -(5,1,1) = {" -ab -aq -bp -bA -ab -bS -bC -bJ -cf -ab -cr -cw -cB -ab -"} -(6,1,1) = {" -ab -be -bq -bB -ck -bT -ak -bU -cg -ab -ab -ab -ab -ab -"} -(7,1,1) = {" -ab -bf -br -bC -bJ -bI -bC -bK -bs -co -cs -cJ -cC -ab -"} -(8,1,1) = {" -ab -bg -ak -bC -bJ -bU -bC -bJ -bS -bC -bJ -cx -bC -ab -"} -(9,1,1) = {" -ac -ax -bs -bC -bK -bS -bX -bJ -ak -bC -bJ -cy -bC -ab -"} -(10,1,1) = {" -ag -ag -af -ag -ag -ay -av -av -av -ay -av -av -av -ay -"} -(11,1,1) = {" -af -at -at -bD -ag -aA -ao -ao -ao -ao -ao -ao -ao -ag -"} -(12,1,1) = {" -ag -bj -at -bD -ag -cE -aG -aK -ch -bV -aO -aT -cL -ag -"} -(13,1,1) = {" -ag -at -at -bD -ag -bV -aH -aL -ch -bV -aP -aU -ch -ag -"} -(14,1,1) = {" -ag -Lt -ag -ag -ag -bV -aC -aE -ch -bV -aX -aZ -ch -ag -"} -(15,1,1) = {" -ag -ao -bt -bQ -bL -bV -aD -aF -ch -bV -aY -ba -ch -ag -"} -(16,1,1) = {" -ag -ao -bu -bF -bL -bV -aI -aD -ch -bV -aH -aV -ch -ag -"} -(17,1,1) = {" -ae -ao -bw -bG -bM -bV -aJ -aN -ch -bV -aQ -aW -cL -ag -"} -(18,1,1) = {" -ag -bk -ao -ao -ao -ao -ao -ao -ao -ao -cI -ao -ao -ag -"} -(19,1,1) = {" -ad -ao -ao -ao -bN -bW -bY -ao -ci -ag -ag -ag -bb -ag -"} -(20,1,1) = {" -ag -ao -ao -ao -bO -cF -bZ -ao -cN -ag -aR -aR -aS -ag -"} -(21,1,1) = {" -ag -au -bx -bx -au -ag -au -cd -au -ag -ct -ct -cM -ag -"} -(22,1,1) = {" -ag -bm -aw -aw -bP -ag -cj -aB -cm -ag -ag -ag -aS -ag -"} -(23,1,1) = {" -ag -bE -aw -aw -cz -ag -cG -aB -cH -ag -cu -bb -aS -ag -"} -(24,1,1) = {" -ag -aM -aM -aM -zh -ag -Eg -aB -cl -ag -ag -ag -cM -ag -"} -(25,1,1) = {" -ag -az -bi -bl -bv -ag -cb -cb -cb -ag -qa -bb -aS -ag -"} -(26,1,1) = {" -ag -bc -bc -bc -bc -ag -ag -ag -ag -ag -ag -ag -ag -ag -"} diff --git a/code/__DEFINES/_multiz.dm b/code/__DEFINES/_multiz.dm index b4c9d0de5774..02dd04549d43 100644 --- a/code/__DEFINES/_multiz.dm +++ b/code/__DEFINES/_multiz.dm @@ -5,3 +5,9 @@ #define GetAbove(A) (HasAbove(A:z) ? get_step(A, UP) : null) #define GetBelow(A) (HasBelow(A:z) ? get_step(A, DOWN) : null) + + +/// Vertical Z movement +#define ZMOVING_VERTICAL 1 +/// Laterial Z movement +#define ZMOVING_LATERAL 2 diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 076efefc7d5f..ed88474afdd1 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -36,7 +36,7 @@ /// probability that the pawn should try resisting out of restraints #define RESIST_SUBTREE_PROB 50 ///macro for whether it's appropriate to resist right now, used by resist subtree -#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || (source.pulledby && source.pulledby.grab_state > GRAB_PASSIVE)) +#define SHOULD_RESIST(source) (source.on_fire || source.buckled || HAS_TRAIT(source, TRAIT_RESTRAINED) || HAS_TRAIT(source, TRAIT_AGGRESSIVE_GRAB)) ///macro for whether the pawn can act, used generally to prevent some horrifying ai disasters #define IS_DEAD_OR_INCAP(source) (source.incapacitated() || source.stat) diff --git a/code/__DEFINES/bodyparts.dm b/code/__DEFINES/bodyparts.dm index 4618a751494a..0a6f0128f943 100644 --- a/code/__DEFINES/bodyparts.dm +++ b/code/__DEFINES/bodyparts.dm @@ -39,15 +39,19 @@ #define BP_CUT_AWAY (1<<10) /// Limb cannot feel pain #define BP_NO_PAIN (1<<11) +/// Limb can be dislocated +#define BP_CAN_BE_DISLOCATED (1<<12) +/// Limb is dislocated +#define BP_DISLOCATED (1<<13) #define HATCH_CLOSED 1 #define HATCH_UNSCREWED 2 #define HATCH_OPENED 3 -#define STOCK_BP_FLAGS_CHEST (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY) -#define STOCK_BP_FLAGS_HEAD (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY) -#define STOCK_BP_FLAGS_ARMS (BP_IS_GRABBY_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY) -#define STOCK_BP_FLAGS_LEGS (BP_IS_MOVEMENT_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY) +#define STOCK_BP_FLAGS_CHEST (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY | BP_CAN_BE_DISLOCATED) +#define STOCK_BP_FLAGS_HEAD (BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_ARTERY | BP_CAN_BE_DISLOCATED) +#define STOCK_BP_FLAGS_ARMS (BP_IS_GRABBY_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY | BP_CAN_BE_DISLOCATED) +#define STOCK_BP_FLAGS_LEGS (BP_IS_MOVEMENT_LIMB | BP_HAS_BLOOD | BP_HAS_BONES | BP_HAS_TENDON | BP_HAS_ARTERY| BP_CAN_BE_DISLOCATED) //check_bones() return values #define CHECKBONES_NONE (1<<0) diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 9d3e5d3e8edf..7ea730b5e8ac 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -113,7 +113,7 @@ //the define for visible message range in combat #define SAMETILE_MESSAGE_RANGE 1 -#define COMBAT_MESSAGE_RANGE 3 +#define COMBAT_MESSAGE_RANGE 4 #define DEFAULT_MESSAGE_RANGE 7 //Shove knockdown lengths (deciseconds) diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm index 2f8ebc836650..91ebec57f7f9 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm @@ -9,7 +9,7 @@ ///from base of atom/ShiftClick(): (/mob) #define COMSIG_CLICK_SHIFT "shift_click" #define COMPONENT_ALLOW_EXAMINATE (1<<0) //Allows the user to examinate regardless of client.eye. -///from base of atom/CtrlClickOn(): (/mob) +///from base of atom/CtrlClickOn(): (/mob, list/params) #define COMSIG_CLICK_CTRL "ctrl_click" ///from base of atom/AltClick(): (/mob) #define COMSIG_CLICK_ALT "alt_click" diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm index bdc082f9c759..eb3fccdbf931 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movement.dm @@ -2,26 +2,23 @@ // When the signal is called: (signal arguments) // All signals send the source datum of the signal as the first argument -///signal sent out by an atom when it checks if it can be pulled, for additional checks -#define COMSIG_ATOM_CAN_BE_PULLED "movable_can_be_pulled" - #define COMSIG_ATOM_CANT_PULL (1 << 0) +///signal sent out by an atom when it checks if it can be grabbed, for additional checks +#define COMSIG_ATOM_CAN_BE_GRABBED "movable_can_be_grabbed" + #define COMSIG_ATOM_NO_GRAB (1 << 0) ///signal sent out by an atom when it is no longer being pulled by something else : (atom/puller) -#define COMSIG_ATOM_NO_LONGER_PULLED "movable_no_longer_pulled" -///signal sent out by an atom when it is no longer pulling something : (atom/pulling) -#define COMSIG_ATOM_NO_LONGER_PULLING "movable_no_longer_pulling" +#define COMSIG_ATOM_NO_LONGER_GRABBED "movable_no_longer_grabbed" +///signal sent out by a living mob when it is no longer pulling something : (atom/pulling) +#define COMSIG_LIVING_NO_LONGER_GRABBING "living_no_longer_grabbing" ///called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) #define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" -///called on a movable (NOT living) when it starts pulling (atom/movable/pulled, state, force) -#define COMSIG_ATOM_START_PULL "movable_start_pull" -///called on /living when someone starts pulling (atom/movable/pulled, state, force) -#define COMSIG_LIVING_START_PULL "living_start_pull" -///called on /living when someone is pulled (mob/living/puller) -#define COMSIG_LIVING_GET_PULLED "living_start_pulled" -///called on /living, when pull is attempted, but before it completes, from base of [/mob/living/start_pulling]: (atom/movable/thing, force) -#define COMSIG_LIVING_TRY_PULL "living_try_pull" - #define COMSIG_LIVING_CANCEL_PULL (1 << 0) -/// Called from /mob/living/update_pull_movespeed -#define COMSIG_LIVING_UPDATING_PULL_MOVESPEED "living_updating_pull_movespeed" +///called on a movable when it starts pulling (atom/movable/pulled, state, force) +#define COMSIG_LIVING_START_GRAB "movable_start_grab" +///called on a movable when it has been grabbed +#define COMSIG_ATOM_GET_GRABBED "movable_start_grabbed" +///called on /living, when a grab is attempted, but before it completes, from base of [/mob/living/make_grab]: (atom/movable/thing, grab_type) +#define COMSIG_LIVING_TRY_GRAB "living_try_pull" + #define COMSIG_LIVING_CANCEL_GRAB (1 << 0) + /// Called from /mob/living/PushAM -- Called when this mob is about to push a movable, but before it moves /// (aotm/movable/being_pushed) #define COMSIG_LIVING_PUSHING_MOVABLE "living_pushing_movable" diff --git a/code/__DEFINES/grab_defines.dm b/code/__DEFINES/grab_defines.dm new file mode 100644 index 000000000000..1b3437a83073 --- /dev/null +++ b/code/__DEFINES/grab_defines.dm @@ -0,0 +1,9 @@ +// Trait sources for grabs +#define AGGRESSIVE_GRAB "source_aggressive_grab" +#define NECK_GRAB "source_neck_grab" +#define KILL_GRAB "source_kill_grab" + +/// Applied to movables that are aggressively grabbed OR HIGHER +#define TRAIT_AGGRESSIVE_GRAB "trait_aggressive_grab" +/// Applied to movables that are being strangled +#define TRAIT_KILL_GRAB "trait_strangle_grab" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index e4c1945cf50a..053da27efe1a 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -224,6 +224,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define isfalsewall(A) (istype(A, /obj/structure/falsewall)) +#define isgrab(A) (istype(A, /obj/item/hand_item/grab)) + //Assemblies #define isassembly(O) (istype(O, /obj/item/assembly)) @@ -271,3 +273,4 @@ GLOBAL_LIST_INIT(book_types, typecacheof(list( #define is_security_officer_job(job_type) (istype(job_type, /datum/job/security_officer)) #define is_research_director_job(job_type) (istype(job_type, /datum/job/research_director)) #define is_unassigned_job(job_type) (istype(job_type, /datum/job/unassigned)) + diff --git a/code/__DEFINES/movement.dm b/code/__DEFINES/movement.dm index 8dd637fb8784..c0e4adc594b1 100644 --- a/code/__DEFINES/movement.dm +++ b/code/__DEFINES/movement.dm @@ -61,7 +61,7 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0) /// Used when the grip on a pulled object shouldn't be broken. #define FALL_RETAIN_PULL (1<<3) -/// Runs check_pulling() by the end of [/atom/movable/proc/zMove] for every movable that's pulling something. Should be kept enabled unless you know what you are doing. +/// Runs recheck_grabs() by the end of [/atom/movable/proc/zMove] for every movable that's pulling something. Should be kept enabled unless you know what you are doing. #define ZMOVE_CHECK_PULLING (1<<0) /// Checks if pulledby is nearby. if not, stop being pulled. #define ZMOVE_CHECK_PULLEDBY (1<<1) @@ -79,6 +79,8 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0) #define ZMOVE_INCLUDE_PULLED (1<<8) /// Skips check for whether the moving atom is anchored or not. #define ZMOVE_ALLOW_ANCHORED (1<<9) +/// Skip CanMoveOnto() checks +#define ZMOVE_SKIP_CANMOVEONTO (1<<10) #define ZMOVE_CHECK_PULLS (ZMOVE_CHECK_PULLING|ZMOVE_CHECK_PULLEDBY) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index cd8e84dc99e2..4fe9b8993eba 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -408,8 +408,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_SPRAY_PAINTABLE "spray_paintable" /// This person is blushing #define TRAIT_BLUSHING "blushing" - -#define TRAIT_NOBLEED "nobleed" //This carbon doesn't bleed +/// This bodypart is being held in a grab, and reduces bleeding +#define TRAIT_BODYPART_GRABBED "bodypart_grabbed" +/// This carbon doesn't bleed +#define TRAIT_NOBLEED "nobleed" /// This atom can ignore the "is on a turf" check for simple AI datum attacks, allowing them to attack from bags or lockers as long as any other conditions are met #define TRAIT_AI_BAGATTACK "bagattack" /// This mobs bodyparts are invisible but still clickable. @@ -887,7 +889,6 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define STATION_TRAIT_UNIQUE_AI "station_trait_unique_ai" #define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation" #define STATION_TRAIT_PREMIUM_INTERNALS "station_trait_premium_internals" -#define STATION_TRAIT_RANDOM_ARRIVALS "station_trait_random_arrivals" #define STATION_TRAIT_HANGOVER "station_trait_hangover" #define STATION_TRAIT_FILLED_MAINT "station_trait_filled_maint" #define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint" diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index cfba25e00440..f81755d0a9f3 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -87,6 +87,15 @@ GLOB.emote_list = init_emote_list() + for(var/datum/grab/G as anything in subtypesof(/datum/grab)) + if(isabstract(G)) + continue + GLOB.all_grabstates[G] = new G + + for(var/path in GLOB.all_grabstates) + var/datum/grab/G = GLOB.all_grabstates[path] + G.refresh_updown() + init_crafting_recipes(GLOB.crafting_recipes) init_loadout_references() init_augment_references() diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm index 0329134a9f2d..39c5ee4ff32e 100644 --- a/code/_onclick/adjacent.dm +++ b/code/_onclick/adjacent.dm @@ -145,3 +145,41 @@ else if( !border_only ) // dense, not on border, cannot pass over return FALSE return TRUE + +/atom/proc/MultiZAdjacent(atom/neighbor) + var/turf/T = get_turf(src) + var/turf/N = get_turf(neighbor) + + // Not on valid turfs. + if(QDELETED(src) || QDELETED(neighbor) || !istype(T) || !istype(N)) + return FALSE + + // On the same z-level, we don't need to care about multiz. + if(N.z == T.z) + return Adjacent(neighbor) + + // More than one z-level away from each other. + if(abs(N.x - T.x) > 1 || abs(N.y - T.y) > 1 || abs(N.z - T.z) > 1) + return FALSE + + + // Are they below us? + if(N.z < T.z && HasBelow(T.z)) + var/turf/B = GetBelow(T) + . = isopenspaceturf(T) && neighbor.Adjacent(B) + if(!.) + B = GetAbove(N) + . = isopenspaceturf(B) && src.Adjacent(B) + return + + + // Are they above us? + if(HasAbove(T.z)) + var/turf/A = GetAbove(T) + . = isopenspaceturf(A) && neighbor.Adjacent(A) + if(!.) + A = GetBelow(N) + . = isopenspaceturf(N) && src.Adjacent(A) + return + + return FALSE diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 94399b1b8c62..e587a08ac1ce 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -123,7 +123,7 @@ A.AICtrlShiftClick(src) /mob/living/silicon/ai/ShiftClickOn(atom/A) A.AIShiftClick(src) -/mob/living/silicon/ai/CtrlClickOn(atom/A) +/mob/living/silicon/ai/CtrlClickOn(atom/A, list/params) A.AICtrlClick(src) /mob/living/silicon/ai/AltClickOn(atom/A) A.AIAltClick(src) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index f80f121fbab6..58cb44fa436b 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -99,7 +99,7 @@ AltClickOn(A) return if(LAZYACCESS(modifiers, CTRL_CLICK)) - CtrlClickOn(A) + CtrlClickOn(A, modifiers) return //PARIAH EDIT ADDITION @@ -351,11 +351,11 @@ * Ctrl click * For most objects, pull */ -/mob/proc/CtrlClickOn(atom/A) - A.CtrlClick(src) +/mob/proc/CtrlClickOn(atom/A, list/params) + A.CtrlClick(src, params) return -/atom/proc/CtrlClick(mob/user) +/atom/proc/CtrlClick(mob/user, list/params) SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) SEND_SIGNAL(user, COMSIG_MOB_CTRL_CLICKED, src) var/mob/living/ML = user @@ -364,7 +364,7 @@ if(!can_interact(user)) return FALSE -/mob/living/CtrlClick(mob/user) +/mob/living/CtrlClick(mob/user, list/params) if(!isliving(user) || !user.CanReach(src) || user.incapacitated()) return ..() @@ -379,7 +379,7 @@ return ..() -/mob/living/carbon/human/CtrlClick(mob/user) +/mob/living/carbon/human/CtrlClick(mob/user, list/params) if(!ishuman(user) || !user.CanReach(src) || user.incapacitated()) return ..() @@ -388,7 +388,13 @@ return FALSE var/mob/living/carbon/human/human_user = user - if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art)) + // If they're wielding a grab item, do the normal click chain. + var/obj/item/hand_item/grab/G = user.get_active_held_item() + if(isgrab(G)) + G.current_grab.hit_with_grab(G, src, params) + return TRUE + + if(human_user.dna.species.grab(human_user, src, human_user.mind.martial_art, params)) human_user.changeNext_move(CLICK_CD_MELEE) human_user.animate_interact(src, INTERACT_GRAB) return TRUE diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index 7f12d938471c..774072aace91 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -99,7 +99,7 @@ A.BorgCtrlShiftClick(src) /mob/living/silicon/robot/ShiftClickOn(atom/A) A.BorgShiftClick(src) -/mob/living/silicon/robot/CtrlClickOn(atom/A) +/mob/living/silicon/robot/CtrlClickOn(atom/A, list/params) A.BorgCtrlClick(src) /mob/living/silicon/robot/AltClickOn(atom/A) A.BorgAltClick(src) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 2509fc9ec2db..5d4fda7800fb 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -395,10 +395,12 @@ /atom/movable/screen/pull/Click() if(isobserver(usr)) return - usr.stop_pulling() + if(isliving(usr) && usr == hud.mymob) + var/mob/living/L = usr + L.release_all_grabs() /atom/movable/screen/pull/update_icon_state() - icon_state = "[base_icon_state][hud?.mymob?.pulling ? null : 0]" + icon_state = "[base_icon_state][LAZYLEN(hud?.mymob?:get_active_grabs()) ? null : 0]" return ..() /atom/movable/screen/resist diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index dc9a43b16b2b..24e9850bbe2e 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -348,3 +348,16 @@ visible_message(span_danger("[attack_message_spectator]"), vision_distance = COMBAT_MESSAGE_RANGE) return 1 + +/** + * Interaction handler for being clicked on with a grab. This is called regardless of user intent. + * + * **Parameters**: + * - `grab` - The grab item being used. + * - `click_params` - List of click parameters. + * + * Returns boolean to indicate whether the attack call was handled or not. If `FALSE`, the next `use_*` proc in the + * resolve chain will be called. + */ +/atom/proc/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + return FALSE diff --git a/code/_onclick/other_mobs.dm b/code/_onclick/other_mobs.dm index 7c68a2ca584a..9085ab8e8687 100644 --- a/code/_onclick/other_mobs.dm +++ b/code/_onclick/other_mobs.dm @@ -122,7 +122,7 @@ return TRUE if(isturf(A) && get_dist(src,A) <= 1) - Move_Pulled(A) + move_grabbed_atoms_towards(A) return TRUE /* diff --git a/code/_onclick/overmind.dm b/code/_onclick/overmind.dm index d6b8994f82f0..555ba4b87d76 100644 --- a/code/_onclick/overmind.dm +++ b/code/_onclick/overmind.dm @@ -25,7 +25,7 @@ if(T) rally_spores(T) -/mob/camera/blob/CtrlClickOn(atom/A) //Create a shield +/mob/camera/blob/CtrlClickOn(atom/A, list/params) //Create a shield var/turf/T = get_turf(A) if(T) create_shield(T) diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm index 08f6dc16b908..d3d1a18da088 100644 --- a/code/datums/ai/generic/generic_behaviors.dm +++ b/code/datums/ai/generic/generic_behaviors.dm @@ -43,7 +43,7 @@ if(get_dist(batman, big_guy) >= give_up_distance) finish_action(controller, FALSE, target_key) - big_guy.start_pulling(batman) + big_guy.try_make_grab(batman) big_guy.setDir(get_dir(big_guy, batman)) batman.visible_message(span_warning("[batman] gets a slightly too tight hug from [big_guy]!"), span_userdanger("You feel your body break as [big_guy] embraces you!")) diff --git a/code/datums/ai/idle_behaviors/idle_dog.dm b/code/datums/ai/idle_behaviors/idle_dog.dm index fd27bc6968e3..6e570e301143 100644 --- a/code/datums/ai/idle_behaviors/idle_dog.dm +++ b/code/datums/ai/idle_behaviors/idle_dog.dm @@ -1,7 +1,7 @@ ///Dog specific idle behavior. /datum/idle_behavior/idle_dog/perform_idle_behavior(delta_time, datum/ai_controller/dog/controller) var/mob/living/living_pawn = controller.pawn - if(!isturf(living_pawn.loc) || living_pawn.pulledby) + if(!isturf(living_pawn.loc) || LAZYLEN(living_pawn.grabbed_by)) return // if we were just ordered to heel, chill out for a bit diff --git a/code/datums/ai/idle_behaviors/idle_monkey.dm b/code/datums/ai/idle_behaviors/idle_monkey.dm index 66b630c35e12..c1c5f2d72b70 100644 --- a/code/datums/ai/idle_behaviors/idle_monkey.dm +++ b/code/datums/ai/idle_behaviors/idle_monkey.dm @@ -1,7 +1,7 @@ /datum/idle_behavior/idle_monkey/perform_idle_behavior(delta_time, datum/ai_controller/controller) var/mob/living/living_pawn = controller.pawn - if(DT_PROB(25, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) + if(DT_PROB(25, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !LAZYLEN(living_pawn.grabbed_by)) var/move_dir = pick(GLOB.alldirs) living_pawn.Move(get_step(living_pawn, move_dir), move_dir) else if(DT_PROB(5, delta_time)) diff --git a/code/datums/ai/idle_behaviors/idle_random_walk.dm b/code/datums/ai/idle_behaviors/idle_random_walk.dm index 1187d8786c1d..8adb8b6e01a8 100644 --- a/code/datums/ai/idle_behaviors/idle_random_walk.dm +++ b/code/datums/ai/idle_behaviors/idle_random_walk.dm @@ -6,7 +6,7 @@ . = ..() var/mob/living/living_pawn = controller.pawn - if(DT_PROB(walk_chance, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !living_pawn.pulledby) + if(DT_PROB(walk_chance, delta_time) && (living_pawn.mobility_flags & MOBILITY_MOVE) && isturf(living_pawn.loc) && !LAZYLEN(living_pawn.grabbed_by)) var/move_dir = pick(GLOB.alldirs) living_pawn.Move(get_step(living_pawn, move_dir), move_dir) diff --git a/code/datums/ai/monkey/monkey_behaviors.dm b/code/datums/ai/monkey/monkey_behaviors.dm index 9132106dd88a..7c8a4499d0a7 100644 --- a/code/datums/ai/monkey/monkey_behaviors.dm +++ b/code/datums/ai/monkey/monkey_behaviors.dm @@ -259,9 +259,15 @@ controller.current_movement_target = target - if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) //Dont steal from my fellow monkeys. + var/monkey_is_grabbing_target = FALSE + for(var/obj/item/hand_item/grab/G as anything in target.grabbed_by) + if(!HAS_AI_CONTROLLER_TYPE(G.assailant, /datum/ai_controller/monkey)) + monkey_is_grabbing_target = TRUE + break + + if(!living_pawn.is_grabbing(target) && !monkey_is_grabbing_target) //Dont steal from my fellow monkeys. if(living_pawn.Adjacent(target) && isturf(target.loc)) - target.grabbedby(living_pawn) + living_pawn.try_make_grab(target) return //Do the rest next turn var/datum/weakref/disposal_ref = controller.blackboard[disposal_target_key] diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 98566a77a36f..08a77309ca3b 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -54,7 +54,7 @@ have ways of interacting with a specific mob and control it. RegisterSignal(new_pawn, COMSIG_MOB_ATTACK_ALIEN, PROC_REF(on_attack_alien)) RegisterSignal(new_pawn, COMSIG_ATOM_BULLET_ACT, PROC_REF(on_bullet_act)) RegisterSignal(new_pawn, COMSIG_ATOM_HITBY, PROC_REF(on_hitby)) - RegisterSignal(new_pawn, COMSIG_LIVING_START_PULL, PROC_REF(on_startpulling)) + RegisterSignal(new_pawn, COMSIG_ATOM_GET_GRABBED, PROC_REF(on_grabbed)) RegisterSignal(new_pawn, COMSIG_LIVING_TRY_SYRINGE, PROC_REF(on_try_syringe)) RegisterSignal(new_pawn, COMSIG_ATOM_HULK_ATTACK, PROC_REF(on_attack_hulk)) RegisterSignal(new_pawn, COMSIG_CARBON_CUFF_ATTEMPTED, PROC_REF(on_attempt_cuff)) @@ -70,7 +70,7 @@ have ways of interacting with a specific mob and control it. COMSIG_ATOM_ATTACK_PAW, COMSIG_ATOM_BULLET_ACT, COMSIG_ATOM_HITBY, - COMSIG_LIVING_START_PULL, + COMSIG_ATOM_GET_GRABBED, COMSIG_LIVING_TRY_SYRINGE, COMSIG_ATOM_HULK_ATTACK, COMSIG_CARBON_CUFF_ATTEMPTED, @@ -189,11 +189,12 @@ have ways of interacting with a specific mob and control it. in_the_way_mob.knockOver(living_pawn) return -/datum/ai_controller/monkey/proc/on_startpulling(datum/source, atom/movable/puller, state, force) +/datum/ai_controller/monkey/proc/on_grabbed(datum/source, atom/movable/puller) SIGNAL_HANDLER + var/mob/living/living_pawn = pawn if(!IS_DEAD_OR_INCAP(living_pawn) && prob(MONKEY_PULL_AGGRO_PROB)) // nuh uh you don't pull me! - retaliate(living_pawn.pulledby) + retaliate(puller) return TRUE /datum/ai_controller/monkey/proc/on_try_syringe(datum/source, mob/user) diff --git a/code/datums/ai/movement/_ai_movement.dm b/code/datums/ai/movement/_ai_movement.dm index fa83d64e7986..c383b15769a7 100644 --- a/code/datums/ai/movement/_ai_movement.dm +++ b/code/datums/ai/movement/_ai_movement.dm @@ -29,7 +29,7 @@ source.delay = controller.movement_delay var/can_move = TRUE - if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && pawn.pulledby) //Need to store more state. Annoying. + if(controller.ai_traits & STOP_MOVING_WHEN_PULLED && LAZYLEN(pawn.grabbed_by)) //Need to store more state. Annoying. can_move = FALSE if(!isturf(pawn.loc)) //No moving if not on a turf diff --git a/code/datums/ai/robot_customer/robot_customer_controller.dm b/code/datums/ai/robot_customer/robot_customer_controller.dm index 901c0f2f69c6..22fc2e267074 100644 --- a/code/datums/ai/robot_customer/robot_customer_controller.dm +++ b/code/datums/ai/robot_customer/robot_customer_controller.dm @@ -21,12 +21,12 @@ if(!istype(new_pawn, /mob/living/simple_animal/robot_customer)) return AI_CONTROLLER_INCOMPATIBLE RegisterSignal(new_pawn, COMSIG_PARENT_ATTACKBY, PROC_REF(on_attackby)) - RegisterSignal(new_pawn, COMSIG_LIVING_GET_PULLED, PROC_REF(on_get_pulled)) + RegisterSignal(new_pawn, COMSIG_ATOM_GET_GRABBED, PROC_REF(on_get_pulled)) RegisterSignal(new_pawn, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_get_punched)) return ..() //Run parent at end /datum/ai_controller/robot_customer/UnpossessPawn(destroy) - UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_LIVING_GET_PULLED, COMSIG_ATOM_ATTACK_HAND)) + UnregisterSignal(pawn, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_GET_GRABBED, COMSIG_ATOM_ATTACK_HAND)) return ..() //Run parent at end /datum/ai_controller/robot_customer/proc/on_attackby(datum/source, obj/item/I, mob/living/user) diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm index 52c418bab37c..e7f3e112663c 100644 --- a/code/datums/brain_damage/special.dm +++ b/code/datums/brain_damage/special.dm @@ -170,18 +170,17 @@ /datum/brain_trauma/special/quantum_alignment/proc/try_entangle() //Check for pulled mobs - if(ismob(owner.pulling)) - entangle(owner.pulling) + var/list/grabs = owner.get_active_grabs() + if(length(grabs)) + for(var/obj/item/hand_item/grab/G in grabs) + entangle(G.affecting) return + //Check for adjacent mobs for(var/mob/living/L in oview(1, owner)) if(owner.Adjacent(L)) entangle(L) return - //Check for pulled objects - if(isobj(owner.pulling)) - entangle(owner.pulling) - return //Check main hand var/obj/item/held_item = owner.get_active_held_item() diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm index 7f93831b78a2..6574667a4b2b 100644 --- a/code/datums/components/butchering.dm +++ b/code/datums/components/butchering.dm @@ -29,6 +29,7 @@ can_be_blunt = _can_be_blunt if(_butcher_callback) butcher_callback = _butcher_callback + if(isitem(parent)) RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(onItemAttack)) @@ -40,49 +41,12 @@ INVOKE_ASYNC(src, PROC_REF(startButcher), source, M, user) return COMPONENT_CANCEL_ATTACK_CHAIN - if(ishuman(M) && source.force && (source.sharpness & SHARP_EDGED)) - var/mob/living/carbon/human/H = M - if((user.pulling == H && user.grab_state >= GRAB_AGGRESSIVE) && user.zone_selected == BODY_ZONE_HEAD) // Only aggressive grabbed can be sliced. - if(H.has_status_effect(/datum/status_effect/neck_slice)) - user.show_message(span_warning("[H]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \ - span_warning("Their neck has already been already cut, you can't make the bleeding any worse!")) - return COMPONENT_CANCEL_ATTACK_CHAIN - INVOKE_ASYNC(src, PROC_REF(startNeckSlice), source, H, user) - return COMPONENT_CANCEL_ATTACK_CHAIN - /datum/component/butchering/proc/startButcher(obj/item/source, mob/living/M, mob/living/user) to_chat(user, span_notice("You begin to butcher [M]...")) playsound(M.loc, butcher_sound, 50, TRUE, -1) if(do_after(user, M, speed, DO_PUBLIC, display = parent) && M.Adjacent(source)) Butcher(user, M) -/datum/component/butchering/proc/startNeckSlice(obj/item/source, mob/living/carbon/human/H, mob/living/user) - if(DOING_INTERACTION_WITH_TARGET(user, H)) - to_chat(user, span_warning("You're already interacting with [H]!")) - return - - user.visible_message(span_danger("[user] is slitting [H]'s throat!"), \ - span_danger("You start slicing [H]'s throat!"), \ - span_hear("You hear a cutting noise!"), ignored_mobs = H) - H.show_message(span_userdanger("Your throat is being slit by [user]!"), MSG_VISUAL, \ - span_userdanger("Something is cutting into your neck!"), NONE) - log_combat(user, H, "attempted throat slitting", source) - - playsound(H.loc, butcher_sound, 50, TRUE, -1) - if(do_after(user, H, clamp(500 / source.force, 30, 100), DO_PUBLIC, display = parent) && H.Adjacent(source)) - if(H.has_status_effect(/datum/status_effect/neck_slice)) - user.show_message(span_warning("[H]'s neck has already been already cut, you can't make the bleeding any worse!"), MSG_VISUAL, \ - span_warning("Their neck has already been already cut, you can't make the bleeding any worse!")) - return - - H.visible_message(span_danger("[user] slits [H]'s throat!"), \ - span_userdanger("[user] slits your throat...")) - log_combat(user, H, "wounded via throat slitting", source) - var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD) - - slit_throat.create_wound_easy(/datum/wound/cut/flesh, 30) - H.apply_status_effect(/datum/status_effect/neck_slice) - /** * Handles a user butchering a target * diff --git a/code/datums/components/drift.dm b/code/datums/components/drift.dm index 58bcff8acdf6..a649b3947f9c 100644 --- a/code/datums/components/drift.dm +++ b/code/datums/components/drift.dm @@ -92,14 +92,14 @@ // This way you can't ride two movements at once while drifting, since that'd be dumb as fuck RegisterSignal(movable_parent, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, PROC_REF(handle_glidesize_update)) // If you stop pulling something mid drift, I want it to retain that momentum - RegisterSignal(movable_parent, COMSIG_ATOM_NO_LONGER_PULLING, PROC_REF(stopped_pulling)) + RegisterSignal(movable_parent, COMSIG_LIVING_NO_LONGER_GRABBING, PROC_REF(stopped_pulling)) /datum/component/drift/proc/drifting_stop() SIGNAL_HANDLER var/atom/movable/movable_parent = parent movable_parent.inertia_moving = FALSE ignore_next_glide = FALSE - UnregisterSignal(movable_parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, COMSIG_ATOM_NO_LONGER_PULLING)) + UnregisterSignal(movable_parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, COMSIG_LIVING_NO_LONGER_GRABBING)) /datum/component/drift/proc/before_move(datum/source) SIGNAL_HANDLER diff --git a/code/datums/components/jetpack.dm b/code/datums/components/jetpack.dm index 73a26b986fd3..1f9f2dda3820 100644 --- a/code/datums/components/jetpack.dm +++ b/code/datums/components/jetpack.dm @@ -109,7 +109,7 @@ return if(!(user.movement_type & FLOATING) || user.buckled)//You don't want use jet in gravity or while buckled. return - if(user.pulledby)//You don't must use jet if someone pull you + if(LAZYLEN(user.grabbed_by))//You don't must use jet if someone pull you return if(user.throwing)//You don't must use jet if you thrown return diff --git a/code/datums/components/riding/riding_mob.dm b/code/datums/components/riding/riding_mob.dm index f6945d215cc1..3a112c079bf3 100644 --- a/code/datums/components/riding/riding_mob.dm +++ b/code/datums/components/riding/riding_mob.dm @@ -13,7 +13,7 @@ . = ..() var/mob/living/living_parent = parent - living_parent.stop_pulling() // was only used on humans previously, may change some other behavior + living_parent.release_all_grabs() // was only used on humans previously, may change some other behavior log_riding(living_parent, riding_mob) riding_mob.set_glide_size(living_parent.glide_size) handle_vehicle_offsets(living_parent.dir) diff --git a/code/datums/components/rot.dm b/code/datums/components/rot.dm index 9e1b186dc8e7..8ec182019770 100644 --- a/code/datums/components/rot.dm +++ b/code/datums/components/rot.dm @@ -43,7 +43,7 @@ RegisterSignal(parent, COMSIG_MOVABLE_BUMP, PROC_REF(rot_react)) if(isliving(parent)) RegisterSignal(parent, COMSIG_LIVING_REVIVE, PROC_REF(react_to_revive)) //mobs stop this when they come to life - RegisterSignal(parent, COMSIG_LIVING_GET_PULLED, PROC_REF(rot_react_touch)) + RegisterSignal(parent, COMSIG_ATOM_GET_GRABBED, PROC_REF(rot_react_touch)) if(iscarbon(parent)) var/mob/living/carbon/carbon_parent = parent RegisterSignal(carbon_parent.reagents, list(COMSIG_REAGENTS_ADD_REAGENT, diff --git a/code/datums/components/shy.dm b/code/datums/components/shy.dm index 433b723e9923..b3823031a3b0 100644 --- a/code/datums/components/shy.dm +++ b/code/datums/components/shy.dm @@ -45,7 +45,7 @@ /datum/component/shy/RegisterWithParent() RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(on_clickon)) - RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, PROC_REF(on_try_pull)) + RegisterSignal(parent, COMSIG_LIVING_TRY_GRAB, PROC_REF(on_try_pull)) RegisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), PROC_REF(on_unarmed_attack)) RegisterSignal(parent, COMSIG_TRY_STRIP, PROC_REF(on_try_strip)) RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, PROC_REF(on_try_alt_action)) @@ -53,7 +53,7 @@ /datum/component/shy/UnregisterFromParent() UnregisterSignal(parent, list( COMSIG_MOB_CLICKON, - COMSIG_LIVING_TRY_PULL, + COMSIG_LIVING_TRY_GRAB, COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_TRY_STRIP, COMSIG_TRY_ALT_ACTION, @@ -118,7 +118,7 @@ /datum/component/shy/proc/on_try_pull(datum/source, atom/movable/target, force) SIGNAL_HANDLER - return is_shy(target) && COMSIG_LIVING_CANCEL_PULL + return is_shy(target) && COMSIG_LIVING_CANCEL_GRAB /datum/component/shy/proc/on_unarmed_attack(datum/source, atom/target, proximity, modifiers) SIGNAL_HANDLER diff --git a/code/datums/components/shy_in_room.dm b/code/datums/components/shy_in_room.dm index cf4319339630..a6a7933a0b4b 100644 --- a/code/datums/components/shy_in_room.dm +++ b/code/datums/components/shy_in_room.dm @@ -16,7 +16,7 @@ /datum/component/shy_in_room/RegisterWithParent() RegisterSignal(parent, COMSIG_MOB_CLICKON, PROC_REF(on_clickon)) - RegisterSignal(parent, COMSIG_LIVING_TRY_PULL, PROC_REF(on_try_pull)) + RegisterSignal(parent, COMSIG_LIVING_TRY_GRAB, PROC_REF(on_try_pull)) RegisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK), PROC_REF(on_unarmed_attack)) RegisterSignal(parent, COMSIG_TRY_STRIP, PROC_REF(on_try_strip)) RegisterSignal(parent, COMSIG_TRY_ALT_ACTION, PROC_REF(on_try_alt_action)) @@ -25,7 +25,7 @@ /datum/component/shy_in_room/UnregisterFromParent() UnregisterSignal(parent, list( COMSIG_MOB_CLICKON, - COMSIG_LIVING_TRY_PULL, + COMSIG_LIVING_TRY_GRAB, COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, COMSIG_TRY_STRIP, @@ -60,7 +60,7 @@ /datum/component/shy_in_room/proc/on_try_pull(datum/source, atom/movable/target, force) SIGNAL_HANDLER - return is_shy(target) && COMSIG_LIVING_CANCEL_PULL + return is_shy(target) && COMSIG_LIVING_CANCEL_GRAB /datum/component/shy_in_room/proc/on_unarmed_attack(datum/source, atom/target, proximity, modifiers) SIGNAL_HANDLER diff --git a/code/datums/components/strong_pull.dm b/code/datums/components/strong_pull.dm index f0cb206a650d..85e251c234ae 100644 --- a/code/datums/components/strong_pull.dm +++ b/code/datums/components/strong_pull.dm @@ -17,7 +17,7 @@ Basically, the items they pull cannot be pulled (except by the puller) /datum/component/strong_pull/RegisterWithParent() . = ..() - RegisterSignal(parent, COMSIG_LIVING_START_PULL, PROC_REF(on_pull)) + RegisterSignal(parent, COMSIG_LIVING_START_GRAB, PROC_REF(on_pull)) /** * Called when the parent grabs something, adds signals to the object to reject interactions @@ -25,8 +25,8 @@ Basically, the items they pull cannot be pulled (except by the puller) /datum/component/strong_pull/proc/on_pull(datum/source, atom/movable/pulled, state, force) SIGNAL_HANDLER strongpulling = pulled - RegisterSignal(strongpulling, COMSIG_ATOM_CAN_BE_PULLED, PROC_REF(reject_further_pulls)) - RegisterSignal(strongpulling, COMSIG_ATOM_NO_LONGER_PULLED, PROC_REF(on_no_longer_pulled)) + RegisterSignal(strongpulling, COMSIG_ATOM_CAN_BE_GRABBED, PROC_REF(reject_further_pulls)) + RegisterSignal(strongpulling, COMSIG_ATOM_NO_LONGER_GRABBED, PROC_REF(on_no_longer_pulled)) if(istype(strongpulling, /obj/structure/closet) && !istype(strongpulling, /obj/structure/closet/body_bag)) var/obj/structure/closet/grabbed_closet = strongpulling grabbed_closet.strong_grab = TRUE @@ -37,13 +37,13 @@ Basically, the items they pull cannot be pulled (except by the puller) /datum/component/strong_pull/proc/reject_further_pulls(datum/source, mob/living/puller) SIGNAL_HANDLER if(puller != parent) //for increasing grabs, you need to have a valid pull. thus, parent should be able to pull the same object again - return COMSIG_ATOM_CANT_PULL + return COMSIG_ATOM_NO_GRAB /* * Unregisters signals and stops any buffs to pulling. */ /datum/component/strong_pull/proc/lose_strong_grip() - UnregisterSignal(strongpulling, list(COMSIG_ATOM_CAN_BE_PULLED, COMSIG_ATOM_NO_LONGER_PULLED)) + UnregisterSignal(strongpulling, list(COMSIG_ATOM_CAN_BE_GRABBED, COMSIG_ATOM_NO_LONGER_GRABBED)) if(istype(strongpulling, /obj/structure/closet)) var/obj/structure/closet/ungrabbed_closet = strongpulling ungrabbed_closet.strong_grab = FALSE diff --git a/code/datums/components/tackle.dm b/code/datums/components/tackle.dm index 68da2882c5d9..c665ee56bddc 100644 --- a/code/datums/components/tackle.dm +++ b/code/datums/components/tackle.dm @@ -74,7 +74,7 @@ if(modifiers[ALT_CLICK] || modifiers[SHIFT_CLICK] || modifiers[CTRL_CLICK] || modifiers[MIDDLE_CLICK]) return - if(!user.throw_mode || user.get_active_held_item() || user.pulling || user.buckled || user.incapacitated()) + if(!user.throw_mode || user.get_active_held_item() || user.get_active_grabs() || user.buckled || user.incapacitated()) return if(!A || !(isturf(A) || isturf(A.loc))) @@ -211,9 +211,7 @@ target.stamina.adjust(-40) target.Paralyze(5) target.Knockdown(30) - if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(S.dna.species, TYPE_PROC_REF(/datum/species, grab), S, T) - S.setGrabState(GRAB_PASSIVE) + S.try_make_grab(target, /datum/grab/normal/aggressive) if(5 to INFINITY) // absolutely BODIED user.visible_message(span_warning("[user] lands a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), span_userdanger("You land a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!"), ignored_mobs = target) @@ -225,10 +223,7 @@ target.stamina.adjust(-40) target.Paralyze(5) target.Knockdown(30) - if(ishuman(target) && ishuman(user)) - INVOKE_ASYNC(S.dna.species, TYPE_PROC_REF(/datum/species, grab), S, T) - S.setGrabState(GRAB_AGGRESSIVE) - + S.try_make_grab(target, /datum/grab/normal/aggressive) return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH diff --git a/code/datums/elements/atmos_requirements.dm b/code/datums/elements/atmos_requirements.dm index ff2ede8401b2..71f074978291 100644 --- a/code/datums/elements/atmos_requirements.dm +++ b/code/datums/elements/atmos_requirements.dm @@ -36,7 +36,7 @@ target.throw_alert(ALERT_NOT_ENOUGH_OXYGEN, /atom/movable/screen/alert/not_enough_oxy) /datum/element/atmos_requirements/proc/is_breathable_atmos(mob/living/target) - if(target.pulledby && target.pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) + if(HAS_TRAIT(target, TRAIT_KILL_GRAB) && atmos_requirements["min_oxy"]) return FALSE if(!isopenturf(target.loc)) diff --git a/code/datums/elements/nerfed_pulling.dm b/code/datums/elements/nerfed_pulling.dm deleted file mode 100644 index c192e04f5c2d..000000000000 --- a/code/datums/elements/nerfed_pulling.dm +++ /dev/null @@ -1,53 +0,0 @@ -/// This living will be slower when pulling/moving anything in the given typecache -/datum/element/nerfed_pulling - element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH - id_arg_index = 2 - - /// The typecache of things that shouldn't be easily movable - var/list/typecache - -/datum/element/nerfed_pulling/Attach(datum/target, list/typecache) - . = ..() - - if (!isliving(target)) - return ELEMENT_INCOMPATIBLE - - src.typecache = typecache - - RegisterSignal(target, COMSIG_LIVING_PUSHING_MOVABLE, PROC_REF(on_push_movable)) - RegisterSignal(target, COMSIG_LIVING_UPDATING_PULL_MOVESPEED, PROC_REF(on_updating_pull_movespeed)) - -/datum/element/nerfed_pulling/Detach(mob/living/source) - source.remove_movespeed_modifier(/datum/movespeed_modifier/nerfed_bump) - source.remove_movespeed_modifier(/datum/movespeed_modifier/nerfed_pull) - - UnregisterSignal(source, list(COMSIG_LIVING_PUSHING_MOVABLE, COMSIG_LIVING_UPDATING_PULL_MOVESPEED)) - - return ..() - -/datum/element/nerfed_pulling/proc/on_push_movable(mob/living/source, atom/movable/being_pushed) - SIGNAL_HANDLER - - if (!will_slow_down(being_pushed)) - return - - source.add_movespeed_modifier(/datum/movespeed_modifier/nerfed_bump) - addtimer(CALLBACK(source, TYPE_PROC_REF(/mob, remove_movespeed_modifier), /datum/movespeed_modifier/nerfed_bump), 1 SECONDS, TIMER_OVERRIDE | TIMER_UNIQUE) - -/datum/element/nerfed_pulling/proc/on_updating_pull_movespeed(mob/living/source) - SIGNAL_HANDLER - - if (!will_slow_down(source.pulling)) - source.remove_movespeed_modifier(/datum/movespeed_modifier/nerfed_pull) - return - - source.add_movespeed_modifier(/datum/movespeed_modifier/nerfed_pull) - -/datum/element/nerfed_pulling/proc/will_slow_down(datum/input) - return !isnull(input) && typecache[input.type] - -/datum/movespeed_modifier/nerfed_pull - multiplicative_slowdown = 5.5 - -/datum/movespeed_modifier/nerfed_bump - multiplicative_slowdown = 5.5 diff --git a/code/datums/keybinding/mob.dm b/code/datums/keybinding/mob.dm index e34142ffd72f..e3bbdc8e4f2d 100644 --- a/code/datums/keybinding/mob.dm +++ b/code/datums/keybinding/mob.dm @@ -12,11 +12,13 @@ . = ..() if(.) return - var/mob/M = user.mob - if(!M.pulling) - to_chat(user, span_notice("You are not pulling anything.")) + var/mob/living/M = user.mob + if(!istype(M)) + return + if(!LAZYLEN(M.get_active_grabs())) + to_chat(user, span_notice("You are not grabbing anything.")) else - M.stop_pulling() + M.release_all_grabs() return TRUE /datum/keybinding/mob/swap_hands diff --git a/code/datums/martial/cqc.dm b/code/datums/martial/cqc.dm index 262d299c4578..6b9702fc67e3 100644 --- a/code/datums/martial/cqc.dm +++ b/code/datums/martial/cqc.dm @@ -122,11 +122,17 @@ add_to_streak("G",D) if(check_streak(A,D)) //if a combo is made no grab upgrade is done return TRUE - old_grab_state = A.grab_state - D.grabbedby(A, 1) + var/obj/item/hand_item/grab/G = A.is_grabbing(D) + if(G) + old_grab_state = G?.current_grab.damage_stage + if(old_grab_state == GRAB_PASSIVE) + G.upgrade() + + else + A.try_make_grab(D, /datum/grab/normal/aggressive) + if(old_grab_state == GRAB_PASSIVE) D.drop_all_held_items() - A.setGrabState(GRAB_AGGRESSIVE) //Instant aggressive grab if on grab intent log_combat(A, D, "grabbed", addition="aggressively") D.visible_message(span_warning("[A] violently grabs [D]!"), \ span_userdanger("You're grabbed violently by [A]!"), span_hear("You hear sounds of aggressive fondling!"), COMBAT_MESSAGE_RANGE, A) @@ -191,15 +197,17 @@ to_chat(A, span_warning("You fail to disarm [D]!")) playsound(D, 'sound/weapons/punchmiss.ogg', 25, TRUE, -1) log_combat(A, D, "disarmed (CQC)", "[I ? " grabbing \the [I]" : ""]") - if(restraining && A.pulling == D) + + var/obj/item/hand_item/grab/G = A.is_grabbing(D) + if(restraining && G) log_combat(A, D, "knocked out (Chokehold)(CQC)") D.visible_message(span_danger("[A] puts [D] into a chokehold!"), \ span_userdanger("You're put into a chokehold by [A]!"), span_hear("You hear shuffling and a muffled groan!"), null, A) to_chat(A, span_danger("You put [D] into a chokehold!")) D.SetSleeping(400) restraining = FALSE - if(A.grab_state < GRAB_NECK && !HAS_TRAIT(A, TRAIT_PACIFISM)) - A.setGrabState(GRAB_NECK) + if(G.current_grab.damage_stage < GRAB_NECK && !HAS_TRAIT(A, TRAIT_PACIFISM)) + G.upgrade() else restraining = FALSE return FALSE diff --git a/code/datums/martial/psychotic_brawl.dm b/code/datums/martial/psychotic_brawl.dm index 329144feebb9..71cea7c1d96e 100644 --- a/code/datums/martial/psychotic_brawl.dm +++ b/code/datums/martial/psychotic_brawl.dm @@ -24,22 +24,24 @@ A.Stun(20) atk_verb = "cried looking at" if(3) - if(A.grab_state >= GRAB_AGGRESSIVE) - D.grabbedby(A, 1) + var/obj/item/hand_item/grab/G = A.is_grabbing(D) + if(G?.current_grab.damage_stage == GRAB_AGGRESSIVE) + G.upgrade(TRUE) else - A.start_pulling(D, supress_message = TRUE) - if(A.pulling) + A.try_make_grab(D) + G = A.is_grabbing(D) + if(G) D.drop_all_held_items() - D.stop_pulling() + D.release_all_grabs() if(grab_attack) log_combat(A, D, "grabbed", addition="aggressively") D.visible_message(span_warning("[A] violently grabs [D]!"), \ span_userdanger("You're violently grabbed by [A]!"), span_hear("You hear sounds of aggressive fondling!"), null, A) to_chat(A, span_danger("You violently grab [D]!")) - A.setGrabState(GRAB_AGGRESSIVE) //Instant aggressive grab + G.upgrade(TRUE) else log_combat(A, D, "grabbed", addition="passively") - A.setGrabState(GRAB_PASSIVE) + if(4) A.do_attack_animation(D, ATTACK_EFFECT_PUNCH) atk_verb = "headbutt" diff --git a/code/datums/martial/wrestling.dm b/code/datums/martial/wrestling.dm index 0ca18ae84110..26f649ed2544 100644 --- a/code/datums/martial/wrestling.dm +++ b/code/datums/martial/wrestling.dm @@ -130,7 +130,7 @@ If you make a derivative work from this code, you must include this notification /datum/martial_art/wrestling/proc/throw_wrassle(mob/living/A, mob/living/D) if(!D) return - if(!A.pulling || A.pulling != D) + if(!A.is_grabbing(D)) to_chat(A, span_warning("You need to have [D] in a cinch!")) return D.forceMove(A.loc) @@ -214,7 +214,7 @@ If you make a derivative work from this code, you must include this notification /datum/martial_art/wrestling/proc/slam(mob/living/A, mob/living/D) if(!D) return - if(!A.pulling || A.pulling != D) + if(!A.is_grabbing(D)) to_chat(A, span_warning("You need to have [D] in a cinch!")) return D.forceMove(A.loc) @@ -452,9 +452,10 @@ If you make a derivative work from this code, you must include this notification /datum/martial_art/wrestling/grab_act(mob/living/A, mob/living/D) if(check_streak(A,D)) return 1 - if(A.pulling == D) + if(A.is_grabbing(D)) + return 1 + if(!A.try_make_grab(D)) return 1 - A.start_pulling(D) D.visible_message(span_danger("[A] gets [D] in a cinch!"), \ span_userdanger("You're put into a cinch by [A]!"), span_hear("You hear aggressive shuffling!"), COMBAT_MESSAGE_RANGE, A) to_chat(A, span_danger("You get [D] in a cinch!")) diff --git a/code/datums/mutations/hulk.dm b/code/datums/mutations/hulk.dm index 2c28e969a08b..32111d8c27af 100644 --- a/code/datums/mutations/hulk.dm +++ b/code/datums/mutations/hulk.dm @@ -102,10 +102,11 @@ return if(!user.throw_mode || user.get_active_held_item() || user.zone_selected != BODY_ZONE_PRECISE_GROIN) return - if(user.grab_state < GRAB_NECK || !iscarbon(user.pulling) || user.buckled || user.incapacitated()) + var/obj/item/hand_item/grab/G = user.get_active_grab() + if(G.current_grab.damage_stage < GRAB_NECK || !iscarbon(G.affecting) || user.buckled || user.incapacitated()) return - var/mob/living/carbon/possible_throwable = user.pulling + var/mob/living/carbon/possible_throwable = G.affecting if(!possible_throwable.getorganslot(ORGAN_SLOT_EXTERNAL_TAIL)) return diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index 610631947394..fce4ef1f1578 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -17,15 +17,6 @@ /datum/station_trait/distant_supply_lines/on_round_start() SSeconomy.pack_price_modifier *= 1.2 -/datum/station_trait/random_spawns - name = "Drive-by landing" - trait_type = STATION_TRAIT_NEGATIVE - weight = 2 - show_in_report = TRUE - report_message = "Sorry for that, we missed your station by a few miles, so we just launched you towards your station in pods. Hope you don't mind!" - trait_to_give = STATION_TRAIT_RANDOM_ARRIVALS - blacklist = list(/datum/station_trait/hangover) - /datum/station_trait/hangover name = "Hangover" trait_type = STATION_TRAIT_NEGATIVE @@ -33,7 +24,6 @@ show_in_report = TRUE report_message = "Ohh....Man....That mandatory office party from last shift...God that was awesome..I woke up in some random toilet 3 sectors away..." trait_to_give = STATION_TRAIT_HANGOVER - blacklist = list(/datum/station_trait/random_spawns) /datum/station_trait/hangover/New() . = ..() diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index 551260b4a11e..b5b8bfd7eec3 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -254,9 +254,9 @@ return var/mob/living/L = AM - if(L.pulling && istype(L.pulling, /obj/item/bodypart/head)) + if(istype(L.get_active_grab()?.affecting, /obj/item/bodypart/head)) to_chat(L, span_notice("Your offering is accepted. You may pass."), confidential = TRUE) - qdel(L.pulling) + qdel(L.get_active_grab()?.affecting) var/turf/LA = get_turf(pick(warp_points)) L.forceMove(LA) L.hallucination = 0 diff --git a/code/game/atoms.dm b/code/game/atoms.dm index da32f9f269f6..6e95c092b8b7 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1389,11 +1389,11 @@ * * Default behaviour is to send the [COMSIG_ATOM_EXIT] */ -/atom/Exit(atom/movable/leaving, direction) +/atom/Exit(atom/movable/leaving, direction, no_side_effects) // Don't call `..()` here, otherwise `Uncross()` gets called. // See the doc comment on `Uncross()` to learn why this is bad. - if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction) & COMPONENT_ATOM_BLOCK_EXIT) + if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, leaving, direction, no_side_effects) & COMPONENT_ATOM_BLOCK_EXIT) return FALSE return TRUE @@ -2218,3 +2218,9 @@ //Currently only changed by Observers to be hearing through their orbit target. /atom/proc/hear_location() return src + +///Reset plane and layer values to their defaults. +/atom/proc/reset_plane_and_layer() + plane = initial(plane) + layer = initial(layer) + diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 004b99dbb652..86bd47b393cd 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -13,7 +13,6 @@ var/throw_range = 7 ///Max range this atom can be thrown via telekinesis var/tk_throw_range = 10 - var/mob/pulledby = null var/initial_language_holder = /datum/language_holder var/datum/language_holder/language_holder // Mindless mobs and objects need language too, some times. Mind holder takes prescedence. var/verb_say = "says" @@ -63,8 +62,6 @@ */ var/movement_type = GROUND - var/atom/movable/pulling - var/grab_state = 0 var/throwforce = 0 var/datum/component/orbiter/orbiting @@ -95,6 +92,9 @@ ///For storing what do_after's someone has, key = string, value = amount of interactions of that type happening. var/list/do_afters + ///A lazylist of grab objects gripping us + var/list/grabbed_by + /mutable_appearance/emissive_blocker /mutable_appearance/emissive_blocker/New() @@ -167,11 +167,6 @@ invisibility = INVISIBILITY_ABSTRACT - if(pulledby) - pulledby.stop_pulling() - if(pulling) - stop_pulling() - if(orbiting) orbiting.end_orbit(src) orbiting = null @@ -221,297 +216,6 @@ if(emissive_block) . += emissive_block -/atom/movable/proc/onZImpact(turf/impacted_turf, levels, message = TRUE) - if(message) - visible_message( - span_danger("[src] slams into [impacted_turf]!"), - blind_message = span_hear("You hear something slam into the deck.") - ) - - INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2) - return TRUE - -/* - * The core multi-z movement proc. Used to move a movable through z levels. - * If target is null, it'll be determined by the can_z_move proc, which can potentially return null if - * conditions aren't met (see z_move_flags defines in __DEFINES/movement.dm for info) or if dir isn't set. - * Bear in mind you don't need to set both target and dir when calling this proc, but at least one or two. - * This will set the currently_z_moving to CURRENTLY_Z_MOVING_GENERIC if unset, and then clear it after - * Forcemove(). - * - * - * Args: - * * dir: the direction to go, UP or DOWN, only relevant if target is null. - * * target: The target turf to move the src to. Set by can_z_move() if null. - * * z_move_flags: bitflags used for various checks in both this proc and can_z_move(). See __DEFINES/movement.dm. - */ -/atom/movable/proc/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS) - var/able_to_climb = (!currently_z_moving && (dir == UP) && isliving(src)) - if(!target) - // We remove feedback if the mover is able to climb to prevent feedback from playing if they end up climbing. - target = can_z_move(dir, get_turf(src), able_to_climb ? z_move_flags & ~ZMOVE_FEEDBACK : z_move_flags) - if(!target) - set_currently_z_moving(FALSE, TRUE) - - if(!target && able_to_climb) - var/obj/climbable = check_zclimb() - if(!climbable) - if(z_move_flags & ZMOVE_FEEDBACK) - // This is kind of stupid, but we do this *again* to properly get feedback - can_z_move(dir, get_turf(src), z_move_flags) - return FALSE - - return ClimbUp(climbable) - - if(!target) - return FALSE - - var/list/moving_movs = get_z_move_affected(z_move_flags) - - for(var/atom/movable/movable as anything in moving_movs) - movable.currently_z_moving = currently_z_moving || CURRENTLY_Z_MOVING_GENERIC - movable.forceMove(target) - movable.set_currently_z_moving(FALSE, TRUE) - - // This is run after ALL movables have been moved, so pulls don't get broken unless they are actually out of range. - if(z_move_flags & ZMOVE_CHECK_PULLS) - for(var/atom/movable/moved_mov as anything in moving_movs) - if(z_move_flags & ZMOVE_CHECK_PULLEDBY && moved_mov.pulledby && (moved_mov.z != moved_mov.pulledby.z || get_dist(moved_mov, moved_mov.pulledby) > 1)) - moved_mov.pulledby.stop_pulling() - if(z_move_flags & ZMOVE_CHECK_PULLING) - moved_mov.check_pulling(TRUE) - return TRUE - -/// Returns a list of movables that should also be affected when src moves through zlevels, and src. -/atom/movable/proc/get_z_move_affected(z_move_flags) - . = list(src) - if(buckled_mobs) - . |= buckled_mobs - if(!(z_move_flags & ZMOVE_INCLUDE_PULLED)) - return - for(var/mob/living/buckled as anything in buckled_mobs) - if(buckled.pulling) - . |= buckled.pulling - if(pulling) - . |= pulling - -/** - * Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so. - * Args: - * * direction: the direction to go, UP or DOWN, only relevant if target is null. - * * start: Each destination has a starting point on the other end. This is it. Most of the times the location of the source. - * * z_move_flags: bitflags used for various checks. See __DEFINES/movement.dm. - * * rider: A living mob in control of the movable. Only non-null when a mob is riding a vehicle through z-levels. - */ -/atom/movable/proc/can_z_move(direction, turf/start, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider) - if(!start) - start = get_turf(src) - if(!start) - CRASH("Something tried to zMove from nullspace.") - - if(!direction) - CRASH("can_z_move() called with no direction") - - if(direction != UP && direction != DOWN) - CRASH("Tried to zMove on the same Z Level.") - - var/turf/destination = get_step_multiz(start, direction) - if(!destination) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_notice("There is nothing of interest in this direction.")) - return FALSE - - // Check flight - if(z_move_flags & ZMOVE_CAN_FLY_CHECKS) - if(!(movement_type & (FLYING|FLOATING)) && has_gravity(start) && (direction == UP)) - if(z_move_flags & ZMOVE_FEEDBACK) - if(rider) - to_chat(rider, span_warning("[src] is is not capable of flight.")) - else - to_chat(src, span_warning("You stare at [destination].")) - return FALSE - - // Check CanZPass - if(!(z_move_flags & ZMOVE_IGNORE_OBSTACLES)) - // Check exit - if(!start.CanZPass(src, direction, z_move_flags)) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("[start] is in the way.")) - return FALSE - - // Check enter - if(!destination.CanZPass(src, direction, z_move_flags)) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("You bump against [destination].")) - return FALSE - - // Check destination movable CanPass - for(var/atom/movable/A as anything in destination) - if(!A.CanMoveOnto(src, direction)) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("You are blocked by [A].")) - return FALSE - - // Check if we would fall. - if(z_move_flags & ZMOVE_FALL_CHECKS) - if((throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start))) - if(z_move_flags & ZMOVE_FEEDBACK) - to_chat(rider || src, span_warning("You see nothing to hold onto.")) - return FALSE - - return destination //used by some child types checks and zMove() - -/// Precipitates a movable (plus whatever buckled to it) to lower z levels if possible and then calls zImpact() -/atom/movable/proc/zFall(levels = 1, force = FALSE, falling_from_move = FALSE) - if(QDELETED(src)) - return FALSE - - var/direction = DOWN - if(has_gravity() == NEGATIVE_GRAVITY) - direction = UP - - var/turf/target = direction == UP ? GetAbove(src) : GetBelow(src) - if(!target) - return FALSE - - if(!CanZFall(get_turf(src), direction)) - set_currently_z_moving(FALSE, TRUE) - return FALSE - - var/isliving = isliving(src) - if(!isliving && !isobj(src)) - return - - if(isliving) - var/mob/living/falling_living = src - //relay this mess to whatever the mob is buckled to. - if(falling_living.buckled) - return falling_living.buckled.zFall() - - if(!falling_from_move && currently_z_moving) - return - - if(!force && !can_z_move(direction, get_turf(src), ZMOVE_FALL_FLAGS)) - set_currently_z_moving(FALSE, TRUE) - return FALSE - - spawn(-1) - _doZFall(target, levels, get_turf(src)) - return TRUE - -/atom/movable/proc/_doZFall(turf/destination, levels, turf/prev_turf) - PRIVATE_PROC(TRUE) - - // So it doesn't trigger other zFall calls. Cleared on zMove. - set_currently_z_moving(CURRENTLY_Z_FALLING) - - zMove(null, destination, ZMOVE_CHECK_PULLEDBY) - destination.zImpact(src, levels, prev_turf) - -/atom/movable/proc/CanZFall(turf/from, direction, anchor_bypass) - if(anchored && !anchor_bypass) - return FALSE - - if(from) - for(var/obj/O in from) - if(O.obj_flags & BLOCK_Z_FALL) - return FALSE - - var/turf/other = direction == UP ? GetAbove(from) : GetBelow(from) - if(!from.CanZPass(from, direction) || !other?.CanZPass(from, direction)) //Kinda hacky but it does work. - return FALSE - - return TRUE - -/// Returns an object we can climb onto -/atom/movable/proc/check_zclimb() - var/turf/above = GetAbove(src) - if(!above?.CanZPass(src, UP)) - return - - var/list/all_turfs_above = get_adjacent_open_turfs(above) - - //Check directly above first - . = get_climbable_surface(above) - if(.) - return - - //Next, try the direction the mob is facing - . = get_climbable_surface(get_step(above, dir)) - if(.) - return - - for(var/turf/T as turf in all_turfs_above) - . = get_climbable_surface(T) - if(.) - return - -/atom/movable/proc/get_climbable_surface(turf/T) - var/climb_target - if(!T.Enter(src)) - return - if(!isopenspaceturf(T) && isfloorturf(T)) - climb_target = T - else - for(var/obj/I in T) - if(I.obj_flags & BLOCK_Z_FALL) - climb_target = I - break - - if(climb_target) - return climb_target - -/atom/movable/proc/ClimbUp(atom/onto) - if(!isturf(loc)) - return FALSE - - var/turf/above = GetAbove(src) - var/turf/destination = get_turf(onto) - if(!above || !above.Adjacent(destination, mover = src)) - return FALSE - - if(has_gravity() > 0) - var/can_overcome - for(var/atom/A in loc) - if(HAS_TRAIT(A, TRAIT_CLIMBABLE)) - can_overcome = TRUE - break; - - if(!can_overcome) - var/list/objects_to_stand_on = list( - /obj/structure/chair, - /obj/structure/bed, - /obj/structure/lattice - ) - for(var/path in objects_to_stand_on) - if(locate(path) in loc) - can_overcome = TRUE - break; - if(!can_overcome) - to_chat(src, span_warning("You cannot reach [onto] from here!")) - return FALSE - - visible_message( - span_notice("[src] starts climbing onto \the [onto]."), - span_notice("You start climbing onto \the [onto].") - ) - - if(!do_after(src, time = 5 SECONDS, timed_action_flags = DO_PUBLIC, display = image('icons/hud/do_after.dmi', "help"))) - return FALSE - - visible_message( - span_notice("[src] climbs onto \the [onto]."), - span_notice("You climb onto \the [onto].") - ) - - var/oldloc = loc - setDir(get_dir(above, destination)) - set_currently_z_moving(CURRENTLY_Z_ASCENDING) - . = zMove(UP, destination, ZMOVE_CHECK_PULLS|ZMOVE_INCAPACITATED_CHECKS) - if(.) - playsound(oldloc, 'sound/effects/stairs_step.ogg', 50) - playsound(destination, 'sound/effects/stairs_step.ogg', 50) - /atom/movable/vv_edit_var(var_name, var_value) var/static/list/banned_edits = list( NAMEOF_STATIC(src, step_x) = TRUE, @@ -563,9 +267,6 @@ if(NAMEOF(src, anchored)) set_anchored(var_value) . = TRUE - if(NAMEOF(src, pulledby)) - set_pulledby(var_value) - . = TRUE if(NAMEOF(src, glide_size)) set_glide_size(var_value) . = TRUE @@ -576,104 +277,6 @@ return ..() - -/atom/movable/proc/start_pulling(atom/movable/pulled_atom, state, force = move_force, supress_message = FALSE) - if(QDELETED(pulled_atom)) - return FALSE - if(!(pulled_atom.can_be_pulled(src, state, force))) - return FALSE - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - if(state == 0) - stop_pulling() - return FALSE - // Are we trying to pull something we are already pulling? Then enter grab cycle and end. - if(pulled_atom == pulling) - setGrabState(state) - if(istype(pulled_atom,/mob/living)) - var/mob/living/pulled_mob = pulled_atom - pulled_mob.grabbedby(src) - return TRUE - stop_pulling() - - if(pulled_atom.pulledby) - log_combat(pulled_atom, pulled_atom.pulledby, "pulled from", src) - pulled_atom.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. - pulling = pulled_atom - pulled_atom.set_pulledby(src) - SEND_SIGNAL(src, COMSIG_ATOM_START_PULL, pulled_atom, state, force) - setGrabState(state) - if(ismob(pulled_atom)) - var/mob/pulled_mob = pulled_atom - log_combat(src, pulled_mob, "grabbed", addition="passive grab") - if(!supress_message) - pulled_mob.visible_message(span_warning("[src] grabs [pulled_mob] passively."), \ - span_danger("[src] grabs you passively.")) - return TRUE - -/atom/movable/proc/stop_pulling() - if(!pulling) - return - pulling.set_pulledby(null) - setGrabState(GRAB_PASSIVE) - var/atom/movable/old_pulling = pulling - pulling = null - SEND_SIGNAL(old_pulling, COMSIG_ATOM_NO_LONGER_PULLED, src) - SEND_SIGNAL(src, COMSIG_ATOM_NO_LONGER_PULLING, old_pulling) - -///Reports the event of the change in value of the pulledby variable. -/atom/movable/proc/set_pulledby(new_pulledby) - if(new_pulledby == pulledby) - return FALSE //null signals there was a change, be sure to return FALSE if none happened here. - . = pulledby - pulledby = new_pulledby - - -/atom/movable/proc/Move_Pulled(atom/moving_atom) - if(!pulling) - return FALSE - if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling)) - stop_pulling() - return FALSE - if(isliving(pulling)) - var/mob/living/pulling_mob = pulling - if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it - stop_pulling() - return FALSE - if(moving_atom == loc && pulling.density) - return FALSE - var/move_dir = get_dir(pulling.loc, moving_atom) - if(!Process_Spacemove(move_dir)) - return FALSE - pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size) - return TRUE - -/mob/living/Move_Pulled(atom/moving_atom) - . = ..() - if(!. || !isliving(moving_atom)) - return - var/mob/living/pulled_mob = moving_atom - set_pull_offsets(pulled_mob, grab_state) - -/** - * Checks if the pulling and pulledby should be stopped because they're out of reach. - * If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs. - */ -/atom/movable/proc/check_pulling(only_pulling = FALSE, z_allowed = FALSE) - if(pulling) - if(get_dist(src, pulling) > 1 || (z != pulling.z && !z_allowed)) - stop_pulling() - else if(!isturf(loc)) - stop_pulling() - else if(pulling && !isturf(pulling.loc) && pulling.loc != loc) //to be removed once all code that changes an object's loc uses forceMove(). - log_game("DEBUG:[src]'s pull on [pulling] wasn't broken despite [pulling] being in [pulling.loc]. Pull stopped manually.") - stop_pulling() - else if(pulling.anchored || pulling.move_resist > move_force) - stop_pulling() - if(!only_pulling && pulledby && moving_diagonally != FIRST_DIAG_STEP && (get_dist(src, pulledby) > 1 || z != pulledby.z)) //separated from our puller and not in the middle of a diagonal move. - pulledby.stop_pulling() - /atom/movable/proc/set_glide_size(target = 8) SEND_SIGNAL(src, COMSIG_MOVABLE_UPDATE_GLIDE_SIZE, target) glide_size = target @@ -699,7 +302,7 @@ // Here's where we rewrite how byond handles movement except slightly different // To be removed on step_ conversion // All this work to prevent a second bump -/atom/movable/Move(atom/newloc, direction, glide_size_override = 0) +/atom/movable/Move(atom/newloc, direction, glide_size_override = 0, z_movement_flags) . = FALSE if(!newloc || newloc == loc) @@ -708,7 +311,7 @@ if(!direction) direction = get_dir(src, newloc) - if(set_dir_on_move && dir != direction) + if(set_dir_on_move && dir != direction && !(dir & (UP|DOWN))) setDir(direction) var/is_multi_tile_object = bound_width > 32 || bound_height > 32 @@ -776,16 +379,21 @@ //////////////////////////////////////// -/atom/movable/Move(atom/newloc, direct, glide_size_override = 0) +/atom/movable/Move(atom/newloc, direct, glide_size_override = 0, z_movement_flags) if(QDELING(src)) CRASH("Illegal Move()! on [type]") - var/atom/movable/pullee = pulling - var/turf/current_turf = loc if(!moving_from_pull) - check_pulling(z_allowed = TRUE) + recheck_grabs(z_allowed = TRUE) + if(!loc || !newloc) return FALSE + + if(direct & (UP|DOWN)) + if(!can_z_move(direct, null, z_movement_flags)) + return FALSE + set_currently_z_moving(ZMOVING_VERTICAL) + var/atom/oldloc = loc //Early override for some cases like diagonal movement if(glide_size_override && glide_size != glide_size_override) @@ -851,27 +459,17 @@ if(!loc || (loc == oldloc && oldloc != newloc)) last_move = 0 - set_currently_z_moving(FALSE, TRUE) + set_currently_z_moving(FALSE) return - if(. && pulling && pulling == pullee && pulling != moving_from_pull) //we were pulling a thing and didn't lose it during our move. - if(pulling.anchored) - stop_pulling() - else - //puller and pullee more than one tile away or in diagonal position and whatever the pullee is pulling isn't already moving from a pull as it'll most likely result in an infinite loop a la ouroborus. - if(!pulling.pulling?.moving_from_pull) - var/pull_dir = get_dir(pulling, src) - var/target_turf = current_turf + if(set_dir_on_move && dir != direct) + setDir(direct) - // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls. - // You may wonder why we're not just forcemoving the pulling movable and regrabbing it. - // The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines. - if(pulling.z != z) - target_turf = get_step(pulling, get_dir(pulling, current_turf)) + if(. && isliving(src) && currently_z_moving != ZMOVING_LATERAL) + var/mob/living/L = src + L.handle_grabs_during_movement(oldloc, direct) - if(target_turf != current_turf || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) - pulling.move_from_pull(src, target_turf, glide_size) - check_pulling() + recheck_grabs(only_pulled = TRUE) //glide_size strangely enough can change mid movement animation and update correctly while the animation is playing //This means that if you don't override it late like this, it will just be set back by the movement update that's called when you move turfs. @@ -880,16 +478,11 @@ last_move = direct - if(set_dir_on_move && dir != direct) - setDir(direct) if(. && has_buckled_mobs() && !handle_buckled_mob_movement(loc, direct, glide_size_override)) //movement failed due to buckled mob(s) . = FALSE if(currently_z_moving) - if(. && loc == newloc) - zFall(falling_from_move = TRUE) - else - set_currently_z_moving(FALSE, TRUE) + set_currently_z_moving(FALSE) /// Called when src is being moved to a target turf because another movable (puller) is moving around. /atom/movable/proc/move_from_pull(atom/movable/puller, turf/target_turf, glide_size_override) @@ -1183,14 +776,12 @@ anchored = anchorvalue SEND_SIGNAL(src, COMSIG_MOVABLE_SET_ANCHORED, anchorvalue) -/// Sets the currently_z_moving variable to a new value. Used to allow some zMovement sources to have precedence over others. -/atom/movable/proc/set_currently_z_moving(new_z_moving_value, forced = FALSE) - if(forced) - currently_z_moving = new_z_moving_value - return TRUE - var/old_z_moving_value = currently_z_moving - currently_z_moving = max(currently_z_moving, new_z_moving_value) - return currently_z_moving > old_z_moving_value +/// Sets the currently_z_moving variable to a new value. Used to temporarily disable some Move() side effects. +/atom/movable/proc/set_currently_z_moving(new_z_moving_value) + if(new_z_moving_value == currently_z_moving) + return FALSE + currently_z_moving = new_z_moving_value + return TRUE /atom/movable/proc/forceMove(atom/destination) if(QDELING(src)) @@ -1210,10 +801,6 @@ var/atom/oldloc = loc var/is_multi_tile = bound_width > world.icon_size || bound_height > world.icon_size if(destination) - ///zMove already handles whether a pull from another movable should be broken. - if(pulledby && !currently_z_moving) - pulledby.stop_pulling() - var/same_loc = oldloc == destination var/area/old_area = get_area(oldloc) var/area/destarea = get_area(destination) @@ -1312,8 +899,19 @@ if(SEND_SIGNAL(src, COMSIG_MOVABLE_SPACEMOVE, movement_dir, continuous_move) & COMSIG_MOVABLE_STOP_SPACEMOVE) return TRUE - if(pulledby && (pulledby.pulledby != src || moving_from_pull)) - return TRUE + // If we are being pulled by something AND (we are NOT pulling them OR we are moving from a pull), do not drift + if(LAZYLEN(grabbed_by)) + var/can_drift = TRUE + if(isliving(src)) + var/mob/living/L = src + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(!L.is_grabbing(G.assailant)) + can_drift = FALSE // Something is grabbing us and we're not grabbing them + else + can_drift = FALSE + + if(!can_drift || moving_from_pull) + return TRUE if(throwing) return TRUE @@ -1373,9 +971,6 @@ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PRE_THROW, args) & COMPONENT_CANCEL_THROW) return - if (pulledby) - pulledby.stop_pulling() - //They are moving! Wouldn't it be cool if we calculated their momentum and added it to the throw? if (thrower && thrower.last_move && thrower.client && thrower.client.move_delay >= world.time + world.tick_lag*2) var/user_momentum = thrower.cached_multiplicative_slowdown @@ -1432,8 +1027,9 @@ thrown_thing.diagonal_error = dist_x/2 - dist_y thrown_thing.start_time = world.time - if(pulledby) - pulledby.stop_pulling() + if(LAZYLEN(grabbed_by)) + free_from_all_grabs() + if (quickstart && (throwing || SSthrowing.state == SS_RUNNING)) //Avoid stack overflow edgecases. quickstart = FALSE throwing = thrown_thing @@ -1638,44 +1234,6 @@ /atom/movable/proc/get_cell() return -/atom/movable/proc/can_be_pulled(user, grab_state, force) - if(src == user || !isturf(loc)) - return FALSE - if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_PULLED, user) & COMSIG_ATOM_CANT_PULL) - return FALSE - if(anchored || throwing) - return FALSE - if(force < (move_resist * MOVE_FORCE_PULL_RATIO)) - return FALSE - return TRUE - -/** - * Updates the grab state of the movable - * - * This exists to act as a hook for behaviour - */ -/atom/movable/proc/setGrabState(newstate) - if(newstate == grab_state) - return - SEND_SIGNAL(src, COMSIG_MOVABLE_SET_GRAB_STATE, newstate) - . = grab_state - grab_state = newstate - switch(grab_state) // Current state. - if(GRAB_PASSIVE) - REMOVE_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) - REMOVE_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) - if(. >= GRAB_NECK) // Previous state was a a neck-grab or higher. - REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) - if(GRAB_AGGRESSIVE) - if(. >= GRAB_NECK) // Grab got downgraded. - REMOVE_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) - else // Grab got upgraded from a passive one. - ADD_TRAIT(pulling, TRAIT_IMMOBILIZED, CHOKEHOLD_TRAIT) - ADD_TRAIT(pulling, TRAIT_HANDS_BLOCKED, CHOKEHOLD_TRAIT) - if(GRAB_NECK, GRAB_KILL) - if(. <= GRAB_AGGRESSIVE) - ADD_TRAIT(pulling, TRAIT_FLOORED, CHOKEHOLD_TRAIT) - /** * Adds the deadchat_plays component to this atom with simple movement commands. * @@ -1735,7 +1293,7 @@ var/current_loc = get_turf(src) var/direction = get_dir(current_loc, destination) if(loc) - if(!loc.Exit(src, direction)) + if(!loc.Exit(src, direction, TRUE)) return FALSE return destination.Enter(src, TRUE) diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index b8b93da05fa7..052c146469d5 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -310,7 +310,7 @@ if(!owner || !isliving(owner)) return var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control - if(remote_eye.zMove(UP)) + if(step(remote_eye, UP)) to_chat(owner, span_notice("You move upwards.")) else to_chat(owner, span_notice("You couldn't move upwards!")) @@ -324,7 +324,7 @@ if(!owner || !isliving(owner)) return var/mob/camera/ai_eye/remote/remote_eye = owner.remote_control - if(remote_eye.zMove(DOWN)) + if(step(remote_eye, DOWN)) to_chat(owner, span_notice("You move downwards.")) else to_chat(owner, span_notice("You couldn't move downwards!")) diff --git a/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm b/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm index 48d4f7d4e348..137cb13d9c38 100644 --- a/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm +++ b/code/game/machinery/datanet/test_equipment/test_equipment_wired.dm @@ -8,7 +8,7 @@ // This equipment is *incredibly* basic, and there's probably a subtype of machinery that you should use instead. // I should probably remove this before pushing, but if not, here it is! -/obj/machinery/test_equipment/wired/CtrlClick(mob/user) +/obj/machinery/test_equipment/wired/CtrlClick(mob/user, list/params) . = ..() reconnect_dataterm() diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 6ff1fd03d62d..c5a5c28378f0 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -369,22 +369,30 @@ GLOBAL_LIST_INIT(dye_registry, list( to_chat(user, span_warning("[src] is busy!")) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(L.buckled || L.has_buckled_mobs()) - return - if(state_open) - if(istype(L, /mob/living/simple_animal/pet)) - L.forceMove(src) - update_appearance() - return - if(!state_open) open_machine() else state_open = FALSE //close the door update_appearance() +/obj/machinery/washing_machine/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(busy) + to_chat(user, span_warning("[src] is busy!")) + return + + if(!isliving(victim)) + return + + var/mob/living/L = victim + if(L.buckled || L.has_buckled_mobs()) + return + if(state_open) + if(istype(L, /mob/living/simple_animal/pet)) + L.forceMove(src) + update_appearance() + return TRUE + /obj/machinery/washing_machine/attack_hand_secondary(mob/user, modifiers) . = ..() if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index 7bf7b875e938..dfff619d5f42 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -102,12 +102,12 @@ if(SEND_SIGNAL(src, COMSIG_MOVABLE_PREBUCKLE, M, force, buckle_mob_flags) & COMPONENT_BLOCK_BUCKLE) return FALSE - if(M.pulledby) + if(LAZYLEN(M.grabbed_by)) if(buckle_prevents_pull) - M.pulledby.stop_pulling() - else if(isliving(M.pulledby)) - var/mob/living/L = M.pulledby - L.reset_pull_offsets(M, TRUE) + M.free_from_all_grabs() + + else if(LAZYLEN(grabbed_by)) + M.reset_pull_offsets(TRUE) if(anchored) ADD_TRAIT(M, TRAIT_NO_FLOATING_ANIM, BUCKLED_TRAIT) @@ -346,7 +346,6 @@ span_notice("You unbuckle yourself from [src]."),\ span_hear("You hear metal clanking.")) add_fingerprint(user) - if(isliving(M.pulledby)) - var/mob/living/L = M.pulledby - L.set_pull_offsets(M, L.grab_state) + + update_offsets() return M diff --git a/code/game/objects/effects/spiderwebs.dm b/code/game/objects/effects/spiderwebs.dm index 709127a37596..d81e1d5086cd 100644 --- a/code/game/objects/effects/spiderwebs.dm +++ b/code/game/objects/effects/spiderwebs.dm @@ -67,8 +67,9 @@ if(istype(mover, /mob/living/simple_animal/hostile/giant_spider)) return TRUE else if(isliving(mover)) - if(istype(mover.pulledby, /mob/living/simple_animal/hostile/giant_spider)) - return TRUE + for(var/obj/item/hand_item/grab/G in mover.grabbed_by) + if(istype(G.assailant, /mob/living/simple_animal/hostile/giant_spider)) + return TRUE if(prob(50)) to_chat(mover, span_danger("You get stuck in \the [src] for a moment.")) return FALSE @@ -95,7 +96,7 @@ if(mover == allowed_mob) return TRUE else if(isliving(mover)) //we change the spider to not be able to go through here - if(mover.pulledby == allowed_mob) + if(allowed_mob.is_grabbing(mover)) return TRUE if(prob(50)) to_chat(mover, span_danger("You get stuck in \the [src] for a moment.")) diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index b95c96aa1408..235c97615852 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -514,8 +514,7 @@ update_appearance() /obj/item/shockpaddles/proc/shock_pulling(dmg, mob/H) - if(isliving(H.pulledby)) //CLEAR! - var/mob/living/M = H.pulledby + for(var/mob/living/M in H.recursively_get_all_grabbers()) if(M.electrocute_act(dmg, H)) M.visible_message(span_danger("[M] is electrocuted by [M.p_their()] contact with [H]!")) M.emote("scream") diff --git a/code/game/objects/items/devices/pressureplates.dm b/code/game/objects/items/devices/pressureplates.dm index c46b42b0c58f..b40e82716182 100644 --- a/code/game/objects/items/devices/pressureplates.dm +++ b/code/game/objects/items/devices/pressureplates.dm @@ -74,7 +74,7 @@ sigdev = null return ..() -/obj/item/pressure_plate/CtrlClick(mob/user) +/obj/item/pressure_plate/CtrlClick(mob/user, list/params) if(protected) to_chat(user, span_warning("You can't quite seem to turn this pressure plate off...")) return diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 102d93618f8a..f2ce888af395 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -228,7 +228,7 @@ if(talking_carbon.handcuffed)// If we're handcuffed, we can't press the button to_chat(talking_carbon, span_warning("You can't use the radio while handcuffed!")) return ITALICS | REDUCE_RANGE - if(talking_carbon.pulledby?.grab_state) + if(HAS_TRAIT(talking_carbon, TRAIT_AGGRESSIVE_GRAB)) to_chat(talking_carbon, span_warning("You can't use the radio while aggressively grabbed!")) return ITALICS | REDUCE_RANGE diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm index f24aa5103144..120eb826ab88 100644 --- a/code/game/objects/items/hand_items.dm +++ b/code/game/objects/items/hand_items.dm @@ -123,7 +123,8 @@ to_chat(user, span_warning("You can't bring yourself to noogie [target]! You don't want to risk harming anyone...")) return - if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target || user.grab_state < GRAB_AGGRESSIVE || HAS_TRAIT(user, TRAIT_EXHAUSTED)) + var/obj/item/hand_item/grab/G = user.is_grabbing(target) + if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || !G || !(G.current_grab.damage_stage >= GRAB_AGGRESSIVE)|| HAS_TRAIT(user, TRAIT_EXHAUSTED)) return FALSE // [user] gives [target] a [prefix_desc] noogie[affix_desc]! @@ -153,7 +154,7 @@ /// The actual meat and bones of the noogie'ing /obj/item/hand_item/noogie/proc/noogie_loop(mob/living/carbon/human/user, mob/living/carbon/target, iteration) - if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || user.pulling != target) + if(!(target?.get_bodypart(BODY_ZONE_HEAD)) || !user.is_grabbing(target)) return FALSE if(HAS_TRAIT(user, TRAIT_EXHAUSTED)) diff --git a/code/game/objects/items/his_grace.dm b/code/game/objects/items/his_grace.dm index feccb047f5d6..e2651dad4373 100644 --- a/code/game/objects/items/his_grace.dm +++ b/code/game/objects/items/his_grace.dm @@ -64,7 +64,7 @@ else ..() -/obj/item/his_grace/CtrlClick(mob/user) //you can't pull his grace +/obj/item/his_grace/CtrlClick(mob/user, list/params) //you can't pull his grace return /obj/item/his_grace/examine(mob/user) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 029f9bb9825d..aea74a3ee574 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -490,8 +490,12 @@ transfer = min(transfer, round((target_stack.source.max_energy - target_stack.source.energy) / target_stack.cost)) else transfer = min(transfer, (limit ? limit : target_stack.max_amount) - target_stack.amount) - if(pulledby) - pulledby.start_pulling(target_stack) + if(LAZYLEN(grabbed_by)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + var/mob/living/grabber = G.assailant + qdel(G) + grabber.try_make_grab(target_stack) + target_stack.copy_evidences(src) use(transfer, transfer = TRUE, check = FALSE) target_stack.add(transfer) diff --git a/code/game/objects/items/storage/bags.dm b/code/game/objects/items/storage/bags.dm index bd8330ea4744..4b2a2bdbb5dc 100644 --- a/code/game/objects/items/storage/bags.dm +++ b/code/game/objects/items/storage/bags.dm @@ -141,8 +141,9 @@ if(!isturf(tile)) return - if(istype(user.pulling, /obj/structure/ore_box)) - box = user.pulling + var/obj/item/hand_item/grab/G = user.get_active_held_item() + if(isgrab(G) && istype(G.affecting, /obj/structure/ore_box)) + box = G.affecting if(atom_storage) for(var/thing in tile) @@ -233,7 +234,7 @@ . = ..() . += span_notice("Ctrl-click to activate seed extraction.") -/obj/item/storage/bag/plants/portaseeder/CtrlClick(mob/user) +/obj/item/storage/bag/plants/portaseeder/CtrlClick(mob/user, list/params) if(user.incapacitated()) return for(var/obj/item/plant in contents) diff --git a/code/game/objects/items/theft_tools.dm b/code/game/objects/items/theft_tools.dm index dfe6df5024d4..5a286cd37ca8 100644 --- a/code/game/objects/items/theft_tools.dm +++ b/code/game/objects/items/theft_tools.dm @@ -169,7 +169,8 @@ return -/obj/item/nuke_core/supermatter_sliver/can_be_pulled(user) // no drag memes +/obj/item/nuke_core/supermatter_sliver/can_be_grabbed(mob/living/grabber, target_zone, force) + // no drag memes return FALSE /obj/item/nuke_core/supermatter_sliver/attackby(obj/item/W, mob/living/user, params) diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 5732aac3ed14..ebb02c17b238 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -96,3 +96,35 @@ L.Paralyze(10 SECONDS) visible_message(span_warning("[src] slams into [highest] from above!")) + +/obj/structure/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!user.combat_mode) + return + if(!grab.target_zone == BODY_ZONE_HEAD) + return + if (!grab.current_grab.enable_violent_interactions) + to_chat(user, span_warning("You need a better grip to do that!")) + return TRUE + + var/mob/living/affecting_mob = grab.get_affecting_mob() + if(!istype(affecting_mob)) + to_chat(user, span_warning("You need to be grabbing a living creature to do that!")) + return TRUE + + // Slam their face against the table. + var/blocked = affecting_mob.run_armor_check(BODY_ZONE_HEAD, MELEE) + if (prob(30 * ((100-blocked)/100))) + affecting_mob.Knockdown(10 SECONDS) + + affecting_mob.apply_damage(30, BRUTE, BODY_ZONE_HEAD, blocked) + visible_message(span_danger("[user] slams [affecting_mob]'s face against \the [src]!")) + playsound(loc, 'sound/items/trayhit1.ogg', 50, 1) + + take_damage(rand(1,5), BRUTE) + for(var/obj/item/shard/S in loc) + if(prob(50)) + affecting_mob.visible_message(span_danger("\The [S] slices into [affecting_mob]'s face!"), span_danger("\The [S] slices into your face!")) + S.melee_attack_chain(user, victim, params) + qdel(grab) + return TRUE diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index e837fe600101..d1672fb5462a 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -212,7 +212,8 @@ if(welded || locked) return FALSE if(strong_grab) - to_chat(user, span_danger("[pulledby] has an incredibly strong grip on [src], preventing it from opening.")) + var/obj/item/hand_item/grab/G = grabbed_by[1] + to_chat(user, span_danger("[G.assailant] has an incredibly strong grip on [src], preventing it from opening.")) return FALSE var/turf/T = get_turf(src) for(var/mob/living/L in T) @@ -309,7 +310,7 @@ for(var/mob/living/M in contents) if(++mobs_stored >= mob_storage_capacity) return FALSE - L.stop_pulling() + L.release_all_grabs() else if(istype(AM, /obj/structure/closet)) return FALSE diff --git a/code/game/objects/structures/ladders.dm b/code/game/objects/structures/ladders.dm index cd5d558f2317..12db214d0175 100644 --- a/code/game/objects/structures/ladders.dm +++ b/code/game/objects/structures/ladders.dm @@ -5,7 +5,7 @@ icon = 'icons/obj/structures.dmi' icon_state = "ladder11" anchored = TRUE - obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN + obj_flags = CAN_BE_HIT var/obj/structure/ladder/down //the ladder below this one var/obj/structure/ladder/up //the ladder above this one var/crafted = FALSE @@ -89,7 +89,7 @@ if(!do_after(user, src, travel_time, DO_PUBLIC)) return - if(!user.zMove(target = target, z_move_flags = ZMOVE_CHECK_PULLEDBY|ZMOVE_ALLOW_BUCKLED|ZMOVE_INCLUDE_PULLED|ZMOVE_IGNORE_OBSTACLES)) + if(!zstep(user, going_up ? UP : DOWN, ZMOVE_INCAPACITATED_CHECKS)) return if(!is_ghost) diff --git a/code/game/objects/structures/plasticflaps.dm b/code/game/objects/structures/plasticflaps.dm index f446c0dbf474..273dc249229e 100644 --- a/code/game/objects/structures/plasticflaps.dm +++ b/code/game/objects/structures/plasticflaps.dm @@ -73,8 +73,12 @@ if(!ventcrawler && living_caller.mob_size != MOB_SIZE_TINY) return FALSE - if(caller?.pulling) - return CanAStarPass(ID, to_dir, caller.pulling, no_id = no_id) + if(isliving(caller)) + var/mob/living/L = caller + var/list/grabs = L.get_active_grabs() + for(var/obj/item/hand_item/grab/G in grabs) + if(!CanAStarPass(ID, to_dir, G.affecting, no_id = no_id)) + return FALSE return TRUE //diseases, stings, etc can pass diff --git a/code/game/objects/structures/railings.dm b/code/game/objects/structures/railings.dm index 4cd10b042c51..04df2e167525 100644 --- a/code/game/objects/structures/railings.dm +++ b/code/game/objects/structures/railings.dm @@ -117,3 +117,27 @@ /obj/structure/railing/proc/check_anchored(checked_anchored) if(anchored == checked_anchored) return TRUE + +/obj/structure/railing/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + var/mob/living/L = grab.get_affecting_mob() + if(!grab.current_grab.enable_violent_interactions || !isliving(L)) + return ..() + + if(!Adjacent(L)) + user.move_grabbed_atoms_towards(get_turf(src)) + return ..() + + if(user.combat_mode) + visible_message(span_danger("[user] slams [L]'s face against \the [src]!")) + playsound(loc, 'sound/effects/grillehit.ogg', 50, 1) + var/blocked = L.run_armor_check(BODY_ZONE_HEAD, MELEE) + if (prob(30 * ((100 - blocked)/100))) + L.Knockdown(10 SECONDS) + L.apply_damage(8, BRUTE, BODY_ZONE_HEAD) + else + if (get_turf(L) == get_turf(src)) + L.forceMove(get_step(src, src.dir)) + else + L.forceMove(get_turf(src)) + L.Knockdown(10 SECONDS) + visible_message(span_danger("[user] throws \the [L] over \the [src].")) diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm index 2d5856c23db6..5c350a2ae064 100644 --- a/code/game/objects/structures/stairs.dm +++ b/code/game/objects/structures/stairs.dm @@ -55,15 +55,15 @@ if(S) S.update_appearance() -/obj/structure/stairs/proc/on_exit(datum/source, atom/movable/leaving, direction) +/obj/structure/stairs/proc/on_exit(datum/source, atom/movable/leaving, direction, no_side_effects) SIGNAL_HANDLER if(leaving == src) return //Let's not block ourselves. if(!isobserver(leaving) && isTerminator() && direction == dir) - leaving.set_currently_z_moving(CURRENTLY_Z_ASCENDING) - INVOKE_ASYNC(src, PROC_REF(stair_ascend), leaving) + if(!no_side_effects) + INVOKE_ASYNC(src, PROC_REF(stair_ascend), leaving) leaving.Bump(src) return COMPONENT_ATOM_BLOCK_EXIT @@ -91,19 +91,19 @@ to_chat(climber, span_warning("Something blocks the path.")) return - if(!target.Enter(climber)) + if(!target.Enter(climber, FALSE)) to_chat(climber, span_warning("Something blocks the path.")) return - climber.forceMove(target) + climber.forceMoveWithGroup(target, z_movement = ZMOVING_VERTICAL) + if(!(climber.throwing || (climber.movement_type & (VENTCRAWLING | FLYING)) || HAS_TRAIT(climber, TRAIT_IMMOBILIZED))) playsound(my_turf, 'sound/effects/stairs_step.ogg', 50) playsound(my_turf, 'sound/effects/stairs_step.ogg', 50) /// Moves anything that's being dragged by src or anything buckled to it to the stairs turf. - climber.pulling?.move_from_pull(climber, loc, climber.glide_size) for(var/mob/living/buckled as anything in climber.buckled_mobs) - buckled.pulling?.move_from_pull(buckled, loc, buckled.glide_size) + buckled.handle_grabs_during_movement(my_turf, get_dir(my_turf, target)) /obj/structure/stairs/intercept_zImpact(list/falling_movables, levels = 1) . = ..() diff --git a/code/game/objects/structures/transit_tubes/station.dm b/code/game/objects/structures/transit_tubes/station.dm index 134b381f6867..fb6ea8be1733 100644 --- a/code/game/objects/structures/transit_tubes/station.dm +++ b/code/game/objects/structures/transit_tubes/station.dm @@ -64,38 +64,43 @@ if(.) return if(!pod_moving) - if(user.pulling && isliving(user.pulling)) - if(open_status == STATION_TUBE_OPEN) - var/mob/living/GM = user.pulling - if(user.grab_state >= GRAB_AGGRESSIVE) - if(GM.buckled || GM.has_buckled_mobs()) - to_chat(user, span_warning("[GM] is attached to something!")) - return - for(var/obj/structure/transit_tube_pod/pod in loc) - pod.visible_message(span_warning("[user] starts putting [GM] into the [pod]!")) - if(do_after(user, src, 15)) - if(open_status == STATION_TUBE_OPEN && GM && user.grab_state >= GRAB_AGGRESSIVE && user.pulling == GM && !GM.buckled && !GM.has_buckled_mobs()) - GM.Paralyze(100) - src.BumpedBy(GM) - break - else - for(var/obj/structure/transit_tube_pod/pod in loc) - if(!pod.moving && (pod.dir in tube_dirs)) - if(open_status == STATION_TUBE_CLOSED) - open_animation() - - else if(open_status == STATION_TUBE_OPEN) - if(pod.contents.len && user.loc != pod) - user.visible_message(span_notice("[user] starts emptying [pod]'s contents onto the floor."), span_notice("You start emptying [pod]'s contents onto the floor...")) - if(do_after(user, src, 10)) //So it doesn't default to close_animation() on fail - if(pod && pod.loc == loc) - for(var/atom/movable/AM in pod) - AM.forceMove(get_turf(user)) - - else - close_animation() - break + for(var/obj/structure/transit_tube_pod/pod in loc) + if(!pod.moving && (pod.dir in tube_dirs)) + if(open_status == STATION_TUBE_CLOSED) + open_animation() + + else if(open_status == STATION_TUBE_OPEN) + if(pod.contents.len && user.loc != pod) + user.visible_message(span_notice("[user] starts emptying [pod]'s contents onto the floor."), span_notice("You start emptying [pod]'s contents onto the floor...")) + if(do_after(user, src, 10)) //So it doesn't default to close_animation() on fail + if(pod && pod.loc == loc) + for(var/atom/movable/AM in pod) + AM.forceMove(get_turf(user)) + + else + close_animation() + break + +/obj/structure/transit_tube/station/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim) || pod_moving) + return + if(!(open_status == STATION_TUBE_OPEN)) + return + + var/mob/living/GM = victim + if(grab.current_grab.damage_stage >= GRAB_AGGRESSIVE) + if(GM.buckled || GM.has_buckled_mobs()) + to_chat(user, span_warning("[GM] is attached to something!")) + return + for(var/obj/structure/transit_tube_pod/pod in loc) + pod.visible_message(span_warning("[user] starts putting [GM] into the [pod]!")) + if(do_after(user, src, 15)) + if(open_status == STATION_TUBE_OPEN && GM && grab.current_grab?.damage_stage >= GRAB_AGGRESSIVE && user.is_grabbing(victim) && !GM.buckled && !GM.has_buckled_mobs()) + GM.Paralyze(100) + src.BumpedBy(GM) + break /obj/structure/transit_tube/station/attackby(obj/item/W, mob/user, params) if(W.tool_behaviour == TOOL_CROWBAR) diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index a3f945e18a62..0ff0ab0aa2e1 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -29,39 +29,6 @@ log_combat(user, swirlie, "swirlied (brute)") swirlie.adjustBruteLoss(5) - else if(user.pulling && isliving(user.pulling)) - user.changeNext_move(CLICK_CD_MELEE) - var/mob/living/GM = user.pulling - if(user.grab_state >= GRAB_AGGRESSIVE) - if(GM.loc != get_turf(src)) - to_chat(user, span_warning("[GM] needs to be on [src]!")) - return - if(!swirlie) - if(open) - GM.visible_message(span_danger("[user] starts to give [GM] a swirlie!"), span_userdanger("[user] starts to give you a swirlie...")) - swirlie = GM - var/was_alive = (swirlie.stat != DEAD) - if(do_after(user, src, 3 SECONDS, timed_action_flags = IGNORE_HELD_ITEM)) - GM.visible_message(span_danger("[user] gives [GM] a swirlie!"), span_userdanger("[user] gives you a swirlie!"), span_hear("You hear a toilet flushing.")) - if(iscarbon(GM)) - var/mob/living/carbon/C = GM - if(!C.internal) - log_combat(user, C, "swirlied (oxy)") - C.adjustOxyLoss(5) - else - log_combat(user, GM, "swirlied (oxy)") - GM.adjustOxyLoss(5) - if(was_alive && swirlie.stat == DEAD && swirlie.client) - swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again! - swirlie = null - else - playsound(src.loc, 'sound/effects/bang.ogg', 25, TRUE) - GM.visible_message(span_danger("[user] slams [GM.name] into [src]!"), span_userdanger("[user] slams you into [src]!")) - log_combat(user, GM, "toilet slammed") - GM.adjustBruteLoss(5) - else - to_chat(user, span_warning("You need a tighter grip!")) - else if(cistern && !open && user.CanReach(src)) if(!contents.len) to_chat(user, span_notice("The cistern is empty.")) @@ -78,6 +45,46 @@ update_appearance() +/obj/structure/toilet/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim)) + return + + user.changeNext_move(CLICK_CD_MELEE) + var/mob/living/GM = victim + if(grab.current_grab.damage_stage >= GRAB_AGGRESSIVE) + if(GM.loc != get_turf(src)) + to_chat(user, span_warning("[GM] needs to be on [src]!")) + return + + if(!swirlie) + if(open) + GM.visible_message(span_danger("[user] starts to give [GM] a swirlie!"), span_userdanger("[user] starts to give you a swirlie...")) + swirlie = GM + var/was_alive = (swirlie.stat != DEAD) + if(do_after(user, src, 3 SECONDS, timed_action_flags = IGNORE_HELD_ITEM)) + GM.visible_message(span_danger("[user] gives [GM] a swirlie!"), span_userdanger("[user] gives you a swirlie!"), span_hear("You hear a toilet flushing.")) + if(iscarbon(GM)) + var/mob/living/carbon/C = GM + if(!C.internal) + log_combat(user, C, "swirlied (oxy)") + C.adjustOxyLoss(5) + else + log_combat(user, GM, "swirlied (oxy)") + GM.adjustOxyLoss(5) + if(was_alive && swirlie.stat == DEAD && swirlie.client) + swirlie.client.give_award(/datum/award/achievement/misc/swirlie, swirlie) // just like space high school all over again! + swirlie = null + return TRUE + else + playsound(src.loc, 'sound/effects/bang.ogg', 25, TRUE) + GM.visible_message(span_danger("[user] slams [GM.name] into [src]!"), span_userdanger("[user] slams you into [src]!")) + log_combat(user, GM, "toilet slammed") + GM.adjustBruteLoss(5) + return TRUE + else + to_chat(user, span_warning("You need a tighter grip!")) + /obj/structure/toilet/update_icon_state() icon_state = "toilet[open][cistern]" return ..() @@ -169,19 +176,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32) . = ..() if(.) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/GM = user.pulling - if(user.grab_state >= GRAB_AGGRESSIVE) - if(GM.loc != get_turf(src)) - to_chat(user, span_notice("[GM.name] needs to be on [src].")) - return - user.changeNext_move(CLICK_CD_MELEE) - user.visible_message(span_danger("[user] slams [GM] into [src]!"), span_danger("You slam [GM] into [src]!")) - GM.adjustBruteLoss(8) - else - to_chat(user, span_warning("You need a tighter grip!")) - else if(exposed) + if(exposed) if(!hiddenitem) to_chat(user, span_warning("There is nothing in the drain holder!")) else @@ -194,6 +190,23 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32) else ..() +/obj/structure/urinal/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim)) + return + + var/mob/living/GM = victim + + if(grab.current_grab.damage_stage >= GRAB_AGGRESSIVE) + if(GM.loc != get_turf(src)) + to_chat(user, span_notice("[GM.name] needs to be on [src].")) + return + user.changeNext_move(CLICK_CD_MELEE) + user.visible_message(span_danger("[user] slams [GM] into [src]!"), span_danger("You slam [GM] into [src]!")) + GM.adjustBruteLoss(8) + else + to_chat(user, span_warning("You need a tighter grip!")) + /obj/structure/urinal/attackby(obj/item/I, mob/living/user, params) if(exposed) if (hiddenitem) diff --git a/code/game/objects/structures/window.dm b/code/game/objects/structures/window.dm index 48233a4d8846..12442a85f256 100644 --- a/code/game/objects/structures/window.dm +++ b/code/game/objects/structures/window.dm @@ -435,6 +435,59 @@ /obj/structure/window/GetExplosionBlock() return reinf && fulltile ? real_explosion_block : 0 +/obj/structure/window/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + if (!user.combat_mode || !grab.current_grab.enable_violent_interactions) + return ..() + + var/mob/living/affecting_mob = grab.get_affecting_mob() + if(!istype(affecting_mob)) + if(isitem(victim)) + var/obj/item/I = victim + I.melee_attack_chain(user, src, params) + return TRUE + + + var/def_zone = deprecise_zone(grab.target_zone) + var/obj/item/bodypart/BP = affecting_mob.get_bodypart(def_zone) + if(!BP) + return + var/blocked = affecting_mob.run_armor_check(def_zone, MELEE) + if(grab.current_grab.damage_stage < GRAB_NECK) + affecting_mob.visible_message(span_danger("[user] bashes [affecting_mob]'s [BP.plaintext_zone] against \the [src]!")) + switch(def_zone) + if(BODY_ZONE_HEAD, BODY_ZONE_CHEST) + if(prob(50 * ((100 - blocked/100)))) + affecting_mob.Knockdown(4 SECONDS) + if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) + if(prob(50 * ((100 - blocked/100)))) + var/side = def_zone == BODY_ZONE_L_ARM ? LEFT_HANDS : RIGHT_HANDS + var/obj/item/I = affecting_mob.get_held_items_for_side(side) + if(I) + affecting_mob.dropItemToGround(I) + affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) + take_damage(10) + qdel(grab) + else + affecting_mob.visible_message(span_danger("[user] crushes [affecting_mob]'s [BP.plaintext_zone] against \the [src]!")) + switch(def_zone) + if(BODY_ZONE_HEAD, BODY_ZONE_CHEST) + affecting_mob.Knockdown(10 SECONDS) + if(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM) + var/side = def_zone == BODY_ZONE_L_ARM ? LEFT_HANDS : RIGHT_HANDS + var/obj/item/I = affecting_mob.get_held_items_for_side(side) + if(I) + affecting_mob.dropItemToGround(I) + + affecting_mob.apply_damage(20, BRUTE, def_zone, blocked) + take_damage(20) + qdel(grab) + + var/obj/effect/decal/cleanable/blood/splatter/over_window/splatter = new(src) + splatter.transfer_mob_blood_dna(victim) + vis_contents += splatter + bloodied = TRUE + return TRUE + /obj/structure/window/spawner/east dir = EAST diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm index 4d1f430736ed..9b6d7683cb51 100644 --- a/code/game/turfs/closed/wall/misc_walls.dm +++ b/code/game/turfs/closed/wall/misc_walls.dm @@ -16,11 +16,11 @@ . = ..() if(istype(gone, /mob/living/simple_animal/hostile/construct/harvester)) //harvesters can go through cult walls, dragging something with var/mob/living/simple_animal/hostile/construct/harvester/H = gone - var/atom/movable/stored_pulling = H.pulling + var/atom/movable/stored_pulling = H.get_active_grab() if(stored_pulling) stored_pulling.setDir(direction) stored_pulling.forceMove(src) - H.start_pulling(stored_pulling, supress_message = TRUE) + H.try_make_grab(stored_pulling) /turf/closed/wall/mineral/cult/artificer name = "runed stone wall" diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index ab15cefc3351..d190e4c0fb7b 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -207,7 +207,7 @@ else slipper.Knockdown(knockdown_amount) slipper.Paralyze(paralyze_amount) - slipper.stop_pulling() + slipper.release_all_grabs() if(buckled_obj) buckled_obj.unbuckle_mob(slipper) diff --git a/code/game/turfs/open/floor/reinforced_floor.dm b/code/game/turfs/open/floor/reinforced_floor.dm index 92f717979c58..71d98b5aa58e 100644 --- a/code/game/turfs/open/floor/reinforced_floor.dm +++ b/code/game/turfs/open/floor/reinforced_floor.dm @@ -88,11 +88,13 @@ /turf/open/floor/engine/attack_paw(mob/user, list/modifiers) return attack_hand(user, modifiers) -/turf/open/floor/engine/attack_hand(mob/user, list/modifiers) +/turf/open/floor/engine/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return - user.Move_Pulled(src) + if(!isliving(user)) + return + user.move_grabbed_atoms_towards(src) //air filled floors; used in atmos pressure chambers diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index 70499f599b6a..75c0ddcc2cce 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -31,28 +31,15 @@ SHOULD_CALL_PARENT(FALSE) return below.examine(user) -/** - * Prepares a moving movable to be precipitated if Move() is successful. - * This is done in Enter() and not Entered() because there's no easy way to tell - * if the latter was called by Move() or forceMove() while the former is only called by Move(). - */ -/turf/open/openspace/Enter(atom/movable/movable, atom/oldloc) - . = ..() - if(.) - //higher priority than CURRENTLY_Z_FALLING so the movable doesn't fall on Entered() - movable.set_currently_z_moving(CURRENTLY_Z_FALLING_FROM_MOVE) - ///Makes movables fall when forceMove()'d to this turf. /turf/open/openspace/Entered(atom/movable/movable) . = ..() - if(movable.set_currently_z_moving(CURRENTLY_Z_FALLING)) - movable.zFall(falling_from_move = TRUE) - -/turf/open/openspace/proc/zfall_if_on_turf(atom/movable/movable) - if(QDELETED(movable) || movable.loc != src) - return movable.zFall() +/turf/open/openspace/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) + . = ..() + hitting_atom.zFall() + /turf/open/openspace/can_have_cabling() if(locate(/obj/structure/lattice/catwalk, src)) return TRUE diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index 21ecdc3ae84f..1230ee74611f 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -123,7 +123,7 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() if(!arrived || src != arrived.loc) return - if(destination_z && destination_x && destination_y && !arrived.pulledby && !arrived.currently_z_moving) + if(destination_z && destination_x && destination_y && !LAZYLEN(arrived.grabbed_by) && !arrived.currently_z_moving) var/tx = destination_x var/ty = destination_y var/turf/DT = locate(tx, ty, destination_z) @@ -144,13 +144,13 @@ GLOBAL_REAL_VAR(space_appearances) = make_space_appearances() if(SEND_SIGNAL(arrived, COMSIG_MOVABLE_LATERAL_Z_MOVE) & COMPONENT_BLOCK_MOVEMENT) return - arrived.zMove(null, DT, ZMOVE_ALLOW_BUCKLED) - var/atom/movable/current_pull = arrived.pulling - while (current_pull) - var/turf/target_turf = get_step(current_pull.pulledby.loc, REVERSE_DIR(current_pull.pulledby.dir)) || current_pull.pulledby.loc - current_pull.zMove(null, target_turf, ZMOVE_ALLOW_BUCKLED) - current_pull = current_pull.pulling + arrived.forceMoveWithGroup(DT, ZMOVING_LATERAL) + if(isliving(arrived)) + var/mob/living/L = arrived + for(var/obj/item/hand_item/grab/G as anything in L.recursively_get_conga_line()) + var/turf/pulled_dest = get_step(G.assailant.loc, REVERSE_DIR(G.assailant.dir)) || G.assailant.loc + G.affecting.forceMoveWithGroup(pulled_dest, ZMOVING_LATERAL) /turf/open/space/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 50207ec1bc75..d441ac2dd2b1 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -201,11 +201,22 @@ GLOBAL_LIST_EMPTY(station_turfs) /turf/clear_signal_refs() return -/turf/attack_hand(mob/user, list/modifiers) +/turf/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return - user.Move_Pulled(src) + if(!isliving(user)) + return + user.move_grabbed_atoms_towards(src) + +/turf/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(.) + return + if(!isliving(user)) + return + user.move_grabbed_atoms_towards(src) + /** * Check whether the specified turf is blocked by something dense inside it with respect to a specific atom. @@ -260,7 +271,7 @@ GLOBAL_LIST_EMPTY(station_turfs) else if(O.obj_flags & BLOCK_Z_OUT_DOWN) return FALSE - return direction == UP //can't go below + return direction & UP //can't go below else if(density) //No fuck off return FALSE @@ -276,8 +287,8 @@ GLOBAL_LIST_EMPTY(station_turfs) ///Called each time the target falls down a z level possibly making their trajectory come to a halt. see __DEFINES/movement.dm. /turf/proc/zImpact(atom/movable/falling, levels = 1, turf/prev_turf) - var/flags = NONE - var/list/falling_movables = falling.get_z_move_affected() + var/flags = FALL_RETAIN_PULL + var/list/falling_movables = falling.get_move_group() var/list/falling_mob_names for(var/atom/movable/falling_mob as anything in falling_movables) @@ -303,20 +314,26 @@ GLOBAL_LIST_EMPTY(station_turfs) if(!(flags & FALL_INTERCEPTED) && falling.zFall(levels + 1)) return FALSE - for(var/atom/movable/falling_mob as anything in falling_movables) + for(var/atom/movable/falling_movable as anything in falling_movables) + var/mob/living/L = falling_movable + if(!isliving(L)) + L = null if(!(flags & FALL_RETAIN_PULL)) - falling_mob.stop_pulling() + L?.release_all_grabs() if(!(flags & FALL_INTERCEPTED)) - falling_mob.onZImpact(src, levels) + falling_movable.onZImpact(src, levels) #ifndef ZMIMIC_MULTIZ_SPEECH //Multiz speech handles this otherwise if(!(flags & FALL_NO_MESSAGE)) prev_turf.audible_message(span_hear("You hear something slam into the deck below.")) #endif - if(falling_mob.pulledby && (falling_mob.z != falling_mob.pulledby.z || get_dist(falling_mob, falling_mob.pulledby) > 1)) - falling_mob.pulledby.stop_pulling() + if(L) + if(LAZYLEN(L.grabbed_by)) + for(var/obj/item/hand_item/grab/G in L.grabbed_by) + if(L.z != G.assailant.z || get_dist(L, G.assailant) > 1) + qdel(G) return TRUE /turf/proc/handleRCL(obj/item/rcl/C, mob/user) @@ -375,7 +392,7 @@ GLOBAL_LIST_EMPTY(station_turfs) return FALSE continue else - if(!firstbump || ((thing.layer > firstbump.layer || (thing.flags_1 & ON_BORDER_1|BUMP_PRIORITY_1)) && !(firstbump.flags_1 & ON_BORDER_1))) + if(!firstbump || ((thing.layer < firstbump.layer || (thing.flags_1 & ON_BORDER_1|BUMP_PRIORITY_1)) && !(firstbump.flags_1 & ON_BORDER_1))) firstbump = thing if(QDELETED(mover)) //Mover deleted from Cross/CanPass/Bump, do not proceed. diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index 8ce7d4eb0aa9..83efdc1e59a6 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -16,20 +16,21 @@ to_chat(owner, span_warning("We are already absorbing!")) return - if(!owner.pulling || !iscarbon(owner.pulling)) + var/obj/item/hand_item/grab/G = owner.get_active_grab() + if(!G) to_chat(owner, span_warning("We must be grabbing a creature to absorb them!")) return - if(owner.grab_state <= GRAB_NECK) + if(!G.current_grab.can_absorb) to_chat(owner, span_warning("We must have a tighter grip to absorb this creature!")) return - var/mob/living/carbon/target = owner.pulling + var/mob/living/carbon/target = G.affecting var/datum/antagonist/changeling/changeling = owner.mind.has_antag_datum(/datum/antagonist/changeling) return changeling.can_absorb_dna(target) -/datum/action/changeling/absorb_dna/sting_action(mob/owner) +/datum/action/changeling/absorb_dna/sting_action(mob/living/owner) var/datum/antagonist/changeling/changeling = owner.mind.has_antag_datum(/datum/antagonist/changeling) - var/mob/living/carbon/human/target = owner.pulling + var/mob/living/carbon/human/target = owner.get_active_grab()?.affecting is_absorbing = TRUE if(!attempt_absorb(target)) diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm index b15ef9498ffd..dc120f31da77 100644 --- a/code/modules/antagonists/changeling/powers/mutations.dm +++ b/code/modules/antagonists/changeling/powers/mutations.dm @@ -326,8 +326,8 @@ H.swap_hand() if(H.get_active_held_item()) return - C.grabbedby(H) - C.grippedby(H, instant = TRUE) //instant aggro grab + + H.try_make_grab(C, /datum/grab/normal/aggressive) /obj/projectile/tentacle/proc/tentacle_stab(mob/living/carbon/human/H, mob/living/carbon/C) if(H.Adjacent(C)) diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index d4b71b770bce..8a7845ea49f0 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -166,7 +166,7 @@ Striking a noncultist, however, will tear their flesh."} else . += "The sword appears to be quite lifeless." -/obj/item/cult_bastard/can_be_pulled(user) +/obj/item/cult_bastard/can_be_grabbed(mob/living/grabber, target_zone, force) return FALSE /obj/item/cult_bastard/attack_self(mob/user) @@ -607,8 +607,9 @@ Striking a noncultist, however, will tear their flesh."} /obj/item/cult_shift/proc/handle_teleport_grab(turf/T, mob/user) var/mob/living/carbon/C = user - if(C.pulling) - var/atom/movable/pulled = C.pulling + var/list/obj/item/hand_item/grab/grabs = C.get_active_grabs() + if(length(grabs)) + var/atom/movable/pulled = grabs[1].affecting do_teleport(pulled, T, channel = TELEPORT_CHANNEL_CULT) . = pulled @@ -636,7 +637,7 @@ Striking a noncultist, however, will tear their flesh."} var/atom/movable/pulled = handle_teleport_grab(destination, C) if(do_teleport(C, destination, channel = TELEPORT_CHANNEL_CULT)) if(pulled) - C.start_pulling(pulled) //forcemove resets pulls, so we need to re-pull + C.try_make_grab(pulled) //forcemove resets pulls, so we need to re-pull new /obj/effect/temp_visual/dir_setting/cult/phase(destination, C.dir) playsound(destination, 'sound/effects/phasein.ogg', 25, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) playsound(destination, SFX_SPARKS, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index 892d8a408090..1cb5eeb90353 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -702,11 +702,19 @@ structure_check() searches for nearby cultist structures required for the invoca fail_invoke() log_game("Summon Cultist rune failed - target died") return - if(cultist_to_summon.pulledby || cultist_to_summon.buckled) - to_chat(user, "[cultist_to_summon] is being held in place!") - fail_invoke() - log_game("Summon Cultist rune failed - target restrained") - return + if(LAZYLEN(cultist_to_summon.grabbed_by) || cultist_to_summon.buckled) + var/grab_check = 0 + if(!cultist_to_summon.buckled) + for(var/obj/item/hand_item/grab/G in cultist_to_summon.grabbed_by) + if(G.current_grab.stop_move) + grab_check++ + + if(grab_check == 0) + to_chat(user, "[cultist_to_summon] is being held in place!") + fail_invoke() + log_game("Summon Cultist rune failed - target restrained") + return + if(!IS_CULTIST(cultist_to_summon)) to_chat(user, "[cultist_to_summon] is not a follower of the Geometer!") fail_invoke() diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index 27f372762cb0..e57a962d4644 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -740,7 +740,7 @@ This is here to make the tiles around the station mininuke change when it's arme if (last_secured_location == get_turf(src)) return FALSE - var/mob/holder = pulledby || get(src, /mob) + var/mob/holder = get(src, /mob) if (isnull(holder?.client)) return FALSE diff --git a/code/modules/aquarium/aquarium.dm b/code/modules/aquarium/aquarium.dm index fff606f84db9..3f6c8f05b9b3 100644 --- a/code/modules/aquarium/aquarium.dm +++ b/code/modules/aquarium/aquarium.dm @@ -142,31 +142,36 @@ return NONE /obj/structure/aquarium/interact(mob/user) - if(!broken && user.pulling && isliving(user.pulling)) - var/mob/living/living_pulled = user.pulling - var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) - if(content_component && content_component.is_ready_to_insert(src)) - try_to_put_mob_in(user) - else if(panel_open) + if(panel_open) . = ..() //call base ui_interact +/obj/structure/aquarium/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(broken || !isliving(victim)) + return + + var/mob/living/living_pulled = victim + var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) + if(content_component && content_component.is_ready_to_insert(src)) + try_to_put_mob_in(user, grab) + /// Tries to put mob pulled by the user in the aquarium after a delay -/obj/structure/aquarium/proc/try_to_put_mob_in(mob/user) - if(user.pulling && isliving(user.pulling)) - var/mob/living/living_pulled = user.pulling - if(living_pulled.buckled || living_pulled.has_buckled_mobs()) - to_chat(user, span_warning("[living_pulled] is attached to something!")) +/obj/structure/aquarium/proc/try_to_put_mob_in(mob/living/user, obj/item/hand_item/grab/G) + var/mob/living/living_pulled = G.affecting + if(living_pulled.buckled || living_pulled.has_buckled_mobs()) + to_chat(user, span_warning("[living_pulled] is attached to something!")) + return + user.visible_message(span_danger("[user] starts to put [living_pulled] into [src]!")) + + if(do_after(user, src, 10 SECONDS)) + if(QDELETED(living_pulled) || !user.is_grabbing(living_pulled) || living_pulled.buckled || living_pulled.has_buckled_mobs()) return - user.visible_message(span_danger("[user] starts to put [living_pulled] into [src]!")) - if(do_after(user, src, 10 SECONDS)) - if(QDELETED(living_pulled) || user.pulling != living_pulled || living_pulled.buckled || living_pulled.has_buckled_mobs()) - return - var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) - if(content_component || content_component.is_ready_to_insert(src)) - return - user.visible_message(span_danger("[user] stuffs [living_pulled] into [src]!")) - living_pulled.forceMove(src) - update_appearance() + var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) + if(content_component || content_component.is_ready_to_insert(src)) + return + user.visible_message(span_danger("[user] stuffs [living_pulled] into [src]!")) + living_pulled.forceMove(src) + update_appearance() /obj/structure/aquarium/ui_data(mob/user) . = ..() diff --git a/code/modules/atmospherics/ZAS/Airflow.dm b/code/modules/atmospherics/ZAS/Airflow.dm index 9accdb2b38aa..b97dbf3a3798 100644 --- a/code/modules/atmospherics/ZAS/Airflow.dm +++ b/code/modules/atmospherics/ZAS/Airflow.dm @@ -36,8 +36,6 @@ This entire system is an absolute mess. /mob/living/airflow_stun(delta_p) if(stat == 2) return FALSE - if(pulledby || pulling) - return FALSE if(last_airflow_stun > world.time - zas_settings.airflow_stun_cooldown) return FALSE if(!(status_flags & CANSTUN) && !(status_flags & CANKNOCKDOWN)) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm index a52c80e0f49e..4852cc3cdae1 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/passive_gate.dm @@ -26,7 +26,7 @@ Passive gate is similar to the regular pump except: ///Stores the radio connection var/datum/radio_frequency/radio_connection -/obj/machinery/atmospherics/components/binary/passive_gate/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/passive_gate/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm index 4b8d32a708f7..e091fc3d8059 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/pressure_valve.dm @@ -23,7 +23,7 @@ ///Which side is the valve regulating? var/regulate_mode = REGULATE_OUTPUT -/obj/machinery/atmospherics/components/binary/pressure_valve/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/pressure_valve/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm index cad775e3b114..27d95f7d7e17 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm @@ -39,7 +39,7 @@ /obj/item/circuit_component/atmos_pump, )) -/obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/pump/CtrlClick(mob/user, list/params) if(can_interact(user)) set_on(!on) investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm index 98ef842b0e8e..bebadfd3c98a 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_gate.dm @@ -17,7 +17,7 @@ ///Check if the gas is moving from one pipenet to the other var/is_gas_flowing = FALSE -/obj/machinery/atmospherics/components/binary/temperature_gate/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/temperature_gate/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm index 425ed4b25bce..dc570ffcb1fd 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/temperature_pump.dm @@ -12,7 +12,7 @@ ///Maximum allowed transfer percentage var/max_heat_transfer_rate = 100 -/obj/machinery/atmospherics/components/binary/temperature_pump/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/temperature_pump/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm index e0c827d10dc7..1bab358d3aeb 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/volume_pump.dm @@ -39,7 +39,7 @@ /obj/item/circuit_component/atmos_volume_pump, )) -/obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/binary/volume_pump/CtrlClick(mob/user, list/params) if(can_interact(user)) set_on(!on) investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm index 73b1a8d6f765..41267ae13e75 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/filter.dm @@ -22,7 +22,7 @@ //Last power draw, for the progress bar in the UI var/last_power_draw = 0 -/obj/machinery/atmospherics/components/trinary/filter/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/trinary/filter/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm index 80fb796f509e..8ad21db4f41e 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm @@ -22,7 +22,7 @@ var/last_power_draw = 0 -/obj/machinery/atmospherics/components/trinary/mixer/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/trinary/mixer/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index c1cc24f86f28..5c52a6a8ac5d 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -459,7 +459,7 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics /obj/machinery/atmospherics/components/unary/cryo_cell/can_interact(mob/user) return ..() && user.loc != src -/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user, list/params) if(can_interact(user) && !state_open) set_on(!on) return ..() diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm index 64f30e857671..c2f6056f1700 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm @@ -20,7 +20,7 @@ ///Rate of operation of the device var/volume_rate = 50 -/obj/machinery/atmospherics/components/unary/outlet_injector/CtrlClick(mob/user) +/obj/machinery/atmospherics/components/unary/outlet_injector/CtrlClick(mob/user, list/params) if(can_interact(user)) on = !on investigate_log("was turned [on ? "on" : "off"] by [key_name(user)]", INVESTIGATE_ATMOS) diff --git a/code/modules/awaymissions/signpost.dm b/code/modules/awaymissions/signpost.dm index ea0d22ac39f6..12e2e832e510 100644 --- a/code/modules/awaymissions/signpost.dm +++ b/code/modules/awaymissions/signpost.dm @@ -20,12 +20,19 @@ var/turf/T = find_safe_turf(zlevels=zlevels) if(T) - var/atom/movable/AM = user.pulling - if(AM) - AM.forceMove(T) + var/atom/movable/preserve_grab + if(isliving(user)) + var/mob/living/L = user + var/list/grabbing= L.get_all_grabbed_movables() + for(var/atom/movable/AM as anything in grabbing) + preserve_grab ||= AM + AM.forceMove(T) + user.forceMove(T) - if(AM) - user.start_pulling(AM) + if(preserve_grab && isliving(user)) + var/mob/living/L = user + L.try_make_grab(preserve_grab) + to_chat(user, span_notice("You blink and find yourself in [get_area_name(T)].")) else to_chat(user, "Nothing happens. You feel that this is a bad sign.") diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 7d337767d90c..0404781ad769 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -369,7 +369,7 @@ var/mob/living/mob_to_insert = to_insert if(mob_to_insert.anchored || mob_to_insert.incorporeal_move) return FALSE - mob_to_insert.stop_pulling() + mob_to_insert.release_all_grabs() else if(isobj(to_insert)) var/obj/obj_to_insert = to_insert diff --git a/code/modules/credits_roll/episode_name.dm b/code/modules/credits_roll/episode_name.dm index 8e312c961cff..44023c41332f 100644 --- a/code/modules/credits_roll/episode_name.dm +++ b/code/modules/credits_roll/episode_name.dm @@ -318,7 +318,7 @@ var/all_braindamaged = TRUE for(var/mob/living/carbon/human/H as anything in SSticker.popcount["human_escapees_list"]) var/obj/item/organ/brain/hbrain = H.getorganslot(ORGAN_SLOT_BRAIN) - if(hbrain.damage < 60) + if(hbrain?.damage < 60) all_braindamaged = FALSE braindamage_total += hbrain.damage var/average_braindamage = braindamage_total / human_escapees diff --git a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm index d83125e7e4cc..e73f1cc40fb6 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/deep_fryer.dm @@ -129,31 +129,42 @@ GLOBAL_LIST_INIT(oilfry_blacklisted_items, typecacheof(list( frying_burnt = FALSE fry_loop.stop() return - else if(user.pulling && iscarbon(user.pulling) && reagents.total_volume) - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, span_warning("You need a better grip to do that!")) - return - var/mob/living/carbon/dunking_target = user.pulling - log_combat(user, dunking_target, "dunked", null, "into [src]") - user.visible_message(span_danger("[user] dunks [dunking_target]'s face in [src]!")) - reagents.expose(dunking_target, TOUCH) - var/permeability = 1 - dunking_target.get_permeability_protection(list(HEAD)) - var/target_temp = dunking_target.bodytemperature - var/cold_multiplier = 1 - if(target_temp < TCMB + 10) // a tiny bit of leeway - dunking_target.visible_message(span_userdanger("[dunking_target] explodes from the entropic difference! Holy fuck!")) - dunking_target.gib() - log_combat(user, dunking_target, "blew up", null, "by dunking them into [src]") - return - else if(target_temp < T0C) - cold_multiplier += round(target_temp * 1.5 / T0C, 0.01) - dunking_target.apply_damage(min(30 * permeability * cold_multiplier, reagents.total_volume), BURN, BODY_ZONE_HEAD) - if(reagents.reagent_list) //This can runtime if reagents has nothing in it. - reagents.remove_any((reagents.total_volume/2)) - dunking_target.Paralyze(60) - user.changeNext_move(CLICK_CD_MELEE) return ..() +/obj/machinery/deepfryer/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!iscarbon(victim) || !reagents.total_volume) + return + + if(grab.current_grab.damage_stage < GRAB_AGGRESSIVE) + to_chat(user, span_warning("You need a better grip to do that!")) + return + + var/mob/living/carbon/dunking_target = victim + + log_combat(user, dunking_target, "dunked", null, "into [src]") + user.visible_message(span_danger("[user] dunks [dunking_target]'s face in [src]!")) + reagents.expose(dunking_target, TOUCH) + + var/permeability = 1 - dunking_target.get_permeability_protection(list(HEAD)) + var/target_temp = dunking_target.bodytemperature + var/cold_multiplier = 1 + if(target_temp < TCMB + 10) // a tiny bit of leeway + dunking_target.visible_message(span_userdanger("[dunking_target] explodes from the entropic difference! Holy fuck!")) + dunking_target.gib() + log_combat(user, dunking_target, "blew up", null, "by dunking them into [src]") + return + + else if(target_temp < T0C) + cold_multiplier += round(target_temp * 1.5 / T0C, 0.01) + + dunking_target.apply_damage(min(30 * permeability * cold_multiplier, reagents.total_volume), BURN, BODY_ZONE_HEAD) + if(reagents.reagent_list) //This can runtime if reagents has nothing in it. + reagents.remove_any((reagents.total_volume/2)) + + dunking_target.Paralyze(60) + user.changeNext_move(CLICK_CD_MELEE) + /obj/machinery/deepfryer/proc/fry(obj/item/frying_item, mob/user) to_chat(user, span_notice("You put [frying_item] into [src].")) if(istype(frying_item, /obj/item/freeze_cube)) diff --git a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm index 2b7af865200a..e3448d3a25be 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/gibber.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/gibber.dm @@ -73,34 +73,39 @@ to_chat(user, span_warning("[src] cannot be used unless bolted to the ground!")) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(!iscarbon(L)) - to_chat(user, span_warning("This item is not suitable for the gibber!")) - return - var/mob/living/carbon/C = L - if(C.buckled ||C.has_buckled_mobs()) - to_chat(user, span_warning("[C] is attached to something!")) - return - - if(!ignore_clothing) - for(var/obj/item/I in C.held_items + C.get_equipped_items()) - if(!HAS_TRAIT(I, TRAIT_NODROP)) - to_chat(user, span_warning("Subject may not have abiotic items on!")) - return - - user.visible_message(span_danger("[user] starts to put [C] into the gibber!")) - - add_fingerprint(user) - - if(do_after(user, src, gibtime)) - if(C && user.pulling == C && !C.buckled && !C.has_buckled_mobs() && !occupant) - user.visible_message(span_danger("[user] stuffs [C] into the gibber!")) - C.forceMove(src) - set_occupant(C) - update_appearance() - else - startgibbing(user) + startgibbing(user) + +/obj/machinery/gibber/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim)) + return + + var/mob/living/L = victim + if(!iscarbon(L)) + to_chat(user, span_warning("This item is not suitable for the gibber!")) + return + + var/mob/living/carbon/C = L + if(C.buckled ||C.has_buckled_mobs()) + to_chat(user, span_warning("[C] is attached to something!")) + return + + if(!ignore_clothing) + for(var/obj/item/I in C.held_items + C.get_equipped_items()) + if(!HAS_TRAIT(I, TRAIT_NODROP)) + to_chat(user, span_warning("Subject may not have abiotic items on!")) + return + + user.visible_message(span_danger("[user] starts to put [C] into the gibber!")) + + add_fingerprint(user) + + if(do_after(user, src, gibtime)) + if(C && user.is_grabbing(C) && !C.buckled && !C.has_buckled_mobs() && !occupant) + user.visible_message(span_danger("[user] stuffs [C] into the gibber!")) + C.forceMove(src) + set_occupant(C) + update_appearance() /obj/machinery/gibber/wrench_act(mob/living/user, obj/item/tool) . = ..() diff --git a/code/modules/food_and_drinks/kitchen_machinery/processor.dm b/code/modules/food_and_drinks/kitchen_machinery/processor.dm index 616d4700adfa..871fa54ed9b9 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/processor.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/processor.dm @@ -118,16 +118,7 @@ if(processing) to_chat(user, span_warning("[src] is in the process of processing!")) return TRUE - if(ismob(user.pulling) && PROCESSOR_SELECT_RECIPE(user.pulling)) - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, span_warning("You need a better grip to do that!")) - return - var/mob/living/pushed_mob = user.pulling - visible_message(span_warning("[user] stuffs [pushed_mob] into [src]!")) - pushed_mob.forceMove(src) - LAZYADD(processor_contents, pushed_mob) - user.stop_pulling() - return + if(!LAZYLEN(processor_contents)) to_chat(user, span_warning("[src] is empty!")) return TRUE @@ -157,6 +148,23 @@ processing = FALSE visible_message(span_notice("\The [src] finishes processing.")) +/obj/machinery/processor/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(processing) + to_chat(user, span_warning("[src] is in the process of processing!")) + return TRUE + + if(ismob(victim) && PROCESSOR_SELECT_RECIPE(victim)) + if(grab.current_grab.damage_stage < GRAB_AGGRESSIVE) + to_chat(user, span_warning("You need a better grip to do that!")) + return + var/mob/living/pushed_mob = victim + visible_message(span_warning("[user] stuffs [pushed_mob] into [src]!")) + qdel(grab) + pushed_mob.forceMove(src) + LAZYADD(processor_contents, pushed_mob) + return TRUE + /obj/machinery/processor/verb/eject() set category = "Object" set name = "Eject Contents" diff --git a/code/modules/grab/grab_carbon.dm b/code/modules/grab/grab_carbon.dm new file mode 100644 index 000000000000..a81f042b942b --- /dev/null +++ b/code/modules/grab/grab_carbon.dm @@ -0,0 +1,8 @@ +/mob/living/carbon/get_active_grabs() + . = list() + for(var/obj/item/hand_item/grab/grab in held_items) + . += grab + +// We're changing the default grab type here. +/mob/living/carbon/make_grab(atom/movable/target, grab_type = /datum/grab/normal/passive) + . = ..() diff --git a/code/modules/grab/grab_datum.dm b/code/modules/grab/grab_datum.dm new file mode 100644 index 000000000000..d8cda6f98a2a --- /dev/null +++ b/code/modules/grab/grab_datum.dm @@ -0,0 +1,416 @@ +/// An associative list of type:instance for grab datums +GLOBAL_LIST_EMPTY(all_grabstates) +/datum/grab + abstract_type = /datum/grab + var/icon = 'icons/hud/screen_gen.dmi' + var/icon_state + + /// The grab that this will upgrade to if it upgrades, null means no upgrade + var/datum/grab/upgrab + /// The grab that this will downgrade to if it downgrades, null means break grab on downgrade + var/datum/grab/downgrab + + // Whether or not the grabbed person can move out of the grab + var/stop_move = FALSE + // Whether the assailant is facing forwards or backwards. + var/reverse_facing = FALSE + /// Whether this grab state is strong enough to, as a changeling, absorb the person you're grabbing. + var/can_absorb = FALSE + /// How much the grab increases point blank damage. + var/point_blank_mult = 1 + /// Affects how much damage is being dealt using certain actions. + var/damage_stage = GRAB_PASSIVE + /// If the grabbed person and the grabbing person are on the same tile. + var/same_tile = FALSE + /// If the grabber can throw the person grabbed. + var/can_throw = FALSE + /// If the grab needs to be downgraded when the grabber does stuff. + var/downgrade_on_action = FALSE + /// If the grab needs to be downgraded when the grabber moves. + var/downgrade_on_move = FALSE + /// If the grab is strong enough to be able to force someone to do something harmful to them, like slam their head into glass. + var/enable_violent_interactions = FALSE + /// If the grab acts like cuffs and prevents action from the victim. + var/restrains = FALSE + + var/grab_slowdown = 0 + + var/shift = 0 + + var/success_up = "You upgrade the grab." + var/success_down = "You downgrade the grab." + + var/fail_up = "You fail to upgrade the grab." + var/fail_down = "You fail to downgrade the grab." + + var/upgrade_cooldown = 4 SECONDS + var/action_cooldown = 4 SECONDS + + var/can_downgrade_on_resist = TRUE + + /// The baseline index of break_chance_table, before modifiers. This can be higher than the length of break_chance_table. + var/breakability = 2 + var/list/break_chance_table = list(100) + + var/can_grab_self = TRUE + + // The names of different intents for use in attack logs + var/help_action = "" + var/disarm_action = "" + var/grab_action = "" + var/harm_action = "" + +/* + These procs shouldn't be overriden in the children unless you know what you're doing with them; they handle important core functions. + Even if you do override them, you should likely be using ..() if you want the behaviour to function properly. That is, of course, + unless you're writing your own custom handling of things. +*/ +/// Called during world/New to setup references. +/datum/grab/proc/refresh_updown() + if(upgrab) + upgrab = GLOB.all_grabstates[upgrab] + + if(downgrab) + downgrab = GLOB.all_grabstates[downgrab] + +/// Called by the grab item's setup() proc. May return FALSE to interrupt, otherwise the grab has succeeded. +/datum/grab/proc/setup(obj/item/hand_item/grab) + SHOULD_CALL_PARENT(TRUE) + return TRUE + +// This is for the strings defined as datum variables. It takes them and swaps out keywords for relevent ones from the grab +// object involved. +/datum/grab/proc/string_process(obj/item/hand_item/grab/G, to_write, obj/item/used_item) + to_write = replacetext(to_write, "rep_affecting", G.affecting) + to_write = replacetext(to_write, "rep_assailant", G.assailant) + if(used_item) + to_write = replacetext(to_write, "rep_item", used_item) + return to_write + +/datum/grab/proc/upgrade(obj/item/hand_item/grab/G) + if(!upgrab) + return + + if (can_upgrade(G)) + upgrade_effect(G) + return upgrab + else + to_chat(G.assailant, span_warning("[string_process(G, fail_up)]")) + return + +/datum/grab/proc/downgrade(obj/item/hand_item/grab/G) + // Starts the process of letting go if there's no downgrade grab + if(can_downgrade()) + downgrade_effect(G) + return downgrab + else + to_chat(G.assailant, span_warning("[string_process(G, fail_down)]")) + return + +/datum/grab/proc/let_go(obj/item/hand_item/grab/G) + SHOULD_NOT_OVERRIDE(TRUE) + if(!G) + return + let_go_effect(G) + G.current_grab = null + if(!QDELETED(G)) + qdel(G) + +/datum/grab/proc/on_target_change(obj/item/hand_item/grab/G, old_zone, new_zone) + remove_bodyzone_effects(G, old_zone, new_zone) + G.special_target_functional = check_special_target(G) + if(G.special_target_functional) + special_bodyzone_change(G, old_zone, new_zone) + special_bodyzone_effects(G) + +/datum/grab/proc/hit_with_grab(obj/item/hand_item/grab/G, atom/target, params) + if(G.is_currently_resolving_hit) + return FALSE + + if(!COOLDOWN_FINISHED(G, action_cd)) + to_chat(G.assailant, span_warning("You must wait [round(COOLDOWN_TIMELEFT(G, action_cd) * 0.1, 0.1)] seconds before you can perform a grab action.")) + return FALSE + + if(downgrade_on_action) + G.downgrade() + + G.is_currently_resolving_hit = TRUE + + var/combat_mode = G.assailant.combat_mode + if(params[RIGHT_CLICK]) + if(on_hit_disarm(G, target)) + . = disarm_action || TRUE + + else if(params[CTRL_CLICK]) + if(on_hit_grab(G, target)) + . = grab_action || TRUE + + + else if(combat_mode) + if(on_hit_harm(G, target)) + . = harm_action || TRUE + else + if(on_hit_help(G, target)) + . = help_action || TRUE + + if(QDELETED(G)) + return + + G.is_currently_resolving_hit = FALSE + + if(!.) + return + + G.action_used() + if(G.assailant) + G.assailant.changeNext_move(CLICK_CD_GRABBING) + if(istext(.) && G.affecting) + make_log(G, "used [.] on") + + if(downgrade_on_action) + G.downgrade() + +/datum/grab/proc/make_log(obj/item/hand_item/grab/G, action) + log_combat(G.assailant, G.affecting, "[action] their victim") + +/* + Override these procs to set how the grab state will work. Some of them are best + overriden in the parent of the grab set (for example, the behaviour for on_hit_intent(var/obj/item/hand_item/grab/G) + procs is determined in /datum/grab/normal and then inherited by each intent). +*/ + +// What happens when you upgrade from one grab state to the next. +/datum/grab/proc/upgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) + update_stage_effects(G, old_grab) + +// Conditions to see if upgrading is possible +/datum/grab/proc/can_upgrade(obj/item/hand_item/grab/G) + if(!upgrab) + return FALSE + + var/mob/living/L = G.get_affecting_mob() + if(!isliving(L)) + return FALSE + + if(!(L.status_flags & CANPUSH) || HAS_TRAIT(L, TRAIT_PUSHIMMUNE)) + to_chat(G.assailant, span_warning("[src] can't be grabbed more aggressively!")) + return FALSE + + if(upgrab.damage_stage >= GRAB_AGGRESSIVE && HAS_TRAIT(G.assailant, TRAIT_PACIFISM)) + to_chat(G.assailant, span_warning("You don't want to risk hurting [src]!")) + return FALSE + return TRUE + +// What happens when you downgrade from one grab state to the next. +/datum/grab/proc/downgrade_effect(obj/item/hand_item/grab/G, datum/grab/old_grab) + update_stage_effects(G, old_grab) + +// Conditions to see if downgrading is possible +/datum/grab/proc/can_downgrade(obj/item/hand_item/grab/G) + return TRUE + +// What happens when you let go of someone by either dropping the grab +// or by downgrading from the lowest grab state. +/datum/grab/proc/let_go_effect(obj/item/hand_item/grab/G) + SEND_SIGNAL(G.affecting, COMSIG_ATOM_NO_LONGER_GRABBED, G.assailant) + SEND_SIGNAL(G.assailant, COMSIG_LIVING_NO_LONGER_GRABBING, G.affecting) + + remove_bodyzone_effects(G, G.target_zone) + if(G.is_grab_unique(src)) + remove_unique_grab_effects(G) + update_stage_effects(G, src, TRUE) + +/// Add effects that apply based on damage_stage here +/datum/grab/proc/update_stage_effects(obj/item/hand_item/grab/G, datum/grab/old_grab, dropping_grab) + var/old_damage_stage = old_grab?.damage_stage || GRAB_PASSIVE + var/new_stage = dropping_grab ? GRAB_PASSIVE : damage_stage + + switch(new_stage) // Current state. + if(GRAB_PASSIVE) + REMOVE_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) + REMOVE_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) + if(old_damage_stage >= GRAB_AGGRESSIVE) + REMOVE_TRAIT(G.affecting, TRAIT_AGGRESSIVE_GRAB, REF(G)) + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) + + if(GRAB_AGGRESSIVE) + if(old_damage_stage >= GRAB_NECK) // Grab got downgraded. + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) + else // Grab got upgraded from a passive one. + ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_AGGRESSIVE_GRAB, REF(G)) + + if(GRAB_NECK, GRAB_KILL) + if(old_damage_stage < GRAB_AGGRESSIVE) + ADD_TRAIT(G.affecting, TRAIT_AGGRESSIVE_GRAB, REF(G)) + if(old_damage_stage <= GRAB_AGGRESSIVE) + ADD_TRAIT(G.affecting, TRAIT_FLOORED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_HANDS_BLOCKED, REF(G)) + ADD_TRAIT(G.affecting, TRAIT_IMMOBILIZED, REF(G)) + + +/// Apply effects that should only be applied when a grab type is first used on a mob. +/datum/grab/proc/apply_unique_grab_effects(obj/item/hand_item/grab/G) + SHOULD_CALL_PARENT(TRUE) + if(G.loc != G.assailant.loc && same_tile) + G.affecting.move_from_pull(G.assailant, get_turf(G.assailant)) + +/// Remove effects added by apply_unique_grab_effects() +/datum/grab/proc/remove_unique_grab_effects(obj/item/hand_item/grab/G) + SHOULD_CALL_PARENT(TRUE) + +/// Handles special targeting like eyes and mouth being covered. +/// CLEAR OUT ANY EFFECTS USING remove_bodyzone_effects() +/datum/grab/proc/special_bodyzone_effects(obj/item/hand_item/grab/G) + SHOULD_CALL_PARENT(TRUE) + var/mob/living/carbon/C = G.affecting + if(!istype(C)) + return + + var/obj/item/bodypart/BP = C.get_bodypart(deprecise_zone(G.target_zone)) + if(!BP) + return + + ADD_TRAIT(BP, TRAIT_BODYPART_GRABBED, REF(G)) + +/// Clear out any effects from special_bodyzone_effects() +/datum/grab/proc/remove_bodyzone_effects(obj/item/hand_item/grab/G, old_zone, new_zone) + SHOULD_CALL_PARENT(TRUE) + var/mob/living/carbon/C = G.affecting + if(!istype(C)) + return + + var/obj/item/bodypart/BP = C.get_bodypart(deprecise_zone(old_zone)) + if(!BP) + return + + REMOVE_TRAIT(BP, TRAIT_BODYPART_GRABBED, REF(G)) + +// Handles when they change targeted areas and something is supposed to happen. +/datum/grab/proc/special_bodyzone_change(obj/item/hand_item/grab/G, diff_zone) + +// Checks if the special target works on the grabbed humanoid. +/datum/grab/proc/check_special_target(obj/item/hand_item/grab/G) + +// What happens when you hit the grabbed person with the grab on help intent. +/datum/grab/proc/on_hit_help(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with the grab on disarm intent. +/datum/grab/proc/on_hit_disarm(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with the grab on grab intent. +/datum/grab/proc/on_hit_grab(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with the grab on harm intent. +/datum/grab/proc/on_hit_harm(obj/item/hand_item/grab/G) + return FALSE + +// What happens when you hit the grabbed person with an open hand and you want it +// to do some special snowflake action based on some other factor such as +// intent. +/datum/grab/proc/resolve_openhand_attack(obj/item/hand_item/grab/G) + return FALSE + +// Used when you want an effect to happen when the grab enters this state as an upgrade +/// Return TRUE unless the grab state is changing during this proc (for example, calling upgrade()) +/datum/grab/proc/enter_as_up(obj/item/hand_item/grab/G, silent) + SHOULD_CALL_PARENT(TRUE) + return TRUE + +// Used when you want an effect to happen when the grab enters this state as a downgrade +/// Return TRUE unless the grab state is changing during this proc (for example, calling upgrade()) +/datum/grab/proc/enter_as_down(obj/item/hand_item/grab/G, silent) + SHOULD_CALL_PARENT(TRUE) + return TRUE + +/datum/grab/proc/item_attack(obj/item/hand_item/grab/G, obj/item) + +/datum/grab/proc/resolve_item_attack(obj/item/hand_item/grab/G, mob/living/carbon/human/user, obj/item/I, target_zone) + return FALSE + +/// Handle resist actions from the affected mob. Returns TRUE if the grab was broken. +/datum/grab/proc/handle_resist(obj/item/hand_item/grab/G) + var/mob/living/carbon/human/affecting = G.affecting + var/mob/living/carbon/human/assailant = G.assailant + + var/break_strength = breakability + size_difference(affecting, assailant) + var/affecting_shock = affecting.getPain() + var/assailant_shock = assailant.getPain() + + // Target modifiers + if(affecting.incapacitated(IGNORE_GRAB)) + break_strength-- + if(affecting.has_status_effect(/datum/status_effect/confusion)) + break_strength-- + if(affecting.eye_blind || HAS_TRAIT(affecting, TRAIT_BLIND)) + break_strength-- + if(affecting.eye_blurry) + break_strength-- + if(affecting_shock >= 10) + break_strength-- + if(affecting_shock >= 30) + break_strength-- + if(affecting_shock >= 50) + break_strength-- + + // User modifiers + if(assailant.has_status_effect(/datum/status_effect/confusion)) + break_strength++ + if(assailant.eye_blind || HAS_TRAIT(assailant, TRAIT_BLIND)) + break_strength++ + if(assailant.eye_blurry) + break_strength++ + if(assailant_shock >= 10) + break_strength++ + if(assailant_shock >= 30) + break_strength++ + if(assailant_shock >= 50) + break_strength++ + + if(break_strength < 1) + to_chat(G.affecting, span_warning("You try to break free but feel that unless something changes, you'll never escape!")) + return FALSE + + if (assailant.incapacitated(IGNORE_GRAB)) + let_go(G) + stack_trace("Someone resisted a grab while the assailant was incapacitated. This shouldn't ever happen.") + return TRUE + + var/break_chance = break_chance_table[clamp(break_strength, 1, length(break_chance_table))] + if(prob(break_chance)) + if(can_downgrade_on_resist && !prob((break_chance+100)/2)) + affecting.visible_message(span_danger("[affecting] has loosened [assailant]'s grip!"), vision_distance = COMBAT_MESSAGE_RANGE) + G.downgrade() + return FALSE + else + affecting.visible_message(span_danger("[affecting] has broken free of [assailant]'s grip!"), vision_distance = COMBAT_MESSAGE_RANGE) + let_go(G) + return TRUE + +/datum/grab/proc/size_difference(mob/living/A, mob/living/B) + return mob_size_difference(A.mob_size, B.mob_size) + +/datum/grab/proc/moved_effect(obj/item/hand_item/grab/G) + return + +/// Add screentip context, user will always be assailant. +/datum/grab/proc/add_context(list/context, obj/item/held_item, mob/living/user, atom/movable/target) + if(!(isgrab(held_item) && held_item:current_grab == src)) + return + + if(disarm_action) + context[SCREENTIP_CONTEXT_RMB] = capitalize(disarm_action) + + if(grab_action) + context[SCREENTIP_CONTEXT_CTRL_LMB] = capitalize(grab_action) + + if(user.combat_mode) + if(harm_action) + context[SCREENTIP_CONTEXT_LMB] = capitalize(harm_action) + + else if(help_action) + context[SCREENTIP_CONTEXT_LMB] = capitalize(help_action) diff --git a/code/modules/grab/grab_helpers.dm b/code/modules/grab/grab_helpers.dm new file mode 100644 index 000000000000..496bfdb99c26 --- /dev/null +++ b/code/modules/grab/grab_helpers.dm @@ -0,0 +1,88 @@ +/mob/living/proc/get_active_grabs() + . = list() + for(var/obj/item/hand_item/grab/G in held_items) + . += G + +/// Returns TRUE if src is grabbing hold of the target +/mob/living/proc/is_grabbing(atom/movable/AM) + RETURN_TYPE(/obj/item/hand_item/grab) + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + if(G.affecting == AM) + return G + +/// Release all grabs we have +/mob/living/proc/release_all_grabs() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + qdel(G) + +/// Release the given movable from a grab +/mob/living/proc/release_grabs(atom/movable/AM) + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + if(G.affecting == AM) + qdel(G) + +/// Returns the currently selected grab item +/mob/living/proc/get_active_grab() + RETURN_TYPE(/obj/item/hand_item/grab) + var/obj/item/hand_item/grab/G = locate() in src + return G + +/mob/living/carbon/get_active_grab() + RETURN_TYPE(/obj/item/hand_item/grab) + var/item = get_active_held_item() + if(isgrab(item)) + return item + return ..() + +/// Releases the currently selected grab item +/mob/living/proc/release_active_grab() + qdel(get_active_grab()) + +/// Returns a list of every movable we are grabbing +/mob/living/proc/get_all_grabbed_movables() + . = list() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + . |= G.affecting + +/// Frees src from all grabs. +/atom/movable/proc/free_from_all_grabs() + grabbed_by?.len = 0 + grabbed_by = null + + +/// Gets every grabber of this atom, and every grabber of those grabbers, repeat +/atom/movable/proc/recursively_get_all_grabbers() + RETURN_TYPE(/list) + . = list() + for(var/obj/item/hand_item/grab/G in grabbed_by) + . |= G.assailant + . |= G.assailant.recursively_get_all_grabbers() + +/// Gets every grabbed atom of this mob, and every grabbed atom of that grabber, repeat +/mob/living/proc/recursively_get_all_grabbed_movables() + RETURN_TYPE(/list) + . = list() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + . |= G.affecting + var/mob/living/L = G.get_affecting_mob() + if(L) + . |= L.recursively_get_all_grabbed_movables() + +/// Gets every grab object owned by this mob, and every grabbed atom of those grabbed mobs +/mob/living/proc/recursively_get_conga_line() + RETURN_TYPE(/list) + . = list() + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + . |= G + var/mob/living/L = G.get_affecting_mob() + if(L) + . |= L.recursively_get_conga_line() + +/// Get every single member of a grab chain +/atom/movable/proc/get_all_grab_chain_members() + RETURN_TYPE(/list) + return recursively_get_all_grabbers() + +/mob/living/get_all_grab_chain_members() + . = ..() + . |= recursively_get_all_grabbed_movables() diff --git a/code/modules/grab/grab_living.dm b/code/modules/grab/grab_living.dm new file mode 100644 index 000000000000..08fec11b1379 --- /dev/null +++ b/code/modules/grab/grab_living.dm @@ -0,0 +1,159 @@ +/mob/living/proc/can_grab(atom/movable/target, target_zone, use_offhand) + if(throwing || !(mobility_flags & MOBILITY_PULL)) + return FALSE + if(!ismob(target) && target.anchored) + to_chat(src, span_warning("\The [target] won't budge!")) + return FALSE + + if(use_offhand) + if(!get_empty_held_index()) + to_chat(src, span_warning("Your hands are full!")) + return FALSE + + else if(get_active_held_item()) + to_chat(src, span_warning("Your [active_hand_index % 2 ? "right" : "left"] hand is full!")) + return FALSE + + if(LAZYLEN(grabbed_by)) + to_chat(src, span_warning("You cannot start grappling while already being grappled!")) + return FALSE + + for(var/obj/item/hand_item/grab/G in target.grabbed_by) + if(G.assailant != src) + continue + if(!target_zone || !ismob(target)) + to_chat(src, span_warning("You already have a grip on \the [target]!")) + return FALSE + if(G.target_zone == target_zone) + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(BP) + to_chat(src, span_warning("You already have a grip on \the [target]'s [BP.plaintext_zone].")) + return FALSE + return TRUE + +/mob/living/can_be_grabbed(mob/living/grabber, target_zone, use_offhand) + . = ..() + if(!.) + return + if((buckled?.buckle_prevents_pull)) + return FALSE + +/// Attempt to create a grab, returns TRUE on success +/mob/living/proc/try_make_grab(atom/movable/target, grab_type, use_offhand) + return canUseTopic(src, USE_IGNORE_TK|USE_CLOSE) && make_grab(target, grab_type, use_offhand) + +/mob/living/proc/make_grab(atom/movable/target, grab_type = /datum/grab/simple, use_offhand) + if(SEND_SIGNAL(src, COMSIG_LIVING_TRY_GRAB, target, grab_type) & COMSIG_LIVING_CANCEL_GRAB) + return + + // Resolve to the 'topmost' atom in the buckle chain, as grabbing someone buckled to something tends to prevent further interaction. + var/atom/movable/original_target = target + var/mob/grabbing_mob = (ismob(target) && target) + + while(istype(grabbing_mob) && grabbing_mob.buckled) + grabbing_mob = grabbing_mob.buckled + + if(grabbing_mob && grabbing_mob != original_target) + target = grabbing_mob + to_chat(src, span_warning("As \the [original_target] is buckled to \the [target], you try to grab that instead!")) + + if(!istype(target)) + return + + face_atom(target) + + var/obj/item/hand_item/grab/grab + if(ispath(grab_type, /datum/grab) && can_grab(target, zone_selected, use_offhand) && target.can_be_grabbed(src, zone_selected, use_offhand)) + grab = new /obj/item/hand_item/grab(src, target, grab_type, use_offhand) + + + if(QDELETED(grab)) + if(original_target != src && ismob(original_target)) + to_chat(original_target, span_warning("\The [src] tries to grab you, but fails!")) + return null + + SEND_SIGNAL(src, COMSIG_LIVING_START_GRAB, target, grab) + SEND_SIGNAL(target, COMSIG_ATOM_GET_GRABBED, src, grab) + + return grab + +/mob/living/proc/add_grab(obj/item/hand_item/grab/grab, use_offhand) + for(var/obj/item/hand_item/grab/other_grab in contents) + if(other_grab != grab) + return FALSE + grab.forceMove(src) + return TRUE + +/mob/living/recheck_grabs(only_pulling = FALSE, only_pulled = FALSE, z_allowed = FALSE) + if(only_pulled) + return ..() + + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + var/atom/movable/pulling = G.affecting + if(!MultiZAdjacent(src, pulling)) + qdel(G) + else if(!isturf(loc)) + qdel(G) + else if(!isturf(pulling.loc)) //to be removed once all code that changes an object's loc uses forceMove(). + qdel(G) + else if(pulling.anchored || pulling.move_resist > move_force) + qdel(G) + + return ..() + +/// Called by grab objects when a grab has been released +/mob/living/proc/after_grab_release(atom/movable/old_target) + animate_interact(old_target, INTERACT_UNPULL) + update_pull_hud_icon() + +/// Called during or immediately after movement. Used to move grab targets around to ensure the grabs do not break during movement. +/mob/living/proc/handle_grabs_during_movement(turf/old_loc, direction) + var/list/grabs_in_grab_chain = get_active_grabs() //recursively_get_conga_line() + if(!LAZYLEN(grabs_in_grab_chain)) + return + + for(var/obj/item/hand_item/grab/G in grabs_in_grab_chain) + var/atom/movable/pulling = G.affecting + if(pulling == src || pulling.loc == loc) + continue + if(pulling.anchored || !isturf(loc)) + qdel(G) + continue + + var/pull_dir = get_dir(pulling, src) + var/target_turf = G.current_grab.same_tile ? loc : old_loc + + // Pulling things down/up stairs. zMove() has flags for check_pulling and stop_pulling calls. + // You may wonder why we're not just forcemoving the pulling movable and regrabbing it. + // The answer is simple. forcemoving and regrabbing is ugly and breaks conga lines. + if(pulling.z != z) + target_turf = get_step(pulling, get_dir(pulling, old_loc)) + + + if(target_turf != old_loc || (moving_diagonally != SECOND_DIAG_STEP && ISDIAGONALDIR(pull_dir)) || get_dist(src, pulling) > 1) + pulling.move_from_pull(G.assailant, get_step(pulling, get_dir(pulling, target_turf)), glide_size) + if(QDELETED(G)) + continue + + if(!pulling.MultiZAdjacent(src)) + qdel(G) + continue + + if(!QDELETED(G)) + G.current_grab.moved_effect(G) + if(G.current_grab.downgrade_on_move) + G.downgrade() + + if(moving_diagonally != FIRST_DIAG_STEP) + pulling.update_offsets() + + var/list/my_grabs = get_active_grabs() + for(var/obj/item/hand_item/grab/G in my_grabs) + if(G.current_grab.reverse_facing || HAS_TRAIT(G.affecting, TRAIT_KEEP_DIRECTION_WHILE_PULLING)) + if(!direction) + direction = get_dir(src, G.affecting) + if(!direction) + continue + setDir(global.reverse_dir[direction]) + + diff --git a/code/modules/grab/grab_movable.dm b/code/modules/grab/grab_movable.dm new file mode 100644 index 000000000000..ab2afce93af6 --- /dev/null +++ b/code/modules/grab/grab_movable.dm @@ -0,0 +1,100 @@ +/atom/movable/proc/can_be_grabbed(mob/living/grabber, target_zone, use_offhand) + if(!istype(grabber) || !isturf(loc) || !isturf(grabber.loc)) + return FALSE + if(SEND_SIGNAL(src, COMSIG_ATOM_CAN_BE_GRABBED, grabber) & COMSIG_ATOM_NO_GRAB) + return FALSE + if(!grabber.canUseTopic(src, USE_CLOSE|USE_IGNORE_TK)) + return FALSE + if(!buckled_grab_check(grabber)) + return FALSE + if(anchored) + to_chat(grabber, span_warning("\The [src] won't budge!")) + return FALSE + if(throwing) + return FALSE + if(pull_force < (move_resist * MOVE_FORCE_PULL_RATIO)) + to_chat(grabber, span_warning("You aren't strong enough to move [src]!")) + return FALSE + return TRUE + +/atom/movable/proc/buckled_grab_check(mob/grabber) + if(grabber.buckled == src && (grabber in buckled_mobs)) + return TRUE + if(grabber.anchored) + return FALSE + if(grabber.buckled) + return FALSE + return TRUE + +/** + * Checks if the pulling and pulledby should be stopped because they're out of reach. + * If z_allowed is TRUE, the z level of the pulling will be ignored.This is to allow things to be dragged up and down stairs. + */ +/atom/movable/proc/recheck_grabs(only_pulling = FALSE, only_pulled = FALSE, z_allowed = FALSE) + if(only_pulling) + return + + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(moving_diagonally != FIRST_DIAG_STEP && !MultiZAdjacent(G.assailant)) //separated from our puller and not in the middle of a diagonal move. + qdel(G) + +/// Move grabbed atoms towards a destination +/mob/living/proc/move_grabbed_atoms_towards(atom/destination) + for(var/obj/item/hand_item/grab/G in get_active_grabs()) + var/atom/movable/pulling = G.affecting + if(pulling.anchored || pulling.move_resist > move_force || !pulling.Adjacent(src, src, pulling)) + qdel(G) + continue + + if(isliving(pulling)) + var/mob/living/pulling_mob = pulling + if(pulling_mob.buckled && pulling_mob.buckled.buckle_prevents_pull) //if they're buckled to something that disallows pulling, prevent it + qdel(G) + continue + + if(destination == loc && pulling.density) + continue + + var/move_dir = get_dir(pulling.loc, destination) + if(!Process_Spacemove(move_dir)) + continue + + // At this point the move was successful + pulling.Move(get_step(pulling.loc, move_dir), move_dir, glide_size) + + pulling.update_offsets() + + +/atom/movable/proc/update_offsets() + var/last_pixel_x = pixel_x + var/last_pixel_y = pixel_y + + var/new_pixel_x = base_pixel_x + var/new_pixel_y = base_pixel_y + + var/list/grabbed_by = list() + + grabbed_by += src.grabbed_by + if(isliving(src)) + var/mob/living/L = src + if(L.buckled) + grabbed_by += L.buckled.grabbed_by + + if(isturf(loc)) + if(length(grabbed_by)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + var/grab_dir = get_dir(G.assailant, src) + if(grab_dir && G.current_grab.shift != 0) + if(grab_dir & WEST) + new_pixel_x = min(new_pixel_x+G.current_grab.shift, base_pixel_x+G.current_grab.shift) + else if(grab_dir & EAST) + new_pixel_x = max(new_pixel_x-G.current_grab.shift, base_pixel_x-G.current_grab.shift) + if(grab_dir & NORTH) + new_pixel_y = max(new_pixel_y-G.current_grab.shift, base_pixel_y-G.current_grab.shift) + else if(grab_dir & SOUTH) + new_pixel_y = min(new_pixel_y+G.current_grab.shift, base_pixel_y+G.current_grab.shift) + + if(last_pixel_x != new_pixel_x || last_pixel_y != new_pixel_y) + animate(src, pixel_x = new_pixel_x, pixel_y = new_pixel_y, 3, 1, (LINEAR_EASING|EASE_IN)) + + UPDATE_OO_IF_PRESENT diff --git a/code/modules/grab/grab_object.dm b/code/modules/grab/grab_object.dm new file mode 100644 index 000000000000..5cb1e3b6e64d --- /dev/null +++ b/code/modules/grab/grab_object.dm @@ -0,0 +1,360 @@ +/obj/item/hand_item/grab + name = "grab" + item_flags = DROPDEL | ABSTRACT | HAND_ITEM | NOBLUDGEON + + /// The initiator of the grab + var/mob/living/assailant = null + /// The thing being grabbed + var/atom/movable/affecting = null + + /// The grab datum currently being used + var/datum/grab/current_grab + + /// Cooldown for actions + COOLDOWN_DECLARE(action_cd) + /// Cooldown for upgrade times + COOLDOWN_DECLARE(upgrade_cd) + /// Indicates if the current grab has special interactions applied to the target organ (eyes and mouth at time of writing) + var/special_target_functional = TRUE + /// Used to avoid stacking interactions that sleep during /decl/grab/proc/on_hit_foo() (ie. do_after() is used) + var/is_currently_resolving_hit = FALSE + /// Records a specific bodypart that was targetted by this grab. + var/target_zone + /// Used by struggle grab datum to keep track of state. + var/done_struggle = FALSE + +/obj/item/hand_item/grab/Initialize(mapload, atom/movable/target, datum/grab/grab_type, use_offhand) + . = ..() + current_grab = GLOB.all_grabstates[grab_type] + + assailant = loc + if(!istype(assailant)) + assailant = null + return INITIALIZE_HINT_QDEL + + affecting = target + if(!istype(assailant) || !assailant.add_grab(src, use_offhand = use_offhand)) + return INITIALIZE_HINT_QDEL + + target_zone = deprecise_zone(assailant.zone_selected) + + if(!current_grab.setup(src)) + return INITIALIZE_HINT_QDEL + + /// Apply any needed updates to the assailant + assailant.update_pull_hud_icon() + LAZYADD(affecting.grabbed_by, src) // This is how we handle affecting being deleted. + + /// Do flavor things like pixel offsets, animation, sound + adjust_position() + assailant.animate_interact(affecting, INTERACT_GRAB) + + var/sound = 'sound/weapons/thudswoosh.ogg' + if(iscarbon(assailant)) + var/mob/living/carbon/C = assailant + if(C.dna.species.grab_sound) + sound = C.dna.species.grab_sound + + playsound(affecting.loc, sound, 50, 1, -1) + + /// Spread diseases + if(isliving(affecting)) + var/mob/living/affecting_mob = affecting + for(var/datum/disease/D as anything in assailant.diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + affecting_mob.ContactContractDisease(D) + + for(var/datum/disease/D as anything in affecting_mob.diseases) + if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) + assailant.ContactContractDisease(D) + + /// Setup the effects applied by grab + current_grab.update_stage_effects(src, null) + current_grab.special_bodyzone_effects(src) + + var/mob/living/L = get_affecting_mob() + if(L && assailant.combat_mode) + upgrade(TRUE) + + /// Update appearance + update_appearance(UPDATE_ICON_STATE) + + /// Setup signals + var/obj/item/bodypart/BP = get_targeted_bodypart() + if(BP) + name = "[initial(name)] ([BP.plaintext_zone])" + RegisterSignal(affecting, COMSIG_CARBON_REMOVED_LIMB, PROC_REF(on_limb_loss)) + + RegisterSignal(assailant, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) + if(affecting != assailant) + RegisterSignal(affecting, COMSIG_PARENT_QDELETING, PROC_REF(target_or_owner_del)) + RegisterSignal(affecting, COMSIG_MOVABLE_PRE_THROW, PROC_REF(target_thrown)) + RegisterSignal(affecting, COMSIG_ATOM_ATTACK_HAND, PROC_REF(intercept_attack_hand)) + + RegisterSignal(assailant, COMSIG_MOB_SELECTED_ZONE_SET, PROC_REF(on_target_change)) + +/obj/item/hand_item/grab/Destroy() + if(affecting) + LAZYREMOVE(affecting.grabbed_by, src) + affecting.update_offsets() + if(affecting && assailant && current_grab) + current_grab.let_go(src) + if(assailant) + assailant.after_grab_release(affecting) + affecting = null + assailant = null + return ..() + +/obj/item/hand_item/grab/examine(mob/user) + . = ..() + var/mob/living/L = get_affecting_mob() + var/obj/item/bodypart/BP = get_targeted_bodypart() + if(L && BP) + to_chat(user, "A grab on \the [L]'s [BP.plaintext_zone].") + +/obj/item/hand_item/grab/update_icon_state() + . = ..() + icon = current_grab.icon + if(current_grab.icon_state) + icon_state = current_grab.icon_state + +/obj/item/hand_item/grab/attack_self(mob/user) + if (!assailant) + return + + if(assailant.combat_mode) + upgrade() + else + downgrade() + + +/obj/item/hand_item/grab/melee_attack_chain(mob/user, atom/target, params) + if (QDELETED(src) || !assailant || !current_grab) + return + + if(target.attack_grab(assailant, affecting, src, params2list(params))) + return + + current_grab.hit_with_grab(src, target, params2list(params)) + +/* + This section is for newly defined useful procs. +*/ + +/obj/item/hand_item/grab/proc/on_target_change(datum/source, new_sel) + SIGNAL_HANDLER + + if(src != assailant.get_active_held_item()) + return // Note that because of this condition, there's no guarantee that target_zone = old_sel + + new_sel = deprecise_zone(new_sel) + if(target_zone == new_sel) + return + + var/obj/item/bodypart/BP + + if(affecting == assailant && iscarbon(assailant)) + BP = assailant.get_bodypart(new_sel) + var/using_slot = assailant.get_held_index_of_item(src) + if(assailant.has_hand_for_held_index(using_slot) == BP) + to_chat(assailant, span_warning("You can't grab your own [BP.plaintext_zone] with itself!")) + return + + var/old_zone = target_zone + target_zone = new_sel + BP = get_targeted_bodypart() + + if (!BP) + to_chat(assailant, span_warning("You fail to grab \the [affecting] there as they do not have that bodypart!")) + return + + name = "[initial(name)] ([BP.plaintext_zone])" + to_chat(assailant, span_notice("You are now holding \the [affecting] by \the [BP.plaintext_zone].")) + + if(!isbodypart(get_targeted_bodypart())) + current_grab.let_go(src) + return + + current_grab.on_target_change(src, old_zone, target_zone) + +/obj/item/hand_item/grab/proc/on_limb_loss(mob/victim, obj/item/bodypart/lost) + SIGNAL_HANDLER + + if(affecting != victim) + stack_trace("A grab switched affecting targets without properly re-registering for dismemberment updates.") + return + var/obj/item/bodypart/BP = get_targeted_bodypart() + if(!istype(BP)) + current_grab.let_go(src) + return // Sanity check in case the lost organ was improperly removed elsewhere in the code. + if(lost != BP) + return + current_grab.let_go(src) + +/// Intercepts attack_hand() calls on our target. +/obj/item/hand_item/grab/proc/intercept_attack_hand(atom/movable/source, user, list/modifiers) + SIGNAL_HANDLER + if(user != assailant) + return + + if(current_grab.resolve_openhand_attack(src)) + return COMPONENT_CANCEL_ATTACK_CHAIN + +// Returns the bodypart of the grabbed person that the grabber is targeting +/obj/item/hand_item/grab/proc/get_targeted_bodypart() + RETURN_TYPE(/obj/item/bodypart) + var/mob/living/L = get_affecting_mob() + return (L?.get_bodypart(deprecise_zone(target_zone))) + +/obj/item/hand_item/grab/proc/resolve_item_attack(mob/living/M, obj/item/I, target_zone) + if((M && ishuman(M)) && I) + return current_grab.resolve_item_attack(src, M, I, target_zone) + else + return 0 + +/obj/item/hand_item/grab/proc/action_used() + COOLDOWN_START(src, action_cd, current_grab.action_cooldown) + leave_forensic_traces() + +/obj/item/hand_item/grab/proc/leave_forensic_traces() + if (!affecting) + return + var/mob/living/carbon/carbo = get_affecting_mob() + if(istype(carbo)) + var/obj/item/clothing/C = carbo.get_item_covering_zone(target_zone) + if(istype(C)) + C.add_fingerprint(assailant) + return + + affecting.add_fingerprint(assailant) //If no clothing; add fingerprint to mob proper. + +/obj/item/hand_item/grab/proc/upgrade(bypass_cooldown, silent) + if(!COOLDOWN_FINISHED(src, upgrade_cd) && !bypass_cooldown) + if(!silent) + to_chat(assailant, span_warning("You must wait [round(COOLDOWN_TIMELEFT(src, upgrade_cd) * 0.1, 0.1)] seconds to upgrade.")) + return + + var/datum/grab/upgrab = current_grab.upgrade(src) + if(!upgrab) + return + + if(is_grab_unique(current_grab)) + current_grab.remove_unique_grab_effects(src) + + current_grab = upgrab + + COOLDOWN_START(src, upgrade_cd, current_grab.upgrade_cooldown) + + adjust_position() + update_appearance() + leave_forensic_traces() + + if(!current_grab.enter_as_up(src, silent)) + return + if(is_grab_unique(current_grab)) + current_grab.apply_unique_grab_effects(src) + +/obj/item/hand_item/grab/proc/downgrade(silent) + var/datum/grab/downgrab = current_grab.downgrade(src) + if(!downgrab) + return + if(is_grab_unique(current_grab)) + current_grab.remove_unique_grab_effects(src) + + current_grab = downgrab + + if(!current_grab.enter_as_down(src)) + return + + if(is_grab_unique(current_grab)) + current_grab.apply_unique_grab_effects(src) + + current_grab.enter_as_down(src, silent) + adjust_position() + update_appearance() + +/// Used to prevent repeated effect application or early effect removal +/obj/item/hand_item/grab/proc/is_grab_unique(datum/grab/grab_datum) + var/count = 0 + for(var/obj/item/hand_item/grab/other as anything in affecting.grabbed_by) + if(other.current_grab == grab_datum) + count++ + + if(count >= 2) + return FALSE + return TRUE + +/obj/item/hand_item/grab/proc/draw_affecting_over() + affecting.plane = assailant.plane + affecting.layer = assailant.layer + 0.01 + +/obj/item/hand_item/grab/proc/draw_affecting_under() + affecting.plane = assailant.plane + affecting.layer = assailant.layer - 0.01 + +/obj/item/hand_item/grab/proc/handle_resist() + current_grab.handle_resist(src) + +/obj/item/hand_item/grab/proc/has_hold_on_bodypart(obj/item/bodypart/BP) + if (!BP) + return FALSE + + if (get_targeted_bodypart() == BP) + return TRUE + + return FALSE + +/obj/item/hand_item/grab/proc/get_affecting_mob() + RETURN_TYPE(/mob/living) + if(isobj(affecting)) + if(length(affecting.buckled_mobs)) + return affecting.buckled_mobs?[1] + return + + if(isliving(affecting)) + return affecting + +/// Primarily used for do_after() callbacks, checks if the grab item is still holding onto something +/obj/item/hand_item/grab/proc/is_grabbing(atom/movable/AM) + return affecting == AM +/* + * This section is for component signal relays/hooks +*/ + +/// Target deleted, ABORT +/obj/item/hand_item/grab/proc/target_or_owner_del(datum/source) + SIGNAL_HANDLER + qdel(src) + +/// If something tries to throw the target. +/obj/item/hand_item/grab/proc/target_thrown(atom/movable/source, list/arguments) + SIGNAL_HANDLER + + if(!current_grab.stop_move) + return + if(arguments[4] == assailant && current_grab.can_throw) + return + + return COMPONENT_CANCEL_THROW + +/obj/item/hand_item/grab/attackby(obj/W, mob/user) + if(user == assailant) + current_grab.item_attack(src, W) + +/obj/item/hand_item/grab/proc/resolve_openhand_attack() + return current_grab.resolve_openhand_attack(src) + +/obj/item/hand_item/grab/proc/adjust_position() + if(QDELETED(assailant) || QDELETED(affecting) || !assailant.Adjacent(affecting)) + qdel(src) + return FALSE + + if(assailant) + assailant.setDir(get_dir(assailant, affecting)) + + if(current_grab.same_tile) + affecting.move_from_pull(assailant, get_turf(assailant)) + affecting.setDir(assailant.dir) + + affecting.update_offsets() + affecting.reset_plane_and_layer() diff --git a/code/modules/grab/grab_silicon.dm b/code/modules/grab/grab_silicon.dm new file mode 100644 index 000000000000..f13f7d448607 --- /dev/null +++ b/code/modules/grab/grab_silicon.dm @@ -0,0 +1,2 @@ +/mob/living/silcion/try_make_grab(atom/movable/target, grab_type = /datum/grab/simple) + . = ..() diff --git a/code/modules/grab/grabs/grab_aggressive.dm b/code/modules/grab/grabs/grab_aggressive.dm new file mode 100644 index 000000000000..6ef2a8f3dc39 --- /dev/null +++ b/code/modules/grab/grabs/grab_aggressive.dm @@ -0,0 +1,72 @@ +/datum/grab/normal/aggressive + upgrab = /datum/grab/normal/neck + downgrab = /datum/grab/normal/passive + + grab_slowdown = 0.7 + shift = 12 + stop_move = TRUE + reverse_facing = FALSE + point_blank_mult = 1.5 + damage_stage = GRAB_AGGRESSIVE + same_tile = FALSE + can_throw = TRUE + enable_violent_interactions = TRUE + breakability = 3 + icon_state = "reinforce1" + + break_chance_table = list(5, 20, 40, 80, 100) + +/datum/grab/normal/aggressive/apply_unique_grab_effects(obj/item/hand_item/grab/G) + . = ..() + if(!isliving(G.affecting)) + return + + var/mob/living/L = G.affecting + RegisterSignal(G.affecting, COMSIG_LIVING_SET_BODY_POSITION, PROC_REF(target_bodyposition_change)) + + if(L.body_position == LYING_DOWN) + ADD_TRAIT(L, TRAIT_FLOORED, AGGRESSIVE_GRAB) + +/datum/grab/normal/aggressive/remove_unique_grab_effects(obj/item/hand_item/grab/G) + . = ..() + UnregisterSignal(G.affecting, COMSIG_LIVING_SET_BODY_POSITION) + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, AGGRESSIVE_GRAB) + +/datum/grab/normal/aggressive/proc/target_bodyposition_change(mob/living/source) + if(source.body_position == LYING_DOWN) + ADD_TRAIT(source, TRAIT_FLOORED, AGGRESSIVE_GRAB) + +/datum/grab/normal/aggressive/can_upgrade(obj/item/hand_item/grab/G) + . = ..() + if(.) + if(!ishuman(G.affecting)) + to_chat(G.assailant, span_warning("You can only upgrade an aggressive grab when grappling a human!")) + return FALSE + if(!(G.target_zone in list(BODY_ZONE_CHEST, BODY_ZONE_HEAD))) + to_chat(G.assailant, span_warning("You need to be grabbing their torso or head for this!")) + return FALSE + + var/mob/living/carbon/human/affecting_mob = G.get_affecting_mob() + if(istype(affecting_mob)) + var/obj/item/clothing/C = affecting_mob.head + if(istype(C)) //hardsuit helmets etc + if((C.clothing_flags & STOPSPRESSUREDAMAGE) && C.returnArmor().getRating(MELEE) > 20) + to_chat(G.assailant, span_warning("\The [C] is in the way!")) + return FALSE + +/datum/grab/normal/aggressive/enter_as_up(obj/item/hand_item/grab/G, silent) + . = ..() + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] tightens their grip on [G.affecting] (now hands)!"), + blind_message = span_hear("You hear aggressive shuffling."), + vision_distance = COMBAT_MESSAGE_RANGE, + ) + +/datum/grab/normal/aggressive/enter_as_down(obj/item/hand_item/grab/G, silent) + . = ..() + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] loosens their grip on [G.affecting], allowing them to breathe!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ) diff --git a/code/modules/grab/grabs/grab_neck.dm b/code/modules/grab/grabs/grab_neck.dm new file mode 100644 index 000000000000..8f1597cb4c39 --- /dev/null +++ b/code/modules/grab/grabs/grab_neck.dm @@ -0,0 +1,47 @@ +/datum/grab/normal/neck + upgrab = /datum/grab/normal/kill + downgrab = /datum/grab/normal/aggressive + + grab_slowdown = 4 + + drop_headbutt = FALSE + shift = -10 + stop_move = TRUE + reverse_facing = TRUE + point_blank_mult = 2 + damage_stage = GRAB_NECK + same_tile = TRUE + can_throw = TRUE + enable_violent_interactions = TRUE + restrains = TRUE + icon_state = "kill" + break_chance_table = list(3, 18, 45, 100) + +/datum/grab/normal/neck/apply_unique_grab_effects(obj/item/hand_item/grab/G) + . = ..() + if(!isliving(G.affecting)) + return + + var/mob/living/L = G.affecting + ADD_TRAIT(L, TRAIT_FLOORED, NECK_GRAB) + +/datum/grab/normal/neck/remove_unique_grab_effects(obj/item/hand_item/grab/G) + . = ..() + REMOVE_TRAIT(G.affecting, TRAIT_FLOORED, NECK_GRAB) + +/datum/grab/normal/neck/enter_as_up(obj/item/hand_item/grab/G, silent) + . = ..() + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] places their arm around [G.affecting]'s neck!"), + blind_message = span_hear("You hear aggressive shuffling."), + vision_distance = COMBAT_MESSAGE_RANGE + ) + +/datum/grab/normal/neck/enter_as_down(obj/item/hand_item/grab/G, silent) + . = ..() + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] stops strangling [G.affecting]."), + vision_distance = COMBAT_MESSAGE_RANGE + ) diff --git a/code/modules/grab/grabs/grab_normal.dm b/code/modules/grab/grabs/grab_normal.dm new file mode 100644 index 000000000000..2f9ebd222b3e --- /dev/null +++ b/code/modules/grab/grabs/grab_normal.dm @@ -0,0 +1,341 @@ +/datum/grab/normal + abstract_type = /datum/grab/normal + + help_action = "inspect" + disarm_action = "pin" + grab_action = "jointlock" + harm_action = "dislocate" + + var/drop_headbutt = 1 + +/datum/grab/normal/setup(obj/item/hand_item/grab/G) + if(!(. = ..())) + return + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP) + return + + if(G.affecting != G.assailant) + G.assailant.visible_message(span_warning("[G.assailant] has grabbed [G.affecting]'s [BP.plaintext_zone]!")) + else + G.assailant.visible_message(span_notice("[G.assailant] has grabbed [G.assailant.p_their()] [BP.plaintext_zone]!")) + +/datum/grab/normal/on_hit_help(obj/item/hand_item/grab/G, atom/A) + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP || (A && A != G.get_affecting_mob())) + return FALSE + return BP.inspect(G.assailant) + +/datum/grab/normal/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) + + var/mob/living/affecting = G.get_affecting_mob() + var/mob/living/assailant = G.assailant + if(affecting && A && A == affecting) + if(affecting.body_position == LYING_DOWN) + to_chat(assailant, span_warning("They're already on the ground.")) + return FALSE + + affecting.visible_message(span_danger("\The [assailant] is trying to pin \the [affecting] to the ground!")) + + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC, display = image('icons/hud/do_after.dmi', "harm"))) + G.action_used() + affecting.visible_message(span_danger("\The [assailant] pins \the [affecting] to the ground!")) + affecting.Knockdown(1 SECOND) // This can only be performed with an aggressive grab, which ensures that once someone is knocked down, they stay down/ + return TRUE + + affecting.visible_message(span_warning("\The [assailant] fails to pin \the [affecting] to the ground.")) + return FALSE + +/datum/grab/normal/on_hit_grab(obj/item/hand_item/grab/G, atom/A) + var/mob/living/affecting = G.get_affecting_mob() + if(!affecting || (A && A != affecting)) + return FALSE + + var/mob/living/assailant = G.assailant + if(!assailant) + return FALSE + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP) + to_chat(assailant, span_warning("\The [affecting] is missing that body part!")) + return FALSE + + assailant.visible_message(span_danger("\The [assailant] begins to [pick("bend", "twist")] \the [affecting]'s [BP.plaintext_zone] into a jointlock!")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC, display = image('icons/hud/do_after.dmi', "harm"))) + G.action_used() + BP.jointlock(assailant) + assailant.visible_message(span_danger("\The [affecting]'s [BP.plaintext_zone] is twisted!")) + playsound(assailant.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + return TRUE + + affecting.visible_message(span_warning("\The [assailant] fails to jointlock \the [affecting]'s [BP.plaintext_zone].")) + return FALSE + +/datum/grab/normal/on_hit_harm(obj/item/hand_item/grab/G, atom/A) + var/mob/living/carbon/affecting = G.get_affecting_mob() + if(!istype(affecting) || (A && A != affecting)) + return FALSE + + var/mob/living/assailant = G.assailant + if(!assailant) + return FALSE + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP) + to_chat(assailant, span_warning("\The [affecting] is missing that body part!")) + return FALSE + + if(BP.bodypart_flags & BP_DISLOCATED) + assailant.visible_message(span_notice("\The [assailant] begins to place [affecting]'s [BP.joint_name] back in it's socket.")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC)) + G.action_used() + BP.set_dislocated(FALSE) + assailant.visible_message(span_warning("\The [affecting]'s [BP.joint_name] pops back into place!")) + affecting.pain_message("AAAHHHHAAGGHHHH", 50, TRUE) + return TRUE + + if(BP.can_be_dislocated()) + assailant.visible_message(span_danger("\The [assailant] begins to dislocate \the [affecting]'s [BP.joint_name]!")) + if(do_after(assailant, affecting, action_cooldown - 1, DO_PUBLIC)) + G.action_used() + BP.set_dislocated(TRUE) + assailant.visible_message(span_danger("\The [affecting]'s [BP.joint_name] [pick("gives way","caves in","collapses")]!")) + playsound(assailant.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + return TRUE + + affecting.visible_message(span_warning("\The [assailant] fails to dislocate \the [affecting]'s [BP.joint_name].")) + return FALSE + + if(BP.can_be_dislocated()) + to_chat(assailant, span_warning("\The [affecting]'s [BP.joint_name] is already dislocated!")) + else + to_chat(assailant, span_warning("You can't dislocate \the [affecting]'s [BP.joint_name]!")) + return FALSE + +/datum/grab/normal/resolve_openhand_attack(obj/item/hand_item/grab/G) + if(!G.assailant.combat_mode) + return FALSE + if(G.target_zone == BODY_ZONE_HEAD) + if(G.assailant.zone_selected == BODY_ZONE_PRECISE_EYES) + if(attack_eye(G)) + return TRUE + else + if(headbutt(G)) + if(drop_headbutt) + let_go() + return TRUE + return FALSE + +/datum/grab/normal/proc/attack_eye(obj/item/hand_item/grab/G) + var/mob/living/carbon/human/target = G.get_affecting_mob() + var/mob/living/carbon/human/attacker = G.assailant + if(!istype(target) || !istype(attacker)) + return + + if(target.is_eyes_covered()) + to_chat(attacker, "You're going to need to remove the eye covering first.") + return + + var/obj/item/organ/eyes/E = target.getorganslot(ORGAN_SLOT_EYES) + if(E) + to_chat(attacker, "You cannot locate any eyes on [target]!") + return + + log_combat(attacker, target, "attacked the eyes of (grab)") + + if(E) + E.applyOrganDamage(rand(3,4)) + attacker.visible_message(span_danger("\The [attacker] jams [G.p_their()] fingers into \the [target]'s [E.name]!")) + if(!(E.organ_flags & ORGAN_SYNTHETIC)) + to_chat(target, span_danger("You experience immense pain as fingers are jammed into your [E.name]!")) + else + to_chat(target, span_danger("You experience fingers being jammed into your [E.name].")) + else + attacker.visible_message(span_danger("\The [attacker] attempts to press [G.p_their()] fingers into \the [target]'s [E.name], but [target.p_they()] doesn't have any!")) + return TRUE + +/datum/grab/normal/proc/headbutt(obj/item/hand_item/grab/G) + var/mob/living/carbon/human/target = G.get_affecting_mob() + var/mob/living/carbon/human/attacker = G.assailant + if(!istype(target) || !istype(attacker)) + return + + if(target.body_position == LYING_DOWN) + return + + var/damage = 20 + var/obj/item/clothing/hat = attacker.head + var/sharpness + if(istype(hat)) + damage += hat.force * 3 + sharpness = hat.sharpness + + if(sharpness & SHARP_POINTY) + if(istype(hat)) + attacker.visible_message(span_danger("\The [attacker] gores \the [target] with \the [hat]!")) + else + attacker.visible_message(span_danger("\The [attacker] gores \the [target]!")) + else + attacker.visible_message(span_danger("\The [attacker] thrusts [attacker.p_their()] head into \the [target]'s skull!")) + + var/armor = target.run_armor_check(BODY_ZONE_HEAD, MELEE) + target.apply_damage(damage, BRUTE, BODY_ZONE_HEAD, armor, sharpness = sharpness) + attacker.apply_damage(10, BRUTE, BODY_ZONE_HEAD) + + if(armor < 0.5 && target.can_head_trauma_ko() && prob(damage)) + target.Unconscious(20 SECONDS) + target.visible_message(span_danger("\The [target] collapses, now fast asleep.")) + + playsound(attacker.loc, "swing_hit", 25, 1, -1) + log_combat(attacker, target, "headbutted") + return 1 + +// Handles special targeting like eyes and mouth being covered. +/datum/grab/normal/special_bodyzone_effects(obj/item/hand_item/grab/G) + . = ..() + var/mob/living/affecting_mob = G.get_affecting_mob() + if(istype(affecting_mob) && G.special_target_functional) + switch(G.target_zone) + if(BODY_ZONE_PRECISE_MOUTH) + ADD_TRAIT(affecting_mob, TRAIT_MUTE, REF(G)) + if(BODY_ZONE_PRECISE_EYES) + ADD_TRAIT(affecting_mob, TRAIT_BLIND, REF(G)) + +/datum/grab/normal/remove_bodyzone_effects(obj/item/hand_item/grab/G) + . = ..() + REMOVE_TRAIT(G.affecting, TRAIT_MUTE, REF(G)) + REMOVE_TRAIT(G.affecting, TRAIT_BLIND, REF(G)) + +// Handles when they change targeted areas and something is supposed to happen. +/datum/grab/normal/special_bodyzone_change(obj/item/hand_item/grab/G, old_zone, new_zone) + old_zone = parse_zone(old_zone) + if((old_zone != BODY_ZONE_HEAD && old_zone != BODY_ZONE_CHEST) || !G.get_affecting_mob()) + return + switch(new_zone) + if(BODY_ZONE_PRECISE_MOUTH) + G.assailant.visible_message("\The [G.assailant] covers [G.affecting]'s mouth!") + if(BODY_ZONE_PRECISE_EYES) + G.assailant.visible_message("\The [G.assailant] covers [G.affecting]'s eyes!") + +/datum/grab/normal/check_special_target(obj/item/hand_item/grab/G) + var/mob/living/affecting_mob = G.get_affecting_mob() + if(!istype(affecting_mob)) + return FALSE + switch(G.target_zone) + if(BODY_ZONE_PRECISE_MOUTH) + if(!affecting_mob.has_mouth()) + to_chat(G.assailant, "You cannot locate a mouth on [G.affecting]!") + return FALSE + if(BODY_ZONE_PRECISE_EYES) + if(!affecting_mob.getorganslot(ORGAN_SLOT_EYES)) + to_chat(G.assailant, "You cannot locate any eyes on [G.affecting]!") + return FALSE + return TRUE + +/datum/grab/normal/resolve_item_attack(obj/item/hand_item/grab/G, mob/living/carbon/human/user, obj/item/I) + switch(G.target_zone) + if(BODY_ZONE_HEAD) + return attack_throat(G, I, user) + else + return attack_tendons(G, I, user, G.target_zone) + +/datum/grab/normal/proc/attack_throat(obj/item/hand_item/grab/G, obj/item/W, mob/living/user) + var/mob/living/carbon/affecting = G.get_affecting_mob() + if(!istype(affecting)) + return + if(!user.combat_mode) + return FALSE // Not trying to hurt them. + + if(!(W.sharpness & SHARP_EDGED) || !W.force || W.damtype != BRUTE) + return FALSE //unsuitable weapon + + if(DOING_INTERACTION(user, "throat slit")) + return FALSE + + user.visible_message("\The [user] begins to slit [affecting]'s throat with \the [W]!") + + user.changeNext_move(CLICK_CD_MELEE) + if(!do_after(user, affecting, 2 SECONDS, DO_PUBLIC, extra_checks = CALLBACK(G, TYPE_PROC_REF(/obj/item/hand_item/grab, is_grabbing), affecting), interaction_key = "throat slit", display = W)) + return FALSE + + if(!(G && G.affecting == affecting)) //check that we still have a grab + return FALSE + + var/armor + + //presumably, if they are wearing a helmet that stops pressure effects, then it probably covers the throat as well + for(var/obj/item/clothing/equipped in affecting.get_equipped_items()) + if((equipped.body_parts_covered & HEAD) && (equipped.clothing_flags & STOPSPRESSUREDAMAGE)) + armor = affecting.run_armor_check(BODY_ZONE_HEAD, MELEE, silent = TRUE) + break + + var/total_damage = 0 + for(var/i in 1 to 3) + var/damage = min(W.force*1.5, 20) + affecting.apply_damage(damage, BRUTE, BODY_ZONE_HEAD, armor, sharpness = W.sharpness) + total_damage += damage + + if(total_damage) + user.visible_message("\The [user] slit [affecting]'s throat open with \the [W]!") + affecting.apply_status_effect(/datum/status_effect/neck_slice) + if(W.hitsound) + playsound(affecting.loc, W.hitsound, 50, 1, -1) + + COOLDOWN_START(G, action_cd, action_cooldown) + + log_combat(user, affecting, "slit throat (grab)") + return 1 + +/datum/grab/normal/proc/attack_tendons(obj/item/hand_item/grab/G, obj/item/W, mob/living/user, target_zone) + var/mob/living/affecting = G.get_affecting_mob() + if(!affecting) + return + if(!user.combat_mode) + return FALSE // Not trying to hurt them. + + if(!(W.sharpness & SHARP_EDGED) || !W.force || W.damtype != BRUTE) + return FALSE //unsuitable weapon + + var/obj/item/bodypart/BP = G.get_targeted_bodypart() + if(!BP || !(BP.check_tendon() == CHECKTENDON_OK)) + return FALSE + + if(DOING_INTERACTION(user, "slice tendon")) + return FALSE + + user.visible_message(span_danger("\The [user] begins to cut \the [affecting]'s [BP.tendon_name] with \the [W]!"), vision_distance = COMBAT_MESSAGE_RANGE) + user.changeNext_move(CLICK_CD_MELEE) + + if(!do_after(user, affecting, 2 SECONDS, DO_PUBLIC, extra_checks = CALLBACK(G, TYPE_PROC_REF(/obj/item/hand_item/grab, is_grabbing), affecting), interaction_key = "slice tendon", display = W)) + return FALSE + + if(!BP || !BP.set_sever_tendon(TRUE)) + return FALSE + + user.visible_message(span_danger("\The [user] cut \the [affecting]'s [BP.tendon_name] with \the [W]!")) + + if(W.hitsound) + playsound(affecting.loc, W.hitsound, 50, 1, -1) + + COOLDOWN_START(G, action_cd, action_cooldown) + + log_combat(user, affecting, "hamstrung (grab)") + return TRUE + +/datum/grab/normal/enter_as_down(obj/item/hand_item/grab/G, silent) + . = ..() + G.assailant.visible_message( + span_warning("[G.assailant] loosens their grip on [G.affecting]."), + ) + +/datum/grab/normal/add_context(list/context, obj/item/held_item, mob/living/user, atom/movable/target) + . = ..() + if(held_item?.sharpness & SHARP_EDGED) + var/obj/item/hand_item/grab/G = user.is_grabbing(target) + switch(G.target_zone) + if(BODY_ZONE_HEAD) + context[SCREENTIP_CONTEXT_LMB] = "Slice neck" + else + context[SCREENTIP_CONTEXT_LMB] = "Attack tendons" diff --git a/code/modules/grab/grabs/grab_passive.dm b/code/modules/grab/grabs/grab_passive.dm new file mode 100644 index 000000000000..09cca4c4e6b7 --- /dev/null +++ b/code/modules/grab/grabs/grab_passive.dm @@ -0,0 +1,24 @@ +/datum/grab/normal/passive + upgrab = /datum/grab/normal/struggle + shift = 8 + stop_move = 0 + reverse_facing = 0 + point_blank_mult = 1.1 + same_tile = 0 + icon_state = "!reinforce" + break_chance_table = list(15, 60, 100) + +/datum/grab/normal/passive/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to pin.")) + return FALSE + +/datum/grab/normal/passive/on_hit_grab(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to jointlock.")) + return FALSE + +/datum/grab/normal/passive/on_hit_harm(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to dislocate.")) + return FALSE + +/datum/grab/normal/passive/resolve_openhand_attack(obj/item/hand_item/grab/G) + return FALSE diff --git a/code/modules/grab/grabs/grab_simple.dm b/code/modules/grab/grabs/grab_simple.dm new file mode 100644 index 000000000000..147b95eabf0b --- /dev/null +++ b/code/modules/grab/grabs/grab_simple.dm @@ -0,0 +1,23 @@ +/datum/grab/simple + shift = 8 + stop_move = FALSE + reverse_facing = FALSE + point_blank_mult = 1 + same_tile = FALSE + icon_state = "!reinforce" + break_chance_table = list(15, 60, 100) + +/datum/grab/simple/upgrade(obj/item/hand_item/grab/G) + return + +/datum/grab/simple/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) + return FALSE + +/datum/grab/simple/on_hit_grab(obj/item/hand_item/grab/G, atom/A) + return FALSE + +/datum/grab/simple/on_hit_harm(obj/item/hand_item/grab/G, atom/A) + return FALSE + +/datum/grab/simple/resolve_openhand_attack(obj/item/hand_item/grab/G) + return FALSE diff --git a/code/modules/grab/grabs/grab_strangle.dm b/code/modules/grab/grabs/grab_strangle.dm new file mode 100644 index 000000000000..0de3afae24dd --- /dev/null +++ b/code/modules/grab/grabs/grab_strangle.dm @@ -0,0 +1,36 @@ +/datum/grab/normal/kill + downgrab = /datum/grab/normal/neck + grab_slowdown = 1.4 + + shift = 0 + stop_move = TRUE + reverse_facing = TRUE + point_blank_mult = 2 + damage_stage = GRAB_KILL + same_tile = TRUE + enable_violent_interactions = TRUE + restrains = TRUE + downgrade_on_action = TRUE + downgrade_on_move = TRUE + icon_state = "kill1" + break_chance_table = list(5, 20, 40, 80, 100) + +/datum/grab/normal/kill/apply_unique_grab_effects(obj/item/hand_item/grab/G) + . = ..() + if(!isliving(G.affecting)) + return + + ADD_TRAIT(G.affecting, TRAIT_KILL_GRAB, REF(G)) + +/datum/grab/normal/kill/remove_unique_grab_effects(obj/item/hand_item/grab/G) + . = ..() + REMOVE_TRAIT(G.affecting, TRAIT_KILL_GRAB, REF(G)) + +/datum/grab/normal/kill/enter_as_up(obj/item/hand_item/grab/G, silent) + . = ..() + if(!silent) + G.assailant.visible_message( + span_danger("[G.assailant] begins to strangle [G.affecting]!"), + blind_message = span_hear("You hear aggressive shuffling."), + vision_distance = COMBAT_MESSAGE_RANGE + ) diff --git a/code/modules/grab/grabs/grab_struggle.dm b/code/modules/grab/grabs/grab_struggle.dm new file mode 100644 index 000000000000..0123c3944955 --- /dev/null +++ b/code/modules/grab/grabs/grab_struggle.dm @@ -0,0 +1,74 @@ +/datum/grab/normal/struggle + upgrab = /datum/grab/normal/aggressive + downgrab = /datum/grab/normal/passive + shift = 8 + stop_move = TRUE + reverse_facing = FALSE + same_tile = FALSE + breakability = 3 + grab_slowdown = 0.7 + upgrade_cooldown = 2 SECONDS + can_downgrade_on_resist = 0 + icon_state = "reinforce" + break_chance_table = list(5, 20, 30, 80, 100) + +/datum/grab/normal/struggle/enter_as_up(obj/item/hand_item/grab/G, silent) + var/mob/living/affecting = G.get_affecting_mob() + var/mob/living/assailant = G.assailant + if(!affecting) + return TRUE + + if(affecting == assailant) + G.done_struggle = TRUE + G.upgrade(TRUE) + return FALSE + + if(!affecting.can_resist() || !affecting.combat_mode) + affecting.visible_message( + span_danger("\The [affecting] isn't prepared to fight back as [assailant] tightens [assailant.p_their()] grip!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ) + G.done_struggle = TRUE + G.upgrade(TRUE, FALSE) + return FALSE + + affecting.visible_message("[affecting] struggles against [assailant]!", vision_distance = COMBAT_MESSAGE_RANGE) + G.done_struggle = FALSE + addtimer(CALLBACK(G, TYPE_PROC_REF(/obj/item/hand_item/grab, handle_resist)), 1 SECOND) + resolve_struggle(G) + return ..() + +/datum/grab/normal/struggle/proc/resolve_struggle(obj/item/hand_item/grab/G) + set waitfor = FALSE + + var/datum/callback/user_incapacitated_callback = CALLBACK(src, PROC_REF(resolve_struggle_check), G) + if(do_after(G.assailant, G.affecting, upgrade_cooldown, DO_PUBLIC|IGNORE_USER_LOC_CHANGE|IGNORE_TARGET_LOC_CHANGE, extra_checks = user_incapacitated_callback)) + G.done_struggle = TRUE + G.upgrade(TRUE) + else + if(!QDELETED(G)) + G.downgrade() + +/// Callback for the above proc +/datum/grab/normal/struggle/proc/resolve_struggle_check(obj/item/hand_item/grab/G) + if(QDELETED(G)) + return FALSE + return TRUE + +/datum/grab/normal/struggle/can_upgrade(obj/item/hand_item/grab/G) + . = ..() && G.done_struggle + +/datum/grab/normal/struggle/on_hit_disarm(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to pin.")) + return FALSE + +/datum/grab/normal/struggle/on_hit_grab(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to jointlock.")) + return FALSE + +/datum/grab/normal/struggle/on_hit_harm(obj/item/hand_item/grab/G, atom/A) + to_chat(G.assailant, span_warning("Your grip isn't strong enough to dislocate.")) + return FALSE + +/datum/grab/normal/struggle/resolve_openhand_attack(obj/item/hand_item/grab/G) + return FALSE diff --git a/code/modules/grab/human_grab.dm b/code/modules/grab/human_grab.dm new file mode 100644 index 000000000000..aa852d5419e4 --- /dev/null +++ b/code/modules/grab/human_grab.dm @@ -0,0 +1,36 @@ +/mob/living/carbon/human/add_grab(obj/item/hand_item/grab/grab, use_offhand = FALSE) + if(use_offhand) + . = put_in_inactive_hand(grab) + else + . = put_in_active_hand(grab) + +/mob/living/carbon/human/can_be_grabbed(mob/living/grabber, target_zone, use_offhand) + . = ..() + if(!.) + return + + var/obj/item/bodypart/BP = get_bodypart(deprecise_zone(target_zone)) + if(!istype(BP)) + to_chat(grabber, span_warning("\The [src] is missing that body part!")) + return FALSE + + if(grabber == src) + var/using_slot = use_offhand ? get_inactive_hand() : get_active_hand() + if(!using_slot) + to_chat(src, span_warning("You cannot grab yourself without a usable hand!")) + return FALSE + + if(using_slot == BP) + to_chat(src, span_warning("You can't grab your own [BP.plaintext_zone] with itself!")) + return FALSE + /* + if(pull_damage()) + to_chat(grabber, span_warning("Pulling \the [src] in their current condition would probably be a bad idea.")) + */ + + var/obj/item/clothing/C = get_item_covering_zone(target_zone) + if(istype(C)) + C.add_fingerprint(grabber) + else + add_fingerprint(grabber) + diff --git a/code/modules/holodeck/items.dm b/code/modules/holodeck/items.dm index 202c7ea00139..80e4167689a1 100644 --- a/code/modules/holodeck/items.dm +++ b/code/modules/holodeck/items.dm @@ -93,21 +93,19 @@ if(user.transferItemToLoc(W, drop_location())) visible_message(span_warning("[user] dunks [W] into \the [src]!")) -/obj/structure/holohoop/attack_hand(mob/living/user, list/modifiers) +/obj/structure/holohoop/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) . = ..() - if(.) + if(!isliving(victim)) return - if(user.pulling && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(user.grab_state < GRAB_AGGRESSIVE) - to_chat(user, span_warning("You need a better grip to do that!")) - return - L.forceMove(loc) - L.Paralyze(100) - visible_message(span_danger("[user] dunks [L] into \the [src]!")) - user.stop_pulling() - else - ..() + var/mob/living/L = victim + if(grab.current_grab.damage_stage < 1) + to_chat(user, span_warning("You need a better grip to do that!")) + return TRUE + L.forceMove(loc) + L.Paralyze(100) + visible_message(span_danger("[user] dunks [L] into \the [src]!")) + user.release_grabs(L) + return TRUE /obj/structure/holohoop/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) if (isitem(AM) && !istype(AM,/obj/projectile)) diff --git a/code/modules/hydroponics/hydroponics.dm b/code/modules/hydroponics/hydroponics.dm index 89b7d1d293d4..cffffe2e88ba 100644 --- a/code/modules/hydroponics/hydroponics.dm +++ b/code/modules/hydroponics/hydroponics.dm @@ -1032,7 +1032,7 @@ if(user) user.examinate(src) -/obj/machinery/hydroponics/CtrlClick(mob/user) +/obj/machinery/hydroponics/CtrlClick(mob/user, list/params) . = ..() if(!user.canUseTopic(src, USE_CLOSE|USE_IGNORE_TK)) return @@ -1130,7 +1130,7 @@ qdel(src) return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN -/obj/machinery/hydroponics/soil/CtrlClick(mob/user) +/obj/machinery/hydroponics/soil/CtrlClick(mob/user, list/params) return //Soil has no electricity. diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 6ae574caed32..5d3081ebc631 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -436,8 +436,6 @@ GLOBAL_LIST_INIT(job_display_order, list( /// Returns an atom where the mob should spawn in. /datum/job/proc/get_roundstart_spawn_point() if(random_spawns_possible) - if(HAS_TRAIT(SSstation, STATION_TRAIT_RANDOM_ARRIVALS)) - return get_safe_random_station_turf(typesof(/area/station/hallway)) || get_latejoin_spawn_point() if(HAS_TRAIT(SSstation, STATION_TRAIT_HANGOVER)) var/obj/effect/landmark/start/hangover_spawn_point for(var/obj/effect/landmark/start/hangover/hangover_landmark in GLOB.start_landmarks_list) diff --git a/code/modules/mob/camera/camera.dm b/code/modules/mob/camera/camera.dm index 77bc82db77b9..4b7bbbff0d84 100644 --- a/code/modules/mob/camera/camera.dm +++ b/code/modules/mob/camera/camera.dm @@ -29,14 +29,14 @@ set name = "Move Upwards" set category = "IC" - if(zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(src, UP, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move upwards.")) /mob/camera/down() set name = "Move Down" set category = "IC" - if(zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(src, UP, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move down.")) /mob/camera/can_z_move(direction, turf/start, z_move_flags = NONE, mob/living/rider) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index ad68a81c6c42..a9218dd53829 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -242,7 +242,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return ghostize(FALSE) -/mob/dead/observer/Move(NewLoc, direct, glide_size_override = 32) +/mob/dead/observer/Move(NewLoc, direct, glide_size_override = 32, z_movement_flags) setDir(direct) if(glide_size_override) diff --git a/code/modules/mob/dead/observer/observer_movement.dm b/code/modules/mob/dead/observer/observer_movement.dm index a0338903bfac..138f33d0f2d7 100644 --- a/code/modules/mob/dead/observer/observer_movement.dm +++ b/code/modules/mob/dead/observer/observer_movement.dm @@ -2,7 +2,7 @@ set name = "Move Upwards" set category = "IC" - if(zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(src, UP, ZMOVE_FEEDBACK)) to_chat(src, "You move upwards.") /mob/dead/observer/can_z_move(direction, turf/start, z_move_flags = NONE, mob/living/rider) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 0987fd3f546b..29889b3f30cf 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -47,6 +47,12 @@ return has_hand_for_held_index(active_hand_index) +/// Returns the first available empty held index +/mob/proc/get_empty_held_index() + for(var/i in 1 to length(held_items)) + if(isnull(held_items[i])) + return i + //Finds the first available (null) index OR all available (null) indexes in held_items based on a side. //Lefts: 1, 3, 5, 7... //Rights:2, 4, 6, 8... @@ -162,11 +168,14 @@ held_items[hand_index] = I I.plane = ABOVE_HUD_PLANE I.equipped(src, ITEM_SLOT_HANDS) + if(QDELETED(I)) // this is here because some ABSTRACT items like slappers and circle hands could be moved from hand to hand then delete, which meant you'd have a null in your hand until you cleared it (say, by dropping it) held_items[hand_index] = null return FALSE - if(I.pulledby) - I.pulledby.stop_pulling() + + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() + update_held_items() I.pixel_x = I.base_pixel_x I.pixel_y = I.base_pixel_y diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm index eed38cba076a..3469d4d706c8 100644 --- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm +++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm @@ -39,10 +39,13 @@ GLOBAL_LIST_INIT(strippable_alien_humanoid_items, create_strippable_list(list( ..(I, cuff_break = INSTANT_CUFFBREAK) /mob/living/carbon/alien/humanoid/resist_grab(moving_resist) - if(pulledby.grab_state) - visible_message(span_danger("[src] breaks free of [pulledby]'s grip!"), \ - span_danger("You break free of [pulledby]'s grip!")) - pulledby.stop_pulling() + if(LAZYLEN(grabbed_by)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + visible_message( + span_danger("[src] breaks free of [G.assailant]'s grip!"), + span_danger("You break free of [G.assailant]'s grip!") + ) + free_from_all_grabs() . = 0 /mob/living/carbon/alien/humanoid/get_permeability_protection(list/target_zones) diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm index afe80cc9a4f6..0135607736b3 100644 --- a/code/modules/mob/living/carbon/alien/larva/larva.dm +++ b/code/modules/mob/living/carbon/alien/larva/larva.dm @@ -69,8 +69,8 @@ /mob/living/carbon/alien/larva/toggle_throw_mode() return -/mob/living/carbon/alien/larva/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) - return +/mob/living/carbon/alien/larva/can_grab(atom/movable/target, target_zone, use_offhand) + return FALSE /mob/living/carbon/alien/larva/canBeHandcuffed() return TRUE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 0a0ac287cafe..5f64d3354a02 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -151,45 +151,59 @@ var/atom/movable/thrown_thing var/obj/item/I = get_active_held_item() + if(isnull(I)) + return + var/neckgrab_throw = FALSE // we can't check for if it's a neckgrab throw when totaling up power_throw since we've already stopped pulling them by then, so get it early - if(!I) - if(pulling && isliving(pulling) && grab_state >= GRAB_AGGRESSIVE) - var/mob/living/throwable_mob = pulling + if(isgrab(I)) + var/obj/item/hand_item/grab/G = I + if(isitem(G.affecting)) + I = G.affecting + thrown_thing = I.on_thrown(src, target) + if(!thrown_thing) + return + release_grabs(I) + else + if(!G.current_grab.can_throw || !isliving(G.affecting)) + return + + var/mob/living/throwable_mob = G.affecting if(!throwable_mob.buckled) thrown_thing = throwable_mob - if(grab_state >= GRAB_NECK) + if(G.current_grab.damage_stage >= GRAB_NECK) neckgrab_throw = TRUE - stop_pulling() + release_grabs(throwable_mob) if(HAS_TRAIT(src, TRAIT_PACIFISM)) to_chat(src, span_notice("You gently let go of [throwable_mob].")) return else thrown_thing = I.on_thrown(src, target) - if(thrown_thing) - - if(isliving(thrown_thing)) - var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors - var/turf/end_T = get_turf(target) - if(start_T && end_T) - log_combat(src, thrown_thing, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") - var/power_throw = 0 - if(HAS_TRAIT(src, TRAIT_HULK)) - power_throw++ - if(HAS_TRAIT(src, TRAIT_DWARF)) - power_throw-- - if(HAS_TRAIT(thrown_thing, TRAIT_DWARF)) - power_throw++ - if(neckgrab_throw) - power_throw++ - do_attack_animation(target, no_effect = TRUE) //PARIAH EDIT ADDITION - AESTHETICS - playsound(loc, 'sound/weapons/punchmiss.ogg', 50, TRUE, -1) //PARIAH EDIT ADDITION - AESTHETICS - visible_message(span_danger("[src] throws [thrown_thing][power_throw ? " really hard!" : "."]"), \ - span_danger("You throw [thrown_thing][power_throw ? " really hard!" : "."]")) - log_message("has thrown [thrown_thing] [power_throw ? "really hard" : ""]", LOG_ATTACK) - newtonian_move(get_dir(target, src)) - thrown_thing.safe_throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed + power_throw, src, null, null, null, move_force) + if(!thrown_thing) + return + + if(isliving(thrown_thing)) + var/turf/start_T = get_turf(loc) //Get the start and target tile for the descriptors + var/turf/end_T = get_turf(target) + if(start_T && end_T) + log_combat(src, thrown_thing, "thrown", addition="grab from tile in [AREACOORD(start_T)] towards tile at [AREACOORD(end_T)]") + var/power_throw = 0 + if(HAS_TRAIT(src, TRAIT_HULK)) + power_throw++ + if(HAS_TRAIT(src, TRAIT_DWARF)) + power_throw-- + if(HAS_TRAIT(thrown_thing, TRAIT_DWARF)) + power_throw++ + if(neckgrab_throw) + power_throw++ + do_attack_animation(target, no_effect = TRUE) //PARIAH EDIT ADDITION - AESTHETICS + playsound(loc, 'sound/weapons/punchmiss.ogg', 50, TRUE, -1) //PARIAH EDIT ADDITION - AESTHETICS + visible_message(span_danger("[src] throws [thrown_thing][power_throw ? " really hard!" : "."]"), \ + span_danger("You throw [thrown_thing][power_throw ? " really hard!" : "."]")) + log_message("has thrown [thrown_thing] [power_throw ? "really hard" : ""]", LOG_ATTACK) + newtonian_move(get_dir(target, src)) + thrown_thing.safe_throw_at(target, thrown_thing.throw_range, thrown_thing.throw_speed + power_throw, src, null, null, null, move_force) /mob/living/carbon/proc/canBeHandcuffed() @@ -776,7 +790,7 @@ /mob/living/carbon/proc/update_handcuffed() if(handcuffed) drop_all_held_items() - stop_pulling() + release_all_grabs() throw_alert(ALERT_HANDCUFFED, /atom/movable/screen/alert/restrained/handcuffed, new_master = src.handcuffed) else clear_alert(ALERT_HANDCUFFED) diff --git a/code/modules/mob/living/carbon/carbon_context.dm b/code/modules/mob/living/carbon/carbon_context.dm index 3ea764aa043f..2577f15a55dd 100644 --- a/code/modules/mob/living/carbon/carbon_context.dm +++ b/code/modules/mob/living/carbon/carbon_context.dm @@ -1,7 +1,7 @@ /mob/living/carbon/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() - if (!isnull(held_item)) + if (!isnull(held_item) && !(held_item.item_flags & (ABSTRACT|HAND_ITEM))) context[SCREENTIP_CONTEXT_CTRL_SHIFT_LMB] = "Offer item" return CONTEXTUAL_SCREENTIP_SET @@ -15,11 +15,8 @@ else if (human_user == src) context[SCREENTIP_CONTEXT_LMB] = "Check injuries" - if (get_bodypart(human_user.zone_selected)?.get_modified_bleed_rate()) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Grab limb" - if (human_user != src) - context[SCREENTIP_CONTEXT_RMB] = "Shove" + context[SCREENTIP_CONTEXT_RMB] = "Disarm" if (!human_user.combat_mode) if (body_position == STANDING_UP) diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm index ca447b99bb32..0a9de6b65671 100644 --- a/code/modules/mob/living/carbon/carbon_defense.dm +++ b/code/modules/mob/living/carbon/carbon_defense.dm @@ -158,7 +158,7 @@ if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) ContactContractDisease(D) - return FALSE + return . || FALSE /mob/living/carbon/attack_paw(mob/living/carbon/human/user, list/modifiers) @@ -290,8 +290,7 @@ COMBAT_MESSAGE_RANGE, ) log_combat(src, target, "shoved", "knocking them down") - target.pulledby?.stop_pulling() - target.stop_pulling() + target.release_all_grabs() return if(target.IsKnockdown()) //KICK HIM IN THE NUTS //That is harm intent. @@ -363,10 +362,9 @@ if(undergoing_cardiac_arrest()) set_heartattack(FALSE) var/list/shocking_queue = list() - if(iscarbon(pulling) && source != pulling) - shocking_queue += pulling - if(iscarbon(pulledby) && source != pulledby) - shocking_queue += pulledby + shocking_queue += get_all_grabbed_movables() + shocking_queue -= source + if(iscarbon(buckled) && source != buckled) shocking_queue += buckled for(var/mob/living/carbon/carried in buckled_mobs) @@ -603,77 +601,3 @@ var/obj/item/bodypart/limb = _limb if (!IS_ORGANIC_LIMB(limb)) . += (limb.brute_dam) + (limb.burn_dam) - -/mob/living/carbon/grabbedby(mob/living/carbon/user, supress_message = FALSE) - if(user != src) - return ..() - - var/obj/item/bodypart/grasped_part = get_bodypart(zone_selected) - if(!grasped_part?.get_modified_bleed_rate()) - return - var/starting_hand_index = active_hand_index - if(starting_hand_index == grasped_part.held_index) - to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!")) - return - - to_chat(src, span_warning("You try grasping at your [grasped_part.name], trying to stop the bleeding...")) - if(!do_after(src, time = 0.75 SECONDS)) - to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) - return - - var/obj/item/hand_item/self_grasp/grasp = new - if(starting_hand_index != active_hand_index || !put_in_active_hand(grasp)) - to_chat(src, span_danger("You fail to grasp your [grasped_part.name].")) - QDEL_NULL(grasp) - return - grasp.grasp_limb(grasped_part) - -/// an abstract item representing you holding your own limb to staunch the bleeding, see [/mob/living/carbon/proc/grabbedby] will probably need to find somewhere else to put this. -/obj/item/hand_item/self_grasp - name = "self-grasp" - desc = "Sometimes all you can do is slow the bleeding." - icon_state = "latexballon" - inhand_icon_state = "nothing" - slowdown = 0.5 - item_flags = DROPDEL | ABSTRACT | NOBLUDGEON | SLOWS_WHILE_IN_HAND | HAND_ITEM - /// The bodypart we're staunching bleeding on, which also has a reference to us in [/obj/item/bodypart/var/grasped_by] - var/obj/item/bodypart/grasped_part - /// The carbon who owns all of this mess - var/mob/living/carbon/user - -/obj/item/hand_item/self_grasp/Destroy() - if(user) - to_chat(user, span_warning("You stop holding onto your[grasped_part ? " [grasped_part.name]" : "self"].")) - UnregisterSignal(user, COMSIG_PARENT_QDELETING) - if(grasped_part) - UnregisterSignal(grasped_part, list(COMSIG_CARBON_REMOVED_LIMB, COMSIG_PARENT_QDELETING)) - grasped_part.grasped_by = null - grasped_part.refresh_bleed_rate() - grasped_part = null - user = null - return ..() - -/// The limb or the whole damn person we were grasping got deleted or dismembered, so we don't care anymore -/obj/item/hand_item/self_grasp/proc/qdel_void() - SIGNAL_HANDLER - qdel(src) - -/// We've already cleared that the bodypart in question is bleeding in [the place we create this][/mob/living/carbon/proc/grabbedby], so set up the connections -/obj/item/hand_item/self_grasp/proc/grasp_limb(obj/item/bodypart/grasping_part) - user = grasping_part.owner - if(!istype(user)) - stack_trace("[src] attempted to try_grasp() with [istype(user, /datum) ? user.type : isnull(user) ? "null" : user] user") - qdel(src) - return - - grasped_part = grasping_part - grasped_part.grasped_by = src - grasped_part.refresh_bleed_rate() - RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(qdel_void)) - RegisterSignal(grasped_part, list(COMSIG_CARBON_REMOVED_LIMB, COMSIG_PARENT_QDELETING), PROC_REF(qdel_void)) - - user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name], trying to stop the bleeding."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE) - playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1) - return TRUE - -#undef SHAKE_ANIMATION_OFFSET diff --git a/code/modules/mob/living/carbon/carbon_movement.dm b/code/modules/mob/living/carbon/carbon_movement.dm index 980a01980117..343c96b813a1 100644 --- a/code/modules/mob/living/carbon/carbon_movement.dm +++ b/code/modules/mob/living/carbon/carbon_movement.dm @@ -6,7 +6,7 @@ ..() return loc.handle_slip(src, knockdown_amount, slipped_on, lube_flags, paralyze, force_drop) -/mob/living/carbon/Move(NewLoc, direct) +/mob/living/carbon/Move(NewLoc, direct, glide_size_override, z_movement_flags) . = ..() if(!(usr == src)) return diff --git a/code/modules/mob/living/carbon/examine.dm b/code/modules/mob/living/carbon/examine.dm index dd7e7053b9e8..03e406ec651c 100644 --- a/code/modules/mob/living/carbon/examine.dm +++ b/code/modules/mob/living/carbon/examine.dm @@ -98,9 +98,6 @@ if(has_status_effect(/datum/status_effect/fire_handler/wet_stacks)) msg += "[t_He] look[p_s()] a little soaked.\n" - if(pulledby?.grab_state) - msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" - msg += "" . += msg.Join("") diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 613d36df97ae..f84712ae80cc 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -208,8 +208,13 @@ msg += "[t_He] look[p_s()] a little soaked.\n" - if(pulledby?.grab_state) - msg += "[t_He] [t_is] restrained by [pulledby]'s grip.\n" + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(G.assailant == src) + msg += "[t_He] [t_is] gripping [t_His] [G.get_targeted_bodypart().plaintext_zone].\n" + continue + if(!G.current_grab.stop_move) + continue + msg += "[t_He] [t_is] restrained by [G.assailant]'s grip.\n" if(nutrition < NUTRITION_LEVEL_STARVING - 50) msg += "[t_He] [t_is] severely malnourished.\n" @@ -237,17 +242,6 @@ if(-INFINITY to BLOOD_VOLUME_BAD) msg += "[span_deadsay("[t_He] resemble[p_s()] a crushed, empty juice pouch.")]\n" - if(is_bleeding()) - var/list/obj/item/bodypart/grasped_limbs = list() - - for(var/obj/item/bodypart/body_part as anything in bodyparts) - if(body_part.grasped_by) - grasped_limbs += body_part - - for(var/i in grasped_limbs) - var/obj/item/bodypart/grasped_part = i - msg += "[t_He] [t_is] holding [t_his] [grasped_part.name] to slow the bleeding!\n" - if(islist(stun_absorption)) for(var/i in stun_absorption) if(stun_absorption[i]["end_time"] > world.time && stun_absorption[i]["examine_message"]) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 4b9f33232d49..171d99721566 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -763,7 +763,9 @@ for(var/obj/item/bodypart/BP as anything in bodyparts) BP.set_sever_artery(FALSE) BP.set_sever_tendon(FALSE) + BP.set_dislocated(FALSE) BP.heal_bones() + BP.adjustPain(-INFINITY) remove_all_embedded_objects() set_heartattack(FALSE) @@ -852,7 +854,8 @@ set_species(newtype) /mob/living/carbon/human/mouse_buckle_handling(mob/living/M, mob/living/user) - if(pulling != M || grab_state != GRAB_AGGRESSIVE || stat != CONSCIOUS) + var/obj/item/hand_item/grab/G = is_grabbing(M) + if(!G || G.current_grab.damage_stage != GRAB_AGGRESSIVE || stat != CONSCIOUS) return FALSE //If they dragged themselves to you and you're currently aggressively grabbing them try to piggyback @@ -1131,3 +1134,29 @@ return FALSE return dna.species.organs[slot] + +//Used by various things that knock people out by applying blunt trauma to the head. +//Checks that the species has a "head" (brain containing organ) and that hit_zone refers to it. +/mob/living/carbon/human/proc/can_head_trauma_ko() + var/obj/item/organ/brain = getorganslot(ORGAN_SLOT_BRAIN) + if(!brain || !needs_organ(ORGAN_SLOT_BRAIN)) + return FALSE + + //if the parent organ is significantly larger than the brain organ, then hitting it is not guaranteed + var/obj/item/bodypart/head = get_bodypart(BODY_ZONE_HEAD) + if(!head) + return FALSE + if(head.w_class > brain.w_class + 1) + return prob(100 / 2**(head.w_class - brain.w_class - 1)) + return TRUE + +/mob/living/carbon/human/up() + . = ..() + if(.) + return + var/obj/climbable = check_zclimb() + if(!climbable) + can_z_move(UP, get_turf(src), ZMOVE_FEEDBACK|ZMOVE_FLIGHT_FLAGS) + return FALSE + + return ClimbUp(climbable) diff --git a/code/modules/mob/living/carbon/human/human_context.dm b/code/modules/mob/living/carbon/human/human_context.dm index 56dc9040bbd8..eaaac631a911 100644 --- a/code/modules/mob/living/carbon/human/human_context.dm +++ b/code/modules/mob/living/carbon/human/human_context.dm @@ -7,17 +7,10 @@ if (user == src) return . - if (pulledby == user) - switch (user.grab_state) - if (GRAB_PASSIVE) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Grip" - if (GRAB_AGGRESSIVE) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Choke" - if (GRAB_NECK) - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Strangle" - else - return . - else - context[SCREENTIP_CONTEXT_CTRL_LMB] = "Pull" + context[SCREENTIP_CONTEXT_CTRL_LMB] = "Grab" + + var/obj/item/hand_item/grab/G = user.is_grabbing(src) + if (G) + G.current_grab.add_context(context, held_item, user, src) return CONTEXTUAL_SCREENTIP_SET diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 43730ea1e6b2..bf7961570ca0 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -163,12 +163,6 @@ return ..() -/mob/living/carbon/human/grippedby(mob/living/user, instant = FALSE) - if(w_uniform) - w_uniform.add_fingerprint(user) - ..() - - /mob/living/carbon/human/attacked_by(obj/item/I, mob/living/user) if(!I || !user) return MOB_ATTACKEDBY_FAIL diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index eba4d1e9145f..4dcd5a3b3a95 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -25,7 +25,7 @@ if(dna.species.negates_gravity(src) || ..()) return TRUE -/mob/living/carbon/human/Move(NewLoc, direct) +/mob/living/carbon/human/Move(NewLoc, direct, glide_size_override, z_movement_flags) . = ..() if(shoes && body_position == STANDING_UP && loc == NewLoc && has_gravity(loc)) SEND_SIGNAL(shoes, COMSIG_SHOES_STEP_ACTION) diff --git a/code/modules/mob/living/carbon/human/human_stripping.dm b/code/modules/mob/living/carbon/human/human_stripping.dm index b43915faf779..bbbbf0835669 100644 --- a/code/modules/mob/living/carbon/human/human_stripping.dm +++ b/code/modules/mob/living/carbon/human/human_stripping.dm @@ -23,12 +23,13 @@ GLOBAL_LIST_INIT(strippable_human_items, create_strippable_list(list( /datum/strippable_item/mob_item_slot/legcuffs, ))) -/mob/living/carbon/human/should_strip(mob/user) +/mob/living/carbon/human/should_strip(mob/living/user) . = ..() if (!.) return FALSE - if (user.grab_state != GRAB_AGGRESSIVE) + var/obj/item/hand_item/grab/G = user.is_grabbing(src) + if (G && G.current_grab.damage_stage != GRAB_AGGRESSIVE) return TRUE if (ishuman(user)) diff --git a/code/modules/mob/living/carbon/human/inventory.dm b/code/modules/mob/living/carbon/human/inventory.dm index c5f0afa51615..7fc1eef67643 100644 --- a/code/modules/mob/living/carbon/human/inventory.dm +++ b/code/modules/mob/living/carbon/human/inventory.dm @@ -184,7 +184,7 @@ update_worn_undersuit() if(wear_suit.breakouttime) //when equipping a straightjacket ADD_TRAIT(src, TRAIT_RESTRAINED, SUIT_TRAIT) - stop_pulling() //can't pull if restrained + release_all_grabs() //can't pull if restrained update_mob_action_buttons() //certain action buttons will no longer be usable. update_worn_oversuit() if(ITEM_SLOT_ICLOTHING) diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 0036cb65704e..28792e3b5035 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -952,7 +952,7 @@ GLOBAL_LIST_EMPTY(features_by_species) user.do_cpr(target) -/datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style) +/datum/species/proc/grab(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style, list/params) if(target.check_block()) target.visible_message(span_warning("[target] blocks [user]'s grab!"), \ span_userdanger("You block [user]'s grab!"), span_hear("You hear a swoosh!"), COMBAT_MESSAGE_RANGE, user) @@ -961,7 +961,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(attacker_style?.grab_act(user,target) == MARTIAL_ATTACK_SUCCESS) return TRUE else - target.grabbedby(user) + user.try_make_grab(target, use_offhand = params?[RIGHT_CLICK]) return TRUE ///This proc handles punching damage. diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm index 6e1930f90e96..da26471014dc 100644 --- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm @@ -746,11 +746,13 @@ return TRUE /datum/action/innate/link_minds/Activate() - if(!isliving(owner.pulling) || owner.grab_state < GRAB_AGGRESSIVE) + var/mob/living/L = owner + var/obj/item/hand_item/grab/G = L.get_active_grab() + if(!isliving(G?.affecting) || G.current_grab.damage_stage < GRAB_AGGRESSIVE) to_chat(owner, span_warning("You need to aggressively grab someone to link minds!")) return - var/mob/living/living_target = owner.pulling + var/mob/living/living_target = G.affecting if(living_target.stat == DEAD) to_chat(owner, span_warning("They're dead!")) return @@ -779,11 +781,9 @@ /datum/action/innate/link_minds/proc/while_link_callback(mob/living/linkee) if(!is_species(owner, req_species)) return FALSE - if(!owner.pulling) - return FALSE - if(owner.pulling != linkee) - return FALSE - if(owner.grab_state < GRAB_AGGRESSIVE) + var/mob/living/L = owner + var/obj/item/hand_item/grab/G = L.is_grabbing(linkee) + if(!G || G.current_grab.damage_stage < GRAB_AGGRESSIVE) return FALSE if(linkee.stat == DEAD) return FALSE diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm index 92a96b6c7e7e..650b8ea0edb7 100644 --- a/code/modules/mob/living/carbon/human/species_types/vampire.dm +++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -176,37 +176,48 @@ if(!COOLDOWN_FINISHED(V, drain_cooldown)) to_chat(H, span_warning("You just drained blood, wait a few seconds!")) return - if(H.pulling && iscarbon(H.pulling)) - var/mob/living/carbon/victim = H.pulling - if(H.blood_volume >= BLOOD_VOLUME_MAXIMUM) - to_chat(H, span_warning("You're already full!")) - return - if(victim.stat == DEAD) - to_chat(H, span_warning("You need a living victim!")) - return - if(!victim.blood_volume || (victim.dna && ((NOBLOOD in victim.dna.species.species_traits) || victim.dna.species.exotic_blood))) - to_chat(H, span_warning("[victim] doesn't have blood!")) - return - COOLDOWN_START(V, drain_cooldown, 3 SECONDS) - if(victim.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) - victim.show_message(span_warning("[H] tries to bite you, but stops before touching you!")) - to_chat(H, span_warning("[victim] is blessed! You stop just in time to avoid catching fire.")) - return - if(victim.has_reagent(/datum/reagent/consumable/garlic)) - victim.show_message(span_warning("[H] tries to bite you, but recoils in disgust!")) - to_chat(H, span_warning("[victim] reeks of garlic! you can't bring yourself to drain such tainted blood.")) - return - if(!do_after(H, victim, 3 SECONDS)) - return - var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - H.blood_volume //How much capacity we have left to absorb blood - var/drained_blood = min(victim.blood_volume, VAMP_DRAIN_AMOUNT, blood_volume_difference) - victim.show_message(span_danger("[H] is draining your blood!")) - to_chat(H, span_notice("You drain some blood!")) - playsound(H, 'sound/items/drink.ogg', 30, TRUE, -2) - victim.blood_volume = clamp(victim.blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) - H.blood_volume = clamp(H.blood_volume + drained_blood, 0, BLOOD_VOLUME_MAXIMUM) - if(!victim.blood_volume) - to_chat(H, span_notice("You finish off [victim]'s blood supply.")) + var/obj/item/hand_item/grab/G = H.get_active_grab() + if(!iscarbon(G?.affecting)) + return + + var/mob/living/carbon/victim = G.affecting + if(H.blood_volume >= BLOOD_VOLUME_MAXIMUM) + to_chat(H, span_warning("You're already full!")) + return + + if(victim.stat == DEAD) + to_chat(H, span_warning("You need a living victim!")) + return + + if(!victim.blood_volume || (victim.dna && ((NOBLOOD in victim.dna.species.species_traits) || victim.dna.species.exotic_blood))) + to_chat(H, span_warning("[victim] doesn't have blood!")) + return + + COOLDOWN_START(V, drain_cooldown, 3 SECONDS) + if(victim.can_block_magic(MAGIC_RESISTANCE_HOLY, charge_cost = 0)) + victim.show_message(span_warning("[H] tries to bite you, but stops before touching you!")) + to_chat(H, span_warning("[victim] is blessed! You stop just in time to avoid catching fire.")) + return + + if(victim.has_reagent(/datum/reagent/consumable/garlic)) + victim.show_message(span_warning("[H] tries to bite you, but recoils in disgust!")) + to_chat(H, span_warning("[victim] reeks of garlic! you can't bring yourself to drain such tainted blood.")) + return + + if(!do_after(H, victim, 3 SECONDS)) + return + + var/blood_volume_difference = BLOOD_VOLUME_MAXIMUM - H.blood_volume //How much capacity we have left to absorb blood + var/drained_blood = min(victim.blood_volume, VAMP_DRAIN_AMOUNT, blood_volume_difference) + + victim.show_message(span_danger("[H] is draining your blood!")) + to_chat(H, span_notice("You drain some blood!")) + playsound(H, 'sound/items/drink.ogg', 30, TRUE, -2) + + victim.blood_volume = clamp(victim.blood_volume - drained_blood, 0, BLOOD_VOLUME_MAXIMUM) + H.blood_volume = clamp(H.blood_volume + drained_blood, 0, BLOOD_VOLUME_MAXIMUM) + if(!victim.blood_volume) + to_chat(H, span_notice("You finish off [victim]'s blood supply.")) /obj/item/organ/heart/vampire name = "vampire heart" diff --git a/code/modules/mob/living/carbon/inventory.dm b/code/modules/mob/living/carbon/inventory.dm index 4c932b08c75f..c9b50fb3f94c 100644 --- a/code/modules/mob/living/carbon/inventory.dm +++ b/code/modules/mob/living/carbon/inventory.dm @@ -67,8 +67,8 @@ if(index) held_items[index] = null - if(I.pulledby) - I.pulledby.stop_pulling() + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() I.screen_loc = null if(client) @@ -299,3 +299,11 @@ covered_flags |= worn_item.body_parts_covered return covered_flags + +///Returns an item that is covering a bodypart. +/mob/living/carbon/proc/get_item_covering_bodypart(obj/item/bodypart/BP) + return get_item_covering_zone(body_zone2cover_flags(BP.body_zone)) + +///Returns an item that is covering a body_zone (BODY_ZONE_CHEST, etc) +/mob/living/carbon/proc/get_item_covering_zone(zone) + for(var/obj/item in get_all_worn_items()) diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 41d28aab82c1..5c0e3cf571c8 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -83,7 +83,7 @@ losebreath = max(2, losebreath + 1) else if(!getorganslot(ORGAN_SLOT_BREATHING_TUBE)) - if((pulledby?.grab_state >= GRAB_KILL) || (lungs?.organ_flags & ORGAN_DEAD)) + if(HAS_TRAIT(src, TRAIT_KILL_GRAB) || (lungs?.organ_flags & ORGAN_DEAD)) losebreath++ //You can't breath at all when in critical or when being choked, so you're going to miss a breath // Recover from breath loss diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index 09423e5001a9..ba82fda8f6c1 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -88,7 +88,7 @@ update_health_hud() med_hud_set_health() med_hud_set_status() - stop_pulling() + release_all_grabs() set_ssd_indicator(FALSE) set_typing_indicator(FALSE) diff --git a/code/modules/mob/living/init_signals.dm b/code/modules/mob/living/init_signals.dm index e832d5d770ff..ca539e53f0a1 100644 --- a/code/modules/mob/living/init_signals.dm +++ b/code/modules/mob/living/init_signals.dm @@ -154,8 +154,7 @@ /mob/living/proc/on_pull_blocked_trait_gain(datum/source) SIGNAL_HANDLER mobility_flags &= ~(MOBILITY_PULL) - if(pulling) - stop_pulling() + release_all_grabs() /// Called when [TRAIT_PULL_BLOCKED] is removed from the mob. /mob/living/proc/on_pull_blocked_trait_loss(datum/source) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index cd7b49d8e5e3..9b911267e0f4 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -133,18 +133,20 @@ ContactContractDisease(D) //Should stop you pushing a restrained person out of the way - if(L.pulledby && L.pulledby != src && HAS_TRAIT(L, TRAIT_RESTRAINED)) + if(LAZYLEN(L.grabbed_by) && !is_grabbing(L) && HAS_TRAIT(L, TRAIT_RESTRAINED)) if(!(world.time % 5)) to_chat(src, span_warning("[L] is restrained, you cannot push past.")) return TRUE - if(L.pulling) - if(ismob(L.pulling)) - var/mob/P = L.pulling - if(HAS_TRAIT(P, TRAIT_RESTRAINED)) - if(!(world.time % 5)) - to_chat(src, span_warning("[L] is restraining [P], you cannot push past.")) - return TRUE + var/list/grabs = L.get_active_grabs() + if(length(grabs)) + for(var/obj/item/hand_item/grab/G in grabs) + if(ismob(G.affecting)) + var/mob/P = G.affecting + if(HAS_TRAIT(P, TRAIT_RESTRAINED)) + if(!(world.time % 5)) + to_chat(src, span_warning("[L] is restraining [P], you cannot push past.")) + return TRUE if(moving_diagonally)//no mob swap during diagonal moves. return TRUE @@ -157,7 +159,7 @@ mob_swap = TRUE else //You can swap with the person you are dragging on grab intent, and restrained people in most cases - if(M.pulledby == src && !too_strong) + if(is_grabbing(M) && !too_strong) mob_swap = TRUE else if( !(HAS_TRAIT(M, TRAIT_NOMOBSWAP) || HAS_TRAIT(src, TRAIT_NOMOBSWAP))&&\ @@ -167,32 +169,18 @@ mob_swap = TRUE if(mob_swap) //switch our position with M - if(loc && !loc.Adjacent(M.loc)) + if(loc && !loc.MultiZAdjacent(M.loc)) return TRUE + now_pushing = TRUE + var/oldloc = loc var/oldMloc = M.loc - - - var/M_passmob = (M.pass_flags & PASSMOB) // we give PASSMOB to both mobs to avoid bumping other mobs during swap. - var/src_passmob = (pass_flags & PASSMOB) - M.pass_flags |= PASSMOB - pass_flags |= PASSMOB - - var/move_failed = FALSE - if(!M.Move(oldloc) || !Move(oldMloc)) - M.forceMove(oldMloc) - forceMove(oldloc) - move_failed = TRUE - if(!src_passmob) - pass_flags &= ~PASSMOB - if(!M_passmob) - M.pass_flags &= ~PASSMOB + forceMove(oldMloc) + M.forceMove(oldloc) now_pushing = FALSE - - if(!move_failed) - return TRUE + return TRUE //okay, so we didn't switch. but should we push? //not if he's not CANPUSH of course @@ -283,8 +271,9 @@ for(var/obj/structure/window/win in get_step(W, dir_to_target)) now_pushing = FALSE return - if(pulling == AM) - stop_pulling() + + release_grabs(AM) + var/current_dir if(isliving(AM)) current_dir = AM.dir @@ -295,128 +284,10 @@ AM.setDir(current_dir) now_pushing = FALSE -/mob/living/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE) - if(!AM || !src) - return FALSE - if(!(AM.can_be_pulled(src, state, force))) - return FALSE - if(throwing || !(mobility_flags & MOBILITY_PULL)) - return FALSE - if(SEND_SIGNAL(src, COMSIG_LIVING_TRY_PULL, AM, force) & COMSIG_LIVING_CANCEL_PULL) - return FALSE - - AM.add_fingerprint(src) - - // If we're pulling something then drop what we're currently pulling and pull this instead. - if(pulling) - // Are we trying to pull something we are already pulling? Then just stop here, no need to continue. - if(AM == pulling) - return - stop_pulling() - - changeNext_move(CLICK_CD_GRABBING) - animate_interact(AM, INTERACT_PULL) - - if(AM.pulledby) - if(!supress_message) - AM.visible_message(span_danger("[src] pulls [AM] from [AM.pulledby]'s grip."), \ - span_danger("[src] pulls you from [AM.pulledby]'s grip."), null, null, src) - to_chat(src, span_notice("You pull [AM] from [AM.pulledby]'s grip!")) - log_combat(AM, AM.pulledby, "pulled from", src) - AM.pulledby.stop_pulling() //an object can't be pulled by two mobs at once. - - pulling = AM - AM.set_pulledby(src) - - SEND_SIGNAL(src, COMSIG_LIVING_START_PULL, AM, state, force) - - if(!supress_message) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(src)) - var/mob/living/carbon/human/H = src - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - if(HAS_TRAIT(H, TRAIT_STRONG_GRABBER)) - sound_to_play = null - playsound(src.loc, sound_to_play, 50, TRUE, -1) - update_pull_hud_icon() - - if(ismob(AM)) - var/mob/M = AM - - log_combat(src, M, "grabbed", addition="passive grab") - if(!supress_message && !(iscarbon(AM) && HAS_TRAIT(src, TRAIT_STRONG_GRABBER))) - if(ishuman(M)) - var/mob/living/carbon/human/grabbed_human = M - var/grabbed_by_hands = (zone_selected == "l_arm" || zone_selected == "r_arm") && grabbed_human.usable_hands > 0 - M.visible_message(span_warning("[src] grabs [M] [grabbed_by_hands ? "by their hands":"passively"]!"), \ - span_warning("[src] grabs you [grabbed_by_hands ? "by your hands":"passively"]!"), null, null, src) - to_chat(src, span_notice("You grab [M] [grabbed_by_hands ? "by their hands":"passively"]!")) - else - M.visible_message(span_warning("[src] grabs [M] passively!"), \ - span_warning("[src] grabs you passively!"), null, null, src) - to_chat(src, span_notice("You grab [M] passively!")) - - if(!iscarbon(src)) - M.LAssailant = null - else - M.LAssailant = WEAKREF(usr) - if(isliving(M)) - var/mob/living/L = M - - SEND_SIGNAL(M, COMSIG_LIVING_GET_PULLED, src) - //Share diseases that are spread by touch - for(var/thing in diseases) - var/datum/disease/D = thing - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - L.ContactContractDisease(D) - - for(var/thing in L.diseases) - var/datum/disease/D = thing - if(D.spread_flags & DISEASE_SPREAD_CONTACT_SKIN) - ContactContractDisease(D) - - if(iscarbon(L)) - var/mob/living/carbon/C = L - if(HAS_TRAIT(src, TRAIT_STRONG_GRABBER)) - C.grippedby(src) - - update_pull_movespeed() - - set_pull_offsets(M, state) - -/mob/living/proc/set_pull_offsets(mob/living/M, grab_state = GRAB_PASSIVE) - if(M.buckled) - return //don't make them change direction or offset them if they're buckled into something. - var/offset = 0 - switch(grab_state) - if(GRAB_PASSIVE) - offset = GRAB_PIXEL_SHIFT_PASSIVE - if(GRAB_AGGRESSIVE) - offset = GRAB_PIXEL_SHIFT_AGGRESSIVE - if(GRAB_NECK) - offset = GRAB_PIXEL_SHIFT_NECK - if(GRAB_KILL) - offset = GRAB_PIXEL_SHIFT_NECK - M.setDir(get_dir(M, src)) - switch(M.dir) - if(NORTH) - animate(M, pixel_x = M.base_pixel_x, pixel_y = M.base_pixel_y + offset, 3) - if(SOUTH) - animate(M, pixel_x = M.base_pixel_x, pixel_y = M.base_pixel_y - offset, 3) - if(EAST) - if(M.lying_angle == 270) //update the dragged dude's direction if we've turned - M.set_lying_angle(90) - animate(M, pixel_x = M.base_pixel_x + offset, pixel_y = M.base_pixel_y, 3) - if(WEST) - if(M.lying_angle == 90) - M.set_lying_angle(270) - animate(M, pixel_x = M.base_pixel_x - offset, pixel_y = M.base_pixel_y, 3) - -/mob/living/proc/reset_pull_offsets(mob/living/M, override) - if(!override && M.buckled) +/mob/living/proc/reset_pull_offsets(override) + if(!override && buckled) return - animate(M, pixel_x = M.base_pixel_x, pixel_y = M.base_pixel_y, 1) + animate(src, pixel_x = src.base_pixel_x, pixel_y = src.base_pixel_y, 1) //mob verbs are a lot faster than object verbs //for more info on why this is not atom/pull, see examinate() in mob.dm @@ -425,22 +296,12 @@ set category = "Object" if(istype(AM) && Adjacent(AM)) - start_pulling(AM) - else if(!combat_mode) //Don;'t cancel pulls if misclicking in combat mode. - stop_pulling() - -/mob/living/stop_pulling() - animate_interact(pulling, INTERACT_UNPULL) - if(ismob(pulling)) - reset_pull_offsets(pulling) - ..() - update_pull_movespeed() - update_pull_hud_icon() + try_make_grab(AM) /mob/living/verb/stop_pulling1() set name = "Stop Pulling" set category = "IC" - stop_pulling() + release_all_grabs() //same as above /mob/living/pointed(atom/A as mob|obj|turf in view(client.view, src)) @@ -487,8 +348,12 @@ if(!(flags & IGNORE_RESTRAINTS) && HAS_TRAIT(src, TRAIT_RESTRAINED)) return TRUE - if(!(flags & IGNORE_GRAB) && pulledby && pulledby.grab_state >= GRAB_AGGRESSIVE) - return TRUE + + if(!(flags & IGNORE_GRAB)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + if(G.current_grab.restrains && !G.assailant == src) + return TRUE + if(!(flags & IGNORE_STASIS) && IS_IN_HARD_STASIS(src)) return TRUE return FALSE @@ -889,7 +754,7 @@ /mob/living/proc/update_wound_overlays() return -/mob/living/Move(atom/newloc, direct, glide_size_override) +/mob/living/Move(atom/newloc, direct, glide_size_override, z_movement_flags) if(lying_angle != 0) lying_angle_on_movement(direct) if (buckled && buckled.loc != newloc) //not updating position @@ -902,15 +767,10 @@ var/old_direction = dir var/turf/T = loc - if(pulling) - update_pull_movespeed() + update_pull_movespeed() . = ..() - if(moving_diagonally != FIRST_DIAG_STEP && isliving(pulledby)) - var/mob/living/L = pulledby - L.set_pull_offsets(src, pulledby.grab_state) - if(active_storage && !((active_storage.parent?.resolve() in important_recursive_contents?[RECURSIVE_CONTENTS_ACTIVE_STORAGE]) || CanReach(active_storage.parent?.resolve(),view_only = TRUE))) active_storage.hide_contents(src) @@ -1035,8 +895,7 @@ SEND_SIGNAL(src, COMSIG_LIVING_RESIST, src) //resisting grabs (as if it helps anyone...) - if(!HAS_TRAIT(src, TRAIT_RESTRAINED) && pulledby) - log_combat(src, pulledby, "resisted grab") + if(!HAS_TRAIT(src, TRAIT_RESTRAINED) && LAZYLEN(grabbed_by)) resist_grab() return @@ -1054,34 +913,24 @@ else if(last_special <= world.time) resist_restraints() //trying to remove cuffs. +/// Attempt to break free of grabs. Returning TRUE means the user broke free and can move. /mob/proc/resist_grab(moving_resist) - return 1 //returning 0 means we successfully broke free + return TRUE /mob/living/resist_grab(moving_resist) + if(!LAZYLEN(grabbed_by)) + return TRUE + + if(!moving_resist) + visible_message(span_danger("\The [src] struggles to break free!")) + . = TRUE - if(pulledby.grab_state || body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS)) - var/altered_grab_state = pulledby.grab_state - if((body_position == LYING_DOWN || HAS_TRAIT(src, TRAIT_GRABWEAKNESS)) && pulledby.grab_state < GRAB_KILL) //If prone, resisting out of a grab is equivalent to 1 grab state higher. won't make the grab state exceed the normal max, however - altered_grab_state++ - var/resist_chance = BASE_GRAB_RESIST_CHANCE /// see defines/combat.dm, this should be baseline 60% - resist_chance = (resist_chance/altered_grab_state) ///Resist chance divided by the value imparted by your grab state. It isn't until you reach neckgrab that you gain a penalty to escaping a grab. - if(prob(resist_chance)) - visible_message(span_danger("[src] breaks free of [pulledby]'s grip!"), \ - span_danger("You break free of [pulledby]'s grip!"), null, null, pulledby) - to_chat(pulledby, span_warning("[src] breaks free of your grip!")) - log_combat(pulledby, src, "broke grab") - pulledby.stop_pulling() - return FALSE - else - stamina.adjust(-rand(15,20)) //failure to escape still imparts a pretty serious penalty - visible_message(span_danger("[src] struggles as they fail to break free of [pulledby]'s grip!"), \ - span_warning("You struggle as you fail to break free of [pulledby]'s grip!"), null, null, pulledby) - to_chat(pulledby, span_danger("[src] struggles as they fail to break free of your grip!")) - if(moving_resist && client) //we resisted by trying to move - client.move_delay = world.time + 4 SECONDS - else - pulledby.stop_pulling() - return FALSE + for(var/obj/item/hand_item/grab/G as anything in grabbed_by) + if(G.assailant == src) //Grabbing our own bodyparts + continue + log_combat(src, G.assailant, "resisted grab") + if(!G.handle_resist()) + . = FALSE /mob/living/proc/resist_buckle() buckled.user_unbuckle_mob(src,src) @@ -1253,7 +1102,7 @@ return /mob/living/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, gentle = FALSE, quickstart = TRUE) - stop_pulling() + release_all_grabs() . = ..() // Used in polymorph code to shapeshift mobs into other creatures @@ -1602,23 +1451,27 @@ GLOBAL_LIST_EMPTY(fire_appearances) "[C] leaps out of [src]'s way!")]") C.Paralyze(40) -/mob/living/can_be_pulled() - return ..() && !(buckled?.buckle_prevents_pull) - - /// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment. /mob/living/proc/on_fall() return /mob/living/forceMove(atom/destination) + var/old_loc = loc if(!currently_z_moving) - stop_pulling() if(buckled && !HAS_TRAIT(src, TRAIT_CANNOT_BE_UNBUCKLED)) buckled.unbuckle_mob(src, force = TRUE) if(has_buckled_mobs()) unbuckle_all_mobs(force = TRUE) + . = ..() - if(. && client) + if(!.) + return + + if(!QDELETED(src) && currently_z_moving == ZMOVING_VERTICAL) // Lateral Z movement handles this on it's own + handle_grabs_during_movement(old_loc, get_dir(old_loc, src)) + recheck_grabs() + + if(client) reset_perspective() @@ -1658,7 +1511,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) var/mob/living/U = user if(isliving(dropping)) var/mob/living/M = dropping - if(U.pulling == M && mob_size > M.mob_size) + if(U.is_grabbing(M) && !U.combat_mode && mob_size > M.mob_size) M.mob_try_pickup(U)//blame kevinz return//dont open the mobs inventory if you are picking them up . = ..() @@ -1987,31 +1840,6 @@ GLOBAL_LIST_EMPTY(fire_appearances) if(old_buckled.buckle_lying == 0 && (resting || HAS_TRAIT(src, TRAIT_FLOORED))) // The buckle forced us to stay up (like a chair) set_lying_down() // We want to rest or are otherwise floored, so let's drop on the ground. -/mob/living/set_pulledby(new_pulledby) - . = ..() - if(. == FALSE) //null is a valid value here, we only want to return if FALSE is explicitly passed. - return - if(pulledby) - if(!. && HAS_TRAIT(src, TRAIT_SOFT_CRITICAL_CONDITION)) - ADD_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) - else if(. && HAS_TRAIT(src, TRAIT_SOFT_CRITICAL_CONDITION)) - REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PULLED_WHILE_SOFTCRIT_TRAIT) - - -/// Updates the grab state of the mob and updates movespeed -/mob/living/setGrabState(newstate) - . = ..() - switch(grab_state) - if(GRAB_PASSIVE) - remove_movespeed_modifier(MOVESPEED_ID_MOB_GRAB_STATE) - if(GRAB_AGGRESSIVE) - add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/aggressive) - if(GRAB_NECK) - add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/neck) - if(GRAB_KILL) - add_movespeed_modifier(/datum/movespeed_modifier/grab_slowdown/kill) - - /// Only defined for carbons who can wear masks and helmets, we just assume other mobs have visible faces /mob/living/proc/is_face_visible() return TRUE diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index b69dd1b1860d..09cba62bf2bc 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -61,7 +61,14 @@ // we need a second, silent armor check to actually know how much to reduce damage taken, as opposed to // on [/atom/proc/bullet_act] where it's just to pass it to the projectile's on_hit(). var/armor_check = check_projectile_armor(def_zone, P, is_silent = TRUE) - apply_damage(P.damage, P.damage_type, def_zone, armor_check, sharpness = P.sharpness, attack_direction = attack_direction) + + var/modifier = 1 + if(LAZYLEN(grabbed_by)) + for(var/obj/item/hand_item/grab/G in grabbed_by) + modifier = max(G.current_grab.point_blank_mult, modifier) + var/damage = P.damage * modifier + + apply_damage(damage, P.damage_type, def_zone, armor_check, sharpness = P.sharpness, attack_direction = attack_direction) apply_effects(P.stun, P.knockdown, P.unconscious, P.slur, P.stutter, P.eyeblur, P.drowsy, armor_check, P.stamina, P.jitter, P.paralyze, P.immobilize) if(P.disorient_length) var/stamina = P.disorient_damage * ((100-armor_check)/100) @@ -137,82 +144,6 @@ adjust_fire_stacks(3) ignite_mob() -/mob/living/proc/grabbedby(mob/living/carbon/user, supress_message = FALSE) - if(user == src || anchored || !isturf(user.loc)) - return FALSE - if(!user.pulling || user.pulling != src) - user.start_pulling(src, supress_message = supress_message) - return - - if(!(status_flags & CANPUSH) || HAS_TRAIT(src, TRAIT_PUSHIMMUNE)) - to_chat(user, span_warning("[src] can't be grabbed more aggressively!")) - return FALSE - - if(user.grab_state >= GRAB_AGGRESSIVE && HAS_TRAIT(user, TRAIT_PACIFISM)) - to_chat(user, span_warning("You don't want to risk hurting [src]!")) - return FALSE - grippedby(user) - -//proc to upgrade a simple pull into a more aggressive grab. -/mob/living/proc/grippedby(mob/living/carbon/user, instant = FALSE) - if(user.grab_state < GRAB_KILL) - user.changeNext_move(CLICK_CD_GRABBING) - var/sound_to_play = 'sound/weapons/thudswoosh.ogg' - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.dna.species.grab_sound) - sound_to_play = H.dna.species.grab_sound - playsound(src.loc, sound_to_play, 50, TRUE, -1) - - if(user.grab_state) //only the first upgrade is instantaneous - var/old_grab_state = user.grab_state - var/grab_upgrade_time = instant ? 0 : 30 - visible_message(span_danger("[user] starts to tighten [user.p_their()] grip on [src]!"), \ - span_userdanger("[user] starts to tighten [user.p_their()] grip on you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You start to tighten your grip on [src]!")) - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - log_combat(user, src, "attempted to neck grab", addition="neck grab") - if(GRAB_NECK) - log_combat(user, src, "attempted to strangle", addition="kill grab") - if(!do_after(user, src, grab_upgrade_time)) - return FALSE - if(!user.pulling || user.pulling != src || user.grab_state != old_grab_state) - return FALSE - user.setGrabState(user.grab_state + 1) - switch(user.grab_state) - if(GRAB_AGGRESSIVE) - var/add_log = "" - if(HAS_TRAIT(user, TRAIT_PACIFISM)) - visible_message(span_danger("[user] firmly grips [src]!"), - span_danger("[user] firmly grips you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You firmly grip [src]!")) - add_log = " (pacifist)" - else - visible_message(span_danger("[user] grabs [src] aggressively!"), \ - span_userdanger("[user] grabs you aggressively!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You grab [src] aggressively!")) - drop_all_held_items() - stop_pulling() - log_combat(user, src, "grabbed", addition="aggressive grab[add_log]") - if(GRAB_NECK) - log_combat(user, src, "grabbed", addition="neck grab") - visible_message(span_danger("[user] grabs [src] by the neck!"),\ - span_userdanger("[user] grabs you by the neck!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You grab [src] by the neck!")) - if(!buckled && !density) - Move(user.loc) - if(GRAB_KILL) - log_combat(user, src, "strangled", addition="kill grab") - visible_message(span_danger("[user] is strangling [src]!"), \ - span_userdanger("[user] is strangling you!"), span_hear("You hear aggressive shuffling!"), null, user) - to_chat(user, span_danger("You're strangling [src]!")) - if(!buckled && !density) - Move(user.loc) - user.set_pull_offsets(src, grab_state) - return TRUE - - /mob/living/attack_slime(mob/living/simple_animal/slime/M) if(!SSticker.HasRoundStarted()) to_chat(M, "You cannot attack people before the game has started.") diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index b0387aad0e10..d9b29efeb9d3 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -147,8 +147,6 @@ var/list/diseases /// list of all diseases in a mob var/list/disease_resistances - var/slowed_by_drag = TRUE ///Whether the mob is slowed down when dragging another prone mob - /// List of changes to body temperature, used by desease symtoms like fever var/list/body_temp_changes = list() diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index cfe982af22bc..b6fbadc0b781 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -96,47 +96,27 @@ remove_movespeed_modifier(/datum/movespeed_modifier/turf_slowdown) current_turf_slowdown = 0 - /mob/living/proc/update_pull_movespeed() - SEND_SIGNAL(src, COMSIG_LIVING_UPDATING_PULL_MOVESPEED) + var/list/obj/item/hand_item/grab/grabs = get_active_grabs() + if(!length(grabs)) + remove_movespeed_modifier(/datum/movespeed_modifier/grabbing) + return - if(pulling) + var/slowdown_total = 0 + for(var/obj/item/hand_item/grab/G as anything in grabs) + var/atom/movable/pulling = G.affecting if(isliving(pulling)) var/mob/living/L = pulling - if(!slowed_by_drag || L.body_position == STANDING_UP || L.buckled || grab_state >= GRAB_AGGRESSIVE) - remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) - return - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/bulky_drag, multiplicative_slowdown = PULL_PRONE_SLOWDOWN) - return + if(G.current_grab.grab_slowdown || L.body_position == LYING_DOWN) + slowdown_total += max(G.current_grab.grab_slowdown, PULL_PRONE_SLOWDOWN) + if(isobj(pulling)) var/obj/structure/S = pulling - if(!slowed_by_drag || !S.drag_slowdown) - remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) - return - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/bulky_drag, multiplicative_slowdown = S.drag_slowdown) - return - remove_movespeed_modifier(/datum/movespeed_modifier/bulky_drag) - -/** - * We want to relay the zmovement to the buckled atom when possible - * and only run what we can't have on buckled.zMove() or buckled.can_z_move() here. - * This way we can avoid esoteric bugs, copypasta and inconsistencies. - */ -/mob/living/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS) - if(buckled) - if(buckled.currently_z_moving) - return FALSE - if(!(z_move_flags & ZMOVE_ALLOW_BUCKLED)) - buckled.unbuckle_mob(src, force = TRUE, can_fall = FALSE) - else - if(!target) - target = can_z_move(dir, get_turf(src), z_move_flags, src) - if(!target) - return FALSE - return buckled.zMove(dir, target, z_move_flags) // Return value is a loc. - return ..() + slowdown_total += S.drag_slowdown + + add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/grabbing, multiplicative_slowdown = slowdown_total) -/mob/living/can_z_move(direction, turf/start, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK, mob/living/rider) +/mob/living/can_z_move(direction, turf/start, z_move_flags, mob/living/rider) // Check physical climbing ability if((z_move_flags & ZMOVE_INCAPACITATED_CHECKS)) if(incapacitated()) @@ -171,11 +151,6 @@ to_chat(src, span_warning("Unbuckle from [buckled] first.")) return FALSE -/mob/set_currently_z_moving(value) - if(buckled) - return buckled.set_currently_z_moving(value) - return ..() - /mob/living/keybind_face_direction(direction) if(stat != CONSCIOUS) return diff --git a/code/modules/mob/living/living_stripping.dm b/code/modules/mob/living/living_stripping.dm index a0956eb26724..793745cf3a4c 100644 --- a/code/modules/mob/living/living_stripping.dm +++ b/code/modules/mob/living/living_stripping.dm @@ -1,7 +1,7 @@ -/mob/living/proc/should_strip(mob/user) +/mob/living/proc/should_strip(mob/living/user) . = TRUE - if(user.pulling == src && isliving(user)) + if(user.is_grabbing(src) && !user.combat_mode && isliving(user)) var/mob/living/living_user = user if(mob_size < living_user.mob_size) // If we're smaller than user return !mob_pickup_checks(user, FALSE) diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 5cd18ca61fd1..bf02f88c7c64 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -1016,14 +1016,14 @@ set name = "Move Upwards" set category = "IC" - if(eyeobj.zMove(UP, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(eyeobj, UP, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move upwards.")) /mob/living/silicon/ai/down() set name = "Move Down" set category = "IC" - if(eyeobj.zMove(DOWN, z_move_flags = ZMOVE_FEEDBACK)) + if(zstep(eyeobj, DOWN, ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move down.")) /// Proc to hook behavior to the changes of the value of [aiRestorePowerRoutine]. diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm index d0246f3b7b5b..77cb0adf204e 100644 --- a/code/modules/mob/living/silicon/ai/freelook/eye.dm +++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm @@ -101,11 +101,6 @@ if(ai.master_multicam) ai.master_multicam.refresh_view() -/mob/camera/ai_eye/zMove(dir, turf/target, z_move_flags = NONE, recursions_left = 1, list/falling_movs) - . = ..() - if(.) - setLoc(loc, force_update = TRUE) - /mob/camera/ai_eye/Move() return diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm index 8036bd1c0d88..deac77c2ce66 100644 --- a/code/modules/mob/living/silicon/pai/pai_shell.dm +++ b/code/modules/mob/living/silicon/pai/pai_shell.dm @@ -55,7 +55,9 @@ . = fold_out(force) return visible_message(span_notice("[src] deactivates its holochassis emitter and folds back into a compact card!")) - stop_pulling() + + release_all_grabs() + if(ismobholder(loc)) var/obj/item/mob_holder/MH = loc MH.release_mob(display_messages = FALSE) @@ -137,7 +139,7 @@ if(loc != card) visible_message(span_notice("[src] [resting? "lays down for a moment..." : "perks up from the ground."]")) -/mob/living/silicon/pai/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/mob/living/silicon/pai/try_make_grab(atom/movable/target, grab_type) return FALSE /mob/living/silicon/pai/proc/toggle_integrated_light() diff --git a/code/modules/mob/living/silicon/silicon_defense.dm b/code/modules/mob/living/silicon/silicon_defense.dm index cde9424d34f8..65145599894e 100644 --- a/code/modules/mob/living/silicon/silicon_defense.dm +++ b/code/modules/mob/living/silicon/silicon_defense.dm @@ -1,7 +1,3 @@ - -/mob/living/silicon/grippedby(mob/living/user, instant = FALSE) - return //can't upgrade a simple pull into a more aggressive grab. - /mob/living/silicon/get_ear_protection()//no ears return 2 diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm index 4ca684cda8d5..d32c28f24a2f 100644 --- a/code/modules/mob/living/simple_animal/constructs.dm +++ b/code/modules/mob/living/simple_animal/constructs.dm @@ -422,13 +422,14 @@ /mob/living/simple_animal/hostile/construct/harvester/Bump(atom/AM) . = ..() if(istype(AM, /turf/closed/wall/mineral/cult) && AM != loc) //we can go through cult walls - var/atom/movable/stored_pulling = pulling + var/atom/movable/stored_pulling = get_active_grab()?.affecting + var/datum/grab/stored_tier = get_active_grab()?.current_grab.type if(stored_pulling) stored_pulling.setDir(get_dir(stored_pulling.loc, loc)) stored_pulling.forceMove(loc) forceMove(AM) if(stored_pulling) - start_pulling(stored_pulling, supress_message = TRUE) //drag anything we're pulling through the wall with us by magic + try_make_grab(stored_pulling, stored_tier) /mob/living/simple_animal/hostile/construct/harvester/AttackingTarget() if(iscarbon(target)) diff --git a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm index e32880794724..504d25e598b1 100644 --- a/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm +++ b/code/modules/mob/living/simple_animal/friendly/drone/inventory.dm @@ -61,8 +61,8 @@ held_items[index] = null update_held_items() - if(I.pulledby) - I.pulledby.stop_pulling() + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() I.screen_loc = null // will get moved if inventory is visible I.forceMove(src) diff --git a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm index 5d79eca81b66..120e7061df63 100644 --- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm +++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm @@ -56,7 +56,7 @@ return eat_plants() - if(pulledby) + if(LAZYLEN(grabbed_by)) return for(var/direction in shuffle(list(1,2,4,8,5,6,9,10))) diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index ae0f67eb6b85..eb39e577788d 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -300,8 +300,8 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians held_items[index] = null update_held_items() - if(I.pulledby) - I.pulledby.stop_pulling() + if(LAZYLEN(I.grabbed_by)) + I.free_from_all_grabs() I.screen_loc = null // will get moved if inventory is visible I.forceMove(src) diff --git a/code/modules/mob/living/simple_animal/heretic_monsters.dm b/code/modules/mob/living/simple_animal/heretic_monsters.dm index 60b060679590..1cc62a77421f 100644 --- a/code/modules/mob/living/simple_animal/heretic_monsters.dm +++ b/code/modules/mob/living/simple_animal/heretic_monsters.dm @@ -219,7 +219,7 @@ /mob/living/simple_animal/hostile/heretic_summon/armsy/has_gravity(turf/T) return TRUE -/mob/living/simple_animal/hostile/heretic_summon/armsy/can_be_pulled() +/mob/living/simple_animal/hostile/heretic_summon/armsy/can_be_grabbed(mob/living/grabber, target_zone, force) return FALSE /// Updates every body in the chain to force move onto a single tile. diff --git a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm index 0747ca686f1e..da17fbd2fb18 100644 --- a/code/modules/mob/living/simple_animal/hostile/giant_spider.dm +++ b/code/modules/mob/living/simple_animal/hostile/giant_spider.dm @@ -67,7 +67,6 @@ if(poison_per_bite) AddElement(/datum/element/venomous, poison_type, poison_per_bite) - AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") /mob/living/simple_animal/hostile/giant_spider/Login() diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 998ab16d6e65..2263216ecd8b 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -437,7 +437,7 @@ return iswallturf(T) || ismineralturf(T) -/mob/living/simple_animal/hostile/Move(atom/newloc, dir , step_x , step_y) +/mob/living/simple_animal/hostile/Move(atom/newloc, dir, glide_size_override, z_movement_flags) if(dodging && approaching_target && prob(dodge_prob) && moving_diagonally == 0 && isturf(loc) && isturf(newloc)) return dodge(newloc,dir) else diff --git a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm index 31e792d02525..0ad9d727d85d 100644 --- a/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm +++ b/code/modules/mob/living/simple_animal/hostile/jungle/leaper.dm @@ -142,7 +142,7 @@ . = ..() remove_verb(src, /mob/living/verb/pulled) -/mob/living/simple_animal/hostile/jungle/leaper/CtrlClickOn(atom/A) +/mob/living/simple_animal/hostile/jungle/leaper/CtrlClickOn(atom/A, list/params) face_atom(A) GiveTarget(A) if(!isturf(loc)) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index c3fa6d9cf43d..fc22a1e781f6 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -556,7 +556,7 @@ Difficulty: Hard . = ..() if(QDELETED(caster)) return FALSE - if(mover == caster.pulledby) + if(caster.is_grabbing(mover)) return if(istype(mover, /obj/projectile)) var/obj/projectile/P = mover diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index b0512edbe159..42a3050fb213 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -383,7 +383,7 @@ GLOBAL_LIST_INIT(mimic_blacklist, list(/obj/structure/table, /obj/structure/cabl for(var/mob/living/M in contents) if(++mobs_stored >= mob_storage_capacity) return FALSE - L.stop_pulling() + L.release_all_grabs() else if(istype(AM, /obj/structure/closet)) return FALSE diff --git a/code/modules/mob/living/simple_animal/hostile/ooze.dm b/code/modules/mob/living/simple_animal/hostile/ooze.dm index fb6918f2bf81..25e7c53da6ea 100644 --- a/code/modules/mob/living/simple_animal/hostile/ooze.dm +++ b/code/modules/mob/living/simple_animal/hostile/ooze.dm @@ -209,16 +209,17 @@ if(!.) return var/mob/living/simple_animal/hostile/ooze/gelatinous/ooze = owner - if(!isliving(ooze.pulling)) - to_chat(src, span_warning("You need to be pulling a creature for this to work!")) + var/mob/living/target = ooze.get_active_grab()?.affecting + if(!isliving(target)) + to_chat(src, span_warning("You need to be gripping a creature for this to work!")) return FALSE if(vored_mob) to_chat(src, span_warning("You are already consuming another creature!")) return FALSE owner.visible_message(span_warning("[ooze] starts attempting to devour [target]!"), span_notice("You start attempting to devour [target].")) - if(!do_after(ooze, ooze.pulling, 1.5 SECONDS)) + if(!do_after(ooze, target, 1.5 SECONDS)) return FALSE - var/mob/living/eat_target = ooze.pulling + var/mob/living/eat_target = target if(!(eat_target.mob_biotypes & MOB_ORGANIC) || eat_target.stat == DEAD) to_chat(src, span_warning("This creature isn't to my tastes!")) @@ -416,14 +417,15 @@ ///Try to put the pulled mob in a cocoon /datum/action/cooldown/gel_cocoon/proc/gel_cocoon() var/mob/living/simple_animal/hostile/ooze/grapes/ooze = owner - if(!iscarbon(ooze.pulling)) + var/mob/living/carbon/target = ooze.get_active_grab()?.affecting + if(!iscarbon(target)) to_chat(src, span_warning("You need to be pulling an intelligent enough creature to assist it with a cocoon!")) return FALSE owner.visible_message(span_nicegreen("[ooze] starts attempting to put [target] into a gel cocoon!"), span_notice("You start attempting to put [target] into a gel cocoon.")) - if(!do_after(ooze, ooze.pulling, 1.5 SECONDS)) + if(!do_after(ooze, target, 1.5 SECONDS)) return FALSE - put_in_cocoon(ooze.pulling) + put_in_cocoon(target) ooze.adjust_ooze_nutrition(-30) ///Mob needs to have enough nutrition diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index 60fa7abe30a0..25a930ddc5a9 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -715,7 +715,6 @@ return FALSE var/mob/living/simple_animal/hostile/carp/newcarp = new /mob/living/simple_animal/hostile/carp(loc) - newcarp.AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) newcarp.AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") if(!is_listed) diff --git a/code/modules/mob/living/simple_animal/parrot.dm b/code/modules/mob/living/simple_animal/parrot.dm index 1a1670c19318..c9be88c0b46b 100644 --- a/code/modules/mob/living/simple_animal/parrot.dm +++ b/code/modules/mob/living/simple_animal/parrot.dm @@ -377,7 +377,7 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list( ..() //Sprite update for when a parrot gets pulled - if(pulledby && !stat && parrot_state != PARROT_WANDER) + if(LAZYLEN(grabbed_by) && !stat && parrot_state != PARROT_WANDER) if(buckled) buckled.unbuckle_mob(src, TRUE) buckled = null diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 69a9ef18b6a2..4cbf4746e625 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -263,7 +263,7 @@ turns_since_move++ if(turns_since_move < turns_per_move) return TRUE - if(stop_automated_movement_when_pulled && pulledby) //Some animals don't move when pulled + if(stop_automated_movement_when_pulled && LAZYLEN(grabbed_by)) //Some animals don't move when pulled return TRUE var/anydir = pick(GLOB.cardinals) if(Process_Spacemove(anydir)) @@ -309,7 +309,7 @@ /mob/living/simple_animal/proc/environment_air_is_safe() . = TRUE - if(pulledby && pulledby.grab_state >= GRAB_KILL && atmos_requirements["min_oxy"]) + if(HAS_TRAIT(src, TRAIT_KILL_GRAB) && atmos_requirements["min_oxy"]) . = FALSE //getting choked if(isturf(loc) && isopenturf(loc)) @@ -648,7 +648,7 @@ stack_trace("Something attempted to set simple animals AI to an invalid state: [togglestatus]") /mob/living/simple_animal/proc/consider_wakeup() - if (pulledby || shouldwakeup) + if (LAZYLEN(grabbed_by) || shouldwakeup) toggle_ai(AI_ON) /mob/living/simple_animal/on_changed_z_level(turf/old_turf, turf/new_turf) diff --git a/code/modules/mob/living/simple_animal/slime/life.dm b/code/modules/mob/living/simple_animal/slime/life.dm index d02608454d5c..89e5f1a0e8ce 100644 --- a/code/modules/mob/living/simple_animal/slime/life.dm +++ b/code/modules/mob/living/simple_animal/slime/life.dm @@ -357,7 +357,7 @@ else if(holding_still) holding_still = max(holding_still - (0.5 * delta_time), 0) - else if (docile && pulledby) + else if (docile && LAZYLEN(grabbed_by)) holding_still = 10 else if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(loc) && prob(33)) step(src, pick(GLOB.cardinals)) diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index 05c1e3fbd938..fd1e6d7d548c 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -270,7 +270,7 @@ /mob/living/simple_animal/slime/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) return -/mob/living/simple_animal/slime/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE) +/mob/living/simple_animal/slime/try_make_grab(atom/movable/target, grab_type) return /mob/living/simple_animal/slime/attack_ui(slot, params) diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 6832cae61937..368bbe87ade8 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -80,8 +80,9 @@ GLOBAL_LIST_INIT(bodyzone_miss_chance, list( if(target.buckled || target.body_position == LYING_DOWN) return zone // if your target is being grabbed aggressively by someone you cannot miss either - if(target.pulledby) - return zone + for(var/obj/item/hand_item/grab/G in target.grabbed_by) + if(G.current_grab.stop_move) + return zone var/miss_chance = GLOB.bodyzone_miss_chance[zone] @@ -516,3 +517,7 @@ GLOBAL_LIST_INIT(bodyzone_miss_chance, list( /// Can this mob SMELL THE SMELLY SMELLS? /mob/proc/can_smell(intensity) return FALSE + +//returns the number of size categories between two mob_sizes, rounded. Positive means A is larger than B +/proc/mob_size_difference(mob_size_A, mob_size_B) + return round(log(2, mob_size_A/mob_size_B), 1) diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index 9500c1abc49b..d97407867a9a 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -104,7 +104,7 @@ if(isAI(mob)) return AIMove(new_loc,direct,mob) - if(Process_Grab()) //are we restrained by someone's grip? + if(Process_Grabs()) //are we restrained by someone's grip? return if(mob.buckled) //if we're buckled to something, tell it we moved. @@ -162,28 +162,20 @@ // as a result of player input and not because they were pulled or any other magic. SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOVED) - var/atom/movable/P = mob.pulling - if(P && !ismob(P) && P.density && !HAS_TRAIT(P, TRAIT_KEEP_DIRECTION_WHILE_PULLING)) - mob.setDir(turn(mob.dir, 180)) - /** * Checks to see if you're being grabbed and if so attempts to break it * * Called by client/Move() */ -/client/proc/Process_Grab() - if(!mob.pulledby) - return FALSE - if(mob.pulledby == mob.pulling && mob.pulledby.grab_state == GRAB_PASSIVE) //Don't autoresist passive grabs if we're grabbing them too. - return FALSE +/client/proc/Process_Grabs() if(HAS_TRAIT(mob, TRAIT_INCAPACITATED)) COOLDOWN_START(src, move_delay, 1 SECONDS) - return TRUE + return FALSE else if(HAS_TRAIT(mob, TRAIT_RESTRAINED)) COOLDOWN_START(src, move_delay, 1 SECONDS) to_chat(src, span_warning("You're restrained! You can't move!")) - return TRUE - return mob.resist_grab(TRUE) + return FALSE + return !mob.resist_grab(TRUE) /** @@ -348,8 +340,16 @@ continue if(rebound.anchored) return rebound - if(pulling == rebound) - continue + if(isliving(rebound)) + var/mob/living/L = rebound + var/_continue = FALSE + if(LAZYLEN(L.grabbed_by)) + for(var/obj/item/hand_item/grab/G in L.grabbed_by) + if(G.assailant == src) + _continue = TRUE + break + if(_continue) + continue return rebound /mob/has_gravity() @@ -539,7 +539,10 @@ var/atom/loc_atom = loc return loc_atom.relaymove(src, UP) - if(zMove(UP, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK)) + //Human's up() override has its own feedback + var/flags = ishuman(src) ? ZMOVE_FLIGHT_FLAGS : ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK + . = zstep(src, UP, flags) + if(.) to_chat(src, span_notice("You move upwards.")) ///Moves a mob down a z level @@ -551,8 +554,9 @@ var/atom/loc_atom = loc return loc_atom.relaymove(src, DOWN) - if(zMove(DOWN, z_move_flags = ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK)) + if(zstep(src, DOWN, ZMOVE_FLIGHT_FLAGS|ZMOVE_FEEDBACK)) to_chat(src, span_notice("You move down.")) + return FALSE /mob/abstract_move(atom/destination) diff --git a/code/modules/mod/mod_ai.dm b/code/modules/mod/mod_ai.dm index 61ffeca5e997..cd6052039021 100644 --- a/code/modules/mod/mod_ai.dm +++ b/code/modules/mod/mod_ai.dm @@ -74,7 +74,7 @@ #define AI_FALL_TIME 1 SECONDS /obj/item/mod/control/relaymove(mob/user, direction) - if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || (wearer?.pulledby?.grab_state > GRAB_PASSIVE)) + if((!active && wearer) || get_charge() < CHARGE_PER_STEP || user != ai || !COOLDOWN_FINISHED(src, cooldown_mod_move) || HAS_TRAIT(wearer, TRAIT_AGGRESSIVE_GRAB)) return FALSE var/timemodifier = MOVE_DELAY * (ISDIAGONALDIR(direction) ? 2 : 1) * (wearer ? WEARER_DELAY : LONE_DELAY) if(wearer && !wearer.Process_Spacemove(direction)) diff --git a/code/modules/mod/modules/modules_maint.dm b/code/modules/mod/modules/modules_maint.dm index 418c9b49671a..dbf71dc5cdb4 100644 --- a/code/modules/mod/modules/modules_maint.dm +++ b/code/modules/mod/modules/modules_maint.dm @@ -268,7 +268,7 @@ qdel(mod.wearer.RemoveElement(/datum/element/forced_gravity, NEGATIVE_GRAVITY)) UnregisterSignal(mod.wearer, COMSIG_MOVABLE_MOVED) REMOVE_TRAIT(mod.wearer, TRAIT_SILENT_FOOTSTEPS, MOD_TRAIT) - mod.wearer.zFall(falling_from_move = TRUE) + mod.wearer.zFall() /obj/item/mod/module/atrocinator/proc/check_upstairs() SIGNAL_HANDLER diff --git a/code/modules/mod/modules/modules_supply.dm b/code/modules/mod/modules/modules_supply.dm index 698dbde97131..12fd3b3d0206 100644 --- a/code/modules/mod/modules/modules_supply.dm +++ b/code/modules/mod/modules/modules_supply.dm @@ -315,8 +315,8 @@ . = ..() if(!.) return - if(istype(mod.wearer.pulling, /obj/structure/closet)) - var/obj/structure/closet/locker = mod.wearer.pulling + var/obj/structure/closet/locker = mod.wearer.get_active_grab()?.affecting + if(istype(locker, /obj/structure/closet)) playsound(locker, 'sound/effects/gravhit.ogg', 75, TRUE) locker.forceMove(mod.wearer.loc) locker.throw_at(target, range = 7, speed = 4, thrower = mod.wearer) @@ -324,7 +324,7 @@ if(!istype(target, /obj/structure/closet) || !(target in view(mod.wearer))) balloon_alert(mod.wearer, "invalid target!") return - var/obj/structure/closet/locker = target + if(locker.anchored || locker.move_resist >= MOVE_FORCE_OVERPOWERING) balloon_alert(mod.wearer, "target anchored!") return @@ -337,23 +337,24 @@ . = ..() if(!.) return - if(istype(mod.wearer.pulling, /obj/structure/closet)) - mod.wearer.stop_pulling() + for(var/obj/item/hand_item/grab/G in mod.wearer.get_active_grabs()) + if(istype(G.affecting, /obj/structure/closet)) + qdel(G) /obj/item/mod/module/magnet/proc/check_locker(obj/structure/closet/locker) if(!mod?.wearer) return if(!locker.Adjacent(mod.wearer) || !isturf(locker.loc) || !isturf(mod.wearer.loc)) return - mod.wearer.start_pulling(locker) + mod.wearer.try_make_grab(locker) locker.strong_grab = TRUE - RegisterSignal(locker, COMSIG_ATOM_NO_LONGER_PULLED, PROC_REF(on_stop_pull)) + RegisterSignal(locker, COMSIG_ATOM_NO_LONGER_GRABBED, PROC_REF(on_stop_pull)) /obj/item/mod/module/magnet/proc/on_stop_pull(obj/structure/closet/locker, atom/movable/last_puller) SIGNAL_HANDLER locker.strong_grab = FALSE - UnregisterSignal(locker, COMSIG_ATOM_NO_LONGER_PULLED) + UnregisterSignal(locker, COMSIG_ATOM_NO_LONGER_GRABBED) /obj/item/mod/module/ash_accretion name = "MOD ash accretion module" diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm index 6d80b4df1fbb..30273877b6bb 100644 --- a/code/modules/modular_computers/computers/item/tablet.dm +++ b/code/modules/modular_computers/computers/item/tablet.dm @@ -73,7 +73,7 @@ remove_pen(user) -/obj/item/modular_computer/tablet/CtrlClick(mob/user) +/obj/item/modular_computer/tablet/CtrlClick(mob/user, list/params) . = ..() if(.) return diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index 38fe0bfc3833..50883711c836 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -83,7 +83,7 @@ blacklisted_movetypes = (FLYING|FLOATING) variable = TRUE -/datum/movespeed_modifier/bulky_drag +/datum/movespeed_modifier/grabbing variable = TRUE /datum/movespeed_modifier/cold diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm new file mode 100644 index 000000000000..2bab8a8a5634 --- /dev/null +++ b/code/modules/multiz/movement.dm @@ -0,0 +1,273 @@ +/// A step() variant that allows passing z_movement_flags. Normal step() is fine if you do not need special movement flags. +/proc/zstep(atom/movable/mover, dir, z_movement_flags) + if(!istype(mover)) + return + + var/turf/destination = get_step_multiz(mover, dir) + if(!mover.can_z_move(dir, z_move_flags = z_movement_flags)) + return FALSE + + if(!destination) + if(z_movement_flags & ZMOVE_FEEDBACK) + to_chat(mover, span_warning("There is nothing of interest in that direction.")) + return FALSE + return mover.Move(destination, dir, null, z_movement_flags) + +/atom/movable/proc/onZImpact(turf/impacted_turf, levels, message = TRUE) + if(message) + visible_message( + span_danger("[src] slams into [impacted_turf]!"), + blind_message = span_hear("You hear something slam into the deck.") + ) + + INVOKE_ASYNC(src, PROC_REF(SpinAnimation), 5, 2) + return TRUE + +/** + * Checks if the destination turf is elegible for z movement from the start turf to a given direction and returns it if so. + * Args: + * * direction: the direction to go, UP or DOWN, only relevant if target is null. + * * start: Each destination has a starting point on the other end. This is it. Most of the times the location of the source. + * * z_move_flags: bitflags used for various checks. See __DEFINES/movement.dm. + * * rider: A living mob in control of the movable. Only non-null when a mob is riding a vehicle through z-levels. + */ +/atom/movable/proc/can_z_move(direction, turf/start, z_move_flags = ZMOVE_FLIGHT_FLAGS, mob/living/rider) + if(!start) + start = get_turf(src) + if(!start) + CRASH("Something tried to zMove from nullspace.") + + if(!(direction & (UP|DOWN))) + CRASH("can_z_move() received an invalid direction ([direction || "null"])") + + var/turf/destination = get_step_multiz(start, direction) + if(!destination) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_notice("There is nothing of interest in this direction.")) + return FALSE + + // Check flight + if(z_move_flags & ZMOVE_CAN_FLY_CHECKS) + if(!(movement_type & (FLYING|FLOATING)) && has_gravity(start) && (direction == UP)) + if(z_move_flags & ZMOVE_FEEDBACK) + if(rider) + to_chat(rider, span_warning("[src] is is not capable of flight.")) + else + to_chat(src, span_warning("You stare at [destination].")) + return FALSE + + // Check CanZPass + if(!(z_move_flags & ZMOVE_IGNORE_OBSTACLES)) + // Check exit + if(!start.CanZPass(src, direction, z_move_flags)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("[start] is in the way.")) + return FALSE + + // Check enter + if(!destination.CanZPass(src, direction, z_move_flags)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("You bump against [destination].")) + return FALSE + + if(!(z_move_flags & ZMOVE_SKIP_CANMOVEONTO)) + // Check destination movable CanPass + for(var/atom/movable/A as anything in destination) + if(!A.CanMoveOnto(src, direction)) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("You are blocked by [A].")) + return FALSE + + // Check if we would fall. + if(z_move_flags & ZMOVE_FALL_CHECKS) + if((throwing || (movement_type & (FLYING|FLOATING)) || !has_gravity(start))) + if(z_move_flags & ZMOVE_FEEDBACK) + to_chat(rider || src, span_warning("You see nothing to hold onto.")) + return FALSE + + return destination //used by some child types checks and zMove() + +/// Precipitates a movable (plus whatever buckled to it) to lower z levels if possible and then calls zImpact() +/atom/movable/proc/zFall(levels = 1, force = FALSE) + if(QDELETED(src)) + return FALSE + + var/direction = DOWN + if(has_gravity() == NEGATIVE_GRAVITY) + direction = UP + + var/turf/target = direction == UP ? GetAbove(src) : GetBelow(src) + if(!target) + return FALSE + + var/isliving = isliving(src) + if(!isliving && !isobj(src)) + return + + if(isliving) + var/mob/living/falling_living = src + //relay this mess to whatever the mob is buckled to. + if(falling_living.buckled) + return falling_living.buckled.zFall(levels, force) + + if(!force && !can_z_move(direction, get_turf(src), direction == DOWN ? ZMOVE_SKIP_CANMOVEONTO|ZMOVE_FALL_FLAGS : ZMOVE_FALL_FLAGS)) + return FALSE + + if(!CanZFall(get_turf(src), direction)) + return FALSE + + . = TRUE + + spawn(0) + _doZFall(target, get_turf(src), levels) + +/atom/movable/proc/_doZFall(turf/destination, turf/prev_turf, levels) + PRIVATE_PROC(TRUE) + SHOULD_NOT_OVERRIDE(TRUE) + + if(QDELETED(src)) + return + + forceMoveWithGroup(destination, z_movement = ZMOVING_VERTICAL) + destination.zImpact(src, levels, prev_turf) + +/atom/movable/proc/CanZFall(turf/from, direction, anchor_bypass) + if(anchored && !anchor_bypass) + return FALSE + + if(from) + for(var/obj/O in from) + if(O.obj_flags & BLOCK_Z_FALL) + return FALSE + + var/turf/other = direction == UP ? GetAbove(from) : GetBelow(from) + if(!from.CanZPass(from, direction) || !other?.CanZPass(from, direction)) //Kinda hacky but it does work. + return FALSE + + return TRUE + +/// Returns an object we can climb onto +/atom/movable/proc/check_zclimb() + var/turf/above = GetAbove(src) + if(!above?.CanZPass(src, UP)) + return + + var/list/all_turfs_above = get_adjacent_open_turfs(above) + + //Check directly above first + . = get_climbable_surface(above) + if(.) + return + + //Next, try the direction the mob is facing + . = get_climbable_surface(get_step(above, dir)) + if(.) + return + + for(var/turf/T as turf in all_turfs_above) + . = get_climbable_surface(T) + if(.) + return + +/atom/movable/proc/get_climbable_surface(turf/T) + var/climb_target + if(!T.Enter(src)) + return + if(!isopenspaceturf(T) && isfloorturf(T)) + climb_target = T + else + for(var/obj/I in T) + if(I.obj_flags & BLOCK_Z_FALL) + climb_target = I + break + + if(climb_target) + return climb_target + +/atom/movable/proc/ClimbUp(atom/onto) + if(!isturf(loc)) + return FALSE + + var/turf/above = GetAbove(src) + var/turf/destination = get_turf(onto) + if(!above || !above.Adjacent(destination, mover = src)) + return FALSE + + if(has_gravity() > 0) + var/can_overcome + for(var/atom/A in loc) + if(HAS_TRAIT(A, TRAIT_CLIMBABLE)) + can_overcome = TRUE + break; + + if(!can_overcome) + var/list/objects_to_stand_on = list( + /obj/structure/chair, + /obj/structure/bed, + /obj/structure/lattice + ) + for(var/path in objects_to_stand_on) + if(locate(path) in loc) + can_overcome = TRUE + break; + if(!can_overcome) + to_chat(src, span_warning("You cannot reach [onto] from here!")) + return FALSE + + visible_message( + span_notice("[src] starts climbing onto \the [onto]."), + span_notice("You start climbing onto \the [onto].") + ) + + if(!do_after(src, time = 5 SECONDS, timed_action_flags = DO_PUBLIC, display = image('icons/hud/do_after.dmi', "help"))) + return FALSE + + visible_message( + span_notice("[src] climbs onto \the [onto]."), + span_notice("You climb onto \the [onto].") + ) + + var/oldloc = loc + setDir(get_dir(above, destination)) + . = Move(destination, null, null, ZMOVE_INCAPACITATED_CHECKS) + if(.) + playsound(oldloc, 'sound/effects/stairs_step.ogg', 50) + playsound(destination, 'sound/effects/stairs_step.ogg', 50) + +/// Returns a list of movables that should also be affected when calling forceMoveWithGroup() +/atom/movable/proc/get_move_group() + SHOULD_CALL_PARENT(TRUE) + RETURN_TYPE(/list) + . = list(src) + if(length(buckled_mobs)) + . |= buckled_mobs + +/// forceMove() wrapper to include things like buckled mobs. +/atom/movable/proc/forceMoveWithGroup(destination, z_movement) + var/list/movers = get_move_group() + for(var/atom/movable/AM as anything in movers) + if(z_movement) + AM.set_currently_z_moving(z_movement) + AM.forceMove(destination) + if(z_movement) + AM.set_currently_z_moving(FALSE) +/* +/** + * We want to relay the zmovement to the buckled atom when possible + * and only run what we can't have on buckled.zMove() or buckled.can_z_move() here. + * This way we can avoid esoteric bugs, copypasta and inconsistencies. + */ +/mob/living/zMove(dir, turf/target, z_move_flags = ZMOVE_FLIGHT_FLAGS) + if(buckled) + if(buckled.currently_z_moving) + return FALSE + if(!(z_move_flags & ZMOVE_ALLOW_BUCKLED)) + buckled.unbuckle_mob(src, force = TRUE, can_fall = FALSE) + else + if(!target) + target = can_z_move(dir, get_turf(src), z_move_flags, src) + if(!target) + return FALSE + return buckled.zMove(dir, target, z_move_flags) // Return value is a loc. + return ..() +*/ diff --git a/code/modules/projectiles/aiming.dm b/code/modules/projectiles/aiming.dm index 8b40dc161b4c..968ca42c5552 100644 --- a/code/modules/projectiles/aiming.dm +++ b/code/modules/projectiles/aiming.dm @@ -34,7 +34,7 @@ RegisterSignal(user, COMSIG_PARENT_QDELETING, PROC_REF(target_del)) RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(check_sight)) RegisterSignal(user, COMSIG_MOB_FIRED_GUN, PROC_REF(user_shot)) - RegisterSignal(user, list(COMSIG_PARENT_ATTACKBY, COMSIG_LIVING_GET_PULLED, COMSIG_HUMAN_DISARM_HIT), PROC_REF(trigger)) + RegisterSignal(user, list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_GET_GRABBED, COMSIG_HUMAN_DISARM_HIT), PROC_REF(trigger)) RegisterSignal( user, list( diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index bbc98258780d..ffee0e6687ea 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -161,32 +161,33 @@ if(recoil && !tk_firing(user)) shake_camera(user, recoil + 1, recoil) fire_sounds() - if(!suppressed) - if(message) - if(tk_firing(user)) - visible_message( - span_danger("[src] fires itself[pointblank ? " point blank at [pbtarget]!" : "!"]"), - blind_message = span_hear("You hear a gunshot!"), - vision_distance = COMBAT_MESSAGE_RANGE - ) - else if(pointblank) - user.visible_message( - span_danger("[user] fires [src] point blank at [pbtarget]!"), - span_danger("You fire [src] point blank at [pbtarget]!"), - span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE, pbtarget - ) - to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!")) - if(pb_knockback > 0 && ismob(pbtarget)) - var/mob/PBT = pbtarget - var/atom/throw_target = get_edge_target_turf(PBT, user.dir) - PBT.throw_at(throw_target, pb_knockback, 2) - else if(!tk_firing(user)) - user.visible_message( - span_danger("[user] fires [src]!"), - blind_message = span_hear("You hear a gunshot!"), - vision_distance = COMBAT_MESSAGE_RANGE, - ignored_mobs = user - ) + if(suppressed || !message) + return + + if(tk_firing(user)) + visible_message( + span_danger("[src] fires itself[pointblank ? " point blank at [pbtarget]!" : "!"]"), + blind_message = span_hear("You hear a gunshot!"), + vision_distance = COMBAT_MESSAGE_RANGE + ) + else if(pointblank) + user.visible_message( + span_danger("[user] fires [src] point blank at [pbtarget]!"), + span_danger("You fire [src] point blank at [pbtarget]!"), + span_hear("You hear a gunshot!"), COMBAT_MESSAGE_RANGE, pbtarget + ) + to_chat(pbtarget, span_userdanger("[user] fires [src] point blank at you!")) + if(pb_knockback > 0 && ismob(pbtarget)) + var/mob/PBT = pbtarget + var/atom/throw_target = get_edge_target_turf(PBT, user.dir) + PBT.throw_at(throw_target, pb_knockback, 2) + else if(!tk_firing(user)) + user.visible_message( + span_danger("[user] fires [src]!"), + blind_message = span_hear("You hear a gunshot!"), + vision_distance = COMBAT_MESSAGE_RANGE, + ignored_mobs = user + ) /obj/item/gun/emp_act(severity) . = ..() diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm index da949c73599a..195039bdd985 100644 --- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm @@ -549,16 +549,17 @@ dancer.spin(30, 2) if(dancer.disgust < 40) dancer.adjust_disgust(10) - if(!dancer.pulledby) + if(!LAZYLEN(dancer.grabbed_by)) return var/dancer_turf = get_turf(dancer) - var/atom/movable/dance_partner = dancer.pulledby - dance_partner.visible_message(span_danger("[dance_partner] tries to hold onto [dancer], but is thrown back!"), span_danger("You try to hold onto [dancer], but you are thrown back!"), null, COMBAT_MESSAGE_RANGE) - var/throwtarget = get_edge_target_turf(dancer_turf, get_dir(dancer_turf, get_step_away(dance_partner, dancer_turf))) - if(overdosed) - dance_partner.throw_at(target = throwtarget, range = 7, speed = 4) - else - dance_partner.throw_at(target = throwtarget, range = 4, speed = 1) //superspeed + for(var/obj/item/hand_item/grab/G in dancer.grabbed_by) + var/atom/movable/dance_partner = G.assailant + dance_partner.visible_message(span_danger("[dance_partner] tries to hold onto [dancer], but is thrown back!"), span_danger("You try to hold onto [dancer], but you are thrown back!"), null, COMBAT_MESSAGE_RANGE) + var/throwtarget = get_edge_target_turf(dancer_turf, get_dir(dancer_turf, get_step_away(dance_partner, dancer_turf))) + if(overdosed) + dance_partner.throw_at(target = throwtarget, range = 7, speed = 4) + else + dance_partner.throw_at(target = throwtarget, range = 4, speed = 1) //superspeed /datum/reagent/drug/saturnx name = "SaturnX" diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index b16c91286531..b87445232e99 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -295,11 +295,13 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) // attack with hand, move pulled object onto conveyor -/obj/machinery/conveyor/attack_hand(mob/user, list/modifiers) +/obj/machinery/conveyor/attack_hand(mob/living/user, list/modifiers) . = ..() if(.) return - user.Move_Pulled(src) + + if(!istype(user)) + user.move_grabbed_atoms_towards(src) /obj/machinery/conveyor/power_change() . = ..() diff --git a/code/modules/recycling/sortingmachinery.dm b/code/modules/recycling/sortingmachinery.dm index 511b1d05759a..f2ede34647d7 100644 --- a/code/modules/recycling/sortingmachinery.dm +++ b/code/modules/recycling/sortingmachinery.dm @@ -381,7 +381,7 @@ new_barcode.cut_multiplier = cut_multiplier // Also the registered percent cut. user.put_in_hands(new_barcode) -/obj/item/sales_tagger/CtrlClick(mob/user) +/obj/item/sales_tagger/CtrlClick(mob/user, list/params) . = ..() payments_acc = null to_chat(user, span_notice("You clear the registered account.")) diff --git a/code/modules/religion/religion_structures.dm b/code/modules/religion/religion_structures.dm index adc4c2e53b9e..c9c41175cb3b 100644 --- a/code/modules/religion/religion_structures.dm +++ b/code/modules/religion/religion_structures.dm @@ -30,20 +30,21 @@ new_overlays += "convertaltarcandle" return new_overlays -/obj/structure/altar_of_gods/attack_hand(mob/living/user, list/modifiers) - if(!Adjacent(user) || !user.pulling) - return ..() - if(!isliving(user.pulling)) - return ..() - var/mob/living/pushed_mob = user.pulling +/obj/structure/altar_of_gods/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + . = ..() + if(!isliving(victim)) + return + + var/mob/living/pushed_mob = victim if(pushed_mob.buckled) to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!")) - return ..() + return TRUE + to_chat(user, span_notice("You try to coax [pushed_mob] onto [src]...")) if(!do_after(user,(5 SECONDS),target = pushed_mob)) - return ..() + return TRUE pushed_mob.forceMove(loc) - return ..() + return TRUE /obj/structure/altar_of_gods/examine_more(mob/user) if(!isobserver(user)) @@ -104,7 +105,7 @@ new /obj/effect/decal/cleanable/ash(drop_location()) qdel(src) -/obj/item/ritual_totem/can_be_pulled(user, grab_state, force) +/obj/item/ritual_totem/can_be_grabbed(mob/living/grabber, target_zone, force) . = ..() return FALSE //no diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm index dc5443e58006..4ae96e9a1b21 100644 --- a/code/modules/research/designs/wiremod_designs.dm +++ b/code/modules/research/designs/wiremod_designs.dm @@ -218,11 +218,6 @@ id = "comp_split" build_path = /obj/item/circuit_component/split -/datum/design/component/pull - name = "Pull Component" - id = "comp_pull" - build_path = /obj/item/circuit_component/pull - /datum/design/component/soundemitter name = "Sound Emitter Component" id = "comp_soundemitter" diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index 2f0872a673e0..e59677a3674c 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -882,11 +882,11 @@ /datum/status_effect/stabilized/black/proc/on_grab(mob/living/source, new_state) SIGNAL_HANDLER - if(new_state < GRAB_KILL || !isliving(source.pulling)) + if(new_state < GRAB_KILL || !isliving(source.get_active_grab()?.affecting)) draining_ref = null return - var/mob/living/draining = source.pulling + var/mob/living/draining = source.get_active_grab()?.affecting if(draining.stat == DEAD) return @@ -902,7 +902,8 @@ return span_warning("[owner.p_they(TRUE)] [owner.p_are()] draining health from [draining]!") /datum/status_effect/stabilized/black/tick() - if(owner.grab_state < GRAB_KILL || !IS_WEAKREF_OF(owner.pulling, draining_ref)) + var/obj/item/hand_item/grab/G = owner.get_active_grab() + if(G.current_grab.damage_stage < GRAB_KILL || !IS_WEAKREF_OF(G.affecting, draining_ref)) return var/mob/living/drained = draining_ref.resolve() diff --git a/code/modules/research/xenobiology/xenobio_camera.dm b/code/modules/research/xenobiology/xenobio_camera.dm index e7bd35a169ce..baaa3b87ee61 100644 --- a/code/modules/research/xenobiology/xenobio_camera.dm +++ b/code/modules/research/xenobiology/xenobio_camera.dm @@ -322,17 +322,17 @@ ..() //scans slimes -/mob/living/simple_animal/slime/CtrlClick(mob/user) +/mob/living/simple_animal/slime/CtrlClick(mob/user, list/params) SEND_SIGNAL(user, COMSIG_XENO_SLIME_CLICK_CTRL, src) ..() //picks up dead monkies -/mob/living/carbon/human/species/monkey/CtrlClick(mob/user) +/mob/living/carbon/human/species/monkey/CtrlClick(mob/user, list/params) SEND_SIGNAL(user, COMSIG_XENO_MONKEY_CLICK_CTRL, src) ..() //places monkies -/turf/open/CtrlClick(mob/user) +/turf/open/CtrlClick(mob/user, list/params) SEND_SIGNAL(user, COMSIG_XENO_TURF_CLICK_CTRL, src) ..() diff --git a/code/modules/shuttle/on_move.dm b/code/modules/shuttle/on_move.dm index 9e3d2289b2ea..fe5df4134cc6 100644 --- a/code/modules/shuttle/on_move.dm +++ b/code/modules/shuttle/on_move.dm @@ -28,9 +28,7 @@ All ShuttleMove procs go here var/mob/living/M = thing if(M.buckled) M.buckled.unbuckle_mob(M, 1) - if(M.pulledby) - M.pulledby.stop_pulling() - M.stop_pulling() + M.release_all_grabs() M.visible_message(span_warning("[shuttle] slams into [M]!")) SSblackbox.record_feedback("tally", "shuttle_gib", 1, M.type) log_attack("[key_name(M)] was shuttle gibbed by [shuttle].") diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index b33cee2b2dc6..d695e9e7c42b 100644 --- a/code/modules/shuttle/special.dm +++ b/code/modules/shuttle/special.dm @@ -226,183 +226,6 @@ if(ID && (ACCESS_CENT_BAR in ID.access)) return TRUE -//Luxury Shuttle Blockers - -/obj/machinery/scanner_gate/luxury_shuttle - name = "luxury shuttle ticket field" - density = FALSE //allows shuttle airlocks to close, nothing but an approved passenger gets past CanPass - locked = TRUE - use_power = NO_POWER_USE - resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - speech_span = SPAN_ROBOT - var/threshold = 500 - var/static/list/approved_passengers = list() - var/static/list/check_times = list() - var/list/payees = list() - -/obj/machinery/scanner_gate/luxury_shuttle/CanAllowThrough(atom/movable/mover, border_dir) - . = ..() - - if(mover in approved_passengers) - set_scanline("scanning", 10) - if(isvehicle(mover)) - var/obj/vehicle/vehicle = mover - for(var/mob/living/rat in vehicle.occupants) - if(!(rat in approved_passengers)) - say("Stowaway detected. Please exit the vehicle first.") - return FALSE - return TRUE - if(isitem(mover)) - return TRUE - if(isstructure(mover)) - var/obj/structure/struct = mover - for(var/mob/living/rat in struct.contents) - say("Stowaway detected. Please exit the structure first.") - return FALSE - return TRUE - - return FALSE - -/obj/machinery/scanner_gate/luxury_shuttle/auto_scan(atom/movable/AM) - return - -/obj/machinery/scanner_gate/luxury_shuttle/attackby(obj/item/W, mob/user, params) - return - -/obj/machinery/scanner_gate/luxury_shuttle/emag_act(mob/user) - return - -#define LUXURY_MESSAGE_COOLDOWN 100 -/obj/machinery/scanner_gate/luxury_shuttle/BumpedBy(atom/movable/AM) - ///If the atom entering the gate is a vehicle, we store it here to add to the approved list to enter/leave the scanner gate. - var/obj/vehicle/vehicle - ///We store the driver of vehicles separately so that we can add them to the approved list once payment is fully processed. - var/mob/living/driver_holdout - if(!isliving(AM) && !isvehicle(AM)) - alarm_beep() - return ..() - - var/datum/bank_account/account - if(istype(AM.pulling, /obj/item/card/id)) - var/obj/item/card/id/I = AM.pulling - if(I.registered_account) - account = I.registered_account - else if(!check_times[AM] || check_times[AM] < world.time) //Let's not spam the message - to_chat(AM, span_notice("This ID card doesn't have an owner associated with it!")) - check_times[AM] = world.time + LUXURY_MESSAGE_COOLDOWN - else if(isliving(AM)) - var/mob/living/L = AM - account = L.get_bank_account() - - else if(isvehicle(AM)) - vehicle = AM - for(var/passenger in vehicle.occupants) - if(!isliving(passenger)) - continue - var/mob/living/rider = passenger - if(vehicle.is_driver(rider)) - driver_holdout = rider - var/obj/item/card/id/id = rider.get_idcard(TRUE) - account = id?.registered_account - break - - if(account) - if(account.account_balance < threshold - payees[AM]) - account.adjust_money(-account.account_balance) - payees[AM] += account.account_balance - else - var/money_owed = threshold - payees[AM] - account.adjust_money(-money_owed) - payees[AM] += money_owed - - //Here is all the possible paygate payment methods. - var/list/counted_money = list() - for(var/obj/item/coin/C in AM.get_all_contents()) //Coins. - if(payees[AM] >= threshold) - break - payees[AM] += C.value - counted_money += C - for(var/obj/item/stack/spacecash/S in AM.get_all_contents()) //Paper Cash - if(payees[AM] >= threshold) - break - payees[AM] += S.value * S.amount - counted_money += S - for(var/obj/item/holochip/H in AM.get_all_contents()) //Holocredits - if(payees[AM] >= threshold) - break - payees[AM] += H.credits - counted_money += H - - if(payees[AM] < threshold && istype(AM.pulling, /obj/item/coin)) //Coins(Pulled). - var/obj/item/coin/C = AM.pulling - payees[AM] += C.value - counted_money += C - - else if(payees[AM] < threshold && istype(AM.pulling, /obj/item/stack/spacecash)) //Cash(Pulled). - var/obj/item/stack/spacecash/S = AM.pulling - payees[AM] += S.value * S.amount - counted_money += S - - else if(payees[AM] < threshold && istype(AM.pulling, /obj/item/holochip)) //Holocredits(pulled). - var/obj/item/holochip/H = AM.pulling - payees[AM] += H.credits - counted_money += H - - if(payees[AM] < threshold) //Suggestions for those with no arms/simple animals. - var/armless - if(!ishuman(AM) && !istype(AM, /mob/living/simple_animal/slime)) - armless = TRUE - else - var/mob/living/carbon/human/H = AM - if(!H.get_bodypart(BODY_ZONE_L_ARM) && !H.get_bodypart(BODY_ZONE_R_ARM)) - armless = TRUE - - if(armless) - if(!AM.pulling || !iscash(AM.pulling) && !istype(AM.pulling, /obj/item/card/id)) - if(!check_times[AM] || check_times[AM] < world.time) //Let's not spam the message - to_chat(AM, span_notice("Try pulling a valid ID, space cash, holochip or coin into \the [src]!")) - check_times[AM] = world.time + LUXURY_MESSAGE_COOLDOWN - - if(payees[AM] >= threshold) - for(var/obj/I in counted_money) - qdel(I) - payees[AM] -= threshold - - var/change = FALSE - if(payees[AM] > 0) - change = TRUE - var/obj/item/holochip/HC = new /obj/item/holochip(AM.loc) //Change is made in holocredits exclusively. - HC.credits = payees[AM] - HC.name = "[HC.credits] credit holochip" - if(istype(AM, /mob/living/carbon/human)) - var/mob/living/carbon/human/H = AM - if(!H.put_in_hands(HC)) - AM.pulling = HC - else - AM.pulling = HC - payees[AM] -= payees[AM] - - say("Welcome to first class, [driver_holdout ? "[driver_holdout]" : "[AM]" ]![change ? " Here is your change." : ""]") - approved_passengers |= AM - if(vehicle) - approved_passengers |= vehicle - if(driver_holdout) - approved_passengers |= driver_holdout - - check_times -= AM - return - else if (payees[AM] > 0) - for(var/obj/I in counted_money) - qdel(I) - if(!check_times[AM] || check_times[AM] < world.time) //Let's not spam the message - to_chat(AM, span_notice("[payees[AM]] cr received. You need [threshold-payees[AM]] cr more.")) - check_times[AM] = world.time + LUXURY_MESSAGE_COOLDOWN - alarm_beep() - return ..() - else - alarm_beep() - return ..() - /mob/living/simple_animal/hostile/bear/fightpit name = "fight pit bear" desc = "This bear's trained through ancient Russian secrets to fear the walls of its glass prison." diff --git a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm index 4cbbca3d9990..0216650f04f1 100644 --- a/code/modules/spells/spell_types/jaunt/bloodcrawl.dm +++ b/code/modules/spells/spell_types/jaunt/bloodcrawl.dm @@ -163,7 +163,7 @@ /datum/action/cooldown/spell/jaunt/bloodcrawl/slaughter_demon/try_enter_jaunt(obj/effect/decal/cleanable/blood, mob/living/jaunter) // Save this before the actual jaunt - var/atom/coming_with = jaunter.pulling + var/atom/coming_with = jaunter.get_active_grab()?.affecting // Does the actual jaunt . = ..() diff --git a/code/modules/spells/spell_types/self/charge.dm b/code/modules/spells/spell_types/self/charge.dm index 87d7ae287d33..d8ee8db134f8 100644 --- a/code/modules/spells/spell_types/self/charge.dm +++ b/code/modules/spells/spell_types/self/charge.dm @@ -21,8 +21,8 @@ . = ..() // Charge people we're pulling first and foremost - if(isliving(cast_on.pulling)) - var/mob/living/pulled_living = cast_on.pulling + if(isliving(cast_on.get_active_grab()?.affecting)) + var/mob/living/pulled_living = cast_on.get_active_grab()?.affecting var/pulled_has_spells = FALSE for(var/datum/action/cooldown/spell/spell in pulled_living.actions) diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index d5240011597d..99a4daed1980 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -124,6 +124,8 @@ var/artery_name = "artery" /// The name of the tendon this limb has var/tendon_name = "tendon" + /// The name of the joint you can dislocate + var/joint_name = "joint" /// The name for the amputation point of the limb var/amputation_point /// Surgical stage. Magic BS. Do not touch @@ -187,8 +189,6 @@ /// How much generic bleedstacks we have on this bodypart var/generic_bleedstacks - /// If something is currently grasping this bodypart and trying to staunch bleeding (see [/obj/item/hand_item/self_grasp]) - var/obj/item/hand_item/self_grasp/grasped_by /// If something is currently supporting this limb as a splint var/obj/item/splint /// The bandage that may-or-may-not be absorbing our blood @@ -262,7 +262,7 @@ . = ..() . += mob_examine() -/obj/item/bodypart/proc/mob_examine(hallucinating, covered) +/obj/item/bodypart/proc/mob_examine(hallucinating, covered, just_wounds_please) . = list() if(covered) @@ -330,6 +330,9 @@ if(6 to INFINITY) flavor_text += "a ton of [wound]\s" + if(just_wounds_please) + return english_list(flavor_text) + if(owner) if(current_damage) . += "[owner.p_they(TRUE)] [owner.p_have()] [english_list(flavor_text)] on [owner.p_their()] [plaintext_zone]." @@ -1014,9 +1017,6 @@ cached_bleed_rate += round(iter_wound.damage / 40, DAMAGE_PRECISION) bodypart_flags |= BP_BLEEDING - if(!cached_bleed_rate) - QDEL_NULL(grasped_by) - // Our bleed overlay is based directly off bleed_rate, so go aheead and update that would you? if(cached_bleed_rate != old_bleed_rate) update_part_wound_overlay() @@ -1028,8 +1028,9 @@ var/bleed_rate = cached_bleed_rate if(owner.body_position == LYING_DOWN) bleed_rate *= 0.75 - if(grasped_by) - bleed_rate *= 0.7 + + if(HAS_TRAIT(src, TRAIT_BODYPART_GRABBED)) + bleed_rate *= 0.4 if(bandage) bleed_rate *= bandage.absorption_rate_modifier @@ -1303,6 +1304,9 @@ if(check_bones() & CHECKBONES_BROKEN) . += tag ? "Fractured" : "Fractured" + if(bodypart_flags & BP_DISLOCATED) + . += tag ? "Dislocated" : "Dislocated" + if (length(cavity_items) || length(embedded_objects)) var/unknown_body = 0 for(var/obj/item/I in cavity_items + embedded_objects) @@ -1371,3 +1375,42 @@ else user.visible_message(span_notice("[user] removes [removed] from [owner]'s [plaintext_zone].")) return + +/obj/item/bodypart/proc/inspect(mob/user) + if(is_stump) + to_chat(user, span_notice("[owner] is missing that bodypart.")) + return + + user.visible_message(span_notice("[user] starts inspecting [owner]'s [plaintext_zone] carefully.")) + if(LAZYLEN(wounds)) + to_chat(user, span_warning("You find [mob_examine(just_wounds_please = TRUE)].")) + var/list/stuff = list() + for(var/datum/wound/wound as anything in wounds) + if(LAZYLEN(wound.embedded_objects)) + stuff |= wound.embedded_objects + + if(length(stuff)) + to_chat(user, span_warning("There's [english_list(stuff)] sticking out of [owner]'s [plaintext_zone].")) + else + to_chat(user, span_notice("You find no visible wounds.")) + + to_chat(user, span_notice("Checking skin now...")) + + if(!do_after(user, owner, 1 SECOND, DO_PUBLIC)) + return + + to_chat(user, span_notice("Checking bones now...")) + if(!do_after(user, owner, 1 SECOND, DO_PUBLIC)) + return + + if(bodypart_flags & BP_BROKEN_BONES) + to_chat(user, span_warning("The [encased ? encased : "bone in the [plaintext_zone]"] moves slightly when you poke it!")) + owner.apply_pain(40, body_zone, "Your [plaintext_zone] hurts where it's poked.") + else + to_chat(user, span_notice("The [encased ? encased : "bones in the [plaintext_zone]"] seem to be fine.")) + + if(bodypart_flags & BP_TENDON_CUT) + to_chat(user, span_warning("The tendons in the [plaintext_zone] are severed!")) + if(bodypart_flags & BP_DISLOCATED) + to_chat(user, span_warning("The [joint_name] is dislocated!")) + return TRUE diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 42fa7dc72fae..1d26ff3f667c 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -31,6 +31,7 @@ encased = "skull" artery_name = "carotid artery" cavity_name = "cranial" + joint_name = "jaw" minimum_break_damage = 30 diff --git a/code/modules/surgery/bodyparts/injuries.dm b/code/modules/surgery/bodyparts/injuries.dm index ad7bebc09729..e9ab90d5475c 100644 --- a/code/modules/surgery/bodyparts/injuries.dm +++ b/code/modules/surgery/bodyparts/injuries.dm @@ -155,6 +155,31 @@ update_disabled() return TRUE +/obj/item/bodypart/proc/can_be_dislocated() + if(!(bodypart_flags & BP_CAN_BE_DISLOCATED)) + return FALSE + if(bodypart_flags & BP_DISLOCATED) + return FALSE + return TRUE + +/obj/item/bodypart/proc/set_dislocated(val, painless) + if(val) + if(!can_be_dislocated()) + return FALSE + else + if(can_be_dislocated()) + return FALSE + + if(val) + bodypart_flags |= BP_DISLOCATED + if(!painless) + owner?.apply_pain(20, body_zone, "A surge of pain shoots through your [plaintext_zone].") + else + bodypart_flags &= BP_DISLOCATED + + return TRUE + + /obj/item/bodypart/proc/clamp_wounds() for(var/datum/wound/W as anything in wounds) . ||= !W.clamped @@ -223,3 +248,14 @@ if(!W) return W.open_wound(min(W.damage * 2, W.damage_list[1] - W.damage)) + +/obj/item/bodypart/proc/jointlock(mob/living/user) + if(!IS_ORGANIC_LIMB(src)) + return + + var/armor = owner.run_armor_check(body_zone, MELEE, silent = TRUE) + if(armor > 70) + return + + var/max_halloss = round(owner.maxHealth * 0.8 * ((100 - armor) / 100)) //up to 80% of passing out, further reduced by armour + owner.apply_pain(max(30, max_halloss - owner.getPain()), body_zone, "your [plaintext_zone] is in excruciating pain") diff --git a/code/modules/surgery/bodyparts/pain.dm b/code/modules/surgery/bodyparts/pain.dm index 4cd293d6b0d7..2da6993e59fd 100644 --- a/code/modules/surgery/bodyparts/pain.dm +++ b/code/modules/surgery/bodyparts/pain.dm @@ -15,10 +15,9 @@ var/lasting_pain = 0 if(bodypart_flags & BP_BROKEN_BONES) lasting_pain += 10 - /* - else if(is_dislocated()) + + else if(bodypart_flags & BP_DISLOCATED) lasting_pain += 5 - */ var/organ_dam = 0 for(var/obj/item/organ/O as anything in contained_organs) diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm index d73430c4efaa..04be40662950 100644 --- a/code/modules/surgery/bodyparts/parts.dm +++ b/code/modules/surgery/bodyparts/parts.dm @@ -16,6 +16,7 @@ encased = "ribcage" artery_name = "aorta" cavity_name = "thoracic" + joint_name = "neck" minimum_break_damage = 35 @@ -105,6 +106,7 @@ px_y = 0 bodypart_trait_source = LEFT_ARM_TRAIT amputation_point = "left shoulder" + joint_name = "left elbow" /obj/item/bodypart/arm/left/set_owner(new_owner) @@ -188,6 +190,7 @@ bodypart_trait_source = RIGHT_ARM_TRAIT can_be_disabled = TRUE amputation_point = "right shoulder" + joint_name = "right elbow" /obj/item/bodypart/arm/right/set_owner(new_owner) . = ..() @@ -297,6 +300,7 @@ can_be_disabled = TRUE bodypart_trait_source = LEFT_LEG_TRAIT amputation_point = "left hip" + joint_name = "left knee" /obj/item/bodypart/leg/left/set_owner(new_owner) @@ -377,6 +381,7 @@ bodypart_trait_source = RIGHT_LEG_TRAIT can_be_disabled = TRUE amputation_point = "right hip" + joint_name = "right knee" /obj/item/bodypart/leg/right/set_owner(new_owner) . = ..() diff --git a/code/modules/surgery/organs/external/wings.dm b/code/modules/surgery/organs/external/wings.dm index d07c14dffd15..4e7b89dc6fa9 100644 --- a/code/modules/surgery/organs/external/wings.dm +++ b/code/modules/surgery/organs/external/wings.dm @@ -106,7 +106,7 @@ var/olddir = human.dir - human.stop_pulling() + human.release_all_grabs() if(buckled_obj) buckled_obj.unbuckle_mob(human) step(buckled_obj, olddir) diff --git a/code/modules/tables/tables_racks.dm b/code/modules/tables/tables_racks.dm index a43f8b267c16..ad16f54b11c9 100644 --- a/code/modules/tables/tables_racks.dm +++ b/code/modules/tables/tables_racks.dm @@ -98,10 +98,6 @@ /obj/structure/table/attack_paw(mob/user, list/modifiers) return attack_hand(user, modifiers) -/obj/structure/table/attack_hand(mob/living/user, list/modifiers) - try_place_pulled_onto_table(user) - return ..() - /obj/structure/table/attack_tk(mob/user) return @@ -238,24 +234,30 @@ else layer = TABLE_LAYER -/obj/structure/table/proc/try_place_pulled_onto_table(mob/living/user) - if(!Adjacent(user) || !user.pulling) +/obj/structure/table/attack_grab(mob/living/user, atom/movable/victim, obj/item/hand_item/grab/grab, list/params) + try_place_pulled_onto_table(user, victim, grab) + return TRUE + +/obj/structure/table/proc/try_place_pulled_onto_table(mob/living/user, atom/movable/target, obj/item/hand_item/grab/grab) + if(!Adjacent(user)) return - if(isliving(user.pulling)) - var/mob/living/pushed_mob = user.pulling + if(isliving(target)) + var/mob/living/pushed_mob = target if(pushed_mob.buckled) to_chat(user, span_warning("[pushed_mob] is buckled to [pushed_mob.buckled]!")) return if(user.combat_mode) - switch(user.grab_state) + switch(grab.current_grab.damage_stage) if(GRAB_PASSIVE) to_chat(user, span_warning("You need a better grip to do that!")) return - if(GRAB_AGGRESSIVE) + + if(GRAB_NECK, GRAB_KILL) tablepush(user, pushed_mob) - if(GRAB_NECK to GRAB_KILL) - tablelimbsmash(user, pushed_mob) + else + if(grab.target_zone == BODY_ZONE_HEAD) + tablelimbsmash(user, pushed_mob) else pushed_mob.visible_message(span_notice("[user] begins to place [pushed_mob] onto [src]..."), \ span_userdanger("[user] begins to place [pushed_mob] onto [src]...")) @@ -263,13 +265,15 @@ tableplace(user, pushed_mob) else return - user.stop_pulling() - else if(user.pulling.pass_flags & PASSTABLE) - user.Move_Pulled(src) - if (user.pulling.loc == loc) - user.visible_message(span_notice("[user] places [user.pulling] onto [src]."), - span_notice("You place [user.pulling] onto [src].")) - user.stop_pulling() + user.release_grabs(pushed_mob) + + else if(target.pass_flags & PASSTABLE) + user.move_grabbed_atoms_towards(src) + if (target.loc == loc) + user.visible_message(span_notice("[user] places [target] onto [src]."), + span_notice("You place [target] onto [src].")) + + user.release_grabs(target) /obj/structure/table/proc/tableplace(mob/living/user, mob/living/pushed_mob) pushed_mob.forceMove(loc) @@ -305,16 +309,24 @@ log_combat(user, pushed_mob, "tabled", null, "onto [src]") /obj/structure/table/proc/tablelimbsmash(mob/living/user, mob/living/pushed_mob) - pushed_mob.Knockdown(30) - var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(user.zone_selected) || pushed_mob.get_bodypart(BODY_ZONE_HEAD) - banged_limb?.receive_damage(30) + var/obj/item/bodypart/banged_limb = pushed_mob.get_bodypart(BODY_ZONE_HEAD) + if(!banged_limb) + return + + var/blocked = pushed_mob.run_armor_check(BODY_ZONE_HEAD, MELEE) + pushed_mob.apply_damage(30, BRUTE, BODY_ZONE_HEAD, blocked) + if (prob(30 * ((100-blocked)/100))) + pushed_mob.Knockdown(10 SECONDS) + pushed_mob.stamina.adjust(-60) take_damage(50) if(user.mind?.martial_art.smashes_tables && user.mind?.martial_art.can_use(user)) deconstruct(FALSE) - playsound(pushed_mob, 'sound/effects/bang.ogg', 90, TRUE) - pushed_mob.visible_message(span_danger("[user] smashes [pushed_mob]'s [banged_limb.name] against \the [src]!"), - span_userdanger("[user] smashes your [banged_limb.name] against \the [src]")) + + playsound(pushed_mob, 'sound/items/trayhit1.ogg', 70, TRUE) + pushed_mob.visible_message( + span_danger("[user] smashes [pushed_mob]'s [banged_limb.plaintext_zone] against \the [src]!"), + ) log_combat(user, pushed_mob, "head slammed", null, "against [src]") /obj/structure/table/screwdriver_act_secondary(mob/living/user, obj/item/tool) diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm index b718da91c2c6..4ec1f7d612c3 100644 --- a/code/modules/unit_tests/chain_pull_through_space.dm +++ b/code/modules/unit_tests/chain_pull_through_space.dm @@ -42,8 +42,8 @@ /datum/unit_test/chain_pull_through_space/Run() // Alice pulls Bob, who pulls Charlie // Normally, when Alice moves forward, the rest follow - alice.start_pulling(bob) - bob.start_pulling(charlie) + bob.try_make_grab(charlie) + alice.try_make_grab(bob) // Walk normally to the left, make sure we're still a chain alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_vox.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_vox.png index 896167f6eaf5..1917786c90cd 100644 Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_vox.png and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_vox.png differ diff --git a/code/modules/wiremod/components/action/pull.dm b/code/modules/wiremod/components/action/pull.dm deleted file mode 100644 index 8c83e938b00a..000000000000 --- a/code/modules/wiremod/components/action/pull.dm +++ /dev/null @@ -1,27 +0,0 @@ -/** - * # Pull Component - * - * Tells the shell to start pulling on a designated atom. Only works on movable shells. - */ -/obj/item/circuit_component/pull - display_name = "Start Pulling" - desc = "A component that can force the shell to pull entities. Only works for drone shells." - category = "Action" - - /// Frequency input - var/datum/port/input/target - circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL|CIRCUIT_FLAG_OUTPUT_SIGNAL - -/obj/item/circuit_component/pull/populate_ports() - target = add_input_port("Target", PORT_TYPE_ATOM) - -/obj/item/circuit_component/pull/input_received(datum/port/input/port) - var/atom/target_atom = target.value - if(!target_atom) - return - - var/mob/shell = parent.shell - if(!istype(shell) || get_dist(shell, target_atom) > 1 || shell.z != target_atom.z) - return - - INVOKE_ASYNC(shell, TYPE_PROC_REF(/atom/movable, start_pulling), target_atom) diff --git a/daedalus.dme b/daedalus.dme index 8c670b34a50d..5fb691b5850d 100644 --- a/daedalus.dme +++ b/daedalus.dme @@ -94,6 +94,7 @@ #include "code\__DEFINES\food.dm" #include "code\__DEFINES\footsteps.dm" #include "code\__DEFINES\ghost.dm" +#include "code\__DEFINES\grab_defines.dm" #include "code\__DEFINES\gravity.dm" #include "code\__DEFINES\greyscale.dm" #include "code\__DEFINES\hud.dm" @@ -1009,7 +1010,6 @@ #include "code\datums\elements\light_eater.dm" #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" -#include "code\datums\elements\nerfed_pulling.dm" #include "code\datums\elements\obj_regen.dm" #include "code\datums\elements\openspace_item_click_handler.dm" #include "code\datums\elements\pet_bonus.dm" @@ -2979,6 +2979,21 @@ #include "code\modules\food_and_drinks\restaurant\custom_order.dm" #include "code\modules\food_and_drinks\restaurant\generic_venues.dm" #include "code\modules\food_and_drinks\restaurant\customers\_customer.dm" +#include "code\modules\grab\grab_carbon.dm" +#include "code\modules\grab\grab_datum.dm" +#include "code\modules\grab\grab_helpers.dm" +#include "code\modules\grab\grab_living.dm" +#include "code\modules\grab\grab_movable.dm" +#include "code\modules\grab\grab_object.dm" +#include "code\modules\grab\grab_silicon.dm" +#include "code\modules\grab\human_grab.dm" +#include "code\modules\grab\grabs\grab_aggressive.dm" +#include "code\modules\grab\grabs\grab_neck.dm" +#include "code\modules\grab\grabs\grab_normal.dm" +#include "code\modules\grab\grabs\grab_passive.dm" +#include "code\modules\grab\grabs\grab_simple.dm" +#include "code\modules\grab\grabs\grab_strangle.dm" +#include "code\modules\grab\grabs\grab_struggle.dm" #include "code\modules\holiday\easter.dm" #include "code\modules\holiday\foreign_calendar.dm" #include "code\modules\holiday\holidays.dm" @@ -3824,6 +3839,7 @@ #include "code\modules\movespeed\modifiers\mobs.dm" #include "code\modules\movespeed\modifiers\reagent.dm" #include "code\modules\movespeed\modifiers\status_effects.dm" +#include "code\modules\multiz\movement.dm" #include "code\modules\ninja\__ninjaDefines.dm" #include "code\modules\ninja\energy_katana.dm" #include "code\modules\ninja\ninja_explosive.dm" @@ -4508,7 +4524,6 @@ #include "code\modules\wiremod\components\action\mmi.dm" #include "code\modules\wiremod\components\action\pathfind.dm" #include "code\modules\wiremod\components\action\printer.dm" -#include "code\modules\wiremod\components\action\pull.dm" #include "code\modules\wiremod\components\action\radio.dm" #include "code\modules\wiremod\components\action\soundemitter.dm" #include "code\modules\wiremod\components\action\speech.dm" diff --git a/goon/icons/items/grab.dmi b/goon/icons/items/grab.dmi new file mode 100644 index 000000000000..eb4b9699b4c2 Binary files /dev/null and b/goon/icons/items/grab.dmi differ diff --git a/icons/hud/screen1.dmi b/icons/hud/screen1.dmi new file mode 100644 index 000000000000..7f8db4256da5 Binary files /dev/null and b/icons/hud/screen1.dmi differ diff --git a/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi index 22e9460f0677..c2a0e48c20d4 100644 Binary files a/icons/hud/screen_gen.dmi and b/icons/hud/screen_gen.dmi differ diff --git a/icons/mob/species/vox/bodyparts.dmi b/icons/mob/species/vox/bodyparts.dmi index f036392f6339..8663e45da845 100644 Binary files a/icons/mob/species/vox/bodyparts.dmi and b/icons/mob/species/vox/bodyparts.dmi differ diff --git a/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm b/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm index e8924f08ec1a..63fddf494caf 100644 --- a/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm +++ b/modular_pariah/modules/customization/modules/clothing/glasses/glasses.dm @@ -2,7 +2,7 @@ var/can_switch_eye = FALSE //Having this default to false means that its easy to make sure this doesnt apply to any pre-existing items var/current_eye = "_R" //Added to the end of the icon_state to make this easy code-wise, L and R being the wearer's Left and Right -/obj/item/clothing/glasses/CtrlClick(mob/user) +/obj/item/clothing/glasses/CtrlClick(mob/user, list/params) . = ..() if(.) return