diff --git a/code/controllers/subsystem/non_firing/SSmapping.dm b/code/controllers/subsystem/non_firing/SSmapping.dm
index a29252861b3..a236dabf7f7 100644
--- a/code/controllers/subsystem/non_firing/SSmapping.dm
+++ b/code/controllers/subsystem/non_firing/SSmapping.dm
@@ -230,8 +230,7 @@ SUBSYSTEM_DEF(mapping)
var/obj/structure/closet/C = pick_n_take(seeded_salvage_closets)
var/salvage_item_type = pick(small_salvage_items)
var/obj/salvage_item = new salvage_item_type(C)
- salvage_item.pixel_x = rand(-5, 5)
- salvage_item.pixel_y = rand(-5, 5)
+ salvage_item.scatter_atom()
max_salvage_attempts -= 1
max_salvage_attempts = rand(10, 15)
@@ -239,8 +238,7 @@ SUBSYSTEM_DEF(mapping)
var/obj/T = pick_n_take(seeded_salvage_surfaces)
var/salvage_item_type = pick(small_salvage_items)
var/obj/salvage_item = new salvage_item_type(T.loc)
- salvage_item.pixel_x = rand(-5, 5)
- salvage_item.pixel_y = rand(-5, 5)
+ salvage_item.scatter_atom()
max_salvage_attempts -= 1
log_startup_progress("Successfully seeded space salvage in [stop_watch(space_salvage_timer)]s.")
diff --git a/code/datums/elements/shatters_when_thrown.dm b/code/datums/elements/shatters_when_thrown.dm
index d05a00bde67..06cdc23dd65 100644
--- a/code/datums/elements/shatters_when_thrown.dm
+++ b/code/datums/elements/shatters_when_thrown.dm
@@ -46,8 +46,7 @@
for(var/iteration in 1 to number_of_shards)
var/obj/item/shard = new shard_type(scatter_turf)
- shard.pixel_x = rand(-6, 6)
- shard.pixel_y = rand(-6, 6)
+ shard.scatter_atom()
playsound(scatter_turf, shattering_sound, 60, TRUE)
if(isobj(source))
var/obj/obj_source = source
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 8be85ac7793..f4eff0e5440 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -69,6 +69,9 @@
/// Used for icon smoothing. Won't smooth if it ain't anchored and can be unanchored. Only set to true on windows
var/can_be_unanchored = FALSE
+ /// How far (in pixels) should this atom scatter when created/dropped/etc. Does not apply to mapped-in items.
+ var/scatter_distance = 0
+
/atom/movable/attempt_init(loc, ...)
var/turf/T = get_turf(src)
if(T && SSatoms.initialized != INITIALIZATION_INSSATOMS && GLOB.space_manager.is_zlevel_dirty(T.z))
@@ -1062,3 +1065,8 @@
/// useful callback for things that want special behavior on crush
/atom/movable/proc/on_crush_thing(atom/thing)
return
+
+/// Used to scatter atoms so that multiple copies aren't all at the exact same spot.
+/atom/movable/proc/scatter_atom(x_offset = 0, y_offset = 0)
+ pixel_x = x_offset + rand(-scatter_distance, scatter_distance)
+ pixel_y = y_offset + rand(-scatter_distance, scatter_distance)
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 00ce6fa5ee5..b002ff4405c 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -415,8 +415,7 @@
var/obj/item/new_item = new D.build_path(BuildTurf)
new_item.materials[MAT_METAL] /= coeff
new_item.materials[MAT_GLASS] /= coeff
- new_item.pixel_y = rand(-5, 5)
- new_item.pixel_x = rand(-5, 5)
+ new_item.scatter_atom()
if(is_station_level(z))
SSblackbox.record_feedback("tally", "station_autolathe_production", 1, "[D.type]")
SStgui.update_uis(src)
diff --git a/code/game/machinery/pipe/pipe_construction.dm b/code/game/machinery/pipe/pipe_construction.dm
index 9d549b50a6c..edc06eb4fa9 100644
--- a/code/game/machinery/pipe/pipe_construction.dm
+++ b/code/game/machinery/pipe/pipe_construction.dm
@@ -157,8 +157,7 @@
connect_types = list(CONNECT_TYPE_NORMAL, CONNECT_TYPE_SUPPLY, CONNECT_TYPE_SCRUBBER)
update(make_from)
- pixel_x = rand(-5, 5)
- pixel_y = rand(-5, 5)
+ scatter_atom()
//update the name and icon of the pipe item depending on the type
diff --git a/code/game/machinery/vendors/vending.dm b/code/game/machinery/vendors/vending.dm
index 457ad4aba68..079765b8ea5 100644
--- a/code/game/machinery/vendors/vending.dm
+++ b/code/game/machinery/vendors/vending.dm
@@ -825,8 +825,7 @@
if(put_on_turf)
var/turf/T = get_turf(src)
vended.forceMove(T)
- vended.pixel_x = rand(-5, 5)
- vended.pixel_y = rand(-5, 5)
+ vended.scatter_atom()
return TRUE
return FALSE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index e011d494106..85435143c15 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -186,6 +186,8 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
/// In tiles, how far this weapon can reach; 1 for adjacent, which is default
var/reach = 1
+ scatter_distance = 5
+
/obj/item/New()
..()
diff --git a/code/game/objects/items/random_items.dm b/code/game/objects/items/random_items.dm
index c32d3397a1c..fc256cf698e 100644
--- a/code/game/objects/items/random_items.dm
+++ b/code/game/objects/items/random_items.dm
@@ -39,19 +39,18 @@
reagents.add_reagent(R, volume)
name = "unlabelled bottle"
icon_state = pick("alco-white","alco-green","alco-blue","alco-clear","alco-red")
- pixel_x = rand(-5, 5)
- pixel_y = rand(-5, 5)
+ scatter_atom()
/obj/item/storage/pill_bottle/random_meds
name = "unlabelled pillbottle"
desc = "The sheer recklessness of this bottle's existence astounds you."
allow_wrap = FALSE
var/labelled = FALSE
+ scatter_distance = 10
/obj/item/storage/pill_bottle/random_meds/Initialize(mapload)
. = ..()
- pixel_x = rand(-10, 10)
- pixel_y = rand(-10, 10)
+ scatter_atom()
/obj/item/storage/pill_bottle/random_meds/populate_contents()
var/list/possible_meds_standard = GLOB.standard_medicines.Copy()
diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm
index 57efa152294..c1c6bbdabc4 100644
--- a/code/game/objects/items/stacks/sheets/mineral.dm
+++ b/code/game/objects/items/stacks/sheets/mineral.dm
@@ -150,8 +150,11 @@ GLOBAL_LIST_INIT(snow_recipes, list(
/obj/item/stack/sheet/mineral/Initialize(mapload, new_amount, merge)
. = ..()
- pixel_x = rand(0,4)-4
- pixel_y = rand(0,4)-4
+ scatter_atom()
+
+/obj/item/stack/sheet/mineral/scatter_atom(offset_x, offset_y)
+ pixel_x = rand(-4,0) + offset_x
+ pixel_y = rand(-4,0) + offset_y
/obj/item/stack/sheet/mineral/sandstone
name = "sandstone brick"
diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm
index 9eb64dd3130..cf05a045714 100644
--- a/code/game/objects/items/stacks/tiles/tile_types.dm
+++ b/code/game/objects/items/stacks/tiles/tile_types.dm
@@ -15,11 +15,11 @@
origin_tech = "materials=1"
var/turf_type = null
var/mineralType = null
+ scatter_distance = 3
/obj/item/stack/tile/Initialize(mapload, new_amount, merge)
. = ..()
- pixel_x = rand(-3, 3)
- pixel_y = rand(-3, 3) //randomize a little
+ scatter_atom()
/obj/item/stack/tile/welder_act(mob/user, obj/item/I)
if(get_amount() < 4)
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index dc9d02135ad..a66e55b12f9 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -142,11 +142,11 @@
name = "arbitrary spent casing item"
desc = "If you can see this and didn't spawn it, make an issue report on GitHub."
icon_state = "gshell"
+ scatter_distance = 10
/obj/item/trash/spentcasing/Initialize(mapload)
. = ..()
- pixel_x = rand(-10, 10)
- pixel_y = rand(-10, 10)
+ scatter_atom()
transform = turn(transform, rand(0, 360))
/obj/item/trash/spentcasing/shotgun
diff --git a/code/game/objects/items/weapons/cigs.dm b/code/game/objects/items/weapons/cigs.dm
index 6d404496973..b927b404cdf 100644
--- a/code/game/objects/items/weapons/cigs.dm
+++ b/code/game/objects/items/weapons/cigs.dm
@@ -333,9 +333,7 @@ LIGHTERS ARE IN LIGHTERS.DM
/obj/item/clothing/mask/cigarette/rollie/Initialize(mapload)
. = ..()
- pixel_x = rand(-5, 5)
- pixel_y = rand(-5, 5)
-
+ scatter_atom()
/obj/item/clothing/mask/cigarette/rollie/custom
list_reagents = list()
@@ -346,11 +344,11 @@ LIGHTERS ARE IN LIGHTERS.DM
icon_state = "cigbutt"
w_class = WEIGHT_CLASS_TINY
throwforce = 1
+ scatter_distance = 10
/obj/item/cigbutt/Initialize(mapload)
. = ..()
- pixel_x = rand(-10, 10)
- pixel_y = rand(-10, 10)
+ scatter_atom()
transform = turn(transform, rand(0, 360))
/obj/item/cigbutt/decompile_act(obj/item/matter_decompiler/C, mob/user)
@@ -364,11 +362,11 @@ LIGHTERS ARE IN LIGHTERS.DM
name = "roach"
desc = "A manky old roach, or for non-stoners, a used rollup."
icon_state = "roach"
+ scatter_distance = 5
/obj/item/cigbutt/roach/Initialize(mapload)
. = ..()
- pixel_x = rand(-5, 5)
- pixel_y = rand(-5, 5)
+ scatter_atom()
//////////////////////////////
// MARK: ROLLING
diff --git a/code/game/objects/items/weapons/gift_wrappaper.dm b/code/game/objects/items/weapons/gift_wrappaper.dm
index 9932bdbe488..124e434ae88 100644
--- a/code/game/objects/items/weapons/gift_wrappaper.dm
+++ b/code/game/objects/items/weapons/gift_wrappaper.dm
@@ -14,11 +14,11 @@
icon_state = "gift1"
item_state = "gift1"
resistance_flags = FLAMMABLE
+ scatter_distance = 10
/obj/item/a_gift/New()
..()
- pixel_x = rand(-10,10)
- pixel_y = rand(-10,10)
+ scatter_atom()
if(w_class > 0 && w_class < 4)
icon_state = "gift[w_class]"
else
diff --git a/code/game/objects/items/weapons/grenades/frag.dm b/code/game/objects/items/weapons/grenades/frag.dm
index ab6a3b8260b..fdaa2ab050b 100644
--- a/code/game/objects/items/weapons/grenades/frag.dm
+++ b/code/game/objects/items/weapons/grenades/frag.dm
@@ -66,11 +66,11 @@
w_class = WEIGHT_CLASS_SMALL
sharp = TRUE
hitsound = 'sound/weapons/pierce.ogg'
+ scatter_distance = 8
/obj/item/shrapnel/Initialize(mapload)
. = ..()
icon_state = pick("shrapnel1", "shrapnel2", "shrapnel3")
- pixel_x = rand(-8, 8)
- pixel_y = rand(-8, 8)
+ scatter_atom()
#undef DEFAULT_SHRAPNEL_RANGE
diff --git a/code/game/objects/items/weapons/storage/storage_base.dm b/code/game/objects/items/weapons/storage/storage_base.dm
index 0b5236d0e31..d61c950c5b0 100644
--- a/code/game/objects/items/weapons/storage/storage_base.dm
+++ b/code/game/objects/items/weapons/storage/storage_base.dm
@@ -103,7 +103,7 @@
/obj/item/storage/proc/removal_allowed_check(mob/user)
return TRUE
-/obj/item/storage/MouseDrop(obj/over_object)
+/obj/item/storage/MouseDrop(obj/over_object, src_location, over_location, src_control, over_control, params)
if(!ismob(usr)) //so monkeys can take off their backpacks -- Urist
return
var/mob/M = usr
@@ -134,8 +134,12 @@
M.face_atom(over_object)
M.visible_message("[M] empties [src] onto [over_object].",
"You empty [src] onto [over_object].")
+ var/list/params_list = params2list(params)
+ var/x_offset = text2num(params_list["icon-x"]) - 16
+ var/y_offset = text2num(params_list["icon-y"]) - 16
for(var/obj/item/I in contents)
remove_from_storage(I, T)
+ I.scatter_atom(x_offset, y_offset)
update_icon() // For content-sensitive icons
return
@@ -608,6 +612,7 @@
hide_from(user)
for(var/obj/item/I in contents)
remove_from_storage(I, T)
+ I.scatter_atom()
CHECK_TICK
/**
diff --git a/code/modules/fish/fish_items.dm b/code/modules/fish/fish_items.dm
index ad47d59cf3d..1f142522d47 100644
--- a/code/modules/fish/fish_items.dm
+++ b/code/modules/fish/fish_items.dm
@@ -159,8 +159,7 @@
/obj/item/shard/shark_teeth/set_initial_icon_state()
icon_state = "teeth"
- pixel_x = rand(-5,5)
- pixel_y = rand(-5,5)
+ scatter_atom()
/obj/item/fish/catfish
name = "catfish"
diff --git a/code/modules/food_and_drinks/food_base.dm b/code/modules/food_and_drinks/food_base.dm
index d7674e34464..6b92c9b0576 100644
--- a/code/modules/food_and_drinks/food_base.dm
+++ b/code/modules/food_and_drinks/food_base.dm
@@ -336,8 +336,7 @@
for(var/i in 1 to (slices_num - slices_lost))
var/obj/slice = new slice_path (loc)
reagents.trans_to(slice,reagents_per_slice)
- slice.pixel_x = rand(-7, 7)
- slice.pixel_y = rand(-7, 7)
+ slice.scatter_atom()
qdel(src)
return ..()
diff --git a/code/modules/hydroponics/beekeeping/honeycomb.dm b/code/modules/hydroponics/beekeeping/honeycomb.dm
index cdbaf069c41..6c9f486ef04 100644
--- a/code/modules/hydroponics/beekeeping/honeycomb.dm
+++ b/code/modules/hydroponics/beekeeping/honeycomb.dm
@@ -7,11 +7,11 @@
volume = 10
list_reagents = list("honey" = 5)
var/honey_color = ""
+ scatter_distance = 8
/obj/item/food/honeycomb/Initialize(mapload)
. = ..()
- pixel_x = rand(8,-8)
- pixel_y = rand(8,-8)
+ scatter_atom()
update_icon(UPDATE_OVERLAYS)
/obj/item/food/honeycomb/update_overlays()
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index ad72da94e35..45062f1abbd 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -36,8 +36,7 @@
else if(seed)
seed = new seed
- pixel_x = rand(-5, 5)
- pixel_y = rand(-5, 5)
+ scatter_atom()
if(dried_type == -1)
dried_type = type
@@ -92,8 +91,7 @@
for(var/i = 1 to (slices_num - slices_lost))
var/obj/slice = new slice_path (loc)
reagents.trans_to(slice, reagents_per_slice)
- slice.pixel_x = rand(-7, 7)
- slice.pixel_y = rand(-7, 7)
+ slice.scatter_atom()
qdel(src)
return ..()
diff --git a/code/modules/hydroponics/grown/towercap.dm b/code/modules/hydroponics/grown/towercap.dm
index 97fe7e539cf..3f48c435c13 100644
--- a/code/modules/hydroponics/grown/towercap.dm
+++ b/code/modules/hydroponics/grown/towercap.dm
@@ -174,8 +174,7 @@
if(!has_buckled_mobs() && do_after(user, 50, target = src))
for(var/I in 1 to 5)
var/obj/item/grown/log/L = new /obj/item/grown/log(loc)
- L.pixel_x += rand(1,4)
- L.pixel_y += rand(1,4)
+ L.scatter_atom()
qdel(src)
return
..()
diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm
index 7b754316f60..d03a901b510 100644
--- a/code/modules/hydroponics/growninedible.dm
+++ b/code/modules/hydroponics/growninedible.dm
@@ -26,9 +26,7 @@
else if(seed)
seed = new seed
- pixel_x = rand(-5, 5)
- pixel_y = rand(-5, 5)
-
+ scatter_atom()
if(seed)
for(var/datum/plant_gene/trait/T in seed.genes)
T.on_new(src)
diff --git a/code/modules/hydroponics/hydroitemdefines.dm b/code/modules/hydroponics/hydroitemdefines.dm
index f99b0dfa202..90eaab7b7fb 100644
--- a/code/modules/hydroponics/hydroitemdefines.dm
+++ b/code/modules/hydroponics/hydroitemdefines.dm
@@ -342,8 +342,7 @@
/obj/item/reagent_containers/glass/bottle/nutrient/killer/Initialize(mapload)
. = ..()
- pixel_x = rand(-5, 5)
- pixel_y = rand(-5, 5)
+ scatter_atom()
/obj/item/reagent_containers/glass/bottle/nutrient/killer/weedkiller
name = "jug of weed killer"
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 2cdeea2ded1..802d830aa53 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -621,8 +621,7 @@
/obj/item/unsorted_seeds/New(obj/item/seeds/template, mutation_level, list/mutation_focus, seed_data_in = null)
..()
template = template.Copy()
- pixel_x = rand(-6, 6)
- pixel_y = rand(-6, 6)
+ scatter_atom()
if(seed_data_in)
seed_data = seed_data_in
else
diff --git a/code/modules/mining/lavaland/ash_flora.dm b/code/modules/mining/lavaland/ash_flora.dm
index 494596b6202..d8ccea7d08a 100644
--- a/code/modules/mining/lavaland/ash_flora.dm
+++ b/code/modules/mining/lavaland/ash_flora.dm
@@ -203,8 +203,7 @@
/obj/item/food/grown/ash_flora/Initialize(mapload)
. = ..()
- pixel_x = rand(-4, 4)
- pixel_y = rand(-4, 4)
+ scatter_atom()
/// for actual crafting
/obj/item/food/grown/ash_flora/shavings
diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm
index dcc350ea9ad..6c794e4b8d5 100644
--- a/code/modules/mining/ores_coins.dm
+++ b/code/modules/mining/ores_coins.dm
@@ -19,8 +19,11 @@
/obj/item/stack/ore/Initialize(mapload, new_amount, merge = TRUE)
. = ..()
- pixel_x = rand(0, 16) - 8
- pixel_y = rand(0, 8) - 8
+ scatter_atom()
+
+/obj/item/stack/ore/scatter_atom(x_offset, y_offset)
+ pixel_x = rand(-8, 8) + x_offset
+ pixel_y = rand(-8, 0) + y_offset
/obj/item/stack/ore/welder_act(mob/user, obj/item/I)
. = TRUE
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 e981995f26d..3c40fd3f44e 100644
--- a/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
+++ b/code/modules/mob/living/simple_animal/friendly/farm_animals.dm
@@ -223,9 +223,11 @@
/mob/living/simple_animal/chick/Initialize(mapload)
. = ..()
+ scatter_atom()
- pixel_x = rand(-6, 6)
- pixel_y = rand(0, 10)
+/mob/living/simple_animal/chick/scatter_atom(x_offset, y_offset)
+ pixel_x = rand(-6, 6) + x_offset
+ pixel_y = rand(0, 10) + y_offset
/mob/living/simple_animal/chick/Life(seconds, times_fired)
. =..()
@@ -302,11 +304,14 @@ GLOBAL_VAR_INIT(chicken_count, 0)
icon_state = "[icon_prefix]_[body_color]"
icon_living = "[icon_prefix]_[body_color]"
icon_dead = "[icon_prefix]_[body_color]_dead"
- pixel_x = rand(-6, 6)
- pixel_y = rand(0, 10)
+ scatter_atom()
update_appearance(UPDATE_ICON_STATE)
GLOB.chicken_count += 1
+/mob/living/simple_animal/chick/scatter_atom(x_offset, y_offset)
+ pixel_x = rand(-6, 6) + x_offset
+ pixel_y = rand(0, 10) + y_offset
+
/mob/living/simple_animal/chicken/death(gibbed)
// Only execute the below if we successfully died
. = ..(gibbed)
@@ -339,8 +344,7 @@ GLOBAL_VAR_INIT(chicken_count, 0)
visible_message("[src] [pick(layMessage)]")
eggsleft--
var/obj/item/E = new egg_type(get_turf(src))
- E.pixel_x = rand(-6,6)
- E.pixel_y = rand(-6,6)
+ E.scatter_atom()
if(eggsFertile)
if(GLOB.chicken_count < MAX_CHICKENS && prob(25))
START_PROCESSING(SSobj, E)
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index 4c92f42070d..a34a8b666d3 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -50,6 +50,8 @@
var/const/crayonfont = "Comic Sans MS"
var/regex/blacklist = new("(