diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm
index ad93dd2d54..939f6698cb 100644
--- a/code/__DEFINES/maths.dm
+++ b/code/__DEFINES/maths.dm
@@ -199,4 +199,6 @@
#define LORENTZ_CUMULATIVE_DISTRIBUTION(x, y, s) ( (1/PI)*TORADIANS(arctan((x-y)/s)) + 1/2 )
#define RULE_OF_THREE(a, b, x) ((a*x)/b)
-// )
\ No newline at end of file
+// )
+
+#define MANHATTAN_DISTANCE(a, b) (abs(a.x - b.x) + abs(a.y - b.y))
\ No newline at end of file
diff --git a/code/__HELPERS/vector.dm b/code/__HELPERS/vector.dm
new file mode 100644
index 0000000000..80295bde0e
--- /dev/null
+++ b/code/__HELPERS/vector.dm
@@ -0,0 +1,57 @@
+// Basic geometry things.
+
+/datum/vector/
+ var/x = 0
+ var/y = 0
+
+/datum/vector/New(var/x, var/y)
+ src.x = x
+ src.y = y
+
+/datum/vector/proc/duplicate()
+ return new /datum/vector(x, y)
+
+/datum/vector/proc/euclidian_norm()
+ return sqrt(x*x + y*y)
+
+/datum/vector/proc/squared_norm()
+ return x*x + y*y
+
+/datum/vector/proc/normalize()
+ var/norm = euclidian_norm()
+ x = x/norm
+ y = y/norm
+ return src
+
+/datum/vector/proc/chebyshev_norm()
+ return max(abs(x), abs(y))
+
+/datum/vector/proc/chebyshev_normalize()
+ var/norm = chebyshev_norm()
+ x = x/norm
+ y = y/norm
+ return src
+
+/datum/vector/proc/is_integer()
+ return ISINTEGER(x) && ISINTEGER(y)
+
+/atom/movable/proc/vector_translate(var/datum/vector/V, var/delay)
+ var/turf/T = get_turf(src)
+ var/turf/destination = locate(T.x + V.x, T.y + V.y, z)
+ var/datum/vector/V_norm = V.duplicate()
+ V_norm.chebyshev_normalize()
+ if (!V_norm.is_integer())
+ return
+ var/turf/destination_temp
+ while (destination_temp != destination)
+ destination_temp = locate(T.x + V_norm.x, T.y + V_norm.y, z)
+ forceMove(destination_temp)
+ T = get_turf(src)
+ sleep(delay + world.tick_lag) // Shortest possible time to sleep
+
+/atom/proc/get_translated_turf(var/datum/vector/V)
+ var/turf/T = get_turf(src)
+ return locate(T.x + V.x, T.y + V.y, z)
+
+/proc/atoms2vector(var/atom/A, var/atom/B)
+ return new /datum/vector((B.x - A.x), (B.y - A.y)) // Vector from A -> B
\ No newline at end of file
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 54c719a6d5..04d9c706ed 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -820,3 +820,11 @@
target.layer = old_layer
target.plane = old_plane
current_button.appearance_cache = target.appearance
+
+/proc/get_action_of_type(mob/M, var/action_type)
+ if(!M.actions || !ispath(action_type, /datum/action))
+ return
+ for(var/datum/action/A in M.actions)
+ if(istype(A, action_type))
+ return A
+ return
\ No newline at end of file
diff --git a/code/modules/food_and_drinks/drinks/drinks.dm b/code/modules/food_and_drinks/drinks/drinks.dm
index cd5c9d850b..19d0162a4c 100644
--- a/code/modules/food_and_drinks/drinks/drinks.dm
+++ b/code/modules/food_and_drinks/drinks/drinks.dm
@@ -107,9 +107,9 @@
smash(hit_atom, throwingdatum?.thrower, TRUE)
/obj/item/reagent_containers/food/drinks/proc/smash(atom/target, mob/thrower, ranged = FALSE)
- if(!isGlass)
+ if(!isGlass && !istype(src, /obj/item/reagent_containers/food/drinks/bottle)) //I don't like this but I also don't want to rework drink container hierarchy
return
- if(QDELING(src) || !target) //Invalid loc
+ if(QDELING(src) || (ranged && !target))
return
if(bartender_check(target) && ranged)
return
@@ -126,12 +126,69 @@
B.transform = M
B.pixel_x = rand(-12, 12)
B.pixel_y = rand(-12, 12)
- if(prob(33))
- new/obj/item/shard(drop_location())
- playsound(src, "shatter", 70, 1)
+ if(isGlass)
+ playsound(src, "shatter", 70, 1)
+ if(prob(33))
+ new/obj/item/shard(drop_location())
+ else
+ B.force = 0
+ B.throwforce = 0
+ B.desc = "A carton with the bottom half burst open. Might give you a papercut."
transfer_fingerprints_to(B)
qdel(src)
+/obj/item/reagent_containers/food/drinks/MouseDrop(atom/over, atom/src_location, atom/over_location, src_control, over_control, params)
+ var/mob/user = usr
+ . = ..()
+ if (!istype(src_location) || !istype(over_location))
+ return
+ if (!user || user.incapacitated() || !user.Adjacent(src))
+ return
+ if (!(locate(/obj/structure/table) in src_location) || !(locate(/obj/structure/table) in over_location))
+ return
+
+ //Are we an expert slider?
+ var/datum/action/innate/D = get_action_of_type(user, /datum/action/innate/drink_fling)
+ if(!D?.active)
+ if (!src_location.Adjacent(over_location)) // Regular users can only do short slides.
+ return
+ if (prob(10))
+ user.visible_message("\The [user] tries to slide \the [src] down the table, but fails miserably.", "You fail to slide \the [src] down the table!")
+ smash(over_location, user, FALSE)
+ return
+ user.visible_message("\The [user] slides \the [src] down the table.", "You slide \the [src] down the table!")
+ forceMove(over_location)
+ return
+ var/distance = MANHATTAN_DISTANCE(over_location, src)
+ if (distance >= 8 || distance == 0) // More than a full screen to go, or trying to slide to the same tile
+ return
+
+ // Geometrically checking if we're on a straight line.
+ var/datum/vector/V = atoms2vector(src, over_location)
+ var/datum/vector/V_norm = V.duplicate()
+ V_norm.normalize()
+ if (!V_norm.is_integer())
+ return // Only a cardinal vector (north, south, east, west) can pass this test
+
+ // Checks if there's tables on the path.
+ var/turf/dest = get_translated_turf(V)
+ var/turf/temp_turf = src_location
+
+ do
+ temp_turf = temp_turf.get_translated_turf(V_norm)
+ if (!locate(/obj/structure/table) in temp_turf)
+ var/datum/vector/V2 = atoms2vector(src, temp_turf)
+ vector_translate(V2, 0.1 SECONDS)
+ user.visible_message("\The [user] slides \the [src] down the table... and straight into the ground!", "You slide \the [src] down the table, and straight into the ground!")
+ smash(over_location, user, FALSE)
+ return
+ while (temp_turf != dest)
+
+ vector_translate(V, 0.1 SECONDS)
+ user.visible_message("\The [user] expertly slides \the [src] down the table.", "You slide \the [src] down the table. What a pro.")
+ return
+
+
////////////////////////////////////////////////////////////////////////////////
/// Drinks. END
////////////////////////////////////////////////////////////////////////////////
@@ -289,28 +346,6 @@
icon_state = "juicebox"
volume = 15 //I figure if you have to craft these it should at least be slightly better than something you can get for free from a watercooler
-/obj/item/reagent_containers/food/drinks/sillycup/smallcarton/smash(atom/target, mob/thrower, ranged = FALSE)
- if(bartender_check(target) && ranged)
- return
- var/obj/item/broken_bottle/B = new (loc)
- B.icon_state = icon_state
- var/icon/I = new('icons/obj/drinks.dmi', src.icon_state)
- I.Blend(B.broken_outline, ICON_OVERLAY, rand(5), 1)
- I.SwapColor(rgb(255, 0, 220, 255), rgb(0, 0, 0, 0))
- B.icon = I
- B.name = "broken [name]"
- B.force = 0
- B.throwforce = 0
- B.desc = "A carton with the bottom half burst open. Might give you a papercut."
- if(ranged)
- var/matrix/M = matrix(B.transform)
- M.Turn(rand(-170, 170))
- B.transform = M
- B.pixel_x = rand(-12, 12)
- B.pixel_y = rand(-12, 12)
- transfer_fingerprints_to(B)
- qdel(src)
-
/obj/item/reagent_containers/food/drinks/sillycup/smallcarton/on_reagent_change(changetype)
if (reagents.reagent_list.len)
switch(reagents.get_master_reagent_id())
diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
index 7f2b24ca11..07026e79de 100644
--- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
@@ -16,40 +16,6 @@
isGlass = TRUE
foodtype = ALCOHOL
-
-/obj/item/reagent_containers/food/drinks/bottle/smash(mob/living/target, mob/thrower, ranged = FALSE)
- //Creates a shattering noise and replaces the bottle with a broken_bottle
- if(bartender_check(target) && ranged)
- return
- var/obj/item/broken_bottle/B = new (loc)
- if(!ranged)
- thrower.put_in_hands(B)
- else
- var/matrix/M = matrix(B.transform)
- M.Turn(rand(-170, 170))
- B.transform = M
- B.pixel_x = rand(-12, 12)
- B.pixel_y = rand(-12, 12)
- B.icon_state = icon_state
-
- var/icon/I = new('icons/obj/drinks.dmi', src.icon_state)
- I.Blend(B.broken_outline, ICON_OVERLAY, rand(5), 1)
- I.SwapColor(rgb(255, 0, 220, 255), rgb(0, 0, 0, 0))
- B.icon = I
-
- if(isGlass)
- if(prob(33))
- new/obj/item/shard(drop_location())
- playsound(src, "shatter", 70, 1)
- else
- B.force = 0
- B.throwforce = 0
- B.desc = "A carton with the bottom half burst open. Might give you a papercut."
- B.name = "broken [name]"
- transfer_fingerprints_to(B)
-
- qdel(src)
-
/obj/item/reagent_containers/food/drinks/bottle/attack(mob/living/target, mob/living/user)
if(!target)
@@ -109,7 +75,7 @@
//Keeping this here for now, I'll ask if I should keep it here.
/obj/item/broken_bottle
name = "broken bottle"
- desc = "A bottle with a sharp broken bottom."
+ desc = "A shattered glass container with sharp edges."
icon = 'icons/obj/drinks.dmi'
icon_state = "broken_bottle"
force = 9
diff --git a/code/modules/jobs/job_types/bartender.dm b/code/modules/jobs/job_types/bartender.dm
index 0ace449757..cffeceea9c 100644
--- a/code/modules/jobs/job_types/bartender.dm
+++ b/code/modules/jobs/job_types/bartender.dm
@@ -28,3 +28,7 @@
backpack_contents = list(/obj/item/storage/box/beanbag=1,/obj/item/book/granter/action/drink_fling=1)
shoes = /obj/item/clothing/shoes/laceup
+/datum/job/bartender/after_spawn(mob/living/H, mob/M, latejoin = FALSE)
+ . = ..()
+ var/datum/action/innate/drink_fling/D = new
+ D.Grant(H)
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index 23176f8a05..6d5c546e7e 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -105,11 +105,11 @@
/obj/item/reagent_containers/proc/bartender_check(atom/target)
. = FALSE
var/turf/T = get_turf(src)
- if(!T || target.CanPass(src, T) || !thrownby || !thrownby.actions)
+ if(!T || !target.CanPass(src, T) || !thrownby || !thrownby.actions)
return
- for(var/datum/action/innate/drink_fling/D in thrownby.actions)
- if(D.active)
- return TRUE
+ var/datum/action/innate/D = get_action_of_type(thrownby, /datum/action/innate/drink_fling)
+ if(D?.active)
+ return TRUE
/obj/item/reagent_containers/proc/ForceResetRotation()
transform = initial(transform)
@@ -131,13 +131,14 @@
if(thrownby)
log_combat(thrownby, M, "splashed", R)
reagents.reaction(target, TOUCH)
-
+
else if(bartender_check(target) && thrown)
visible_message("[src] lands onto the [target.name] without spilling a single drop.")
transform = initial(transform)
addtimer(CALLBACK(src, .proc/ForceResetRotation), 1)
return
+
else
if(isturf(target) && reagents.reagent_list.len && thrownby)
log_combat(thrownby, target, "splashed (thrown) [english_list(reagents.reagent_list)]", "in [AREACOORD(target)]")
diff --git a/tgstation.dme b/tgstation.dme
index a747c823db..927027d448 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -152,6 +152,7 @@
#include "code\__HELPERS\type2type_vr.dm"
#include "code\__HELPERS\typelists.dm"
#include "code\__HELPERS\unsorted.dm"
+#include "code\__HELPERS\vector.dm"
#include "code\__HELPERS\view.dm"
#include "code\__HELPERS\sorts\__main.dm"
#include "code\__HELPERS\sorts\InsertSort.dm"