diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index d77e62978c4..fd919cc648a 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -25,6 +25,10 @@
#define INCORPOREAL_SHADE 4 // Shady
#define INCORPOREAL_MECH 5 // stripped down bstech
+#define MOB_GRAB_NONE 0
+#define MOB_GRAB_NORMAL 1
+#define MOB_GRAB_FIREMAN 2
+
// Grab levels.
#define GRAB_PASSIVE 1
#define GRAB_AGGRESSIVE 2
diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm
index a739205e43e..d9e00701600 100644
--- a/code/game/objects/effects/portals.dm
+++ b/code/game/objects/effects/portals.dm
@@ -53,16 +53,25 @@
if(does_teleport)
teleport(user)
-/obj/effect/portal/proc/teleport(atom/movable/M)
+/obj/effect/portal/proc/teleport(atom/movable/movable)
if(!does_teleport) // just to be safe
return
- if(istype(M, /obj/effect)) //sparks don't teleport
+ if(istype(movable, /obj/effect)) //sparks don't teleport
return
if(!target)
qdel(src)
return
var/has_teleported = FALSE
- if(istype(M, /atom/movable))
+ var/list/things_to_teleport = list(movable)
+ if(ismob(movable))
+ var/mob/M = movable
+ if(M.pulling)
+ things_to_teleport += M.pulling
+ if(ishuman(movable))
+ var/mob/living/carbon/human/H = movable
+ for(var/obj/item/grab/G in list(H.l_hand, H.r_hand))
+ things_to_teleport += G.affecting
+ for(var/atom/movable/M in things_to_teleport)
if(has_failed) //oh dear a problem, put em in deep space
icon_state = "portal1" // only tell people the portal failed after a teleport has been done
desc = "A bluespace tear in space, reaching directly to another point within this region. Definitely unstable."
@@ -72,7 +81,7 @@
if(do_teleport(M, target, precision))
has_teleported = TRUE
if(!has_teleported)
- visible_message(SPAN_WARNING("\The [src] oscillates violently as \the [M] comes into contact with it, and collapses! Seems like the rift was unstable..."))
+ visible_message(SPAN_WARNING("\The [src] oscillates violently as \the [movable] comes into contact with it, and collapses! Seems like the rift was unstable..."))
qdel(src)
/obj/effect/portal/Destroy()
diff --git a/code/modules/mob/living/carbon/alien/alien_attacks.dm b/code/modules/mob/living/carbon/alien/alien_attacks.dm
index afd2ec36093..26cf988661a 100644
--- a/code/modules/mob/living/carbon/alien/alien_attacks.dm
+++ b/code/modules/mob/living/carbon/alien/alien_attacks.dm
@@ -15,7 +15,7 @@
if (I_GRAB)
if (M == src)
return
- var/obj/item/grab/G = new /obj/item/grab( M, src )
+ var/obj/item/grab/G = new /obj/item/grab(M, M, src)
M.put_in_active_hand(G)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 330d569e22d..961461b515b 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -167,7 +167,7 @@
/mob/living/carbon/swap_hand()
var/obj/item/item_in_hand = src.get_active_hand()
if(item_in_hand) //this segment checks if the item in your hand is twohanded.
- if(istype(item_in_hand,/obj/item/material/twohanded) || istype(item_in_hand,/obj/item/gun) || istype(item_in_hand,/obj/item/pickaxe))
+ if(istype(item_in_hand,/obj/item/material/twohanded) || istype(item_in_hand,/obj/item/gun) || istype(item_in_hand,/obj/item/pickaxe) || istype(item_in_hand, /obj/item/grab))
if(item_in_hand:wielded == 1)
to_chat(usr, SPAN_WARNING("Your other hand is too busy holding the [item_in_hand.name]"))
return
diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm
index 372f300708d..d8d0097e440 100644
--- a/code/modules/mob/living/carbon/human/human_attackhand.dm
+++ b/code/modules/mob/living/carbon/human/human_attackhand.dm
@@ -134,7 +134,7 @@
if(w_uniform)
w_uniform.add_fingerprint(M)
- var/obj/item/grab/G = new /obj/item/grab(M, src)
+ var/obj/item/grab/G = new /obj/item/grab(M, M, src)
if(buckled_to)
to_chat(M, "You cannot grab [src], [src.get_pronoun("he")] [get_pronoun("is")] buckled in!")
if(!G) //the grab will delete itself in New if affecting is anchored
diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm
index b82713acccf..a90545ae5cd 100644
--- a/code/modules/mob/living/carbon/human/human_defense.dm
+++ b/code/modules/mob/living/carbon/human/human_defense.dm
@@ -558,7 +558,7 @@ emp_act
if(src.w_uniform)
src.w_uniform.add_fingerprint(src)
- var/obj/item/grab/G = new /obj/item/grab(user, src)
+ var/obj/item/grab/G = new /obj/item/grab(user, user, src)
if(buckled_to)
to_chat(user, "You cannot grab [src], [get_pronoun("he")] [get_pronoun("is")] buckled in!")
if(!G) //the grab will delete itself in New if affecting is anchored
diff --git a/code/modules/mob/living/carbon/slime/slime.dm b/code/modules/mob/living/carbon/slime/slime.dm
index 0a68226782b..8bc6ab6b45f 100644
--- a/code/modules/mob/living/carbon/slime/slime.dm
+++ b/code/modules/mob/living/carbon/slime/slime.dm
@@ -338,7 +338,7 @@
if(I_GRAB)
if(M == src || anchored)
return
- var/obj/item/grab/G = new /obj/item/grab(M, src)
+ var/obj/item/grab/G = new /obj/item/grab(M, M, src)
M.put_in_active_hand(G)
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index dd8c4173166..4401daa8f85 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -481,7 +481,7 @@
if (!attempt_grab(M))
return
- var/obj/item/grab/G = new /obj/item/grab(M, src)
+ var/obj/item/grab/G = new /obj/item/grab(M, M, src)
M.put_in_active_hand(G)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index a80159f0dc4..657a14b4ad7 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -936,11 +936,12 @@
else if(buckled_to)
anchored = 1
canmove = 0
- if(buckled_to.buckle_lying != -1)
- lying = buckled_to.buckle_lying
- if(buckled_to.buckle_movable)
- anchored = 0
- canmove = 1
+ if(isobj(buckled_to))
+ if(buckled_to.buckle_lying != -1)
+ lying = buckled_to.buckle_lying
+ if(buckled_to.buckle_movable)
+ anchored = 0
+ canmove = 1
else if(captured)
anchored = 1
canmove = 0
@@ -957,6 +958,10 @@
density = initial(density)
for(var/obj/item/grab/G in grabbed_by)
+ if(G.wielded)
+ canmove = FALSE
+ lying = TRUE
+ break
if(G.state >= GRAB_AGGRESSIVE)
canmove = 0
break
diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm
index 866bc2af492..59dac40bcd4 100644
--- a/code/modules/mob/mob_grab.dm
+++ b/code/modules/mob/mob_grab.dm
@@ -42,6 +42,9 @@
var/dancing //determines if assailant and affecting keep looking at each other. Basically a wrestling position
var/has_choked = FALSE //Used as a counter for choking people.
+ var/obj/item/grab/linked_grab
+ var/wielded = FALSE
+
layer = SCREEN_LAYER
abstract = 1
item_state = "nothing"
@@ -51,15 +54,13 @@
pickup_sound = null
equip_sound = null
-/obj/item/grab/New(mob/user, mob/victim)
- ..()
- loc = user
+/obj/item/grab/Initialize(mapload, mob/user, mob/victim)
+ . = ..()
assailant = user
affecting = victim
if(affecting.anchored || !assailant.Adjacent(victim))
- qdel(src)
- return
+ return INITIALIZE_HINT_QDEL
affecting.grabbed_by += src
@@ -81,7 +82,7 @@
//Used by throw code to hand over the mob, instead of throwing the grab. The grab is then deleted by the throw code.
/obj/item/grab/proc/throw_held()
if(affecting)
- if(affecting.buckled_to)
+ if(affecting.buckled_to && affecting.buckled_to != assailant) // can't throw in fireman carries atm, but future proofing
return null
if(state >= GRAB_AGGRESSIVE)
animate(affecting, pixel_x = affecting.get_standard_pixel_x(), pixel_y = affecting.get_standard_pixel_y(), 4, 1)
@@ -203,13 +204,20 @@
/obj/item/grab/proc/adjust_position()
if(!affecting)
return
- if(affecting.buckled_to)
+ if(affecting.buckled_to && affecting.buckled_to != assailant)
animate(affecting, pixel_x = affecting.get_standard_pixel_x(), pixel_y = affecting.get_standard_pixel_y(), 4, 1, LINEAR_EASING)
return
- if(affecting.lying || force_down)
+ if(affecting.lying || force_down || wielded)
affecting.update_canmove()
- animate(affecting, pixel_x = affecting.get_standard_pixel_x(), pixel_y = affecting.get_standard_pixel_y(), 5, 1, LINEAR_EASING)
- affecting.set_dir(SOUTH)
+ var/shift_amount = wielded ? 6 : 0
+ animate(affecting, pixel_x = affecting.get_standard_pixel_x(), pixel_y = affecting.get_standard_pixel_y() + shift_amount, 5, 1, LINEAR_EASING)
+ var/set_dir = wielded ? assailant.dir : SOUTH
+ affecting.set_dir(set_dir)
+ if(wielded)
+ if(assailant.dir != NORTH)
+ affecting.layer = assailant.layer - 0.1
+ else
+ affecting.layer = assailant.layer + 0.1
return
var/shift = 0
var/adir = get_dir(assailant, affecting)
@@ -251,6 +259,8 @@
return
if(state == GRAB_UPGRADING)
return
+ if(wielded)
+ return
if(!assailant.canClick())
return
if(!assailant.canmove || assailant.lying)
@@ -400,8 +410,18 @@
var/destroying = 0
/obj/item/grab/Destroy()
+ if(!QDELETED(linked_grab))
+ qdel(linked_grab)
+
+ if(wielded)
+ if(affecting.buckled_to == assailant)
+ affecting.buckled_to = null
+ affecting.update_canmove()
+ affecting.anchored = FALSE
+ moved_event.unregister(assailant, src, /obj/item/grab/proc/move_affecting)
+
animate(affecting, pixel_x = affecting.get_standard_pixel_x(), pixel_y = affecting.get_standard_pixel_y(), 4, 1, LINEAR_EASING)
- affecting.layer = 4
+ affecting.layer = initial(affecting.layer)
if(affecting)
ADD_FALLING_ATOM(affecting) // Makes the grabbee check if they can fall.
affecting.grabbed_by -= src
@@ -414,3 +434,68 @@
hud = null
destroying = 1 // stops us calling qdel(src) on dropped()
return ..()
+
+/obj/item/grab/MouseDrop(mob/living/carbon/human/H)
+ if(wielded || affecting.buckled_to || !istype(H) || assailant != H || H.get_active_hand() != src)
+ return
+ if(!ishuman(affecting))
+ to_chat(H, SPAN_WARNING("You can only fireman carry humanoids!"))
+ return
+ var/mob/living/carbon/human/affected_human = affecting
+ if(affected_human.species.mob_size > 25)
+ to_chat(H, SPAN_WARNING("\The [affected_human] is way too big to fireman carry!"))
+ return
+ if(state < GRAB_AGGRESSIVE)
+ to_chat(H, SPAN_WARNING("You need an aggressive grab before you can fireman carry someone!"))
+ return
+ if(H.get_inactive_hand())
+ to_chat(H, SPAN_WARNING("Your other hand must be empty to fireman carry someone!"))
+ return
+
+ H.visible_message("[H] starts lifting \the [affecting] onto their shoulders...", SPAN_NOTICE("You start lifting \the [affecting] onto your shoulders..."))
+
+ if(!do_after(H, 3 SECONDS, TRUE))
+ return
+
+ if(affecting.buckled_to)
+ return
+
+ if(H.get_inactive_hand())
+ to_chat(H, SPAN_WARNING("Your other hand must be empty to fireman carry someone!"))
+ return
+
+ var/obj/item/grab/offhand/OH = new /obj/item/grab/offhand(H, H, affecting, src)
+ H.put_in_hands(OH)
+
+ H.visible_message("[H] lifts \the [affecting] onto their shoulders!", SPAN_NOTICE("You lift \the [affecting] onto your shoulders!"))
+
+ affecting.buckled_to = assailant
+ affecting.forceMove(H.loc)
+ adjust_position()
+ moved_event.register(assailant, src, /obj/item/grab/proc/move_affecting)
+
+/obj/item/grab/proc/move_affecting()
+ if(affecting && assailant.Adjacent(affecting)) // Only move if it's near us.
+ affecting.forceMove(assailant.loc)
+
+/obj/item/grab/offhand
+ icon_state = "!reinforce"
+
+/obj/item/grab/offhand/Initialize(mapload, mob/user, mob/victim, var/obj/item/grab/linked)
+ . = ..()
+ linked_grab = linked
+ linked.linked_grab = src
+ linked_grab.wielded = TRUE
+
+ linked_grab.state = GRAB_AGGRESSIVE
+
+ icon_state = "!reinforce"
+ hud.icon_state = "!reinforce"
+ linked_grab.icon_state = "!reinforce"
+ linked_grab.hud.icon_state = "!reinforce"
+
+/obj/item/grab/offhand/process()
+ return
+
+/obj/item/grab/offhand/adjust_position()
+ return
\ No newline at end of file
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 75e5de49ffa..3c5363d43ae 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -1230,4 +1230,19 @@ proc/is_blind(A)
/mob/proc/can_hear_radio(var/list/speaker_coverage = list())
var/turf/ear = get_turf(src)
if(ear && speaker_coverage[ear])
- return TRUE
\ No newline at end of file
+ return TRUE
+
+/mob/proc/has_grab()
+ . = MOB_GRAB_NONE
+ if(istype(l_hand, /obj/item/grab))
+ var/obj/item/grab/l_grab = l_hand
+ if(l_grab.wielded)
+ . = max(MOB_GRAB_FIREMAN, .)
+ else
+ . = max(MOB_GRAB_NORMAL, .)
+ if(istype(r_hand, /obj/item/grab))
+ var/obj/item/grab/r_grab = r_hand
+ if(r_grab.wielded)
+ . = max(MOB_GRAB_FIREMAN, .)
+ else
+ . = max(MOB_GRAB_NORMAL, .)
\ No newline at end of file
diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm
index fa9966809f8..6ce8cb54123 100644
--- a/code/modules/mob/mob_movement.dm
+++ b/code/modules/mob/mob_movement.dm
@@ -334,7 +334,10 @@
//We are now going to move
moving = 1
//Something with pulling things
- if (mob_is_human && (istype(mob:l_hand, /obj/item/grab) || istype(mob:r_hand, /obj/item/grab)))
+ var/grab_level = mob.has_grab()
+ if(grab_level == MOB_GRAB_FIREMAN)
+ move_delay++
+ if (mob_is_human && grab_level == MOB_GRAB_NORMAL)
move_delay = max(move_delay, world.time + 7)
var/list/L = mob.ret_grab()
if(istype(L, /list))
diff --git a/html/changelogs/geeves-fireman_carrying.yml b/html/changelogs/geeves-fireman_carrying.yml
new file mode 100644
index 00000000000..06fc229bede
--- /dev/null
+++ b/html/changelogs/geeves-fireman_carrying.yml
@@ -0,0 +1,7 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "It is now possible to fireman carry someone, provided you have an aggressive grab or higher on them. Simply drag the grab in your active hand onto your sprite and wait for a bit. To release, drop the grab."
+ - rscadd: "Pulling or grabbing someone and dragging them into a portal will now teleport it/them with you."
\ No newline at end of file