diff --git a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
index 82f4d367780..85b662d04aa 100644
--- a/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
+++ b/_maps/RandomRuins/LavaRuins/lavaland_biodome_beach.dmm
@@ -406,6 +406,10 @@
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer4,
/turf/open/floor/plating,
/area/ruin/powered/beach)
+"mv" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast,
+/area/ruin/powered/beach)
"mw" = (
/obj/structure/flora/coconuts,
/turf/open/misc/beach/sand,
@@ -985,6 +989,7 @@
dir = 1
},
/obj/machinery/light/directional/south,
+/obj/effect/spawner/message_in_a_bottle/low_prob,
/turf/open/misc/beach/coast/corner{
dir = 8
},
@@ -1990,7 +1995,7 @@ bG
bL
VI
VI
-yT
+mv
pz
Eu
pz
diff --git a/_maps/RandomZLevels/TheBeach.dmm b/_maps/RandomZLevels/TheBeach.dmm
index 602e11e3f33..ea38a0cc477 100644
--- a/_maps/RandomZLevels/TheBeach.dmm
+++ b/_maps/RandomZLevels/TheBeach.dmm
@@ -92,6 +92,10 @@
/obj/structure/sign/clock/directional/north,
/turf/open/floor/wood,
/area/awaymission/beach)
+"aY" = (
+/obj/item/broken_bottle,
+/turf/open/misc/beach/sand,
+/area/awaymission/beach)
"bg" = (
/obj/structure/no_effect_signpost{
pixel_y = 6;
@@ -174,6 +178,11 @@
/obj/machinery/light/directional/west,
/turf/open/floor/carpet/cyan,
/area/awaymission/beach)
+"cu" = (
+/obj/effect/mob_spawn/corpse/human/old_pirate_captain,
+/obj/structure/bed/maint,
+/turf/open/misc/beach/sand,
+/area/awaymission/beach)
"cv" = (
/obj/structure/table/wood,
/obj/item/food/grown/watermelon{
@@ -207,6 +216,12 @@
dir = 4
},
/area/awaymission/beach)
+"cL" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast{
+ dir = 1
+ },
+/area/awaymission/beach)
"cR" = (
/turf/open/floor/iron/freezer,
/area/awaymission/beach)
@@ -278,16 +293,6 @@
/obj/structure/musician/piano,
/turf/open/floor/wood,
/area/awaymission/beach)
-"dx" = (
-/obj/structure/sign/directions/dorms/north_arrow{
- pixel_y = 35
- },
-/obj/structure/sign/directions/medical/north_arrow{
- pixel_y = 29
- },
-/obj/machinery/computer/slot_machine,
-/turf/open/floor/wood/large,
-/area/awaymission/beach)
"dL" = (
/obj/machinery/door/airlock/public/glass{
name = "Locker Room"
@@ -505,10 +510,6 @@
},
/turf/closed/wall/mineral/wood,
/area/awaymission/beach)
-"gq" = (
-/obj/item/broken_bottle,
-/turf/open/misc/beach/sand,
-/area/awaymission/beach)
"gw" = (
/obj/item/stack/sheet/mineral/sandstone{
pixel_x = -8
@@ -575,6 +576,10 @@
},
/turf/open/floor/wood,
/area/awaymission/beach)
+"hr" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast,
+/area/awaymission/beach)
"hC" = (
/obj/structure/railing{
dir = 8
@@ -898,17 +903,6 @@
/obj/structure/table/wood,
/turf/open/floor/wood/large,
/area/awaymission/beach)
-"lk" = (
-/obj/effect/turf_decal/siding/wood{
- dir = 9
- },
-/obj/effect/turf_decal/tile/dark/half{
- pixel_y = -1;
- pixel_x = 5;
- dir = 8
- },
-/turf/open/water/beach,
-/area/awaymission/beach)
"ll" = (
/obj/machinery/door/airlock/wood{
name = "Room 11";
@@ -1195,10 +1189,6 @@
},
/turf/open/misc/beach/sand,
/area/awaymission/beach)
-"pq" = (
-/obj/effect/spawner/random/trash/cigbutt,
-/turf/open/misc/beach/sand,
-/area/awaymission/beach)
"pr" = (
/obj/item/reagent_containers/cup/soda_cans/space_mountain_wind{
pixel_x = -17;
@@ -1284,6 +1274,11 @@
/obj/structure/sink/kitchen/directional/south,
/turf/open/floor/wood/large,
/area/awaymission/beach)
+"qb" = (
+/obj/structure/closet/crate/bin,
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/floor/wood,
+/area/awaymission/beach)
"qf" = (
/obj/structure/table/bronze,
/obj/item/pai_card{
@@ -1710,6 +1705,12 @@
},
/turf/open/water/beach,
/area/awaymission/beach)
+"vC" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast{
+ dir = 4
+ },
+/area/awaymission/beach)
"vF" = (
/obj/structure/table/bronze,
/obj/structure/sign/poster/random/directional/east,
@@ -1840,6 +1841,18 @@
},
/turf/open/floor/wood/large,
/area/awaymission/beach)
+"xt" = (
+/obj/effect/turf_decal/siding/wood{
+ dir = 9
+ },
+/obj/effect/turf_decal/tile/dark/half{
+ pixel_y = -1;
+ pixel_x = 5;
+ dir = 8
+ },
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/water/beach,
+/area/awaymission/beach)
"xv" = (
/turf/closed/wall/mineral/wood,
/area/awaymission/beach)
@@ -2581,13 +2594,6 @@
},
/turf/open/misc/beach/sand,
/area/awaymission/beach)
-"FJ" = (
-/obj/item/instrument/guitar{
- pixel_y = 4;
- pixel_x = -1
- },
-/turf/open/misc/beach/coast,
-/area/awaymission/beach)
"FK" = (
/obj/item/toy/seashell{
pixel_x = 12;
@@ -2629,6 +2635,17 @@
},
/turf/open/floor/wood/parquet,
/area/awaymission/beach)
+"FX" = (
+/obj/item/paper/fluff/old_pirate_note{
+ pixel_x = -5;
+ pixel_y = -7
+ },
+/obj/item/pen/fountain{
+ pixel_y = -11;
+ pixel_x = -8
+ },
+/turf/open/misc/beach/sand,
+/area/awaymission/beach)
"FY" = (
/obj/effect/turf_decal/sand,
/obj/effect/turf_decal/siding/wood/corner{
@@ -2681,6 +2698,12 @@
/obj/machinery/light/directional/north,
/turf/open/floor/wood/large,
/area/awaymission/beach)
+"Gy" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast/corner{
+ dir = 4
+ },
+/area/awaymission/beach)
"GC" = (
/turf/open/floor/carpet,
/area/awaymission/beach)
@@ -2864,17 +2887,6 @@
/obj/machinery/light/directional/west,
/turf/open/floor/wood/large,
/area/awaymission/beach)
-"Jv" = (
-/obj/item/paper/fluff/old_pirate_note{
- pixel_x = -5;
- pixel_y = -7
- },
-/obj/item/pen/fountain{
- pixel_y = -11;
- pixel_x = -8
- },
-/turf/open/misc/beach/sand,
-/area/awaymission/beach)
"JF" = (
/obj/structure/marker_beacon/burgundy,
/turf/open/water/beach,
@@ -3070,6 +3082,14 @@
/obj/machinery/light/directional/north,
/turf/open/floor/wood,
/area/awaymission/beach)
+"LT" = (
+/obj/item/instrument/guitar{
+ pixel_y = 4;
+ pixel_x = -1
+ },
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast,
+/area/awaymission/beach)
"Mm" = (
/obj/structure/table/bronze,
/turf/open/floor/wood/large,
@@ -3349,12 +3369,6 @@
},
/turf/open/misc/beach/sand,
/area/awaymission/beach)
-"Pb" = (
-/obj/item/fishing_rod,
-/obj/structure/closet/crate/trashcart,
-/obj/item/reagent_containers/cup/glass/bottle/rum/aged,
-/turf/open/misc/beach/sand,
-/area/awaymission/beach)
"Pe" = (
/turf/open/misc/beach/coast{
dir = 5
@@ -3403,11 +3417,6 @@
},
/turf/open/misc/beach/sand,
/area/awaymission/beach)
-"PU" = (
-/obj/effect/mob_spawn/corpse/human/old_pirate_captain,
-/obj/structure/bed/maint,
-/turf/open/misc/beach/sand,
-/area/awaymission/beach)
"Qe" = (
/obj/effect/turf_decal/sand,
/obj/machinery/door/airlock/wood{
@@ -3424,6 +3433,10 @@
"Qg" = (
/turf/open/floor/wood/parquet,
/area/awaymission/beach)
+"Qi" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast/corner,
+/area/awaymission/beach)
"Qk" = (
/obj/structure/closet/secure_closet/personal/cabinet,
/obj/item/clothing/suit/jacket/miljacket,
@@ -3550,6 +3563,12 @@
},
/turf/open/floor/wood/large,
/area/awaymission/beach)
+"RH" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/misc/beach/coast/corner{
+ dir = 8
+ },
+/area/awaymission/beach)
"RI" = (
/obj/machinery/door/airlock/medical/glass{
name = "Medbay Clinic"
@@ -3750,6 +3769,10 @@
/obj/machinery/light/directional/east,
/turf/open/floor/wood/large,
/area/awaymission/beach)
+"UV" = (
+/obj/effect/spawner/random/trash/cigbutt,
+/turf/open/misc/beach/sand,
+/area/awaymission/beach)
"UX" = (
/obj/structure/flora/tree/palm/style_2{
pixel_y = 12;
@@ -3897,6 +3920,10 @@
},
/turf/open/misc/beach/sand,
/area/awaymission/beach)
+"WD" = (
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/water/beach,
+/area/awaymission/beach)
"WM" = (
/obj/item/reagent_containers/cup/soda_cans/starkist{
pixel_x = -6
@@ -3911,6 +3938,12 @@
/obj/item/clothing/head/soft/black,
/turf/open/floor/wood/parquet,
/area/awaymission/beach)
+"WU" = (
+/obj/item/fishing_rod,
+/obj/structure/closet/crate/trashcart,
+/obj/item/reagent_containers/cup/glass/bottle/rum/aged,
+/turf/open/misc/beach/sand,
+/area/awaymission/beach)
"Xd" = (
/obj/structure/table/optable,
/turf/open/floor/wood/large,
@@ -3979,6 +4012,11 @@
},
/turf/open/misc/beach/sand,
/area/awaymission/beach)
+"Yp" = (
+/obj/structure/closet/crate/trashcart/filled,
+/obj/effect/spawner/message_in_a_bottle/low_prob,
+/turf/open/floor/wood/large,
+/area/awaymission/beach)
"Yq" = (
/obj/item/reagent_containers/cup/watering_can{
pixel_y = 19;
@@ -4026,6 +4064,16 @@
"Zp" = (
/turf/open/floor/wood/large,
/area/awaymission/beach)
+"Zq" = (
+/obj/structure/sign/directions/dorms/north_arrow{
+ pixel_y = 35
+ },
+/obj/structure/sign/directions/medical/north_arrow{
+ pixel_y = 29
+ },
+/obj/machinery/computer/slot_machine,
+/turf/open/floor/wood/large,
+/area/awaymission/beach)
"Zt" = (
/obj/structure/table/bronze,
/obj/item/storage/toolbox/fishing{
@@ -7443,10 +7491,10 @@ XB
XB
XB
XB
-Vf
+cL
YV
-Pb
-gq
+WU
+aY
hL
ni
XB
@@ -7700,10 +7748,10 @@ XB
XB
XB
XB
-Vf
+cL
VY
-pq
-PU
+UV
+cu
Er
BK
XB
@@ -7959,7 +8007,7 @@ XB
XB
Vf
VY
-Jv
+FX
VY
VY
BK
@@ -8217,7 +8265,7 @@ XB
Vf
IZ
VY
-pq
+UV
VY
BK
XB
@@ -8473,7 +8521,7 @@ XB
XB
Pe
Fb
-tJ
+RH
zr
VY
BK
@@ -11423,7 +11471,7 @@ FM
LJ
Gn
kI
-cp
+qb
xv
Zp
xv
@@ -11441,7 +11489,7 @@ VP
vM
Zt
xv
-MF
+Gy
Wb
It
VY
@@ -12190,7 +12238,7 @@ XB
XB
XB
XB
-XB
+WD
xv
xv
xv
@@ -13240,7 +13288,7 @@ xv
xv
xv
xv
-dx
+Zq
Zp
Zp
mb
@@ -13780,7 +13828,7 @@ hL
Le
Le
Le
-MF
+Gy
VY
VY
VY
@@ -14760,7 +14808,7 @@ XB
XB
HV
mi
-uV
+Yp
xv
eE
un
@@ -15073,7 +15121,7 @@ VY
VY
ls
VY
-BK
+hr
XB
XB
XB
@@ -16314,7 +16362,7 @@ vx
Bq
Xd
Ej
-lk
+xt
LC
XB
XB
@@ -17609,7 +17657,7 @@ xv
xv
xv
xv
-Fb
+vC
mf
VY
KL
@@ -22783,7 +22831,7 @@ VY
EC
VY
VY
-Kh
+Qi
Rj
XB
XB
@@ -25323,7 +25371,7 @@ XB
XB
XB
MZ
-MF
+Gy
VY
xv
xv
@@ -25611,7 +25659,7 @@ VY
VY
Ff
VY
-BK
+hr
XB
XB
XB
@@ -28665,7 +28713,7 @@ XB
XB
XB
XB
-Vf
+cL
wA
VY
xi
@@ -29450,7 +29498,7 @@ VY
VY
Rn
VY
-FJ
+LT
xv
CG
Gh
@@ -29696,7 +29744,7 @@ XB
XB
XB
XB
-Vf
+cL
VY
VY
IZ
diff --git a/code/__DEFINES/database.dm b/code/__DEFINES/database.dm
index 3d20b3b9a3c..22351b5052d 100644
--- a/code/__DEFINES/database.dm
+++ b/code/__DEFINES/database.dm
@@ -4,3 +4,6 @@
#define DB_QUERY_FINISHED 1
/// When there was a problem with the execution of a query.
#define DB_QUERY_BROKEN 2
+
+///The probability of non-maploaded photos and papers being saved as bottle messages at the end of the round.
+#define MESSAGE_BOTTLE_CHANCE 0.2
diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm
index ec991b713ec..d6bcc2ec796 100644
--- a/code/__DEFINES/fish.dm
+++ b/code/__DEFINES/fish.dm
@@ -86,6 +86,7 @@
#define FISH_ICON_GEM "gem"
#define FISH_ICON_CRAB "crab"
#define FISH_ICON_JELLYFISH "jellyfish"
+#define FISH_ICON_BOTTLE "bottle"
#define FISH_ICON_BONE "bone"
#define FISH_ICON_ELECTRIC "electric"
#define FISH_ICON_WEAPON "weapon"
diff --git a/code/__DEFINES/paper.dm b/code/__DEFINES/paper.dm
index 0d70a2f3ca4..9cede4214bd 100644
--- a/code/__DEFINES/paper.dm
+++ b/code/__DEFINES/paper.dm
@@ -18,3 +18,29 @@
#define BARCODE_SCANNER_INVENTORY "inventory"
#define IS_WRITING_UTENSIL(thing) (thing?.get_writing_implement_details()?["interaction_mode"] == MODE_WRITING)
+
+/**
+ * key defines used when converting a paper to and fro' a data/json list. It's really important that they stay the same
+ * lest we break persistence.
+ */
+#define LIST_PAPER_COLOR "paper_color"
+#define LIST_PAPER_NAME "paper_name"
+
+#define LIST_PAPER_RAW_TEXT_INPUT "raw_text_input"
+#define LIST_PAPER_RAW_FIELD_INPUT "raw_field_input"
+#define LIST_PAPER_RAW_STAMP_INPUT "raw_stamp_input"
+
+#define LIST_PAPER_RAW_TEXT "raw_text"
+#define LIST_PAPER_FONT "font"
+#define LIST_PAPER_FIELD_COLOR "color"
+#define LIST_PAPER_BOLD "bold"
+#define LIST_PAPER_ADVANCED_HTML "advanced_html"
+
+#define LIST_PAPER_FIELD_INDEX "field_index"
+#define LIST_PAPER_FIELD_DATA "field_data"
+#define LIST_PAPER_IS_SIGNATURE "is_signature"
+
+#define LIST_PAPER_CLASS "class"
+#define LIST_PAPER_STAMP_X "x"
+#define LIST_PAPER_STAMP_Y "y"
+#define LIST_PAPER_ROTATION "rotation"
diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm
index eee89e7d3ca..c82b54491eb 100644
--- a/code/__DEFINES/traits/declarations.dm
+++ b/code/__DEFINES/traits/declarations.dm
@@ -233,6 +233,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_FISHING_SPOT "fishing_spot"
///Trait given to mobs that can fish without a rod
#define TRAIT_PROFOUND_FISHER "profound_fisher"
+/// If an atom has this trait, then you can toss a bottle with a message in it.
+#define TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION "message_in_a_bottle_location"
/// This trait lets you evaluate someone's fitness level against your own
#define TRAIT_EXAMINE_FITNESS "reveal_power_level"
/// These mobs have particularly hygienic tongues
diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm
index 2cf71438e82..6bf5bba956c 100644
--- a/code/_globalvars/traits/_traits.dm
+++ b/code/_globalvars/traits/_traits.dm
@@ -23,6 +23,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
"TRAIT_FOOD_SILVER" = TRAIT_FOOD_SILVER,
"TRAIT_KEEP_TOGETHER" = TRAIT_KEEP_TOGETHER,
"TRAIT_LIGHTING_DEBUGGED" = TRAIT_LIGHTING_DEBUGGED,
+ "TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION" = TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION,
"TRAIT_RECENTLY_COINED" = TRAIT_RECENTLY_COINED,
"TRAIT_RUSTY" = TRAIT_RUSTY,
"TRAIT_SPINNING" = TRAIT_SPINNING,
diff --git a/code/controllers/subsystem/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm
index a8c42631488..6438015d11d 100644
--- a/code/controllers/subsystem/persistence/_persistence.dm
+++ b/code/controllers/subsystem/persistence/_persistence.dm
@@ -48,6 +48,17 @@ SUBSYSTEM_DEF(persistence)
var/tram_hits_this_round = 0
var/tram_hits_last_round = 0
+ /// A json database to data/message_bottles.json
+ var/datum/json_database/message_bottles_database
+ /// An index used to create unique ids for the message bottles database
+ var/message_bottles_index = 0
+ /**
+ * A list of non-maploaded photos or papers that met the 0.2% chance to be saved in the message bottles database
+ * because I don't want the database to feel empty unless there's someone constantly throwing bottles in the
+ * sea or beach/ocean fishing portals.
+ */
+ var/list/queued_message_bottles
+
/datum/controller/subsystem/persistence/Initialize()
load_poly()
load_wall_engravings()
@@ -72,11 +83,13 @@ SUBSYSTEM_DEF(persistence)
save_scars()
save_custom_outfits()
save_delamination_counter()
+ save_queued_message_bottles()
if(SStransport.can_fire)
for(var/datum/transport_controller/linear/tram/transport as anything in SStransport.transports_by_type[TRANSPORT_TYPE_TRAM])
save_tram_history(transport.specific_transport_id)
save_tram_counter()
+
///Loads up Poly's speech buffer.
/datum/controller/subsystem/persistence/proc/load_poly()
for(var/mob/living/basic/parrot/poly/bird in GLOB.alive_mob_list)
diff --git a/code/controllers/subsystem/persistence/message_bottles.dm b/code/controllers/subsystem/persistence/message_bottles.dm
new file mode 100644
index 00000000000..ce1efe5e59c
--- /dev/null
+++ b/code/controllers/subsystem/persistence/message_bottles.dm
@@ -0,0 +1,54 @@
+///This proc is used to save photos, papers and cash stored inside a bottle when tossed into the ocean.
+/datum/controller/subsystem/persistence/proc/save_message_bottle(obj/item/message, bottle_type = /obj/item/reagent_containers/cup/glass/bottle)
+ if(isnull(message_bottles_database))
+ message_bottles_database = new("data/message_bottles.json")
+
+ var/list/data = list()
+ data["bottle_type"] = text2path(bottle_type)
+ if(istype(message, /obj/item/paper))
+ var/obj/item/paper/paper = message
+ if(!length(paper.raw_text_inputs) && !length(paper.raw_stamp_data) && !length(paper.raw_field_input_data))
+ return
+ data["paper"] = paper.convert_to_data()
+ else if(istype(message, /obj/item/photo))
+ var/obj/item/photo/photo = message
+ if(!photo.picture?.id)
+ return
+ data["photo_id"] = photo.picture.id
+ else if(istype(message, /obj/item/stack/spacecash))
+ var/obj/item/stack/spacecash/cash = message
+ data["cash"] = text2path(cash.type)
+ data["amount"] = cash.amount
+ message_bottles_index++
+ message_bottles_database.set_key("message-[GLOB.round_id]-[message_bottles_index]", data)
+
+/datum/controller/subsystem/persistence/proc/load_message_bottle(atom/loc)
+ if(isnull(message_bottles_database))
+ message_bottles_database = new("data/message_bottles.json")
+
+ var/list/data = message_bottles_database.pick_and_take_key()
+ if(!data)
+ var/obj/item/reagent_containers/cup/glass/bottle/bottle = new(loc)
+ return bottle
+
+ var/bottle_type = text2path(data["bottle_type"]) || /obj/item/reagent_containers/cup/glass/bottle
+ var/obj/item/reagent_containers/cup/glass/bottle/bottle = new bottle_type(loc)
+ bottle.reagents.remove_all(bottle.reagents.maximum_volume)
+ if(data["photo_id"])
+ var/obj/item/photo/old/photo = load_photo_from_disk(data["photo_id"], bottle)
+ bottle.message_in_a_bottle = photo
+ else if(data["cash"])
+ var/cash_type = text2path(data["cash"]) || /obj/item/stack/spacecash/c10
+ var/obj/item/stack/spacecash/cash = new cash_type(bottle, data["amount"])
+ bottle.message_in_a_bottle = cash
+ else if(data["paper"])
+ var/obj/item/paper/paper = new(bottle)
+ paper.write_from_data(data["paper"])
+ bottle.message_in_a_bottle = paper
+
+ bottle.update_icon(UPDATE_OVERLAYS)
+
+/datum/controller/subsystem/persistence/proc/save_queued_message_bottles()
+ for(var/item in queued_message_bottles)
+ save_message_bottle(item)
+ queued_message_bottles = null
diff --git a/code/datums/components/fishing_spot.dm b/code/datums/components/fishing_spot.dm
index 6638c822ff6..cc21f22eb4b 100644
--- a/code/datums/components/fishing_spot.dm
+++ b/code/datums/components/fishing_spot.dm
@@ -12,7 +12,7 @@
fish_source = configuration
else
return COMPONENT_INCOMPATIBLE
- fish_source.on_fishing_spot_init()
+ fish_source.on_fishing_spot_init(src)
RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(handle_attackby))
RegisterSignal(parent, COMSIG_FISHING_ROD_CAST, PROC_REF(handle_cast))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined))
@@ -22,6 +22,7 @@
ADD_TRAIT(parent, TRAIT_FISHING_SPOT, REF(src))
/datum/component/fishing_spot/Destroy()
+ fish_source.on_fishing_spot_del(src)
fish_source = null
return ..()
diff --git a/code/datums/json_database.dm b/code/datums/json_database.dm
index ea3ff354b48..6baeb44ab09 100644
--- a/code/datums/json_database.dm
+++ b/code/datums/json_database.dm
@@ -61,6 +61,16 @@
/datum/json_database/proc/get_key(key)
return cached_data[key]
+/// Picks the data of a random key and then removes that key from the database.
+/// Since the list is no longer inside the database, you can mutate and use it as you like.
+/datum/json_database/proc/pick_and_take_key()
+ if(!length(cached_data))
+ return null
+ var/key = pick(cached_data)
+ . = cached_data[key]
+ cached_data -= key
+ queue_save()
+
/// Sets the data at the key to the value, and queues a save.
/datum/json_database/proc/set_key(key, value)
cached_data[key] = value
diff --git a/code/game/objects/effects/spawners/message_in_a_bottle.dm b/code/game/objects/effects/spawners/message_in_a_bottle.dm
new file mode 100644
index 00000000000..c4ac63ad0be
--- /dev/null
+++ b/code/game/objects/effects/spawners/message_in_a_bottle.dm
@@ -0,0 +1,25 @@
+/obj/effect/spawner/message_in_a_bottle
+ name = "message in a bottle"
+ desc = "Sending out an SOS"
+ icon = 'icons/effects/random_spawners.dmi'
+ icon_state = "message_bottle"
+ var/probability = 100
+
+/obj/effect/spawner/message_in_a_bottle/Initialize(mapload)
+ . = ..()
+ if(!prob(probability))
+ return INITIALIZE_HINT_QDEL
+ if(!SSpersistence.initialized)
+ RegisterSignal(SSpersistence, COMSIG_SUBSYSTEM_POST_INITIALIZE, PROC_REF(on_persistence_init))
+ else
+ SSpersistence.load_message_bottle(loc)
+ return INITIALIZE_HINT_QDEL
+
+/obj/effect/spawner/message_in_a_bottle/proc/on_persistence_init(datum/source)
+ SIGNAL_HANDLER
+ UnregisterSignal(SSpersistence, COMSIG_SUBSYSTEM_POST_INITIALIZE)
+ SSpersistence.load_message_bottle(loc)
+ qdel(src)
+
+/obj/effect/spawner/message_in_a_bottle/low_prob
+ probability = 1.5
diff --git a/code/game/objects/items/food/misc.dm b/code/game/objects/items/food/misc.dm
index 26852066bae..53781e8a1ea 100644
--- a/code/game/objects/items/food/misc.dm
+++ b/code/game/objects/items/food/misc.dm
@@ -292,6 +292,14 @@
foodtypes = FRUIT | ALCOHOL
crafting_complexity = FOOD_COMPLEXITY_2
+/obj/item/food/melonkeg/CheckParts(list/parts_list)
+ . = ..()
+ var/obj/item/reagent_containers/cup/glass/bottle/bottle = locate() in contents
+ if(!bottle)
+ return
+ if(bottle.message_in_a_bottle)
+ bottle.message_in_a_bottle.forceMove(drop_location())
+
/obj/item/food/honeybar
name = "honey nut bar"
desc = "Oats and nuts compressed together into a bar, held together with a honey glaze."
diff --git a/code/game/turfs/open/sand.dm b/code/game/turfs/open/sand.dm
index fb10d297e18..254c595fcbd 100644
--- a/code/game/turfs/open/sand.dm
+++ b/code/game/turfs/open/sand.dm
@@ -41,6 +41,10 @@
clawfootstep = FOOTSTEP_WATER
heavyfootstep = FOOTSTEP_WATER
+/turf/open/misc/beach/coast/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION, INNATE_TRAIT)
+
/turf/open/misc/beach/coast/break_tile()
. = ..()
icon_state = "beach"
diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm
index 20c6215209a..de895754d03 100644
--- a/code/game/turfs/open/water.dm
+++ b/code/game/turfs/open/water.dm
@@ -43,6 +43,10 @@
immerse_overlay_color = "#7799AA"
fishing_datum = /datum/fish_source/ocean/beach
+/turf/open/water/beach/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION, INNATE_TRAIT)
+
/turf/open/water/lavaland_atmos
initial_gas_mix = LAVALAND_DEFAULT_ATMOS
planetary_atmos = TRUE
diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm
index c675352d024..a10bb3da5c0 100644
--- a/code/modules/cargo/order.dm
+++ b/code/modules/cargo/order.dm
@@ -10,10 +10,14 @@
#define MANIFEST_ERROR_ITEM (1 << 2)
/obj/item/paper/fluff/jobs/cargo/manifest
+ can_become_message_in_bottle = FALSE //A lot of these are spawned each round, they'd only dilute the pool and make it boring.
var/order_cost = 0
var/order_id = 0
var/errors = 0
+/obj/item/paper/requisition
+ can_become_message_in_bottle = FALSE //A lot of these are spawned each round, they'd only dilute the pool and make it boring.
+
/obj/item/paper/fluff/jobs/cargo/manifest/Initialize(mapload, id, cost, manifest_can_fail = TRUE)
. = ..()
order_id = id
@@ -99,7 +103,7 @@
return round(cost)
/datum/supply_order/proc/generateRequisition(turf/T)
- var/obj/item/paper/requisition_paper = new(T)
+ var/obj/item/paper/requisition/requisition_paper = new(T)
requisition_paper.name = "requisition form - #[id] ([pack.name])"
var/requisition_text = "
[station_name()] Supply Requisition
"
diff --git a/code/modules/cargo/orderconsole.dm b/code/modules/cargo/orderconsole.dm
index d835c52f1e8..ae46d994381 100644
--- a/code/modules/cargo/orderconsole.dm
+++ b/code/modules/cargo/orderconsole.dm
@@ -329,7 +329,7 @@
else
//create the paper from the SSshuttle.shopping_list
if(length(SSshuttle.shopping_list))
- var/obj/item/paper/requisition_paper = new(get_turf(src))
+ var/obj/item/paper/requisition/requisition_paper = new(get_turf(src))
requisition_paper.name = "requisition form - [station_time_timestamp()]"
var/requisition_text = "[station_name()] Supply Requisition
"
requisition_text += "
"
diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm
index 93a86ff82a9..5db1ee6458b 100644
--- a/code/modules/fishing/fishing_minigame.dm
+++ b/code/modules/fishing/fishing_minigame.dm
@@ -62,7 +62,7 @@
/// Background icon state from fishing_hud.dmi
var/background = "background_default"
/// Fish icon state from fishing_hud.dmi
- var/fish_icon = "fish"
+ var/fish_icon = FISH_ICON_DEF
/// Fishing line visual
var/datum/beam/fishing_line
@@ -164,7 +164,7 @@
completion -= MAX_FISH_COMPLETION_MALUS * (difficulty * 0.01)
if(HAS_MIND_TRAIT(user, TRAIT_REVEAL_FISH))
- fish_icon = GLOB.specific_fish_icons[reward_path] || "fish"
+ fish_icon = GLOB.specific_fish_icons[reward_path] || FISH_ICON_DEF
mover.adjust_to_difficulty()
@@ -352,6 +352,8 @@
send_alert("critter!!!")
if(FISH_ICON_SEED)
send_alert("seed!!!")
+ if(FISH_ICON_BOTTLE)
+ send_alert("bottle!!!")
else
send_alert("!!!")
animate(lure, pixel_y = 3, time = 5, loop = -1, flags = ANIMATION_RELATIVE)
diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm
index dc9b910ad8f..5aa03ae8c0c 100644
--- a/code/modules/fishing/sources/_fish_source.dm
+++ b/code/modules/fishing/sources/_fish_source.dm
@@ -18,6 +18,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons())
/mob/living/basic/mining = FISH_ICON_HOSTILE,
/obj/effect/decal/remains = FISH_ICON_BONE,
/obj/effect/mob_spawn/corpse = FISH_ICON_BONE,
+ /obj/effect/spawner/message_in_a_bottle = FISH_ICON_BOTTLE,
/obj/item/coin = FISH_ICON_COIN,
/obj/item/fish = FISH_ICON_DEF,
/obj/item/fish/armorfish = FISH_ICON_CRAB,
@@ -46,6 +47,7 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons())
/obj/item/stack/sheet/mineral = FISH_ICON_GEM,
/obj/item/stack/ore = FISH_ICON_GEM,
/obj/structure/closet/crate = FISH_ICON_COIN,
+ /obj/structure/mystery_box = FISH_ICON_COIN,
))
return_list[FISHING_RANDOM_SEED] = FISH_ICON_SEED
@@ -97,9 +99,12 @@ GLOBAL_LIST_INIT(specific_fish_icons, generate_specific_fish_icons())
return ..()
///Called when src is set as the fish source of a fishing spot component
-/datum/fish_source/proc/on_fishing_spot_init(/datum/component/fishing_spot/spot)
+/datum/fish_source/proc/on_fishing_spot_init(datum/component/fishing_spot/spot)
return
+///Called whenever a fishing spot with this fish source attached is deleted
+/datum/fish_source/proc/on_fishing_spot_del(datum/component/fishing_spot/spot)
+
/// Can we fish in this spot at all. Returns DENIAL_REASON or null if we're good to go
/datum/fish_source/proc/reason_we_cant_fish(obj/item/fishing_rod/rod, mob/fisherman, atom/parent)
return rod.reason_we_cant_fish(src)
diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm
index 5b4b6fe3624..6a7c8d3e1ef 100644
--- a/code/modules/fishing/sources/source_types.dm
+++ b/code/modules/fishing/sources/source_types.dm
@@ -1,6 +1,7 @@
/datum/fish_source/ocean
fish_table = list(
- FISHING_DUD = 15,
+ FISHING_DUD = 11,
+ /obj/effect/spawner/message_in_a_bottle = 4,
/obj/item/coin/gold = 7,
/obj/item/fish/clownfish = 15,
/obj/item/fish/pufferfish = 15,
@@ -11,7 +12,7 @@
/obj/item/fish/zipzap = 7,
/obj/item/fish/clownfish/lube = 5,
/obj/item/fish/swordfish = 5,
- /obj/structure/mystery_box/fishing = 1,
+ /obj/structure/mystery_box/fishing = 2,
)
fish_counts = list(
/obj/item/fish/clownfish/lube = 2,
@@ -96,7 +97,8 @@
/datum/fish_source/portal/beach
fish_table = list(
- FISHING_DUD = 10,
+ FISHING_DUD = 7,
+ /obj/effect/spawner/message_in_a_bottle = 3,
/obj/item/fish/clownfish = 10,
/obj/item/fish/pufferfish = 10,
/obj/item/fish/cardinal = 10,
@@ -107,6 +109,12 @@
radial_state = "palm_beach"
overlay_state = "portal_beach"
+/datum/fish_source/portal/beach/on_fishing_spot_init(datum/component/fishing_spot/spot)
+ ADD_TRAIT(spot.parent, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION, INNATE_TRAIT)
+
+/datum/fish_source/portal/beach/on_fishing_spot_del(datum/component/fishing_spot/spot)
+ REMOVE_TRAIT(spot.parent, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION, INNATE_TRAIT)
+
/datum/fish_source/portal/chasm
background = "background_lavaland"
fish_table = list(
@@ -123,7 +131,8 @@
/datum/fish_source/portal/ocean
fish_table = list(
- FISHING_DUD = 5,
+ FISHING_DUD = 3,
+ /obj/effect/spawner/message_in_a_bottle = 2,
/obj/item/fish/lanternfish = 5,
/obj/item/fish/firefish = 5,
/obj/item/fish/dwarf_moonfish = 5,
@@ -146,6 +155,12 @@
overlay_state = "portal_ocean"
radial_state = "seaboat"
+/datum/fish_source/portal/ocean/on_fishing_spot_init(datum/component/fishing_spot/spot)
+ ADD_TRAIT(spot.parent, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION, INNATE_TRAIT)
+
+/datum/fish_source/portal/ocean/on_fishing_spot_del(datum/component/fishing_spot/spot)
+ REMOVE_TRAIT(spot.parent, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION, INNATE_TRAIT)
+
/datum/fish_source/portal/hyperspace
fish_table = list(
FISHING_DUD = 5,
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
index e5589fcc42d..5a754361ffb 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_drink.dm
@@ -1,6 +1,10 @@
// This is the home of drink related tablecrafting recipes, I have opted to only let players bottle fancy boozes to reduce the number of entries.
+///Abstract types for all drink recipes that use bottles and result in another bottle, so that the message_in_a_bottle item is properly transferred.
+/datum/crafting_recipe/bottled
+ parts = list(/obj/item/reagent_containers/cup/glass/bottle = 1)
+
///////////////// Booze & Bottles ///////////////////
/datum/crafting_recipe/lizardwine
@@ -14,7 +18,7 @@
result = /obj/item/reagent_containers/cup/glass/bottle/lizardwine
category = CAT_DRINK
-/datum/crafting_recipe/moonshinejug
+/datum/crafting_recipe/bottled/moonshinejug
name = "Moonshine Jug"
time = 30
reqs = list(
@@ -24,7 +28,7 @@
result = /obj/item/reagent_containers/cup/glass/bottle/moonshine
category = CAT_DRINK
-/datum/crafting_recipe/hoochbottle
+/datum/crafting_recipe/bottled/hoochbottle
name = "Hooch Bottle"
time = 30
reqs = list(
@@ -35,7 +39,7 @@
result = /obj/item/reagent_containers/cup/glass/bottle/hooch
category = CAT_DRINK
-/datum/crafting_recipe/blazaambottle
+/datum/crafting_recipe/bottled/blazaambottle
name = "Blazaam Bottle"
time = 20
reqs = list(
@@ -45,7 +49,7 @@
result = /obj/item/reagent_containers/cup/glass/bottle/blazaam
category = CAT_DRINK
-/datum/crafting_recipe/champagnebottle
+/datum/crafting_recipe/bottled/champagnebottle
name = "Champagne Bottle"
time = 30
reqs = list(
@@ -55,7 +59,7 @@
result = /obj/item/reagent_containers/cup/glass/bottle/champagne
category = CAT_DRINK
-/datum/crafting_recipe/trappistbottle
+/datum/crafting_recipe/bottled/trappistbottle
name = "Trappist Bottle"
time = 15
reqs = list(
@@ -65,7 +69,7 @@
result = /obj/item/reagent_containers/cup/glass/bottle/trappist
category = CAT_DRINK
-/datum/crafting_recipe/goldschlagerbottle
+/datum/crafting_recipe/bottled/goldschlagerbottle
name = "Goldschlager Bottle"
time = 30
reqs = list(
@@ -75,7 +79,7 @@
result = /obj/item/reagent_containers/cup/glass/bottle/goldschlager
category = CAT_DRINK
-/datum/crafting_recipe/patronbottle
+/datum/crafting_recipe/bottled/patronbottle
name = "Patron Bottle"
time = 30
reqs = list(
@@ -87,7 +91,7 @@
////////////////////// Non-alcoholic recipes ///////////////////
-/datum/crafting_recipe/holybottle
+/datum/crafting_recipe/bottled/holybottle
name = "Holy Water Flask"
time = 30
reqs = list(
@@ -99,7 +103,7 @@
//flask of unholy water is a beaker for some reason, I will try making it a bottle and add it here once the antag freeze is over. t. kryson
-/datum/crafting_recipe/nothingbottle
+/datum/crafting_recipe/bottled/nothingbottle
name = "Nothing Bottle"
time = 30
reqs = list(
@@ -116,7 +120,7 @@
reqs = list(/obj/item/stack/sheet/cardboard = 1)
category = CAT_CONTAINERS
-/datum/crafting_recipe/candycornliquor
+/datum/crafting_recipe/bottled/candycornliquor
name = "candy corn liquor"
result = /obj/item/reagent_containers/cup/glass/bottle/candycornliquor
time = 30
@@ -125,7 +129,7 @@
/obj/item/reagent_containers/cup/glass/bottle = 1)
category = CAT_DRINK
-/datum/crafting_recipe/kong
+/datum/crafting_recipe/bottled/kong
name = "Kong"
result = /obj/item/reagent_containers/cup/glass/bottle/kong
time = 30
diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm
index fe2de7e7520..403d6d269ed 100644
--- a/code/modules/paperwork/paper.dm
+++ b/code/modules/paperwork/paper.dm
@@ -64,6 +64,9 @@
///If TRUE, staff can read paper everywhere, but usually from requests panel.
var/request_state = FALSE
+ ///If this paper can be selected as a candidate for a future message in a bottle when spawned outside of mapload. Doesn't affect manually doing that.
+ var/can_become_message_in_bottle = TRUE
+
/obj/item/paper/Initialize(mapload)
. = ..()
pixel_x = base_pixel_x + rand(-9, 9)
@@ -74,10 +77,14 @@
update_appearance()
+ if(can_become_message_in_bottle && !mapload && prob(MESSAGE_BOTTLE_CHANCE))
+ LAZYADD(SSpersistence.queued_message_bottles, src)
+
/obj/item/paper/Destroy()
- . = ..()
camera_holder = null
clear_paper()
+ LAZYREMOVE(SSpersistence.queued_message_bottles, src)
+ return ..()
/// Determines whether this paper has been written or stamped to.
/obj/item/paper/proc/is_empty()
@@ -527,22 +534,10 @@
static_data["user_name"] = user.real_name
- static_data["raw_text_input"] = list()
- for(var/datum/paper_input/text_input as anything in raw_text_inputs)
- static_data["raw_text_input"] += list(text_input.to_list())
-
- static_data["raw_field_input"] = list()
- for(var/datum/paper_field/field_input as anything in raw_field_input_data)
- static_data["raw_field_input"] += list(field_input.to_list())
-
- static_data["raw_stamp_input"] = list()
- for(var/datum/paper_stamp/stamp_input as anything in raw_stamp_data)
- static_data["raw_stamp_input"] += list(stamp_input.to_list())
+ static_data += convert_to_data()
static_data["max_length"] = MAX_PAPER_LENGTH
static_data["max_input_field_length"] = MAX_PAPER_INPUT_FIELD_LENGTH
- static_data["paper_color"] = color ? color : COLOR_WHITE
- static_data["paper_name"] = name
static_data["default_pen_font"] = PEN_FONT
static_data["default_pen_color"] = COLOR_BLACK
@@ -550,6 +545,43 @@
return static_data;
+/obj/item/paper/proc/convert_to_data()
+ var/list/data = list()
+
+ data[LIST_PAPER_RAW_TEXT_INPUT] = list()
+ for(var/datum/paper_input/text_input as anything in raw_text_inputs)
+ data[LIST_PAPER_RAW_TEXT_INPUT] += list(text_input.to_list())
+
+ data[LIST_PAPER_RAW_FIELD_INPUT] = list()
+ for(var/datum/paper_field/field_input as anything in raw_field_input_data)
+ data[LIST_PAPER_RAW_FIELD_INPUT] += list(field_input.to_list())
+
+ data[LIST_PAPER_RAW_STAMP_INPUT] = list()
+ for(var/datum/paper_stamp/stamp_input as anything in raw_stamp_data)
+ data[LIST_PAPER_RAW_STAMP_INPUT] += list(stamp_input.to_list())
+
+ data[LIST_PAPER_COLOR] = color ? color : COLOR_WHITE
+ data[LIST_PAPER_NAME] = name
+
+ return data
+
+/obj/item/paper/proc/write_from_data(list/data)
+ for(var/list/input as anything in data[LIST_PAPER_RAW_TEXT_INPUT])
+ add_raw_text(input[LIST_PAPER_RAW_TEXT], input[LIST_PAPER_FONT], input[LIST_PAPER_FIELD_COLOR], input[LIST_PAPER_BOLD], input[LIST_PAPER_ADVANCED_HTML])
+
+ for(var/list/field as anything in data[LIST_PAPER_RAW_FIELD_INPUT])
+ var/list/input = field[LIST_PAPER_FIELD_DATA]
+ add_field_input(field[LIST_PAPER_FIELD_INDEX], input[LIST_PAPER_RAW_TEXT], input[LIST_PAPER_FONT], input[LIST_PAPER_FIELD_COLOR], input[LIST_PAPER_BOLD], field[LIST_PAPER_IS_SIGNATURE])
+
+ for(var/list/stamp as anything in data[LIST_PAPER_RAW_STAMP_INPUT])
+ add_stamp(stamp[LIST_PAPER_CLASS], stamp[LIST_PAPER_STAMP_X], stamp[LIST_PAPER_STAMP_Y], stamp[LIST_PAPER_ROTATION])
+
+ var/new_color = data[LIST_PAPER_COLOR]
+ if(new_color != COLOR_WHITE)
+ add_atom_colour(new_color, FIXED_COLOUR_PRIORITY)
+
+ name = data[LIST_PAPER_NAME]
+
/obj/item/paper/ui_data(mob/user)
var/list/data = list()
@@ -753,11 +785,11 @@
/datum/paper_input/proc/to_list()
return list(
- raw_text = raw_text,
- font = font,
- color = colour,
- bold = bold,
- advanced_html = advanced_html,
+ LIST_PAPER_RAW_TEXT = raw_text,
+ LIST_PAPER_FONT = font,
+ LIST_PAPER_FIELD_COLOR = colour,
+ LIST_PAPER_BOLD = bold,
+ LIST_PAPER_ADVANCED_HTML = advanced_html,
)
/// Returns the raw contents of the input as html, with **ZERO SANITIZATION**
@@ -793,10 +825,10 @@
/datum/paper_stamp/proc/to_list()
return list(
- class = class,
- x = stamp_x,
- y = stamp_y,
- rotation = rotation,
+ LIST_PAPER_CLASS = class,
+ LIST_PAPER_STAMP_X = stamp_x,
+ LIST_PAPER_STAMP_Y = stamp_y,
+ LIST_PAPER_ROTATION = rotation,
)
/// A reference to some data that replaces a modifiable input field at some given index in paper raw input parsing.
@@ -818,9 +850,9 @@
/datum/paper_field/proc/to_list()
return list(
- field_index = field_index,
- field_data = field_data.to_list(),
- is_signature = is_signature,
+ LIST_PAPER_FIELD_INDEX = field_index,
+ LIST_PAPER_FIELD_DATA = field_data.to_list(),
+ LIST_PAPER_IS_SIGNATURE = is_signature,
)
/obj/item/paper/construction
diff --git a/code/modules/photography/photos/photo.dm b/code/modules/photography/photos/photo.dm
index b009c5b2e79..adc99a08cf2 100644
--- a/code/modules/photography/photos/photo.dm
+++ b/code/modules/photography/photos/photo.dm
@@ -9,12 +9,21 @@
w_class = WEIGHT_CLASS_TINY
resistance_flags = FLAMMABLE
max_integrity = 50
+ drop_sound = 'sound/items/handling/paper_drop.ogg'
+ pickup_sound = 'sound/items/handling/paper_pickup.ogg'
grind_results = list(/datum/reagent/iodine = 4)
var/datum/picture/picture
var/scribble //Scribble on the back.
/obj/item/photo/Initialize(mapload, datum/picture/P, datum_name = TRUE, datum_desc = TRUE)
set_picture(P, datum_name, datum_desc, TRUE)
+ //Photos are quite rarer than papers, so they're more likely to be added to the queue to make things even.
+ if(!mapload && prob(MESSAGE_BOTTLE_CHANCE * 5) && picture?.id)
+ LAZYADD(SSpersistence.queued_message_bottles, src)
+ return ..()
+
+/obj/item/photo/Destroy()
+ LAZYREMOVE(SSpersistence.queued_message_bottles, src)
return ..()
/obj/item/photo/proc/set_picture(datum/picture/P, setname, setdesc, name_override = FALSE)
diff --git a/code/modules/reagents/reagent_containers/cups/glassbottle.dm b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
index 7b2183b1f22..a214ea47eb1 100644
--- a/code/modules/reagents/reagent_containers/cups/glassbottle.dm
+++ b/code/modules/reagents/reagent_containers/cups/glassbottle.dm
@@ -39,11 +39,93 @@
var/bottle_knockdown_duration = BOTTLE_KNOCKDOWN_DEFAULT_DURATION
tool_behaviour = TOOL_ROLLINGPIN // Used to knock out the Chef.
toolspeed = 1.3 //it's a little awkward to use, but it's a cylinder alright.
+ /// A contained piece of paper, a photo, or space cash, that we can use as a message or gift to future spessmen.
+ var/obj/item/message_in_a_bottle
/obj/item/reagent_containers/cup/glass/bottle/Initialize(mapload, vol)
. = ..()
var/static/list/recipes = list(/datum/crafting_recipe/molotov)
AddElement(/datum/element/slapcrafting, recipes)
+ register_context()
+ register_item_context()
+
+/obj/item/reagent_containers/cup/glass/bottle/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
+ if(message_in_a_bottle)
+ return NONE
+ if(istype(held_item, /obj/item/paper) || istype(held_item, /obj/item/stack/spacecash) || istype(held_item, /obj/item/photo))
+ context[SCREENTIP_CONTEXT_LMB] = "Insert message"
+ return CONTEXTUAL_SCREENTIP_SET
+ return NONE
+
+/obj/item/reagent_containers/cup/glass/bottle/add_item_context(obj/item/source, list/context, atom/target, mob/living/user)
+ if(message_in_a_bottle && HAS_TRAIT(target, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION))
+ context[SCREENTIP_CONTEXT_RMB] = "Toss message"
+ return CONTEXTUAL_SCREENTIP_SET
+ return NONE
+
+/obj/item/reagent_containers/cup/glass/bottle/Exited(atom/movable/gone, atom/newloc)
+ if(gone == message_in_a_bottle)
+ message_in_a_bottle = null
+ if(!QDELETED(src))
+ update_icon(UPDATE_OVERLAYS)
+ return ..()
+
+/obj/item/reagent_containers/cup/glass/bottle/CheckParts(list/parts_list)
+ . = ..()
+ var/obj/item/reagent_containers/cup/glass/bottle/bottle = locate() in contents
+ if(bottle.message_in_a_bottle)
+ message_in_a_bottle = bottle.message_in_a_bottle
+ bottle.message_in_a_bottle.forceMove(src)
+
+/obj/item/reagent_containers/cup/glass/bottle/examine(mob/user)
+ . = ..()
+ if(message_in_a_bottle)
+ . += span_info("there's \a [message_in_a_bottle] inside it. Break it to take it out, or find a beach or ocean and toss it with [EXAMINE_HINT("right-click")].")
+ else if(isGlass)
+ . += span_tinynoticeital("you could place a paper, photo or space cash inside it...")
+
+/obj/item/reagent_containers/cup/glass/bottle/update_overlays()
+ . = ..()
+ if(message_in_a_bottle)
+ var/overlay = add_message_overlay()
+ if(overlay)
+ . += overlay
+
+/obj/item/reagent_containers/cup/glass/bottle/interact_with_atom_secondary(atom/target, mob/living/user, list/modifiers)
+ if(user.combat_mode || !HAS_TRAIT(target, TRAIT_MESSAGE_IN_A_BOTTLE_LOCATION))
+ return ..()
+ if(!user.temporarilyRemoveItemFromInventory(src))
+ balloon_alert(user, "it's stuck to your hand!")
+ return ITEM_INTERACT_BLOCKING
+ user.visible_message(span_notice("[user] tosses [src] in [target]"), span_notice("You toss [src] in [target]"), span_notice("you hear a splash."))
+ SSpersistence.save_message_bottle(message_in_a_bottle, type)
+ playsound(target, 'sound/effects/bigsplash.ogg', 70)
+ qdel(src)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/reagent_containers/cup/glass/bottle/item_interaction(mob/living/user, obj/item/item, list/modifiers)
+ if(!isGlass)
+ return NONE
+ if(!istype(item, /obj/item/paper) && !istype(item, /obj/item/stack/spacecash) && !istype(item, /obj/item/photo))
+ return NONE
+ if(message_in_a_bottle)
+ balloon_alert(user, "has a message already!")
+ return ITEM_INTERACT_BLOCKING
+ if(!user.transferItemToLoc(item, src))
+ balloon_alert(user, "it's stuck to your hand!")
+ return ITEM_INTERACT_BLOCKING
+ balloon_alert(user, "message inserted")
+ message_in_a_bottle = item
+ update_icon(UPDATE_OVERLAYS)
+ return ITEM_INTERACT_SUCCESS
+
+/obj/item/reagent_containers/cup/glass/bottle/proc/add_message_overlay()
+ if(istype(message_in_a_bottle, /obj/item/paper))
+ return "paper_in_bottle"
+ if(istype(message_in_a_bottle, /obj/item/photo))
+ return "photo_in_bottle"
+ if(istype(message_in_a_bottle, /obj/item/stack/spacecash))
+ return "cash_in_bottle"
/obj/item/reagent_containers/cup/glass/bottle/small
name = "small glass bottle"
@@ -56,14 +138,16 @@
if(bartender_check(target) && ranged)
return
SplashReagents(target, ranged, override_spillable = TRUE)
- var/obj/item/broken_bottle/B = new(drop_location())
+ var/obj/item/broken_bottle/broken = new(drop_location())
if(!ranged && thrower)
- thrower.put_in_hands(B)
- B.mimic_broken(src, target, break_top)
- B.inhand_icon_state = broken_inhand_icon_state
+ thrower.put_in_hands(broken)
+ broken.mimic_broken(src, target, break_top)
+ broken.inhand_icon_state = broken_inhand_icon_state
+ if(message_in_a_bottle)
+ message_in_a_bottle.forceMove(drop_location())
qdel(src)
- target.Bumped(B)
+ target.Bumped(broken)
/obj/item/reagent_containers/cup/glass/bottle/try_splash(mob/living/user, atom/target)
@@ -339,6 +423,9 @@
list_reagents = list(/datum/reagent/water/holywater = 100)
drink_type = NONE
+/obj/item/reagent_containers/cup/glass/bottle/holywater/add_message_overlay()
+ return //looks too weird...
+
/obj/item/reagent_containers/cup/glass/bottle/holywater/hell
desc = "A flask of holy water...it's been sitting in the Necropolis a while though."
icon_state = "unholyflask"
@@ -501,7 +588,6 @@
list_reagents = list(/datum/reagent/consumable/ethanol/sake = 100)
/obj/item/reagent_containers/cup/glass/bottle/sake/Initialize(mapload)
- . = ..()
if(prob(10))
name = "Fluffy Tail Sake"
desc += " On the bottle is a picture of a kitsune with nine touchable tails."
@@ -510,6 +596,12 @@
name = "Inubashiri's Home Brew"
desc += " Awoo."
icon_state = "sakebottle_i"
+ return ..()
+
+/obj/item/reagent_containers/cup/glass/bottle/sake/add_message_overlay()
+ if(icon_state == "sakebottle_k") //doesn't fit the sprite
+ return
+ return ..()
/obj/item/reagent_containers/cup/glass/bottle/fernet
name = "Fernet Bronca"
@@ -530,6 +622,9 @@
icon_state = "curacao_bottle"
list_reagents = list(/datum/reagent/consumable/ethanol/curacao = 100)
+/obj/item/reagent_containers/cup/glass/bottle/curacao/add_message_overlay()
+ return //doesn't fit the sprite
+
/obj/item/reagent_containers/cup/glass/bottle/navy_rum
name = "Pride of the Union Navy-Strength Rum"
desc = "Ironically named, given it's made in Bermuda."
@@ -574,6 +669,9 @@
///Whether this bottle was a victim of a successful sabrage attempt
var/sabraged = FALSE
+/obj/item/reagent_containers/cup/glass/bottle/champagne/add_message_overlay()
+ return //doesn't stylistically fit the sprite
+
/obj/item/reagent_containers/cup/glass/bottle/champagne/cursed
sabrage_success_percentile = 0 //force of the sharp item used to sabrage will not increase success chance
@@ -733,12 +831,18 @@
icon_state = "hoochbottle"
list_reagents = list(/datum/reagent/consumable/ethanol/hooch = 100)
+/obj/item/reagent_containers/cup/glass/bottle/hooch/add_message_overlay()
+ return //doesn't fit the sprite
+
/obj/item/reagent_containers/cup/glass/bottle/moonshine
name = "moonshine jug"
desc = "It is said that the ancient Applalacians used these stoneware jugs to capture lightning in a bottle."
icon_state = "moonshinebottle"
list_reagents = list(/datum/reagent/consumable/ethanol/moonshine = 100)
+/obj/item/reagent_containers/cup/glass/bottle/moonshine/add_message_overlay()
+ return //doesn't fit the sprite
+
/obj/item/reagent_containers/cup/glass/bottle/mushi_kombucha
name = "Solzara Brewing Company Mushi Kombucha"
desc = "Best drunk over ice to savour the mushroomy flavour."
@@ -790,15 +894,15 @@
)
/obj/item/reagent_containers/cup/glass/bottle/molotov/CheckParts(list/parts_list)
- ..()
- var/obj/item/reagent_containers/cup/glass/bottle/B = locate() in contents
- if(B)
- icon_state = B.icon_state
- B.reagents.copy_to(src, 100)
- if(istype(B, /obj/item/reagent_containers/cup/glass/bottle/juice))
- desc += " You're not sure if making this out of a carton was the brightest idea."
- isGlass = FALSE
- return
+ . = ..()
+ var/obj/item/reagent_containers/cup/glass/bottle/bottle = locate() in contents
+ if(!bottle)
+ return
+ icon_state = bottle.icon_state
+ bottle.reagents.copy_to(src, 100)
+ if(istype(bottle, /obj/item/reagent_containers/cup/glass/bottle/juice))
+ desc += " You're not sure if making this out of a carton was the brightest idea."
+ isGlass = FALSE
/obj/item/reagent_containers/cup/glass/bottle/molotov/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum, do_splash = FALSE)
..(hit_atom, throwingdatum, do_splash = FALSE)
diff --git a/icons/effects/random_spawners.dmi b/icons/effects/random_spawners.dmi
index 4e560833003..d6e06fb140d 100644
Binary files a/icons/effects/random_spawners.dmi and b/icons/effects/random_spawners.dmi differ
diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi
index 0a952615d58..db36e241a2f 100644
Binary files a/icons/hud/fishing_hud.dmi and b/icons/hud/fishing_hud.dmi differ
diff --git a/icons/obj/drinks/bottles.dmi b/icons/obj/drinks/bottles.dmi
index cd3cf9fef09..2bdacf04265 100644
Binary files a/icons/obj/drinks/bottles.dmi and b/icons/obj/drinks/bottles.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 204142715ac..2ebb1b85729 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -740,6 +740,7 @@
#include "code\controllers\subsystem\persistence\counter_tram_hits.dm"
#include "code\controllers\subsystem\persistence\custom_outfits.dm"
#include "code\controllers\subsystem\persistence\engravings.dm"
+#include "code\controllers\subsystem\persistence\message_bottles.dm"
#include "code\controllers\subsystem\persistence\photo_albums.dm"
#include "code\controllers\subsystem\persistence\piggy_banks.dm"
#include "code\controllers\subsystem\persistence\recipes.dm"
@@ -2291,6 +2292,7 @@
#include "code\game\objects\effects\spawners\bombspawner.dm"
#include "code\game\objects\effects\spawners\costume.dm"
#include "code\game\objects\effects\spawners\gibspawner.dm"
+#include "code\game\objects\effects\spawners\message_in_a_bottle.dm"
#include "code\game\objects\effects\spawners\structure.dm"
#include "code\game\objects\effects\spawners\xeno_egg_delivery.dm"
#include "code\game\objects\effects\spawners\random\ai_module.dm"