diff --git a/code/_onclick/telekinesis.dm b/code/_onclick/telekinesis.dm
index 78f99228a9f..0eadbff6696 100644
--- a/code/_onclick/telekinesis.dm
+++ b/code/_onclick/telekinesis.dm
@@ -141,7 +141,7 @@ var/const/tk_maxrange = 15
else
apply_focus_overlay()
- focus.throw_at(target, 10, 1)
+ focus.throw_at(target, 10, 1, user)
last_throw = world.time
return
diff --git a/code/defines/obj.dm b/code/defines/obj.dm
index 2eae6a301af..7ad6c7ff6f6 100644
--- a/code/defines/obj.dm
+++ b/code/defines/obj.dm
@@ -387,9 +387,7 @@ var/global/list/PDA_Manifest = list()
throw_speed = 1
throw_range = 20
flags = FPRINT | TABLEPASS | CONDUCT
-// afterattack(atom/target as mob|obj|turf|area, mob/user as mob)
-// user.drop_item()
-// src.throw_at(target, throw_range, throw_speed)
+
/obj/effect/stop
var/victim = null
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 8d89ad23f8e..e3607665395 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -7,6 +7,8 @@
var/l_move_time = 1
var/m_flag = 1
var/throwing = 0
+ var/thrower
+ var/turf/throw_source = null
var/throw_speed = 2
var/throw_range = 7
var/moved_recently = 0
@@ -83,11 +85,13 @@
if(A.density && !A.throwpass) // **TODO: Better behaviour for windows which are dense, but shouldn't always stop movement
src.throw_impact(A,speed)
-/atom/movable/proc/throw_at(atom/target, range, speed)
+/atom/movable/proc/throw_at(atom/target, range, speed, thrower)
if(!target || !src) return 0
//use a modified version of Bresenham's algorithm to get from the atom's current position to that of the target
src.throwing = 1
+ src.thrower = thrower
+ src.throw_source = get_turf(src) //store the origin turf
if(usr)
if(M_HULK in usr.mutations)
@@ -176,6 +180,8 @@
//done throwing, either because it hit something or it finished moving
if(isobj(src)) src.throw_impact(get_turf(src),speed)
src.throwing = 0
+ src.thrower = null
+ src.throw_source = null
//Overlays
diff --git a/code/game/machinery/bots/farmbot.dm b/code/game/machinery/bots/farmbot.dm
index d7a8e163141..f2981c70fe1 100644
--- a/code/game/machinery/bots/farmbot.dm
+++ b/code/game/machinery/bots/farmbot.dm
@@ -396,7 +396,7 @@
if ( emagged ) // Warning, hungry humans detected: throw fertilizer at them
spawn(0)
fert.loc = src.loc
- fert.throw_at(target, 16, 3)
+ fert.throw_at(target, 16, 3, src)
src.visible_message("\red [src] launches [fert.name] at [target.name]!")
flick("farmbot_broke", src)
spawn (FARMBOT_EMAG_DELAY)
diff --git a/code/game/machinery/kitchen/gibber.dm b/code/game/machinery/kitchen/gibber.dm
index 6be3782dc72..8d020c5c268 100644
--- a/code/game/machinery/kitchen/gibber.dm
+++ b/code/game/machinery/kitchen/gibber.dm
@@ -172,7 +172,7 @@
var/obj/item/meatslab = allmeat[i]
var/turf/Tx = locate(src.x - i, src.y, src.z)
meatslab.loc = src.loc
- meatslab.throw_at(Tx,i,3)
+ meatslab.throw_at(Tx,i,3,src)
if (!Tx.density)
new /obj/effect/decal/cleanable/blood/gibs(Tx,i)
src.operating = 0
diff --git a/code/game/machinery/kitchen/smartfridge.dm b/code/game/machinery/kitchen/smartfridge.dm
index 123eeee2ee5..e51b11aafa4 100644
--- a/code/game/machinery/kitchen/smartfridge.dm
+++ b/code/game/machinery/kitchen/smartfridge.dm
@@ -203,5 +203,3 @@
-
-
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index 3ba71a125d8..97019615ad8 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -545,7 +545,7 @@
if (!throw_item)
return 0
spawn(0)
- throw_item.throw_at(target, 16, shoot_speed)
+ throw_item.throw_at(target, 16, 3, src)
src.visible_message("\red [src] launches [throw_item.name] at [target.name]!")
return 1
diff --git a/code/game/mecha/equipment/tools/tools.dm b/code/game/mecha/equipment/tools/tools.dm
index e1fbd1803d6..42406003a5e 100644
--- a/code/game/mecha/equipment/tools/tools.dm
+++ b/code/game/mecha/equipment/tools/tools.dm
@@ -475,7 +475,7 @@
return
else if(target!=locked)
if(locked in view(chassis))
- locked.throw_at(target, 14, 1.5)
+ locked.throw_at(target, 14, 1.5, chassis)
locked = null
send_byjax(chassis.occupant,"exosuit.browser","\ref[src]",src.get_equip_info())
set_ready_state(0)
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index 0adfc28c47e..3605c493246 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -312,7 +312,7 @@
var/obj/item/missile/M = new projectile(chassis.loc)
M.primed = 1
playsound(chassis, fire_sound, 50, 1)
- M.throw_at(target, missile_range, missile_speed)
+ M.throw_at(target, missile_range, missile_speed, chassis)
projectiles--
log_message("Fired from [src.name], targeting [target].")
do_after_cooldown()
@@ -349,7 +349,7 @@
set_ready_state(0)
var/obj/item/weapon/grenade/flashbang/F = new projectile(chassis.loc)
playsound(chassis, fire_sound, 50, 1)
- F.throw_at(target, missile_range, missile_speed)
+ F.throw_at(target, missile_range, missile_speed, chassis)
projectiles--
log_message("Fired from [src.name], targeting [target].")
spawn(det_time)
@@ -391,7 +391,7 @@
set_ready_state(0)
var/obj/item/weapon/bananapeel/B = new projectile(chassis.loc)
playsound(chassis, fire_sound, 60, 1)
- B.throw_at(target, missile_range, missile_speed)
+ B.throw_at(target, missile_range, missile_speed, chassis)
projectiles--
log_message("Bananed from [src.name], targeting [target]. HONK!")
do_after_cooldown()
@@ -422,8 +422,8 @@
var/obj/item/device/assembly/mousetrap/M = new projectile(chassis.loc)
M.secured = 1
playsound(chassis, fire_sound, 60, 1)
- M.throw_at(target, missile_range, missile_speed)
+ M.throw_at(target, missile_range, missile_speed, chassis)
projectiles--
log_message("Launched a mouse-trap from [src.name], targeting [target]. HONK!")
do_after_cooldown()
- return
+ return
diff --git a/code/game/objects/items/weapons/extinguisher.dm b/code/game/objects/items/weapons/extinguisher.dm
index 76d5b30acaa..37eb1384e9e 100644
--- a/code/game/objects/items/weapons/extinguisher.dm
+++ b/code/game/objects/items/weapons/extinguisher.dm
@@ -151,16 +151,19 @@
C = usr.buckled
var/obj/B = usr.buckled
var/movementdirection = turn(direction,180)
- if(C) C.propelled = 1
- B.Move(get_step(usr,movementdirection), movementdirection)
- sleep(1)
+ if(C) C.propelled = 4
B.Move(get_step(usr,movementdirection), movementdirection)
sleep(1)
B.Move(get_step(usr,movementdirection), movementdirection)
+ if(C) C.propelled = 3
+ sleep(1)
+ B.Move(get_step(usr,movementdirection), movementdirection)
sleep(1)
B.Move(get_step(usr,movementdirection), movementdirection)
+ if(C) C.propelled = 2
sleep(2)
B.Move(get_step(usr,movementdirection), movementdirection)
+ if(C) C.propelled = 1
sleep(2)
B.Move(get_step(usr,movementdirection), movementdirection)
if(C) C.propelled = 0
diff --git a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm
index 932319d5940..17ac70a7aa9 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/chairs.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/chairs.dm
@@ -136,7 +136,7 @@
if(propelled)
var/mob/living/occupant = buckled_mob
unbuckle()
- occupant.throw_at(A, 3, 2)
+ occupant.throw_at(A, 3, propelled)
occupant.apply_effect(6, STUN, 0)
occupant.apply_effect(6, WEAKEN, 0)
occupant.apply_effect(6, STUTTER, 0)
diff --git a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm
index 605fc427e6b..118016cceeb 100644
--- a/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm
+++ b/code/game/objects/structures/stool_bed_chair_nest/wheelchair.dm
@@ -136,7 +136,12 @@
if(propelled || (pulling && (pulling.a_intent == "harm")))
var/mob/living/occupant = buckled_mob
unbuckle()
- occupant.throw_at(A, 3, 2)
+
+ if (pulling && (pulling.a_intent == "hurt"))
+ occupant.throw_at(A, 3, 3, pulling)
+ else if (propelled)
+ occupant.throw_at(A, 3, propelled)
+
occupant.apply_effect(6, STUN, 0)
occupant.apply_effect(6, WEAKEN, 0)
occupant.apply_effect(6, STUTTER, 0)
diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm
new file mode 100644
index 00000000000..60adf1e7046
--- /dev/null
+++ b/code/modules/games/cards.dm
@@ -0,0 +1,222 @@
+/datum/playingcard
+ var/name = "playing card"
+ var/card_icon = "card_back"
+
+/obj/item/weapon/deck
+ name = "deck of cards"
+ desc = "A simple deck of playing cards."
+ icon = 'icons/obj/playing_cards.dmi'
+ icon_state = "deck"
+ w_class = 2
+
+ var/list/cards = list()
+
+/obj/item/weapon/deck/New()
+ ..()
+
+ var/datum/playingcard/P
+ for(var/suit in list("spades","clubs","diamonds","hearts"))
+
+ var/colour
+ if(suit == "spades" || suit == "clubs")
+ colour = "black_"
+ else
+ colour = "red_"
+
+ for(var/number in list("ace","two","three","four","five","six","seven","eight","nine","ten"))
+ P = new()
+ P.name = "[number] of [suit]"
+ P.card_icon = "[colour]num"
+ cards += P
+
+ for(var/number in list("jack","queen","king"))
+ P = new()
+ P.name = "[number] of [suit]"
+ P.card_icon = "[colour]col"
+ cards += P
+
+
+ for(var/i = 0,i<2,i++)
+ P = new()
+ P.name = "joker"
+ P.card_icon = "joker"
+ cards += P
+
+/obj/item/weapon/deck/attackby(obj/O as obj, mob/user as mob)
+ if(istype(O,/obj/item/weapon/hand))
+ var/obj/item/weapon/hand/H = O
+ for(var/datum/playingcard/P in H.cards)
+ cards += P
+ del(O)
+ user << "You place your cards on the bottom of the deck."
+ return
+ ..()
+
+/obj/item/weapon/deck/verb/draw_card()
+
+ set category = "Object"
+ set name = "Draw"
+ set desc = "Draw a card from a deck."
+ set src in oview(1)
+
+ if(usr.stat || !Adjacent(usr)) return
+
+ if(!istype(usr,/mob/living/carbon))
+ return
+
+ var/mob/living/carbon/user = usr
+
+ if(!cards.len)
+ usr << "There are no cards in the deck."
+ return
+
+ var/obj/item/weapon/hand/H
+ if(user.l_hand && istype(user.l_hand,/obj/item/weapon/hand))
+ H = user.l_hand
+ else if(user.r_hand && istype(user.r_hand,/obj/item/weapon/hand))
+ H = user.r_hand
+ else
+ H = new(get_turf(src))
+ user.put_in_hands(H)
+
+ if(!H || !user) return
+
+ var/datum/playingcard/P = cards[1]
+ H.cards += P
+ cards -= P
+ H.update_icon()
+ user.visible_message("\The [user] draws a card.")
+ user << "It's the [P]."
+
+/obj/item/weapon/deck/verb/deal_card()
+
+ set category = "Object"
+ set name = "Deal"
+ set desc = "Deal a card from a deck."
+
+ if(usr.stat || !Adjacent(usr)) return
+
+ if(!cards.len)
+ usr << "There are no cards in the deck."
+ return
+
+ var/list/players = list()
+ for(var/mob/living/player in orange(3))
+ if(!player.stat)
+ players += player
+ players -= usr
+
+ var/mob/living/M = input("Who do you wish to deal a card?") as null|anything in players
+ if(!usr || !src || !M) return
+
+ var/obj/item/weapon/hand/H = new(get_turf(src))
+
+ H.cards += cards[1]
+ cards -= cards[1]
+ H.concealed = 1
+ H.update_icon()
+ usr.visible_message("\The [usr] deals a card to \the [M].")
+ H.throw_at(get_step(M,M.dir),10,1,H)
+
+/obj/item/weapon/hand/attackby(obj/O as obj, mob/user as mob)
+ if(istype(O,/obj/item/weapon/hand))
+ var/obj/item/weapon/hand/H = O
+ for(var/datum/playingcard/P in H.cards)
+ cards += P
+ del(O)
+ user.put_in_hands(src)
+ update_icon()
+ return
+ ..()
+
+/obj/item/weapon/deck/attack_self(var/mob/user as mob)
+
+ var/list/newcards = list()
+ while(cards.len)
+ var/datum/playingcard/P = pick(cards)
+ newcards += P
+ cards -= P
+ cards = newcards
+ user.visible_message("\The [user] shuffles [src].")
+
+/obj/item/weapon/hand
+ name = "hand of cards"
+ desc = "Some playing cards."
+ icon = 'icons/obj/playing_cards.dmi'
+ icon_state = "empty"
+ w_class = 1
+
+ var/concealed = 0
+ var/list/cards = list()
+
+/obj/item/weapon/hand/verb/discard()
+
+ set category = "Object"
+ set name = "Discard"
+ set desc = "Place a card from your hand in front of you."
+
+ var/list/to_discard = list()
+ for(var/datum/playingcard/P in cards)
+ to_discard[P.name] = P
+ var/discarding = input("Which card do you wish to put down?") as null|anything in to_discard
+
+ if(!discarding || !to_discard[discarding] || !usr || !src) return
+
+ var/datum/playingcard/card = to_discard[discarding]
+ del(to_discard)
+
+ var/obj/item/weapon/hand/H = new(src.loc)
+ H.cards += card
+ cards -= card
+ H.concealed = 0
+ H.update_icon()
+ usr.visible_message("\The [usr] plays \the [discarding].")
+ H.loc = get_step(usr,usr.dir)
+
+ if(!cards.len)
+ del(src)
+
+/obj/item/weapon/hand/attack_self(var/mob/user as mob)
+ concealed = !concealed
+ update_icon()
+ user.visible_message("\The [user] [concealed ? "conceals" : "reveals"] their hand.")
+
+/obj/item/weapon/hand/examine()
+ ..()
+ if((!concealed || src.loc == usr) && cards.len)
+ usr << "It contains: "
+ for(var/datum/playingcard/P in cards)
+ usr << "The [P.name]."
+
+/obj/item/weapon/hand/update_icon()
+
+ if(!cards.len)
+ del(src)
+ return
+ else if(cards.len > 1)
+ name = "hand of cards"
+ desc = "Some playing cards."
+ else
+ name = "a playing card"
+ desc = "A playing card."
+
+ overlays.Cut()
+
+
+ if(cards.len == 1)
+ var/datum/playingcard/P = cards[1]
+ var/image/I = new(src.icon, (concealed ? "card_back" : "[P.card_icon]") )
+ I.pixel_x += (-5+rand(10))
+ I.pixel_y += (-5+rand(10))
+ overlays += I
+ return
+
+ var/origin = -12
+ var/offset = Floor(32/cards.len)
+
+ var/i = 0
+ for(var/datum/playingcard/P in cards)
+ var/image/I = new(src.icon, (concealed ? "card_back" : "[P.card_icon]") )
+ I.pixel_x = origin+(offset*i)
+ overlays += I
+ i++
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index eb47e5da66d..4d1542ad704 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -416,7 +416,7 @@
*/
- item.throw_at(target, item.throw_range, item.throw_speed)
+ item.throw_at(target, item.throw_range, item.throw_speed, src)
/mob/living/carbon/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
..()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index d49ea30b668..a43201a2a22 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1548,7 +1548,7 @@
status_flags |= LEAPING
src.visible_message("\The [src] leaps at [T]!")
- src.throw_at(get_step(get_turf(T),get_turf(src)), 5, 1)
+ src.throw_at(get_step(get_turf(T),get_turf(src)), 5, 1, src)
playsound(src.loc, 'sound/voice/shriek1.ogg', 50, 1)
sleep(5)
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index 422d622da6f..388045c8b6d 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -72,7 +72,6 @@
/mob/living/hitby(atom/movable/AM as mob|obj,var/speed = 5)//Standardization and logging -Sieve
if(istype(AM,/obj/))
var/obj/O = AM
- var/zone = ran_zone("chest",75)//Hits a random part of the body, geared towards the chest
var/dtype = BRUTE
if(istype(O,/obj/item/weapon))
var/obj/item/weapon/W = O
@@ -84,14 +83,25 @@
var/throw_damage = O.throwforce*(speed/5)
+ var/zone
+ if (istype(O.thrower, /mob/living))
+ var/mob/living/L = O.thrower
+ zone = check_zone(L.zone_sel.selecting)
+ else
+ zone = ran_zone("chest",75) //Hits a random part of the body, geared towards the chest
+
//def_zone = get_zone_with_miss_chance(zone, src, 15*AM.throwing_dist_travelled)
- zone = get_zone_with_miss_chance(zone, src) //TODO: store the location of the thrower and adjust miss chance with distance
+ if (O.throw_source)
+ var/distance = get_dist(O.throw_source, loc)
+ zone = get_zone_with_miss_chance(zone, src, min(15*(distance-2), 0))
+ else
+ zone = get_zone_with_miss_chance(zone, src, 15)
if(!zone)
- visible_message("\blue \The [AM] misses [src] narrowly!")
+ visible_message("\blue \The [O] misses [src] narrowly!")
return
- AM.throwing = 0 //it hit, so stop moving
+ O.throwing = 0 //it hit, so stop moving
if (istype(src, /mob/living/carbon/human))
var/mob/living/carbon/human/H = src
@@ -148,7 +158,7 @@
//This is called when the mob is thrown into a dense turf
/mob/living/proc/turf_collision(var/turf/T, var/speed)
- src.take_organ_damage(speed*4)
+ src.take_organ_damage(speed*5)
/mob/living/proc/near_wall(var/direction,var/distance=1)
var/turf/T = get_step(get_turf(src),direction)
diff --git a/code/modules/projectiles/guns/alien.dm b/code/modules/projectiles/guns/alien.dm
index 0fba816ab06..0acbdc5ffae 100644
--- a/code/modules/projectiles/guns/alien.dm
+++ b/code/modules/projectiles/guns/alien.dm
@@ -110,7 +110,7 @@
user.visible_message("\red [user] fires [src]!", "\red You fire [src]!")
spike.loc = get_turf(src)
- spike.throw_at(target,10,fire_force)
+ spike.throw_at(target,10,fire_force,user)
spike = null
update_icon()
diff --git a/code/modules/projectiles/guns/projectile/bow.dm b/code/modules/projectiles/guns/projectile/bow.dm
index 23b055b806b..bb9f6fda179 100644
--- a/code/modules/projectiles/guns/projectile/bow.dm
+++ b/code/modules/projectiles/guns/projectile/bow.dm
@@ -199,7 +199,7 @@
var/obj/item/weapon/arrow/A = arrow
A.loc = get_turf(user)
- A.throw_at(target,10,tension*release_speed)
+ A.throw_at(target,10,tension*release_speed,user)
arrow = null
tension = 0
icon_state = "crossbow"
diff --git a/code/modules/projectiles/guns/projectile/pneumatic.dm b/code/modules/projectiles/guns/projectile/pneumatic.dm
index 007a45b0a41..64033bf1840 100644
--- a/code/modules/projectiles/guns/projectile/pneumatic.dm
+++ b/code/modules/projectiles/guns/projectile/pneumatic.dm
@@ -131,7 +131,7 @@
user.visible_message("[user] fires [src] and launches [object] at [target]!","You fire [src] and launch [object] at [target]!")
src.remove_from_storage(object,user.loc)
- object.throw_at(target,10,speed)
+ object.throw_at(target,10,speed,user)
var/lost_gas_amount = tank.air_contents.total_moles*(pressure_setting/100)
var/datum/gas_mixture/removed = tank.air_contents.remove(lost_gas_amount)
diff --git a/code/modules/projectiles/guns/projectile/rocket.dm b/code/modules/projectiles/guns/projectile/rocket.dm
index e25a6ae3a9c..5f939e59073 100644
--- a/code/modules/projectiles/guns/projectile/rocket.dm
+++ b/code/modules/projectiles/guns/projectile/rocket.dm
@@ -42,7 +42,7 @@
var/obj/item/missile/M = new projectile(user.loc)
playsound(user.loc, 'sound/effects/bang.ogg', 50, 1)
M.primed = 1
- M.throw_at(target, missile_range, missile_speed)
+ M.throw_at(target, missile_range, missile_speed,user)
message_admins("[key_name_admin(user)] fired a rocket from a rocket launcher ([src.name]).")
log_game("[key_name_admin(user)] used a rocket launcher ([src.name]).")
rockets -= I
diff --git a/code/modules/reagents/grenade_launcher.dm b/code/modules/reagents/grenade_launcher.dm
index d5f1ca734df..43fe7311938 100644
--- a/code/modules/reagents/grenade_launcher.dm
+++ b/code/modules/reagents/grenade_launcher.dm
@@ -56,7 +56,7 @@
var/obj/item/weapon/grenade/chem_grenade/F = grenades[1] //Now with less copypasta!
grenades -= F
F.loc = user.loc
- F.throw_at(target, 30, 2)
+ F.throw_at(target, 30, 2, user)
message_admins("[key_name_admin(user)] fired a grenade ([F.name]) from a grenade launcher ([src.name]).")
log_game("[key_name_admin(user)] used a grenade ([src.name]).")
F.active = 1