diff --git a/_maps/map_files/BoxStation/BoxStation.dmm b/_maps/map_files/BoxStation/BoxStation.dmm
index 25d64ac7b2..78fb75524f 100644
--- a/_maps/map_files/BoxStation/BoxStation.dmm
+++ b/_maps/map_files/BoxStation/BoxStation.dmm
@@ -49262,7 +49262,6 @@
name = "MiniSat External Access";
req_access_txt = "65;13"
},
-/obj/structure/fans/tiny,
/turf/open/floor/plating,
/area/ai_monitored/turret_protected/aisat_interior)
"ctb" = (
@@ -54064,6 +54063,16 @@
},
/turf/open/floor/plasteel,
/area/security/processing)
+"eXJ" = (
+/obj/effect/mapping_helpers/airlock/cyclelink_helper{
+ dir = 1
+ },
+/obj/machinery/door/airlock/external{
+ name = "Port Docking Bay 1"
+ },
+/obj/structure/fans/tiny,
+/turf/open/floor/plating,
+/area/hallway/secondary/entry)
"eXL" = (
/obj/machinery/atmospherics/pipe/heat_exchanging/simple{
dir = 10
@@ -54848,18 +54857,6 @@
/obj/structure/lattice/catwalk,
/turf/open/space/basic,
/area/space/nearstation)
-"gJA" = (
-/obj/machinery/conveyor{
- dir = 8;
- id = "QMLoad"
- },
-/obj/machinery/door/poddoor{
- id = "QMLoaddoor";
- name = "supply dock loading door"
- },
-/obj/structure/fans/tiny,
-/turf/open/floor/plating,
-/area/cargo/storage)
"gJN" = (
/obj/structure/rack,
/obj/item/book/manual/wiki/engineering_singulo_tesla,
@@ -55933,16 +55930,6 @@
},
/turf/open/floor/plating,
/area/maintenance/port/fore)
-"jqd" = (
-/obj/effect/mapping_helpers/airlock/cyclelink_helper{
- dir = 1
- },
-/obj/machinery/door/airlock/external{
- name = "Port Docking Bay 1"
- },
-/obj/structure/fans/tiny,
-/turf/open/floor/plating,
-/area/hallway/secondary/entry)
"jqv" = (
/obj/structure/chair/wood/normal{
dir = 1
@@ -58150,18 +58137,6 @@
},
/turf/open/floor/plasteel,
/area/commons/fitness/pool)
-"ojC" = (
-/obj/machinery/conveyor{
- dir = 4;
- id = "QMLoad2"
- },
-/obj/machinery/door/poddoor{
- id = "QMLoaddoor2";
- name = "supply dock loading door"
- },
-/obj/structure/fans/tiny,
-/turf/open/floor/plating,
-/area/cargo/storage)
"okK" = (
/obj/machinery/light/small{
dir = 8
@@ -58471,6 +58446,18 @@
},
/turf/open/floor/plating,
/area/service/abandoned_gambling_den)
+"oLn" = (
+/obj/machinery/conveyor{
+ dir = 4;
+ id = "QMLoad2"
+ },
+/obj/machinery/door/poddoor{
+ id = "QMLoaddoor2";
+ name = "supply dock loading door"
+ },
+/obj/structure/fans/tiny,
+/turf/open/floor/plating,
+/area/cargo/storage)
"oMT" = (
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plating,
@@ -58909,6 +58896,11 @@
dir = 4
},
/area/service/theater/clown)
+"pIt" = (
+/obj/effect/turf_decal/bot,
+/mob/living/simple_animal/pet/dog/cheems,
+/turf/open/floor/plasteel,
+/area/cargo/storage)
"pJR" = (
/obj/structure/disposalpipe/segment,
/obj/structure/chair/comfy/brown{
@@ -58933,14 +58925,6 @@
/obj/machinery/atmospherics/pipe/simple/supply/hidden,
/turf/open/floor/plasteel,
/area/hallway/primary/port)
-"pOo" = (
-/obj/effect/mapping_helpers/airlock/cyclelink_helper,
-/obj/machinery/door/airlock/external{
- name = "Port Docking Bay 1"
- },
-/obj/structure/fans/tiny,
-/turf/open/floor/plating,
-/area/hallway/secondary/entry)
"pPi" = (
/obj/machinery/atmospherics/pipe/simple/scrubbers/hidden{
dir = 4
@@ -59587,10 +59571,6 @@
},
/turf/open/floor/plasteel,
/area/engineering/gravity_generator)
-"rty" = (
-/mob/living/simple_animal/pet/dog/cheems,
-/turf/open/floor/plasteel,
-/area/cargo/storage)
"rtC" = (
/obj/effect/decal/cleanable/blood/old,
/turf/open/floor/plating,
@@ -59653,14 +59633,6 @@
},
/turf/open/floor/plasteel,
/area/commons/fitness)
-"ryN" = (
-/obj/machinery/door/airlock/external{
- name = "Solar Maintenance";
- req_access_txt = "10; 13"
- },
-/obj/structure/fans/tiny,
-/turf/open/floor/plating,
-/area/maintenance/aft)
"rAR" = (
/obj/machinery/atmospherics/pipe/manifold/supply/hidden{
dir = 1
@@ -60801,6 +60773,18 @@
/obj/machinery/atmospherics/pipe/manifold/purple/visible,
/turf/open/floor/plasteel,
/area/engineering/atmos)
+"ueh" = (
+/obj/machinery/conveyor{
+ dir = 8;
+ id = "QMLoad"
+ },
+/obj/machinery/door/poddoor{
+ id = "QMLoaddoor";
+ name = "supply dock loading door"
+ },
+/obj/structure/fans/tiny,
+/turf/open/floor/plating,
+/area/cargo/storage)
"uei" = (
/obj/structure/sign/warning/vacuum/external{
pixel_y = 32
@@ -62017,6 +62001,14 @@
},
/turf/open/floor/plasteel,
/area/hallway/secondary/service)
+"wsp" = (
+/obj/effect/mapping_helpers/airlock/cyclelink_helper,
+/obj/machinery/door/airlock/external{
+ name = "Port Docking Bay 1"
+ },
+/obj/structure/fans/tiny,
+/turf/open/floor/plating,
+/area/hallway/secondary/entry)
"wuO" = (
/obj/effect/spawner/lootdrop/maintenance{
lootcount = 2;
@@ -62482,6 +62474,14 @@
/obj/structure/closet/secure_closet/personal,
/turf/open/floor/plasteel,
/area/commons/locker)
+"xEc" = (
+/obj/machinery/door/airlock/external{
+ name = "Solar Maintenance";
+ req_access_txt = "10; 13"
+ },
+/obj/structure/fans/tiny,
+/turf/open/floor/plating,
+/area/maintenance/aft)
"xEB" = (
/obj/effect/turf_decal/bot_white/right,
/obj/effect/turf_decal/tile/neutral{
@@ -72458,7 +72458,7 @@ awZ
ayl
azy
auP
-jqd
+eXJ
aaa
aaa
aaa
@@ -72466,7 +72466,7 @@ aaa
aaa
aaa
aaa
-pOo
+wsp
auP
cIh
ayl
@@ -74257,7 +74257,7 @@ axh
aIK
azy
auP
-jqd
+eXJ
aaa
aaa
aaa
@@ -74265,7 +74265,7 @@ aaa
aaa
aaa
aaa
-pOo
+wsp
auP
cIh
ayl
@@ -80455,11 +80455,11 @@ aPz
aaa
aaa
boI
-ojC
+oLn
brJ
boI
brJ
-gJA
+ueh
boI
aaa
aaa
@@ -83024,7 +83024,7 @@ bjo
bjr
bjr
bmh
-boK
+pIt
bjr
boK
bjr
@@ -83282,7 +83282,7 @@ bjr
ama
bmh
bjr
-rty
+bjr
bjr
bjr
bjr
@@ -104656,7 +104656,7 @@ czY
cNW
bAw
bAw
-ryN
+xEc
aag
gJi
aaa
diff --git a/code/__DEFINES/citadel_defines.dm b/code/__DEFINES/citadel_defines.dm
index 541b50e9bd..04eeb515c9 100644
--- a/code/__DEFINES/citadel_defines.dm
+++ b/code/__DEFINES/citadel_defines.dm
@@ -101,6 +101,7 @@
#define NO_AUTO_WAG (1<<12)
#define GENITAL_EXAMINE (1<<13)
#define VORE_EXAMINE (1<<14)
+#define TRASH_FORCEFEED (1<<15)
#define TOGGLES_CITADEL 0
//belly sound pref things
diff --git a/code/__DEFINES/construction.dm b/code/__DEFINES/construction.dm
index d3caedac22..f4b0fe1593 100644
--- a/code/__DEFINES/construction.dm
+++ b/code/__DEFINES/construction.dm
@@ -81,7 +81,6 @@
#define CAT_AMMO "Ammunition"
#define CAT_PARTS "Weapon Parts"
#define CAT_ROBOT "Robots"
-#define CAT_MISC "Misc"
#define CAT_MISCELLANEOUS "Miscellaneous"
#define CAT_TOOL "Tools & Storage"
#define CAT_FURNITURE "Furniture"
diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm
index 3f8a85d8c0..1431ad2109 100644
--- a/code/__DEFINES/inventory.dm
+++ b/code/__DEFINES/inventory.dm
@@ -192,6 +192,18 @@
#define TINT_DARKENED 2 //Threshold of tint level to apply weld mask overlay
#define TINT_BLIND 3 //Threshold of tint level to obscure vision fully
+// defines for AFK theft
+/// How many messages you can remember while logged out before you stop remembering new ones
+#define AFK_THEFT_MAX_MESSAGES 10
+/// If someone logs back in and there are entries older than this, just tell them they can't remember who it was or when
+#define AFK_THEFT_FORGET_DETAILS_TIME 5 MINUTES
+/// The index of the entry in 'afk_thefts' with the person's visible name at the time
+#define AFK_THEFT_NAME 1
+/// The index of the entry in 'afk_thefts' with the text
+#define AFK_THEFT_MESSAGE 2
+/// The index of the entry in 'afk_thefts' with the time it happened
+#define AFK_THEFT_TIME 3
+
//Allowed equipment lists for security vests and hardsuits.
GLOBAL_LIST_INIT(advanced_hardsuit_allowed, typecacheof(list(
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 3082856ccf..eb9bdccc22 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -221,6 +221,7 @@
#define TRAIT_LIVING_NO_DENSITY "living_no_density"
/// forces us to not render our overlays
#define TRAIT_HUMAN_NO_RENDER "human_no_render"
+#define TRAIT_TRASHCAN "trashcan"
// mobility flag traits
// IN THE FUTURE, IT WOULD BE NICE TO DO SOMETHING SIMILAR TO https://github.com/tgstation/tgstation/pull/48923/files (ofcourse not nearly the same because I have my.. thoughts on it)
diff --git a/code/__DEFINES/voreconstants.dm b/code/__DEFINES/voreconstants.dm
index d2eb034813..372c5b776c 100644
--- a/code/__DEFINES/voreconstants.dm
+++ b/code/__DEFINES/voreconstants.dm
@@ -8,17 +8,22 @@
#define DM_UNABSORB "Un-absorb"
#define DIGESTABLE (1<<0)
-#define SHOW_VORE_PREFS (1<<1)
-#define DEVOURABLE (1<<2)
-#define FEEDING (1<<3)
-#define NO_VORE (1<<4)
-#define OPEN_PANEL (1<<5)
-#define ABSORBED (1<<6)
-#define VORE_INIT (1<<7)
-#define VOREPREF_INIT (1<<8)
-#define LICKABLE (1<<9)
+#define DEVOURABLE (1<<1)
+#define FEEDING (1<<2)
+#define NO_VORE (1<<3)
+#define ABSORBED (1<<4)
+#define VORE_INIT (1<<5)
+#define VOREPREF_INIT (1<<6)
+#define LICKABLE (1<<7)
+/// Can be smelled?
+#define SMELLABLE (1<<8)
+/// Can get absorbed?
+#define ABSORBABLE (1<<9)
+/// Can get simplemob vored?
+#define MOBVORE (1<<10)
-#define MAX_VORE_FLAG (1<<10)-1 // change this whenever you add a vore flag, must be largest vore flag*2-1
+/// Change this whenever you add a vore flag, must be largest vore flag*2-1
+#define MAX_VORE_FLAG (1<<11)-1
#define isbelly(A) istype(A, /obj/belly)
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 7e2abebcf9..2d0601d839 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -19,6 +19,8 @@
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= length(L) ? L[I] : null) : L[I]) : null)
#define LAZYSET(L, K, V) if(!L) { L = list(); } L[K] = V;
#define LAZYLEN(L) length(L)
+//Sets a list to null
+#define LAZYNULL(L) L = null
#define LAZYCLEARLIST(L) if(L) L.Cut()
#define SANITIZE_LIST(L) ( islist(L) ? L : list() )
#define reverseList(L) reverseRange(L.Copy())
diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index b41b7356a8..daa4f5879d 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -112,6 +112,7 @@ GLOBAL_LIST_INIT(maintenance_loot, list(
/obj/item/storage/box/marshmallow = 2,
/obj/item/clothing/gloves/tackler/offbrand = 1,
/obj/item/stack/sticky_tape = 1,
+ /obj/effect/spawner/lootdrop/grille_or_trash = 15,
"" = 3
))
diff --git a/code/datums/components/crafting/craft.dm b/code/datums/components/crafting/craft.dm
deleted file mode 100644
index 3283c514b8..0000000000
--- a/code/datums/components/crafting/craft.dm
+++ /dev/null
@@ -1,491 +0,0 @@
-/datum/component/personal_crafting/Initialize()
- if(!ismob(parent))
- return COMPONENT_INCOMPATIBLE
- RegisterSignal(parent, COMSIG_MOB_CLIENT_LOGIN, .proc/create_mob_button)
-
-/datum/component/personal_crafting/proc/create_mob_button(mob/user, client/CL)
- var/datum/hud/H = user.hud_used
- var/obj/screen/craft/C = new()
- C.icon = H.ui_style
- H.static_inventory += C
- if(!CL.prefs.widescreenpref)
- C.screen_loc = ui_boxcraft
- CL.screen += C
- RegisterSignal(C, COMSIG_CLICK, .proc/component_ui_interact)
-
-/datum/component/personal_crafting
- var/busy
- var/viewing_category = 1
- var/viewing_subcategory = 1
- var/list/categories = list(
- CAT_WEAPONRY = list(
- CAT_WEAPON,
- CAT_AMMO,
- ),
- CAT_ROBOT = CAT_NONE,
- CAT_MISC = list(
- CAT_MISCELLANEOUS,
- CAT_TOOL,
- CAT_FURNITURE,
- ),
- CAT_PRIMAL = CAT_NONE,
- CAT_FOOD = list(
- CAT_BREAD,
- CAT_BURGER,
- CAT_CAKE,
- CAT_DONUT,
- CAT_EGG,
- CAT_ICE,
- CAT_MEAT,
- CAT_MEXICAN,
- CAT_MISCFOOD,
- CAT_PASTRY,
- CAT_PIE,
- CAT_PIZZA,
- CAT_SEAFOOD,
- CAT_SALAD,
- CAT_SANDWICH,
- CAT_SOUP,
- CAT_SPAGHETTI,
- ),
- CAT_DRINK = CAT_NONE,
- CAT_CLOTHING = CAT_NONE,
- )
-
- var/cur_category = CAT_NONE
- var/cur_subcategory = CAT_NONE
- var/datum/action/innate/crafting/button
- var/display_craftable_only = FALSE
- var/display_compact = TRUE
-
-
-
-
-/* This is what procs do:
- get_environment - gets a list of things accessable for crafting by user
- get_surroundings - takes a list of things and makes a list of key-types to values-amounts of said type in the list
- check_contents - takes a recipe and a key-type list and checks if said recipe can be done with available stuff
- check_tools - takes recipe, a key-type list, and a user and checks if there are enough tools to do the stuff, checks bugs one level deep
- construct_item - takes a recipe and a user, call all the checking procs, calls do_after, checks all the things again, calls del_reqs, creates result, calls CheckParts of said result with argument being list returned by deel_reqs
- del_reqs - takes recipe and a user, loops over the recipes reqs var and tries to find everything in the list make by get_environment and delete it/add to parts list, then returns the said list
-*/
-
-
-
-/**
- * Check that the contents of the recipe meet the requirements.
- *
- * user: The /mob that initated the crafting.
- * R: The /datum/crafting_recipe being attempted.
- * contents: List of items to search for R's reqs.
- */
-/datum/component/personal_crafting/proc/check_contents(mob/user, datum/crafting_recipe/R, list/contents)
- var/list/item_instances = contents["instances"]
- contents = contents["other"]
-
- var/list/requirements_list = list()
-
- // Process all requirements
- for(var/requirement_path in R.reqs)
- // Check we have the appropriate amount available in the contents list
- var/needed_amount = R.reqs[requirement_path]
- for(var/content_item_path in contents)
- // Right path and not blacklisted
- if(!ispath(content_item_path, requirement_path) || R.blacklist.Find(requirement_path))
- continue
-
- needed_amount -= contents[content_item_path]
- if(needed_amount <= 0)
- break
-
- if(needed_amount > 0)
- return FALSE
-
- // Store the instances of what we will use for R.check_requirements() for requirement_path
- var/list/instances_list = list()
- for(var/instance_path in item_instances)
- if(ispath(instance_path, requirement_path))
- instances_list += item_instances[instance_path]
-
- requirements_list[requirement_path] = instances_list
-
- for(var/requirement_path in R.chem_catalysts)
- if(contents[requirement_path] < R.chem_catalysts[requirement_path])
- return FALSE
-
- return R.check_requirements(user, requirements_list)
-
-/datum/component/personal_crafting/proc/get_environment(mob/user)
- . = list()
- for(var/obj/item/I in user.held_items)
- . += I
- if(!isturf(user.loc))
- return
- var/list/L = block(get_step(user, SOUTHWEST), get_step(user, NORTHEAST))
- for(var/A in L)
- var/turf/T = A
- if(T.Adjacent(user))
- for(var/B in T)
- var/atom/movable/AM = B
- if(AM.flags_1 & HOLOGRAM_1)
- continue
- . += AM
- for(var/slot in list(SLOT_R_STORE, SLOT_L_STORE))
- . += user.get_item_by_slot(slot)
-
-/datum/component/personal_crafting/proc/get_surroundings(mob/user)
- . = list()
- .["tool_behaviour"] = list()
- .["other"] = list()
- .["instances"] = list()
- for(var/obj/item/I in get_environment(user))
- if(I.flags_1 & HOLOGRAM_1)
- continue
- if(.["instances"][I.type])
- .["instances"][I.type] += I
- else
- .["instances"][I.type] = list(I)
- if(istype(I, /obj/item/stack))
- var/obj/item/stack/S = I
- .["other"][I.type] += S.amount
- else if(I.tool_behaviour)
- .["tool_behaviour"] += I.tool_behaviour
- .["other"][I.type] += 1
- else
- if(istype(I, /obj/item/reagent_containers))
- var/obj/item/reagent_containers/RC = I
- if(RC.is_drainable())
- for(var/datum/reagent/A in RC.reagents.reagent_list)
- .["other"][A.type] += A.volume
- .["other"][I.type] += 1
-
-/datum/component/personal_crafting/proc/check_tools(mob/user, datum/crafting_recipe/R, list/contents)
- if(!R.tools.len)
- return TRUE
- var/list/possible_tools = list()
- var/list/present_qualities = list()
- present_qualities |= contents["tool_behaviour"]
- for(var/obj/item/I in user.contents)
- if(istype(I, /obj/item/storage))
- for(var/obj/item/SI in I.contents)
- possible_tools += SI.type
- if(SI.tool_behaviour)
- present_qualities.Add(SI.tool_behaviour)
-
- possible_tools += I.type
-
- if(I.tool_behaviour)
- present_qualities.Add(I.tool_behaviour)
-
- possible_tools |= contents["other"]
-
- main_loop:
- for(var/A in R.tools)
- if(A in present_qualities)
- continue
- else
- for(var/I in possible_tools)
- if(ispath(I, A))
- continue main_loop
- return FALSE
- return TRUE
-
-/datum/component/personal_crafting/proc/construct_item(mob/user, datum/crafting_recipe/R)
- var/list/contents = get_surroundings(user)
- var/send_feedback = TRUE
- if(check_contents(user, R, contents))
- if(check_tools(user, R, contents))
- if(do_after(user, R.time, target = user))
- contents = get_surroundings(user)
- if(!check_contents(user, R, contents))
- return ", missing component."
- if(!check_tools(user, R, contents))
- return ", missing tool."
- var/list/parts = del_reqs(R, user)
- var/atom/movable/I = new R.result (get_turf(user.loc))
- I.CheckParts(parts, R)
- if(isitem(I))
- if(isfood(I))
- var/obj/item/reagent_containers/food/food_result = I
- var/total_quality = 0
- var/total_items = 0
- for(var/obj/item/reagent_containers/food/ingredient in parts)
- total_items += 1
- total_quality += ingredient.food_quality
- if(total_items == 0)
- food_result.adjust_food_quality(50)
- else
- food_result.adjust_food_quality(total_quality / total_items)
- user.put_in_hands(I)
- if(send_feedback)
- SSblackbox.record_feedback("tally", "object_crafted", 1, I.type)
- log_craft("[I] crafted by [user] at [loc_name(I.loc)]")
- return FALSE
- return "."
- return ", missing tool."
- return ", missing component."
-
-
-/*Del reqs works like this:
-
- Loop over reqs var of the recipe
- Set var amt to the value current cycle req is pointing to, its amount of type we need to delete
- Get var/surroundings list of things accessable to crafting by get_environment()
- Check the type of the current cycle req
- If its reagent then do a while loop, inside it try to locate() reagent containers, inside such containers try to locate needed reagent, if there isnt remove thing from surroundings
- If there is enough reagent in the search result then delete the needed amount, create the same type of reagent with the same data var and put it into deletion list
- If there isnt enough take all of that reagent from the container, put into deletion list, substract the amt var by the volume of reagent, remove the container from surroundings list and keep searching
- While doing above stuff check deletion list if it already has such reagnet, if yes merge instead of adding second one
- If its stack check if it has enough amount
- If yes create new stack with the needed amount and put in into deletion list, substract taken amount from the stack
- If no put all of the stack in the deletion list, substract its amount from amt and keep searching
- While doing above stuff check deletion list if it already has such stack type, if yes try to merge them instead of adding new one
- If its anything else just locate() in in the list in a while loop, each find --s the amt var and puts the found stuff in deletion loop
-
- Then do a loop over parts var of the recipe
- Do similar stuff to what we have done above, but now in deletion list, until the parts conditions are satisfied keep taking from the deletion list and putting it into parts list for return
-
- After its done loop over deletion list and delete all the shit that wasnt taken by parts loop
-
- del_reqs return the list of parts resulting object will receive as argument of CheckParts proc, on the atom level it will add them all to the contents, on all other levels it calls ..() and does whatever is needed afterwards but from contents list already
-*/
-
-/datum/component/personal_crafting/proc/del_reqs(datum/crafting_recipe/R, mob/user)
- var/list/surroundings
- var/list/Deletion = list()
- . = list()
- var/data
- var/amt
- main_loop:
- for(var/A in R.reqs)
- amt = R.reqs[A]
- surroundings = get_environment(user)
- surroundings -= Deletion
- if(ispath(A, /datum/reagent))
- var/datum/reagent/RG = new A
- var/datum/reagent/RGNT
- while(amt > 0)
- var/obj/item/reagent_containers/RC = locate() in surroundings
- RG = RC.reagents.get_reagent(A)
- if(RG)
- if(!locate(RG.type) in Deletion)
- Deletion += new RG.type()
- if(RG.volume > amt)
- RG.volume -= amt
- data = RG.data
- RC.reagents.conditional_update(RC)
- RG = locate(RG.type) in Deletion
- RG.volume = amt
- RG.data += data
- continue main_loop
- else
- surroundings -= RC
- amt -= RG.volume
- RC.reagents.reagent_list -= RG
- RC.reagents.conditional_update(RC)
- RGNT = locate(RG.type) in Deletion
- RGNT.volume += RG.volume
- RGNT.data += RG.data
- qdel(RG)
- RC.on_reagent_change()
- else
- surroundings -= RC
- else if(ispath(A, /obj/item/stack))
- var/obj/item/stack/S
- var/obj/item/stack/SD
- while(amt > 0)
- S = locate(A) in surroundings
- if(S.amount >= amt)
- if(!locate(S.type) in Deletion)
- SD = new S.type()
- Deletion += SD
- S.use(amt)
- SD = locate(S.type) in Deletion
- SD.amount += amt
- continue main_loop
- else
- amt -= S.amount
- if(!locate(S.type) in Deletion)
- Deletion += S
- else
- data = S.amount
- S = locate(S.type) in Deletion
- S.add(data)
- surroundings -= S
- else
- var/atom/movable/I
- while(amt > 0)
- I = locate(A) in surroundings
- Deletion += I
- surroundings -= I
- amt--
- var/list/partlist = list(R.parts.len)
- for(var/M in R.parts)
- partlist[M] = R.parts[M]
- for(var/A in R.parts)
- if(istype(A, /datum/reagent))
- var/datum/reagent/RG = locate(A) in Deletion
- if(RG.volume > partlist[A])
- RG.volume = partlist[A]
- . += RG
- Deletion -= RG
- continue
- else if(istype(A, /obj/item/stack))
- var/obj/item/stack/ST = locate(A) in Deletion
- if(ST.amount > partlist[A])
- ST.amount = partlist[A]
- . += ST
- Deletion -= ST
- continue
- else
- while(partlist[A] > 0)
- var/atom/movable/AM = locate(A) in Deletion
- . += AM
- Deletion -= AM
- partlist[A] -= 1
- while(Deletion.len)
- var/DL = Deletion[Deletion.len]
- Deletion.Cut(Deletion.len)
- qdel(DL)
-
-/datum/component/personal_crafting/proc/component_ui_interact(obj/screen/craft/image, location, control, params, user)
- if(user == parent)
- ui_interact(user)
-
-/datum/component/personal_crafting/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.not_incapacitated_turf_state)
- ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
- if(!ui)
- cur_category = categories[1]
- if(islist(categories[cur_category]))
- var/list/subcats = categories[cur_category]
- cur_subcategory = subcats[1]
- else
- cur_subcategory = CAT_NONE
- ui = new(user, src, ui_key, "personal_crafting", "Crafting Menu", 700, 800, master_ui, state)
- ui.open()
-
-
-/datum/component/personal_crafting/ui_data(mob/user)
- var/list/data = list()
- data["busy"] = busy
- data["category"] = cur_category
- data["subcategory"] = cur_subcategory
- data["display_craftable_only"] = display_craftable_only
- data["display_compact"] = display_compact
-
- var/list/surroundings = get_surroundings(user)
- var/list/craftability = list()
- for(var/rec in GLOB.crafting_recipes)
- var/datum/crafting_recipe/R = rec
-
- if(!R.always_availible && !(R.type in user?.mind?.learned_recipes)) //User doesn't actually know how to make this.
- continue
-
- if((R.category != cur_category) || (R.subcategory != cur_subcategory))
- continue
-
- craftability["[REF(R)]"] = check_contents(user, R, surroundings)
-
- data["craftability"] = craftability
- return data
-
-/datum/component/personal_crafting/ui_static_data(mob/user)
- var/list/data = list()
-
- var/list/crafting_recipes = list()
- for(var/rec in GLOB.crafting_recipes)
- var/datum/crafting_recipe/R = rec
-
- if(R.name == "") //This is one of the invalid parents that sneaks in
- continue
-
- if(!R.always_availible && !(R.type in user?.mind?.learned_recipes)) //User doesn't actually know how to make this.
- continue
-
- if(isnull(crafting_recipes[R.category]))
- crafting_recipes[R.category] = list()
-
- if(R.subcategory == CAT_NONE)
- crafting_recipes[R.category] += list(build_recipe_data(R))
- else
- if(isnull(crafting_recipes[R.category][R.subcategory]))
- crafting_recipes[R.category][R.subcategory] = list()
- crafting_recipes[R.category]["has_subcats"] = TRUE
- crafting_recipes[R.category][R.subcategory] += list(build_recipe_data(R))
-
- data["crafting_recipes"] = crafting_recipes
- return data
-
-
-/datum/component/personal_crafting/ui_act(action, params)
- if(..())
- return
- switch(action)
- if("make")
- var/datum/crafting_recipe/TR = locate(params["recipe"]) in GLOB.crafting_recipes
- ui_interact(usr)
- if(busy)
- to_chat(usr, "You are already making something!")
- return
- busy = TRUE
- var/fail_msg = construct_item(usr, TR)
- if(!fail_msg)
- to_chat(usr, "[TR.name] constructed.")
- else
- to_chat(usr, "Construction failed[fail_msg]")
- busy = FALSE
- if("toggle_recipes")
- display_craftable_only = !display_craftable_only
- . = TRUE
- if("toggle_compact")
- display_compact = !display_compact
- . = TRUE
- if("set_category")
- if(!isnull(params["category"]))
- cur_category = params["category"]
- if(!isnull(params["subcategory"]))
- if(params["subcategory"] == "0")
- cur_subcategory = ""
- else
- cur_subcategory = params["subcategory"]
- . = TRUE
-
-/datum/component/personal_crafting/proc/build_recipe_data(datum/crafting_recipe/R)
- var/list/data = list()
- data["name"] = R.name
- data["ref"] = "[REF(R)]"
- var/req_text = ""
- var/tool_text = ""
- var/catalyst_text = ""
-
- for(var/a in R.reqs)
- //We just need the name, so cheat-typecast to /atom for speed (even tho Reagents are /datum they DO have a "name" var)
- //Also these are typepaths so sadly we can't just do "[a]"
- var/atom/A = a
- req_text += " [R.reqs[A]] [initial(A.name)],"
- req_text = replacetext(req_text,",","",-1)
- data["req_text"] = req_text
-
- for(var/a in R.chem_catalysts)
- var/atom/A = a //cheat-typecast
- catalyst_text += " [R.chem_catalysts[A]] [initial(A.name)],"
- catalyst_text = replacetext(catalyst_text,",","",-1)
- data["catalyst_text"] = catalyst_text
-
- for(var/a in R.tools)
- if(ispath(a, /obj/item))
- var/obj/item/b = a
- tool_text += " [initial(b.name)],"
- else
- tool_text += " [a],"
- tool_text = replacetext(tool_text,",","",-1)
- data["tool_text"] = tool_text
-
- return data
-
-//Mind helpers
-
-/datum/mind/proc/teach_crafting_recipe(R)
- if(!learned_recipes)
- learned_recipes = list()
- learned_recipes |= R
diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm
index 1a9f4c0512..147dd287ca 100644
--- a/code/datums/components/crafting/crafting.dm
+++ b/code/datums/components/crafting/crafting.dm
@@ -4,6 +4,10 @@
/datum/component/personal_crafting/proc/create_mob_button(mob/user, client/CL)
var/datum/hud/H = user.hud_used
+ for(var/huds in H.static_inventory)
+ if(istype(huds, /obj/screen/craft))
+ return
+ //We don't want to be stacking multiple crafting huds on relogs
var/obj/screen/craft/C = new()
C.icon = H.ui_style
H.static_inventory += C
@@ -20,7 +24,7 @@
CAT_AMMO,
),
CAT_ROBOT = CAT_NONE,
- CAT_MISC = list(
+ CAT_MISCELLANEOUS = list(
CAT_MISCELLANEOUS,
CAT_TOOL,
CAT_FURNITURE,
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index 625e850212..ad37f770ed 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -8,7 +8,7 @@
time = 1
reqs = list(/obj/item/stack/sheet/plastic = 1,
/obj/item/stack/sheet/mineral/wood = 1)
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
subcategory = CAT_TOOL
/datum/crafting_recipe/showercurtain
@@ -18,7 +18,7 @@
/obj/item/stack/rods = 1)
result = /obj/structure/curtain
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/guillotine
name = "Guillotine"
@@ -29,7 +29,7 @@
/obj/item/stack/cable_coil = 10)
tools = list(TOOL_SCREWDRIVER, TOOL_WRENCH, TOOL_WELDER)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/femur_breaker
name = "Femur Breaker"
@@ -39,7 +39,7 @@
/obj/item/stack/cable_coil = 30)
tools = list(TOOL_SCREWDRIVER, TOOL_WRENCH, TOOL_WELDER)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
// Blood Sucker stuff //
/datum/crafting_recipe/bloodsucker/blackcoffin
@@ -54,7 +54,7 @@
///obj/item/pipe = 2)
time = 150
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
always_availible = TRUE
/datum/crafting_recipe/bloodsucker/meatcoffin
@@ -66,7 +66,7 @@
/obj/item/restraints/handcuffs/cable = 1)
time = 150
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
always_availible = TRUE
/datum/crafting_recipe/bloodsucker/metalcoffin
@@ -77,7 +77,7 @@
reqs = list(/obj/item/stack/sheet/metal = 5)
time = 100
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
always_availible = TRUE
/datum/crafting_recipe/bloodsucker/vassalrack
@@ -100,7 +100,7 @@
// )
time = 150
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
always_availible = FALSE // Disabled until learned
@@ -117,7 +117,7 @@
)
time = 100
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
always_availible = FALSE // Disabled til learned
/datum/crafting_recipe/furnace
@@ -129,7 +129,7 @@
/obj/item/stack/rods = 2)
tools = list(TOOL_CROWBAR)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/tableanvil
name = "Table Anvil"
@@ -139,7 +139,7 @@
/obj/item/stack/rods = 2)
tools = list(TOOL_SCREWDRIVER, TOOL_WRENCH, TOOL_WELDER)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/sandvil
name = "Sandstone Anvil"
@@ -148,7 +148,7 @@
reqs = list(/obj/item/stack/sheet/mineral/sandstone = 24)
tools = list(TOOL_CROWBAR)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/basaltblock
name = "Sintered Basalt Block"
@@ -157,7 +157,7 @@
reqs = list(/obj/item/stack/ore/glass/basalt = 50)
tools = list(TOOL_WELDER)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/basaltanvil
name = "Basalt Anvil"
@@ -166,7 +166,7 @@
reqs = list(/obj/item/basaltblock = 5)
tools = list(TOOL_CROWBAR)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
///////////////////
//Tools & Storage//
///////////////////
@@ -177,7 +177,7 @@
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/space_cleaner/sterilizine = 5)
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
subcategory = CAT_TOOL
/datum/crafting_recipe/brute_pack
@@ -186,7 +186,7 @@
time = 1
reqs = list(/obj/item/stack/medical/gauze/adv = 1,
/datum/reagent/medicine/styptic_powder = 10)
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
subcategory = CAT_TOOL
/datum/crafting_recipe/burn_pack
@@ -195,7 +195,7 @@
time = 1
reqs = list(/obj/item/stack/medical/gauze/adv = 1,
/datum/reagent/medicine/silver_sulfadiazine = 10)
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
subcategory = CAT_TOOL
/datum/crafting_recipe/ghettojetpack
@@ -206,7 +206,7 @@
/obj/item/extinguisher = 1,
/obj/item/pipe = 3,
/obj/item/stack/cable_coil = 30)
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
subcategory = CAT_TOOL
tools = list(TOOL_WRENCH, TOOL_WELDER, TOOL_WIRECUTTER)
@@ -220,7 +220,7 @@
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/toolboxhammer
name = "Toolbox Hammer"
@@ -231,7 +231,7 @@
/obj/item/stack/rods = 2)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/papersack
name = "Paper Sack"
@@ -239,7 +239,7 @@
time = 10
reqs = list(/obj/item/paper = 5)
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/smallcarton
name = "Small Carton"
@@ -247,7 +247,7 @@
time = 10
reqs = list(/obj/item/stack/sheet/cardboard = 1)
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/bronze_driver
name = "Bronze Plated Screwdriver"
@@ -259,7 +259,7 @@
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/bronze_welder
name = "Bronze Plated Welding Tool"
@@ -271,7 +271,7 @@
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/bronze_wirecutters
name = "Bronze Plated Wirecutters"
@@ -283,7 +283,7 @@
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/bronze_crowbar
name = "Bronze Plated Crowbar"
@@ -295,7 +295,7 @@
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/bronze_wrench
name = "Bronze Plated Wrench"
@@ -307,7 +307,7 @@
/datum/reagent/water = 15)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/rcl
name = "Makeshift Rapid Cable Layer"
@@ -316,7 +316,7 @@
tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WRENCH)
reqs = list(/obj/item/stack/sheet/metal = 15)
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/picket_sign
name = "Picket Sign"
@@ -325,7 +325,7 @@
/obj/item/stack/sheet/cardboard = 2)
time = 80
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/electrochromatic_kit
name = "Electrochromatic Kit"
@@ -334,7 +334,7 @@
/obj/item/stack/cable_coil = 1)
time = 5
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/blackmarket_uplink
name = "Black Market Uplink"
@@ -346,7 +346,7 @@
/obj/item/radio = 1,
/obj/item/analyzer = 1)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/heretic/codex
name = "Codex Cicatrix"
@@ -358,7 +358,7 @@
/obj/item/stack/sheet/animalhide/human = 1)
time = 150
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
always_availible = FALSE
////////////
@@ -372,7 +372,7 @@
/obj/item/stack/rods = 8)
time = 100
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/motorized_wheelchair
name = "Hoverchair"
@@ -386,7 +386,7 @@
tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WRENCH)
time = 200
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/skateboard
name = "Skateboard"
@@ -395,7 +395,7 @@
reqs = list(/obj/item/stack/sheet/metal = 5,
/obj/item/stack/rods = 10)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/scooter
name = "Scooter"
@@ -404,7 +404,7 @@
reqs = list(/obj/item/stack/sheet/metal = 5,
/obj/item/stack/rods = 12)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/////////
//Toys///
@@ -415,28 +415,28 @@
reqs = list(/obj/item/light/bulb = 1, /obj/item/stack/cable_coil = 1, /obj/item/stack/sheet/plastic = 4)
result = /obj/item/toy/sword
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/extendohand
name = "Extendo-Hand"
reqs = list(/obj/item/bodypart/r_arm/robot = 1, /obj/item/clothing/gloves/boxing = 1)
result = /obj/item/extendohand
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/toyneb
name = "Non-Euplastic Blade"
reqs = list(/obj/item/light/tube = 1, /obj/item/stack/cable_coil = 1, /obj/item/stack/sheet/plastic = 4)
result = /obj/item/toy/sword/cx
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/catgirlplushie
name = "Catgirl Plushie"
reqs = list(/obj/item/toy/plush/hairball = 3)
result = /obj/item/toy/plush/catgirl
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
////////////
//Unsorted//
@@ -450,7 +450,7 @@
reqs = list(/obj/item/stack/sheet/mineral/wood = 1)
result = /obj/item/stick
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/swordhilt
@@ -459,14 +459,14 @@
reqs = list(/obj/item/stack/sheet/mineral/wood = 2)
result = /obj/item/swordhandle
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/blackcarpet
name = "Black Carpet"
reqs = list(/obj/item/stack/tile/carpet = 50, /obj/item/toy/crayon/black = 1)
result = /obj/item/stack/tile/carpet/black/fifty
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/paperframes
name = "Paper Frames"
@@ -474,7 +474,7 @@
time = 10
reqs = list(/obj/item/stack/sheet/mineral/wood = 5, /obj/item/paper = 20)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/naturalpaper
name = "Hand-Pressed Paper"
@@ -483,7 +483,7 @@
tools = list(/obj/item/hatchet)
result = /obj/item/paper_bin/bundlenatural
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/bluespacehonker
name = "Bluespace Bike horn"
@@ -493,7 +493,7 @@
/obj/item/toy/crayon/blue = 1,
/obj/item/bikehorn = 1)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/mousetrap
name = "Mouse Trap"
@@ -502,7 +502,7 @@
reqs = list(/obj/item/stack/sheet/cardboard = 1,
/obj/item/stack/rods = 1)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/flashlight_eyes
name = "Flashlight Eyes"
@@ -513,7 +513,7 @@
/obj/item/restraints/handcuffs/cable = 1
)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/pressureplate
name = "Pressure Plate"
@@ -524,7 +524,7 @@
/obj/item/stack/cable_coil = 2,
/obj/item/assembly/igniter = 1)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/gold_horn
name = "Golden Bike Horn"
@@ -533,7 +533,7 @@
reqs = list(/obj/item/stack/sheet/mineral/bananium = 5,
/obj/item/bikehorn = 1)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/spooky_camera
name = "Camera Obscura"
@@ -543,7 +543,7 @@
/datum/reagent/water/holywater = 10)
parts = list(/obj/item/camera = 1)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/coconut_bong
name = "Coconut Bong"
@@ -552,7 +552,7 @@
/obj/item/reagent_containers/food/snacks/grown/coconut = 1)
time = 70
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
//////////////
//Banners/////
@@ -565,7 +565,7 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/captain/parade = 1)
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/engineering_banner
name = "Engitopia Banner"
@@ -574,7 +574,7 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/engineering/engineer = 1)
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/cargo_banner
name = "Cargonia Banner"
@@ -583,7 +583,7 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/cargo/tech = 1)
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/science_banner
name = "Sciencia Banner"
@@ -592,7 +592,7 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/rnd/scientist = 1)
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/medical_banner
name = "Meditopia Banner"
@@ -601,7 +601,7 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/medical/doctor = 1)
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/security_banner
name = "Securistan Banner"
@@ -610,4 +610,4 @@
reqs = list(/obj/item/stack/rods = 2,
/obj/item/clothing/under/rank/security/officer = 1)
subcategory = CAT_FURNITURE
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
diff --git a/code/datums/elements/trash.dm b/code/datums/elements/trash.dm
new file mode 100644
index 0000000000..3a4d5b0150
--- /dev/null
+++ b/code/datums/elements/trash.dm
@@ -0,0 +1,19 @@
+/datum/element/trash
+ element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
+
+/datum/element/trash/Attach(datum/target)
+ . = ..()
+ RegisterSignal(target, COMSIG_ITEM_ATTACK, .proc/UseFromHand)
+
+/datum/element/trash/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user)
+ if((M == user || user.vore_flags & TRASH_FORCEFEED) && ishuman(user))
+ var/mob/living/carbon/human/H = user
+ if(HAS_TRAIT(H, TRAIT_TRASHCAN))
+ playsound(H.loc,'sound/items/eatfood.ogg', rand(10,50), 1)
+ if(H.vore_selected)
+ H.visible_message("[H] [H.vore_selected.vore_verb]s the [source] into their [H.vore_selected]",
+ "You [H.vore_selected.vore_verb]s the [source] into your [H.vore_selected]")
+ source.forceMove(H.vore_selected)
+ else
+ H.visible_message("[H] consumes the [source].")
+ qdel(source)
diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm
index 18d565ed5a..2f0667d942 100644
--- a/code/datums/traits/neutral.dm
+++ b/code/datums/traits/neutral.dm
@@ -154,3 +154,11 @@
/datum/quirk/longtimer/on_spawn()
var/mob/living/carbon/C = quirk_holder
C.generate_fake_scars(rand(min_scars, max_scars))
+
+/datum/quirk/trashcan
+ name = "Trashcan"
+ desc = "You are able to consume and digest trash."
+ value = 0
+ gain_text = "You feel like munching on a can of soda."
+ lose_text = "You no longer feel like you should be eating trash."
+ mob_trait = TRAIT_TRASHCAN
diff --git a/code/game/objects/items/cigs_lighters.dm b/code/game/objects/items/cigs_lighters.dm
index 4280e7105f..16ebe20e51 100644
--- a/code/game/objects/items/cigs_lighters.dm
+++ b/code/game/objects/items/cigs_lighters.dm
@@ -387,6 +387,10 @@ CIGARETTE PACKETS ARE IN FANCY.DM
throwforce = 0
grind_results = list(/datum/reagent/carbon = 2)
+/obj/item/cigbutt/Initialize()
+ . = ..()
+ AddElement(/datum/element/trash)
+
/obj/item/cigbutt/cigarbutt
name = "cigar butt"
desc = "A manky old cigar butt."
diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm
index c58fe95f09..11370d6c3b 100644
--- a/code/game/objects/items/stacks/sheets/leather.dm
+++ b/code/game/objects/items/stacks/sheets/leather.dm
@@ -13,6 +13,7 @@
GLOBAL_LIST_INIT(human_recipes, list( \
new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/hooded/bloated_human, 5), \
+ new/datum/stack_recipe("human skin hat", /obj/item/clothing/head/human_leather, 1), \
))
/obj/item/stack/sheet/animalhide/human/get_main_recipes()
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 57fe8dd0c2..97cef8f621 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -181,6 +181,7 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \
GLOBAL_LIST_INIT(plasteel_recipes, list ( \
new/datum/stack_recipe("AI core", /obj/structure/AIcore, 4, time = 50, one_per_turf = TRUE), \
new/datum/stack_recipe("bomb assembly", /obj/machinery/syndicatebomb/empty, 10, time = 50), \
+ new/datum/stack_recipe("plasteel keg", /obj/structure/custom_keg, 10, time = 50), \
new/datum/stack_recipe("micro powered fan assembly", /obj/machinery/fan_assembly, 5, time = 50, one_per_turf = TRUE, on_floor = TRUE), \
new /datum/stack_recipe_list("crates", list( \
new /datum/stack_recipe("gray crate", /obj/structure/closet/crate, 5, time = 50, one_per_turf = 1, on_floor = 1), \
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 2109d1038e..1816f26843 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -7,6 +7,10 @@
w_class = WEIGHT_CLASS_TINY
resistance_flags = FLAMMABLE
+/obj/item/trash/Initialize()
+ . = ..()
+ AddElement(/datum/element/trash)
+
/obj/item/trash/raisins
name = "\improper 4no raisins"
icon_state= "4no_raisins"
@@ -80,6 +84,3 @@
name = "boritos bag"
icon_state = "boritos"
grind_results = list(/datum/reagent/aluminium = 1) //from the mylar bag
-
-/obj/item/trash/attack(mob/M, mob/living/user)
- return
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 32040b8573..a70d9d4678 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -48,14 +48,34 @@
return // no message spam
..()
+/obj/structure/mirror/attacked_by(obj/item/I, mob/living/user)
+ if(broken || !istype(user) || !I.force)
+ return ..()
+
+ . = ..()
+ if(broken) // breaking a mirror truly gets you bad luck!
+ to_chat(user, "A chill runs down your spine as [src] shatters...")
+ user.AddComponent(/datum/component/omen, silent=TRUE) // we have our own message
+
+/obj/structure/mirror/bullet_act(obj/item/projectile/P)
+ if(broken || !isliving(P.firer) || !P.damage)
+ return ..()
+
+ . = ..()
+ if(broken) // breaking a mirror truly gets you bad luck!
+ var/mob/living/unlucky_dude = P.firer
+ to_chat(unlucky_dude, "A chill runs down your spine as [src] shatters...")
+ unlucky_dude.AddComponent(/datum/component/omen, silent=TRUE) // we have our own message
+
/obj/structure/mirror/obj_break(damage_flag, mapload)
- if(!broken && !(flags_1 & NODECONSTRUCT_1))
- icon_state = "mirror_broke"
- if(!mapload)
- playsound(src, "shatter", 70, 1)
- if(desc == initial(desc))
- desc = "Oh no, seven years of bad luck!"
- broken = TRUE
+ if(broken || (flags_1 & NODECONSTRUCT_1))
+ return
+ icon_state = "mirror_broke"
+ if(!mapload)
+ playsound(src, "shatter", 70, TRUE)
+ if(desc == initial(desc))
+ desc = "Oh no, seven years of bad luck!"
+ broken = TRUE
/obj/structure/mirror/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index 35f8c59f7c..bb5ca6da1a 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -72,7 +72,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
// basic doesn't initialize and this will cause issues
// no warning though because this can happen naturaly as a result of it being built on top of
path = /turf/open/space
- if(!GLOB.use_preloader && path == type && !(flags & CHANGETURF_FORCEOP)) // Don't no-op if the map loader requires it to be reconstructed
+ if(!GLOB.use_preloader && path == type && !(flags & CHANGETURF_FORCEOP) && (baseturfs == new_baseturfs)) // Don't no-op if the map loader requires it to be reconstructed, or if this is a new set of baseturfs
return src
if(flags & CHANGETURF_SKIP)
return new path(src)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 7c136e2721..b1de3d3834 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -199,6 +199,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/vore_flags = 0
var/list/belly_prefs = list()
var/vore_taste = "nothing in particular"
+ var/vore_smell = null
var/toggleeatingnoise = TRUE
var/toggledigestionnoise = TRUE
var/hound_sleeper = TRUE
@@ -1105,6 +1106,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Voracious MediHound sleepers:[(cit_toggles & MEDIHOUND_SLEEPER) ? "Yes" : "No"] "
dat += "Hear Vore Sounds:[(cit_toggles & EATING_NOISES) ? "Yes" : "No"] "
dat += "Hear Vore Digestion Sounds:[(cit_toggles & DIGESTION_NOISES) ? "Yes" : "No"] "
+ dat += "Allow trash forcefeeding (requires Trashcan quirk)[(cit_toggles & TRASH_FORCEFEED) ? "Yes" : "No"] "
dat += "Forced Feminization:[(cit_toggles & FORCED_FEM) ? "Allowed" : "Disallowed"] "
dat += "Forced Masculinization:[(cit_toggles & FORCED_MASC) ? "Allowed" : "Disallowed"] "
dat += "Lewd Hypno:[(cit_toggles & HYPNO) ? "Allowed" : "Disallowed"] "
@@ -2961,6 +2963,9 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("toggledigestionnoise")
cit_toggles ^= DIGESTION_NOISES
+ if("toggleforcefeedtrash")
+ cit_toggles ^= TRASH_FORCEFEED
+
if("breast_enlargement")
cit_toggles ^= BREAST_ENLARGEMENT
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 60fac0fdd1..1e7b5e045a 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -835,6 +835,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["vore_flags"] >> vore_flags
S["vore_taste"] >> vore_taste
+ S["vore_smell"] >> vore_smell
var/char_vr_path = "[vr_path]/character_[default_slot]_v2.json"
if(fexists(char_vr_path))
var/list/json_from_file = json_decode(file2text(char_vr_path))
@@ -1007,6 +1008,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
vore_flags = sanitize_integer(vore_flags, 0, MAX_VORE_FLAG, 0)
vore_taste = copytext(vore_taste, 1, MAX_TASTE_LEN)
+ vore_smell = copytext(vore_smell, 1, MAX_TASTE_LEN)
belly_prefs = SANITIZE_LIST(belly_prefs)
cit_character_pref_load(S)
@@ -1162,6 +1164,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["vore_flags"] , vore_flags)
WRITE_FILE(S["vore_taste"] , vore_taste)
+ WRITE_FILE(S["vore_smell"] , vore_smell)
var/char_vr_path = "[vr_path]/character_[default_slot]_v2.json"
var/belly_prefs_json = safe_json_encode(list("belly_prefs" = belly_prefs))
if(fexists(char_vr_path))
diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm
index 124ece8fdc..fd181779f1 100644
--- a/code/modules/clothing/head/misc.dm
+++ b/code/modules/clothing/head/misc.dm
@@ -482,3 +482,9 @@
icon_state = "kabuto"
item_state = "kabuto"
flags_inv = HIDEHAIR|HIDEEARS
+
+/obj/item/clothing/head/human_leather
+ name = "human skin hat"
+ desc = "This will scare them. All will know my power."
+ icon_state = "human_leather"
+ item_state = "human_leather"
diff --git a/code/modules/events/cat_surgeon.dm b/code/modules/events/cat_surgeon.dm
index ebcd55d8aa..2d5651b9b1 100644
--- a/code/modules/events/cat_surgeon.dm
+++ b/code/modules/events/cat_surgeon.dm
@@ -5,7 +5,7 @@
weight = 8
/datum/round_event/cat_surgeon/announce(fake)
- priority_announce("One of our... ahem... 'special' cases has escaped. Our sensors now show their tracker implant on your station. On an unrelated note, has anyone seen our cats?",
+ priority_announce("One of our... ahem... 'special' cases has escaped. As it happens their last known location before their tracker went dead is your station so keep an eye out for them. On an unrelated note, has anyone seen our cats?",
sender_override = "Nanotrasen Psych Ward")
/datum/round_event/cat_surgeon/start()
diff --git a/code/modules/hydroponics/fermenting_barrel.dm b/code/modules/hydroponics/fermenting_barrel.dm
index 76e36a1725..3d6b90eb2d 100644
--- a/code/modules/hydroponics/fermenting_barrel.dm
+++ b/code/modules/hydroponics/fermenting_barrel.dm
@@ -73,3 +73,40 @@
icon_state = "barrel_open"
else
icon_state = "barrel"
+
+/obj/structure/custom_keg
+ name = "Plasteel Keg"
+ desc = "A large plasteel keg. You can use it to hold liquids. You may wanna label this, too."
+ icon = 'icons/obj/objects.dmi'
+ icon_state = "keg"
+ density = TRUE
+ anchored = FALSE
+ pressure_resistance = 2 * ONE_ATMOSPHERE
+ max_integrity = 300
+ var/open = FALSE
+
+/obj/structure/custom_keg/Initialize()
+ create_reagents(1000, DRAINABLE | AMOUNT_VISIBLE)
+ . = ..()
+
+/obj/structure/custom_keg/examine(mob/user)
+ . = ..()
+ . += "It is currently [open?"open, letting you pour liquids in.":"closed, letting you draw liquids from the tap."]"
+
+/obj/structure/custom_keg/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
+ open = !open
+ if(open)
+ DISABLE_BITFIELD(reagents.reagents_holder_flags, DRAINABLE)
+ ENABLE_BITFIELD(reagents.reagents_holder_flags, REFILLABLE)
+ to_chat(user, "You open [src], letting you fill it.")
+ else
+ DISABLE_BITFIELD(reagents.reagents_holder_flags, REFILLABLE)
+ ENABLE_BITFIELD(reagents.reagents_holder_flags, DRAINABLE)
+ to_chat(user, "You close [src], letting you draw from its tap.")
+ update_icon()
+
+/obj/structure/custom_keg/update_icon_state()
+ if(open)
+ icon_state = "keg_open"
+ else
+ icon_state = "keg"
diff --git a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
index e17dc43950..3834eb9231 100644
--- a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
+++ b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm
@@ -679,7 +679,7 @@
icon_state = "crow"
matrixed_sections = MATRIX_RED
-/datum/sprite_accessory/tails/mam_tail/cow
+/datum/sprite_accessory/tails/mam_tails/cow
name = "Cow"
icon_state = "cow"
matrixed_sections = MATRIX_RED
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index c9e4598aa8..8340b385c7 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -58,6 +58,8 @@
var/obj/item/ears_extra = null
var/obj/item/wrists = null
//
+ /// When an braindead player has their equipment fiddled with, we log that info here for when they come back so they know who took their ID while they were DC'd for 30 seconds
+ var/list/afk_thefts
var/special_voice = "" // For changing our voice. Used by a symptom.
diff --git a/code/modules/mob/living/carbon/human/login.dm b/code/modules/mob/living/carbon/human/login.dm
index a89921143a..ebf76eeafa 100644
--- a/code/modules/mob/living/carbon/human/login.dm
+++ b/code/modules/mob/living/carbon/human/login.dm
@@ -2,3 +2,33 @@
..()
if(dna?.species?.has_field_of_vision && CONFIG_GET(flag/use_field_of_vision))
LoadComponent(/datum/component/field_of_vision, field_of_vision_type)
+
+ if(!LAZYLEN(afk_thefts))
+ return
+
+ var/list/print_msg = list()
+ print_msg += "*---------*"
+ print_msg += "As you snap back to consciousness, you recall people messing with your stuff..."
+
+ afk_thefts = reverseRange(afk_thefts)
+
+ for(var/list/iter_theft as anything in afk_thefts)
+ if(!islist(iter_theft) || LAZYLEN(iter_theft) != AFK_THEFT_TIME)
+ stack_trace("[src] ([ckey]) returned to their body and had a null/malformed afk_theft entry. Contents: [json_encode(iter_theft)]")
+ continue
+
+ var/thief_name = iter_theft[AFK_THEFT_NAME]
+ var/theft_message = iter_theft[AFK_THEFT_MESSAGE]
+ var/time_since = world.time - iter_theft[AFK_THEFT_TIME]
+
+ if(time_since > AFK_THEFT_FORGET_DETAILS_TIME)
+ print_msg += "\tSomeone [theft_message], but it was at least [DisplayTimeText(AFK_THEFT_FORGET_DETAILS_TIME)] ago."
+ else
+ print_msg += "\t[thief_name] [theft_message] roughly [DisplayTimeText(time_since, 10)] ago."
+
+ if(LAZYLEN(afk_thefts) >= AFK_THEFT_MAX_MESSAGES)
+ print_msg += "There may have been more, but that's all you can remember..."
+ print_msg += "*---------*"
+
+ to_chat(src, print_msg.Join("\n"))
+ LAZYNULL(afk_thefts)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 9c7258fe61..500e7f0d3d 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -920,6 +920,12 @@
"[src] tries to remove your [what.name].", target = src,
target_message = "You try to remove [who]'s [what.name].")
what.add_fingerprint(src)
+ if(ishuman(who))
+ var/mob/living/carbon/human/victim_human = who
+ if(victim_human.key && !victim_human.client) // AKA braindead
+ if(victim_human.stat <= SOFT_CRIT && LAZYLEN(victim_human.afk_thefts) <= AFK_THEFT_MAX_MESSAGES)
+ var/list/new_entry = list(list(src.name, "tried unequipping your [what]", world.time))
+ LAZYADD(victim_human.afk_thefts, new_entry)
else
to_chat(src,"You try to remove [who]'s [what.name].")
what.add_fingerprint(src)
@@ -966,6 +972,13 @@
to_chat(src, "\The [what.name] doesn't fit in that place!")
return
+ if(ishuman(who))
+ var/mob/living/carbon/human/victim_human = who
+ if(victim_human.key && !victim_human.client) // AKA braindead
+ if(victim_human.stat <= SOFT_CRIT && LAZYLEN(victim_human.afk_thefts) <= AFK_THEFT_MAX_MESSAGES)
+ var/list/new_entry = list(list(src.name, "tried equipping you with [what]", world.time))
+ LAZYADD(victim_human.afk_thefts, new_entry)
+
who.visible_message("[src] tries to put [what] on [who].",
"[src] tries to put [what] on you.", target = src,
target_message = "You try to put [what] on [who].")
diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm
index 95f8f2acc1..01a30bb90a 100644
--- a/code/modules/mob/living/simple_animal/hostile/hostile.dm
+++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm
@@ -364,11 +364,10 @@
/mob/living/simple_animal/hostile/proc/AttackingTarget()
SEND_SIGNAL(src, COMSIG_HOSTILE_ATTACKINGTARGET, target)
in_melee = TRUE
- /* sorry for the simplemob vore fans
if(vore_active)
if(isliving(target))
var/mob/living/L = target
- if(!client && L.Adjacent(src) && CHECK_BITFIELD(L.vore_flags,DEVOURABLE)) // aggressive check to ensure vore attacks can be made
+ if(!client && L.Adjacent(src) && CHECK_BITFIELD(L.vore_flags, DEVOURABLE) && CHECK_BITFIELD(L.vore_flags, MOBVORE)) // aggressive check to ensure vore attacks can be made
if(prob(voracious_chance))
vore_attack(src,L,src)
else
@@ -379,7 +378,6 @@
return target.attack_animal(src)
else
return target.attack_animal(src)
- */
return target.attack_animal(src)
/mob/living/simple_animal/hostile/proc/Aggro()
diff --git a/code/modules/tgui/states/vorepanel.dm b/code/modules/tgui/states/vorepanel.dm
new file mode 100644
index 0000000000..b68dfb970a
--- /dev/null
+++ b/code/modules/tgui/states/vorepanel.dm
@@ -0,0 +1,18 @@
+ /**
+ * tgui state: vorepanel_state
+ *
+ * Only checks that the user and src_object are the same.
+ **/
+
+GLOBAL_DATUM_INIT(ui_vorepanel_state, /datum/ui_state/vorepanel_state, new)
+
+/datum/ui_state/vorepanel_state/can_use_topic(src_object, mob/user)
+ if(src_object != user)
+ // Note, in order to allow others to look at others vore panels, change this to
+ // UI_UPDATE
+ return UI_CLOSE
+ if(!user.client)
+ return UI_CLOSE
+ if(user.stat == DEAD)
+ return UI_DISABLED
+ return UI_INTERACTIVE
diff --git a/code/modules/tgui/tgui_alert.dm b/code/modules/tgui/tgui_alert.dm
index 1a86cca705..d144588ad9 100644
--- a/code/modules/tgui/tgui_alert.dm
+++ b/code/modules/tgui/tgui_alert.dm
@@ -9,7 +9,7 @@
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
*/
-/proc/tgui_alert(mob/user, message, title, list/buttons, timeout = 60 SECONDS)
+/proc/tgui_alert(mob/user, message = null, title = null, list/buttons = list("Ok"), timeout = 0)
if (!user)
user = usr
if (!istype(user))
@@ -35,9 +35,9 @@
* * title - The of the alert modal, shown on the top of the TGUI window.
* * buttons - The options that can be chosen by the user, each string is assigned a button on the UI.
* * callback - The callback to be invoked when a choice is made.
- * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Set to zero for no timeout.
+ * * timeout - The timeout of the alert, after which the modal will close and qdel itself. Disabled by default, can be set to seconds otherwise.
*/
-/proc/tgui_alert_async(mob/user, message, title, list/buttons, datum/callback/callback, timeout = 60 SECONDS)
+/proc/tgui_alert_async(mob/user, message = null, title = null, list/buttons = list("Ok"), datum/callback/callback, timeout = 0)
if (!user)
user = usr
if (!istype(user))
@@ -90,7 +90,7 @@
* the window was closed by the user.
*/
/datum/tgui_modal/proc/wait()
- while (!choice && !closed)
+ while (!choice && !closed && !QDELETED(src))
stoplag(1)
/datum/tgui_modal/ui_interact(mob/user, datum/tgui/ui)
@@ -124,10 +124,13 @@
if("choose")
if (!(params["choice"] in buttons))
return
- choice = params["choice"]
+ set_choice(params["choice"])
SStgui.close_uis(src)
return TRUE
+/datum/tgui_modal/proc/set_choice(choice)
+ src.choice = choice
+
/**
* # async tgui_modal
*
@@ -138,23 +141,17 @@
var/datum/callback/callback
/datum/tgui_modal/async/New(mob/user, message, title, list/buttons, callback, timeout)
- ..(user, title, message, buttons, timeout)
+ ..(user, message, title, buttons, timeout)
src.callback = callback
/datum/tgui_modal/async/Destroy(force, ...)
QDEL_NULL(callback)
. = ..()
-/datum/tgui_modal/async/ui_close(mob/user)
+/datum/tgui_modal/async/set_choice(choice)
. = ..()
- qdel(src)
-
-/datum/tgui_modal/async/ui_act(action, list/params)
- . = ..()
- if (!. || choice == null)
- return
- callback.InvokeAsync(choice)
- qdel(src)
+ if(!isnull(src.choice))
+ callback?.InvokeAsync(src.choice)
/datum/tgui_modal/async/wait()
return
diff --git a/code/modules/vore/eating/bellymodes.dm b/code/modules/vore/eating/bellymodes.dm
index 7cf36ebc50..8d64ee28f1 100644
--- a/code/modules/vore/eating/bellymodes.dm
+++ b/code/modules/vore/eating/bellymodes.dm
@@ -150,7 +150,7 @@
SEND_SOUND(M,prey_digest)
play_sound = pick(pred_digest)
- if(M.vore_flags & ABSORBED)
+ if(M.vore_flags & ABSORBED || !(M.vore_flags & ABSORBABLE)) //Negative.
continue
if(M.nutrition >= 100) //Drain them until there's no nutrients left. Slowly "absorb" them.
diff --git a/code/modules/vore/eating/living.dm b/code/modules/vore/eating/living.dm
index ca5ee0f476..6bc44e6489 100644
--- a/code/modules/vore/eating/living.dm
+++ b/code/modules/vore/eating/living.dm
@@ -1,17 +1,24 @@
///////////////////// Mob Living /////////////////////
/mob/living
var/vore_flags = 0
- var/showvoreprefs = TRUE // Determines if the mechanical vore preferences button will be displayed on the mob or not.
- var/obj/belly/vore_selected // Default to no vore capability.
- var/list/vore_organs = list() // List of vore containers inside a mob
- var/vore_taste = null // What the character tastes like
+ // Determines if the mechanical vore preferences button will be displayed on the mob or not.
+ var/showvoreprefs = TRUE
+ /// Default to no vore capability.
+ var/obj/belly/vore_selected
+ /// List of vore containers inside a mob
+ var/list/vore_organs = list()
+ /// What the character tastes like
+ var/vore_taste = null
+ /// What the character smells like
+ var/vore_smell = null
+ /// Next time vore sounds get played for the prey, do not change manually as it is intended to be set automatically
var/next_preyloop
//
// Hook for generic creation of stuff on new creatures
//
/hook/living_new/proc/vore_setup(mob/living/M)
- add_verb(M, list(/mob/living/proc/preyloop_refresh, /mob/living/proc/lick, /mob/living/proc/escapeOOC))
+ add_verb(M, list(/mob/living/proc/preyloop_refresh, /mob/living/proc/lick, /mob/living/proc/smell, /mob/living/proc/escapeOOC))
if(M.vore_flags & NO_VORE) //If the mob isn't supposed to have a stomach, let's not give it an insidepanel so it can make one for itself, or a stomach.
return TRUE
@@ -256,6 +263,7 @@
client.prefs.vore_flags = vore_flags // there's garbage data in here, but it doesn't matter
client.prefs.vore_taste = vore_taste
+ client.prefs.vore_smell = vore_smell
var/list/serialized = list()
for(var/belly in vore_organs)
@@ -264,6 +272,8 @@
client.prefs.belly_prefs = serialized
+ client.prefs.save_character()
+
return TRUE
//
@@ -274,8 +284,9 @@
to_chat(src,"You attempted to apply your vore prefs but somehow you're in this character without a client.prefs variable. Tell a dev.")
return FALSE
ENABLE_BITFIELD(vore_flags,VOREPREF_INIT)
- COPY_SPECIFIC_BITFIELDS(vore_flags,client.prefs.vore_flags,DIGESTABLE | DEVOURABLE | FEEDING | LICKABLE)
+ COPY_SPECIFIC_BITFIELDS(vore_flags, client.prefs.vore_flags, DIGESTABLE | DEVOURABLE | FEEDING | LICKABLE | SMELLABLE | ABSORBABLE | MOBVORE)
vore_taste = client.prefs.vore_taste
+ vore_smell = client.prefs.vore_smell
release_vore_contents(silent = TRUE)
QDEL_LIST(vore_organs)
@@ -379,6 +390,56 @@
else
taste_message += "a plain old normal [src]"
return taste_message
+
+//
+// Equally important as the above
+//
+/mob/living/proc/smell()
+ set name = "Smell Someone"
+ set category = "Vore"
+ set desc = "Smell someone nearby!"
+
+ if(incapacitated(ignore_restraints = TRUE))
+ to_chat(src, "You can't do that while incapacitated.")
+ return
+ if(!CheckActionCooldown())
+ to_chat(src, "You can't do that so fast, slow down.")
+ return
+
+ DelayNextAction(CLICK_CD_MELEE, flush = TRUE)
+
+ var/list/smellable = list()
+ for(var/mob/living/L in view(1))
+ if(L != src && (!L.ckey || L.client?.prefs.vore_flags & SMELLABLE) && Adjacent(L))
+ LAZYADD(smellable, L)
+ for(var/mob/living/listed in smellable)
+ smellable[listed] = new /mutable_appearance(listed)
+
+ if(!smellable)
+ return
+
+ var/mob/living/sniffed = show_radial_menu(src, src, smellable, radius = 40, require_near = TRUE)
+
+ if(QDELETED(sniffed) || (sniffed.ckey && !(sniffed.client?.prefs.vore_flags & SMELLABLE)) || !Adjacent(sniffed) || incapacitated(ignore_restraints = TRUE))
+ return
+
+ visible_message("[src] smells [sniffed]!","You smell [sniffed]. They smell like [sniffed.get_smell_message()].","Sniff!")
+
+/mob/living/proc/get_smell_message(allow_generic = TRUE, datum/species/mrace)
+ if(!vore_smell && !allow_generic)
+ return FALSE
+
+ var/smell_message = ""
+ if(vore_smell && (vore_smell != ""))
+ smell_message += "[vore_smell]"
+ else
+ if(ishuman(src))
+ var/mob/living/carbon/human/H = src
+ smell_message += "a normal [H.custom_species ? H.custom_species : H.dna.species]"
+ else
+ smell_message += "a plain old normal [src]"
+ return smell_message
+
// Check if an object is capable of eating things, based on vore_organs
//
/proc/has_vore_belly(var/mob/living/O)
diff --git a/code/modules/vore/eating/vorepanel.dm b/code/modules/vore/eating/vorepanel.dm
index aac8cbe745..a3adc89352 100644
--- a/code/modules/vore/eating/vorepanel.dm
+++ b/code/modules/vore/eating/vorepanel.dm
@@ -2,746 +2,730 @@
// Vore management panel for players
//
-#define BELLIES_MAX 20
+#define BELLIES_MAX 40
#define BELLIES_NAME_MIN 2
-#define BELLIES_NAME_MAX 24
+#define BELLIES_NAME_MAX 40
#define BELLIES_DESC_MAX 4096
+#define FLAVOR_MAX 400
+
+/mob/living
+ var/datum/vore_look/vorePanel
/mob/living/proc/insidePanel()
set name = "Vore Panel"
set category = "Vore"
- var/datum/vore_look/picker_holder = new()
- picker_holder.loop = picker_holder
- picker_holder.selected = vore_selected
+ if(!vorePanel)
+ log_game("VORE: [src] ([type], \ref[src]) didn't have a vorePanel and tried to use the verb.")
+ vorePanel = new(src)
- var/dat = picker_holder.gen_vui(src)
-
- picker_holder.popup = new(src, "insidePanel","Vore Panel", 450, 700, picker_holder)
- picker_holder.popup.set_content(dat)
- picker_holder.popup.open()
- vore_flags |= OPEN_PANEL
+ vorePanel.ui_interact(src)
/mob/living/proc/updateVRPanel() //Panel popup update call from belly events.
- if(vore_flags & OPEN_PANEL)
- var/datum/vore_look/picker_holder = new()
- picker_holder.loop = picker_holder
- picker_holder.selected = vore_selected
-
- var/dat = picker_holder.gen_vui(src)
-
- picker_holder.popup = new(src, "insidePanel","Vore Panel", 450, 700, picker_holder)
- picker_holder.popup.set_content(dat)
- picker_holder.popup.open()
+ SStgui.update_uis(vorePanel)
//
// Callback Handler for the Inside form
//
/datum/vore_look
- var/obj/belly/selected
- var/show_interacts = FALSE
- var/datum/browser/popup
- var/loop = null; // Magic self-reference to stop the handler from being GC'd before user takes action.
+ var/mob/living/host // Note, we do this in case we ever want to allow people to view others vore panels
+ var/unsaved_changes = FALSE
+ var/show_pictures = TRUE
+
+/datum/vore_look/New(mob/living/new_host)
+ if(istype(new_host))
+ host = new_host
+ . = ..()
/datum/vore_look/Destroy()
- loop = null
- selected = null
- ..() //this is a must
- return QDEL_HINT_HARDDEL
+ host = null
+ . = ..()
-/datum/vore_look/Topic(href,href_list[])
- if (vp_interact(href, href_list))
- popup.set_content(gen_vui(usr))
- usr << output(popup.get_content(), "insidePanel.browser")
+/datum/vore_look/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "VorePanel", "Vore Panel")
+ ui.open()
-/datum/vore_look/proc/gen_vui(var/mob/living/user)
- var/dat
- dat += "Remember to toggle the vore mode, it's to the left of your combat toggle. Open mouth means you're voracious! "
- dat += "Remember that the prey is blind, use audible mode subtle messages to communicate to them with posts! "
- dat += ""
- var/atom/userloc = user.loc
- if (isbelly(userloc))
- var/obj/belly/inside_belly = userloc
- var/mob/living/eater = inside_belly.owner
+// This looks weird, but all tgui_host is used for is state checking
+// So this allows us to use the self_state just fine.
+/datum/vore_look/ui_host(mob/user)
+ return host
- //Don't display this part if we couldn't find the belly since could be held in hand.
- if(inside_belly)
- dat += "You are currently [(user.vore_flags & ABSORBED) ? "absorbed into " : "inside "] [eater]'s [inside_belly]!
"
+// Note, in order to allow others to look at others vore panels, this state would need
+// to be modified.
+/datum/vore_look/ui_state(mob/user)
+ return GLOB.ui_vorepanel_state
- if(inside_belly.desc)
- dat += "[inside_belly.desc]
"
+/datum/vore_look
+ var/static/list/nom_icons
- if (inside_belly.contents.len > 1)
- dat += "You can see the following around you: "
- for (var/atom/movable/O in inside_belly)
- if(istype(O,/mob/living))
- var/mob/living/M = O
- //That's just you
- if(M == user)
- continue
+/datum/vore_look/proc/cached_nom_icon(atom/target)
+ LAZYINITLIST(nom_icons)
- //That's an absorbed person you're checking
- if(M.vore_flags & ABSORBED)
- if(user.vore_flags & ABSORBED)
- dat += "[O]"
- continue
- else
- continue
-
- //Anything else
- dat += "[O]"
-
- //Zero-width space, for wrapping
- dat += ""
+ var/key = ""
+ if(isobj(target))
+ key = "[target.type]"
+ else if(ismob(target))
+ var/mob/M = target
+ key = "\ref[target][M.real_name]"
+ if(nom_icons[key])
+ . = nom_icons[key]
else
- dat += "You aren't inside anyone."
-
- dat += ""
-
- dat += ""
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(B == selected)
- dat += "
"
- dat += ""
-
- // Selected Belly (contents, configuration)
- if(!selected)
- dat += "No belly selected. Click one to select it."
- else
- if(selected.contents.len)
- dat += "Contents: "
- for(var/O in selected)
-
- //Mobs can be absorbed, so treat them separately from everything else
- if(istype(O,/mob/living))
- var/mob/living/M = O
-
- //Absorbed gets special color OOoOOOOoooo
- if(M.vore_flags & ABSORBED)
- dat += "[O]"
- continue
-
- //Anything else
- dat += "[O]"
-
- //Zero-width space, for wrapping
- dat += ""
-
- //If there's more than one thing, add an [All] button
- if(selected.contents.len > 1)
- dat += "\[All\]"
-
- dat += ""
-
- //Belly Name Button
- dat += "Name:"
- dat += " '[selected.name]'"
-
- //Belly Type button
- dat += " Is Fleshy:"
- dat += "[selected.is_wet ? "Yes" : "No"]"
- if(selected.is_wet)
- dat += " Internal loop for prey?:"
- dat += "[selected.wet_loop ? "Yes" : "No"]"
-
- //Digest Mode Button
- dat += " Belly Mode:"
- dat += " [selected.digest_mode]"
-
- //Belly verb
- dat += " Vore Verb:"
- dat += " '[selected.vore_verb]'"
-
- //Inside flavortext
- dat += " Flavor Text:"
- dat += " '[selected.desc]'"
-
- //Belly sound
- dat += " Vore Sound: [selected.vore_sound]"
- dat += "Test"
-
- //Release sound
- dat += " Release Sound: [selected.release_sound]"
- dat += "Test"
-
- //Belly messages
- dat += " Belly Messages"
-
- //Can belly taste?
- dat += " Can Taste:"
- dat += " [selected.can_taste ? "Yes" : "No"]"
-
- //Minimum size prey must be to show up.
- dat += " Required examine size:"
- dat += " [selected.bulge_size*100]%"
-
- //Belly escapability
- dat += " Belly Interactions ([selected.escapable ? "On" : "Off"])"
- if(selected.escapable)
- dat += "[show_interacts ? "Hide" : "Show"]"
-
- if(show_interacts && selected.escapable)
- dat += ""
- dat += "Interaction Settings ?"
- dat += " Set Belly Escape Chance"
- dat += " [selected.escapechance]%"
-
- dat += " Set Belly Escape Time"
- dat += " [selected.escapetime/10]s"
-
- //Special here to add a gap
- dat += " "
- dat += " Set Belly Transfer Chance"
- dat += " [selected.transferchance]%"
-
- dat += " Set Belly Transfer Location"
- dat += " [selected.transferlocation ? selected.transferlocation : "Disabled"]"
-
- //Special here to add a gap
- dat += " "
- dat += " Set Belly Absorb Chance"
- dat += " [selected.absorbchance]%"
-
- dat += " Set Belly Digest Chance"
- dat += " [selected.digestchance]%"
- dat += ""
-
- //Delete button
- dat += " Delete Belly"
-
- dat += "Set Flavor"
-
- dat += ""
-
- //Under the last HR, save and stuff.
- dat += "Save Prefs"
- dat += "Refresh"
- dat += "Reload Slot Prefs"
-
- dat += ""
- var/pref_on = "#173d15"
- var/pref_off = "#990000"
- dat += " Toggle Digestable (Currently: [(user.vore_flags & DIGESTABLE) ? "ON" : "OFF"])"
- dat += " Toggle Devourable (Currently: [(user.vore_flags & DEVOURABLE) ? "ON" : "OFF"])"
- dat += " Toggle Feeding (Currently: [(user.vore_flags & FEEDING) ? "ON" : "OFF"])"
- if(user.client.prefs)
- dat += " Toggle Licking (Currently: [(user.client.prefs.vore_flags & LICKABLE) ? "ON" : "OFF"])"
- //Returns the dat html to the vore_look
- return dat
-
-/datum/vore_look/proc/vp_interact(href, href_list)
- var/mob/living/user = usr
- for(var/H in href_list)
-
- if(href_list["close"])
- qdel(src) // Cleanup
- user.vore_flags &= ~OPEN_PANEL
- return
-
- if(href_list["show_int"])
- show_interacts = !show_interacts
- return TRUE //Force update
-
- if(href_list["int_help"])
- alert("These control how your belly responds to someone using 'resist' while inside you. The percent chance to trigger each is listed below, \
- and you can change them to whatever you see fit. Setting them to 0% will disable the possibility of that interaction. \
- These only function as long as interactions are turned on in general. Keep in mind, the 'belly mode' interactions (digest/absorb) \
- will affect all prey in that belly, if one resists and triggers digestion/absorption. If multiple trigger at the same time, \
- only the first in the order of 'Escape > Transfer > Absorb > Digest' will occur.","Interactions Help")
- return FALSE //Force update
-
- if(href_list["outsidepick"])
- var/atom/movable/tgt = locate(href_list["outsidepick"])
- var/obj/belly/OB = locate(href_list["outsidebelly"])
- if(!istype(OB))
- return
- if(!(tgt in OB)) //Aren't here anymore, need to update menu.
- return TRUE
- var/intent = "Examine"
-
- if(istype(tgt,/mob/living))
- var/mob/living/M = tgt
- intent = alert("What do you want to do to them?","Query","Examine","Help Out","Devour")
- switch(intent)
- if("Examine") //Examine a mob inside another mob
- M.examine(user)
-
- if("Help Out") //Help the inside-mob out
- if(user.stat || user.vore_flags & ABSORBED || M.vore_flags & ABSORBED)
- to_chat(user,"You can't do that in your state!")
- return TRUE
-
- to_chat(user,"You begin to push [M] to freedom!")
- to_chat(M,"[usr] begins to push you to freedom!")
- to_chat(M.loc,"Someone is trying to escape from inside you!")
- sleep(50)
- if(prob(33))
- OB.release_specific_contents(M)
- to_chat(usr,"You manage to help [M] to safety!")
- to_chat(M,"[user] pushes you free!")
- to_chat(OB.owner,"[M] forces free of the confines of your body!")
- else
- to_chat(user,"[M] slips back down inside despite your efforts.")
- to_chat(M," Even with [user]'s help, you slip back inside again.")
- to_chat(OB.owner,"Your body efficiently shoves [M] back where they belong.")
-
- if("Devour") //Eat the inside mob
- if(user.vore_flags & ABSORBED || user.stat)
- to_chat(user,"You can't do that in your state!")
- return TRUE
-
- if(!user.vore_selected)
- to_chat(user,"Pick a belly on yourself first!")
- return TRUE
-
- var/obj/belly/TB = user.vore_selected
- to_chat(user,"You begin to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!")
- to_chat(M,"[user] begins to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!")
- to_chat(OB.owner,"Someone inside you is eating someone else!")
-
- sleep(TB.nonhuman_prey_swallow_time) //Can't do after, in a stomach, weird things abound.
- if((user in OB) && (M in OB)) //Make sure they're still here.
- to_chat(user,"You manage to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!")
- to_chat(M,"[user] manages to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!")
- to_chat(OB.owner,"Someone inside you has eaten someone else!")
- TB.nom_mob(M)
-
- else if(istype(tgt,/obj/item))
- var/obj/item/T = tgt
- if(!(tgt in OB))
- //Doesn't exist anymore, update.
- return TRUE
- intent = alert("What do you want to do to that?","Query","Examine","Use Hand")
- switch(intent)
- if("Examine")
- T.examine(user)
-
- if("Use Hand")
- if(user.stat)
- to_chat(user,"You can't do that in your state!")
- return TRUE
-
- user.ClickOn(T)
- sleep(5) //Seems to exit too fast for the panel to update
-
- if(href_list["insidepick"])
- var/intent
-
- //Handle the [All] choice. Ugh inelegant. Someone make this pretty.
- if(href_list["pickall"])
- intent = alert("Eject all, Move all?","Query","Eject all","Cancel","Move all")
- switch(intent)
- if("Cancel")
- return FALSE
-
- if("Eject all")
- if(user.stat)
- to_chat(user,"You can't do that in your state!")
- return FALSE
-
- selected.release_all_contents()
-
- if("Move all")
- if(user.stat)
- to_chat(user,"You can't do that in your state!")
- return FALSE
-
- var/obj/belly/choice = input("Move all where?","Select Belly") as null|anything in user.vore_organs
- if(!choice)
- return FALSE
-
- for(var/atom/movable/tgt in selected)
- to_chat(tgt,"You're squished from [user]'s [lowertext(selected)] to their [lowertext(choice.name)]!")
- selected.transfer_contents(tgt, choice, 1)
-
- var/atom/movable/tgt = locate(href_list["insidepick"])
- if(!(tgt in selected)) //Old menu, needs updating because they aren't really there.
- return TRUE //Forces update
- intent = "Examine"
- intent = alert("Examine, Eject, Move? Examine if you want to leave this box.","Query","Examine","Eject","Move")
- switch(intent)
- if("Examine")
- tgt.examine(user)
-
- if("Eject")
- if(user.stat)
- to_chat(user,"You can't do that in your state!")
- return FALSE
-
- selected.release_specific_contents(tgt)
-
- if("Move")
- if(user.stat)
- to_chat(user,"You can't do that in your state!")
- return FALSE
-
- var/obj/belly/choice = input("Move [tgt] where?","Select Belly") as null|anything in user.vore_organs
- if(!choice || !(tgt in selected))
- return FALSE
-
- to_chat(tgt,"You're squished from [user]'s [lowertext(selected.name)] to their [lowertext(choice.name)]!")
- selected.transfer_contents(tgt, choice)
-
- if(href_list["newbelly"])
- if(user.vore_organs.len >= BELLIES_MAX)
- return FALSE
-
- var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null)
-
- var/failure_msg
- if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
- failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
- // else if(whatever) //Next test here.
- else
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(lowertext(new_name) == lowertext(B.name))
- failure_msg = "No duplicate belly names, please."
- break
-
- if(failure_msg) //Something went wrong.
- alert(user,failure_msg,"Error!")
- return FALSE
-
- var/obj/belly/NB = new(user)
- NB.name = new_name
- selected = NB
-
- if(href_list["bellypick"])
- selected = locate(href_list["bellypick"])
- user.vore_selected = selected
-
- ////
- //Please keep these the same order they are on the panel UI for ease of coding
- ////
- if(href_list["b_name"])
- var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null)
-
- var/failure_msg
- if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
- failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
- // else if(whatever) //Next test here.
- else
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(lowertext(new_name) == lowertext(B.name))
- failure_msg = "No duplicate belly names, please."
- break
-
- if(failure_msg) //Something went wrong.
- alert(user,failure_msg,"Error!")
- return FALSE
-
- selected.name = new_name
-
- if(href_list["b_wetness"])
- selected.is_wet = !selected.is_wet
-
- if(href_list["b_wetloop"])
- selected.wet_loop = !selected.wet_loop
-
- if(href_list["b_mode"])
- var/list/menu_list = selected.digest_modes
-
- var/new_mode = input("Choose Mode (currently [selected.digest_mode])") as null|anything in menu_list
- if(!new_mode)
- return FALSE
- selected.digest_mode = new_mode
-
- if(href_list["b_desc"])
- var/new_desc = html_encode(input(usr,"Belly Description ([BELLIES_DESC_MAX] char limit):","New Description",selected.desc) as message|null)
-
- if(new_desc)
- new_desc = readd_quotes(new_desc)
- if(length(new_desc) > BELLIES_DESC_MAX)
- alert("Entered belly desc too long. [BELLIES_DESC_MAX] character limit.","Error")
- return FALSE
- selected.desc = new_desc
- else //Returned null
- return FALSE
-
- if(href_list["b_msgs"])
- var/list/messages = list(
- "Digest Message (to prey)",
- "Digest Message (to you)",
- "Struggle Message (outside)",
- "Struggle Message (inside)",
- "Examine Message (when full)",
- "Reset All To Default"
+ . = icon2base64(getFlatIcon(target,defdir=SOUTH,no_anim=TRUE))
+ nom_icons[key] = .
+
+/datum/vore_look/ui_data(mob/user)
+ var/list/data = list()
+
+ if(!host)
+ return data
+
+ data["unsaved_changes"] = unsaved_changes
+ data["show_pictures"] = show_pictures
+
+ var/atom/hostloc = host.loc
+ var/list/inside = list()
+ if(isbelly(hostloc))
+ var/obj/belly/inside_belly = hostloc
+ var/mob/living/pred = inside_belly.owner
+
+ inside = list(
+ "absorbed" = host.vore_flags & ABSORBED,
+ "belly_name" = inside_belly.name,
+ "belly_mode" = inside_belly.digest_mode,
+ "desc" = inside_belly.desc || "No description.",
+ "pred" = pred,
+ "ref" = "\ref[inside_belly]",
)
- alert(user,"Setting abusive or deceptive messages will result in a ban. Consider this your warning. Max 150 characters per message, max 10 messages per topic.","Really, don't.")
- var/choice = input(user,"Select a type to modify. Messages from each topic are pulled at random when needed.","Pick Type") as null|anything in messages
- var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name."
+ var/list/inside_contents = list()
+ for(var/atom/movable/O in inside_belly)
+ if(O == host)
+ continue
- switch(choice)
- if("Digest Message (to prey)")
- var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",selected.get_messages("dmp")) as message
- if(new_message)
- selected.set_messages(new_message,"dmp")
+ var/list/info = list(
+ "name" = "[O]",
+ "absorbed" = FALSE,
+ "stat" = 0,
+ "ref" = "\ref[O]",
+ "outside" = FALSE,
+ )
+ if(show_pictures)
+ info["icon"] = cached_nom_icon(O)
+ if(isliving(O))
+ var/mob/living/M = O
+ info["stat"] = M.stat
+ if(M.vore_flags & ABSORBED)
+ info["absorbed"] = TRUE
+ inside_contents.Add(list(info))
+ inside["contents"] = inside_contents
+ data["inside"] = inside
- if("Digest Message (to you)")
- var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",selected.get_messages("dmo")) as message
- if(new_message)
- selected.set_messages(new_message,"dmo")
+ var/list/our_bellies = list()
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ our_bellies.Add(list(list(
+ "selected" = (B == host.vore_selected),
+ "name" = B.name,
+ "ref" = "\ref[B]",
+ "digest_mode" = B.digest_mode,
+ "contents" = LAZYLEN(B.contents),
+ )))
+ data["our_bellies"] = our_bellies
- if("Struggle Message (outside)")
- var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",selected.get_messages("smo")) as message
- if(new_message)
- selected.set_messages(new_message,"smo")
+ var/list/selected_list = null
+ if(host.vore_selected)
+ var/obj/belly/selected = host.vore_selected
+ selected_list = list(
+ "belly_name" = selected.name,
+ "is_wet" = selected.is_wet,
+ "wet_loop" = selected.wet_loop,
+ "mode" = selected.digest_mode,
+ "verb" = selected.vore_verb,
+ "desc" = selected.desc,
+ "sound" = selected.vore_sound,
+ "release_sound" = selected.release_sound,
+ "can_taste" = selected.can_taste,
+ "bulge_size" = selected.bulge_size,
+ )
- if("Struggle Message (inside)")
- var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",selected.get_messages("smi")) as message
- if(new_message)
- selected.set_messages(new_message,"smi")
+ selected_list["escapable"] = selected.escapable
+ selected_list["interacts"] = list()
+ if(selected.escapable)
+ selected_list["interacts"]["escapechance"] = selected.escapechance
+ selected_list["interacts"]["escapetime"] = selected.escapetime
+ selected_list["interacts"]["transferchance"] = selected.transferchance
+ selected_list["interacts"]["transferlocation"] = selected.transferlocation
+ selected_list["interacts"]["absorbchance"] = selected.absorbchance
+ selected_list["interacts"]["digestchance"] = selected.digestchance
- if("Examine Message (when full)")
- var/new_message = input(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",selected.get_messages("em")) as message
- if(new_message)
- selected.set_messages(new_message,"em")
+ var/list/selected_contents = list()
+ for(var/O in selected)
+ var/list/info = list(
+ "name" = "[O]",
+ "absorbed" = FALSE,
+ "stat" = 0,
+ "ref" = "\ref[O]",
+ "outside" = TRUE,
+ )
+ if(show_pictures)
+ info["icon"] = cached_nom_icon(O)
+ if(isliving(O))
+ var/mob/living/M = O
+ info["stat"] = M.stat
+ if(M.vore_flags & ABSORBED)
+ info["absorbed"] = TRUE
+ selected_contents.Add(list(info))
+ selected_list["contents"] = selected_contents
- if("Reset All To Default")
- var/confirm = alert(user,"This will delete any custom messages. Are you sure?","Confirmation","DELETE","Cancel")
- if(confirm == "DELETE")
- selected.digest_messages_prey = initial(selected.digest_messages_prey)
- selected.digest_messages_owner = initial(selected.digest_messages_owner)
- selected.struggle_messages_outside = initial(selected.struggle_messages_outside)
- selected.struggle_messages_inside = initial(selected.struggle_messages_inside)
+ data["selected"] = selected_list
+ data["prefs"] = list(
+ "digestable" = CHECK_BITFIELD(host.vore_flags, DIGESTABLE),
+ "devourable" = CHECK_BITFIELD(host.vore_flags, DEVOURABLE),
+ "feeding" = CHECK_BITFIELD(host.vore_flags, FEEDING),
+ "absorbable" = CHECK_BITFIELD(host.vore_flags, ABSORBABLE),
+ "allowmobvore" = CHECK_BITFIELD(host.vore_flags, MOBVORE),
+ "vore_sounds" = CHECK_BITFIELD(host.client.prefs.cit_toggles, EATING_NOISES),
+ "digestion_sounds" = CHECK_BITFIELD(host.client.prefs.cit_toggles, DIGESTION_NOISES),
+ "lickable" = CHECK_BITFIELD(host.vore_flags, LICKABLE),
+ "smellable" = CHECK_BITFIELD(host.vore_flags, SMELLABLE),
+ )
- if(href_list["b_verb"])
- var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null)
+ return data
- if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN)
- alert("Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error")
- return FALSE
+/datum/vore_look/ui_act(action, params)
+ if(..())
+ return TRUE
- selected.vore_verb = new_verb
+ switch(action)
+ if("show_pictures")
+ show_pictures = !show_pictures
+ return TRUE
+ if("int_help")
+ tgui_alert(usr, "These control how your belly responds to someone using 'resist' while inside you. The percent chance to trigger each is listed below, \
+ and you can change them to whatever you see fit. Setting them to 0% will disable the possibility of that interaction. \
+ These only function as long as interactions are turned on in general. Keep in mind, the 'belly mode' interactions (digest/absorb) \
+ will affect all prey in that belly, if one resists and triggers digestion/absorption. If multiple trigger at the same time, \
+ only the first in the order of 'Escape > Transfer > Absorb > Digest' will occur.","Interactions Help")
+ return TRUE
- if(href_list["b_release"])
- var/choice = input(user,"Currently set to [selected.release_sound]","Select Sound") as null|anything in GLOB.pred_release_sounds
+ // Host is inside someone else, and is trying to interact with something else inside that person.
+ if("pick_from_inside")
+ return pick_from_inside(usr, params)
- if(!choice)
- return
+ // Host is trying to interact with something in host's belly.
+ if("pick_from_outside")
+ return pick_from_outside(usr, params)
- selected.release_sound = choice
-
- if(href_list["b_releasesoundtest"])
- var/sound/releasetest = GLOB.prey_release_sounds[selected.release_sound]
- if(releasetest)
- SEND_SOUND(user, releasetest)
-
- if(href_list["b_sound"])
- var/choice = input(user,"Currently set to [selected.vore_sound]","Select Sound") as null|anything in GLOB.pred_vore_sounds
-
- if(!choice)
- return
-
- selected.vore_sound = choice
-
- if(href_list["b_soundtest"])
- var/sound/voretest = GLOB.prey_vore_sounds[selected.vore_sound]
- if(voretest)
- SEND_SOUND(user, voretest)
-
- if(href_list["b_tastes"])
- selected.can_taste = !selected.can_taste
-
- if(href_list["b_bulge_size"])
- var/new_bulge = input(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.") as num|null
- if(new_bulge == null)
- return
- if(new_bulge == 0) //Disable.
- selected.bulge_size = 0
- to_chat(user,"Your stomach will not be seen on examine.")
- else if (!ISINRANGE(new_bulge,25,200))
- selected.bulge_size = 0.25 //Set it to the default.
- to_chat(user,"Invalid size.")
- else if(new_bulge)
- selected.bulge_size = (new_bulge/100)
-
- if(href_list["b_escapable"])
- if(selected.escapable == FALSE) //Possibly escapable and special interactions.
- selected.escapable = TRUE
- to_chat(usr,"Prey now have special interactions with your [lowertext(selected.name)] depending on your settings.")
- else if(selected.escapable == TRUE) //Never escapable.
- selected.escapable = FALSE
- to_chat(usr,"Prey will not be able to have special interactions with your [lowertext(selected.name)].")
- show_interacts = FALSE //Force the hiding of the panel
- else
- alert("Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.","Error") //If they somehow have a varable that's not 0 or 1
- selected.escapable = FALSE
- show_interacts = FALSE //Force the hiding of the panel
-
- if(href_list["b_escapechance"])
- var/escape_chance_input = input(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance") as num|null
- if(!isnull(escape_chance_input)) //These have to be 'null' because both cancel and 0 are valid, separate options
- selected.escapechance = sanitize_integer(escape_chance_input, 0, 100, initial(selected.escapechance))
-
- if(href_list["b_escapetime"])
- var/escape_time_input = input(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time") as num|null
- if(!isnull(escape_time_input))
- selected.escapetime = sanitize_integer(escape_time_input*10, 10, 600, initial(selected.escapetime))
-
- if(href_list["b_transferchance"])
- var/transfer_chance_input = input(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null
- if(!isnull(transfer_chance_input))
- selected.transferchance = sanitize_integer(transfer_chance_input, 0, 100, initial(selected.transferchance))
-
- if(href_list["b_transferlocation"])
- var/obj/belly/choice = input("Where do you want your [lowertext(selected.name)] to lead if prey resists?","Select Belly") as null|anything in (user.vore_organs + "None - Remove" - selected)
-
- if(!choice) //They cancelled, no changes
- return FALSE
- else if(choice == "None - Remove")
- selected.transferlocation = null
- else
- selected.transferlocation = choice.name
-
- if(href_list["b_absorbchance"])
- var/absorb_chance_input = input(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance") as num|null
- if(!isnull(absorb_chance_input))
- selected.absorbchance = sanitize_integer(absorb_chance_input, 0, 100, initial(selected.absorbchance))
-
- if(href_list["b_digestchance"])
- var/digest_chance_input = input(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance") as num|null
- if(!isnull(digest_chance_input))
- selected.digestchance = sanitize_integer(digest_chance_input, 0, 100, initial(selected.digestchance))
-
- if(href_list["b_del"])
- var/alert = alert("Are you sure you want to delete your [lowertext(selected.name)]?","Confirmation","Delete","Cancel")
- if(!alert == "Delete")
- return FALSE
-
- var/failure_msg = ""
-
- var/dest_for //Check to see if it's the destination of another vore organ.
- for(var/belly in user.vore_organs)
- var/obj/belly/B = belly
- if(B.transferlocation == selected)
- dest_for = B.name
- failure_msg += "This is the destiantion for at least '[dest_for]' belly transfers. Remove it as the destination from any bellies before deleting it. "
- break
-
- if(selected.contents.len)
- failure_msg += "You cannot delete bellies with contents! " //These end with spaces, to be nice looking. Make sure you do the same.
- if(selected.immutable)
- failure_msg += "This belly is marked as undeletable. "
- if(user.vore_organs.len == 1)
- failure_msg += "You must have at least one belly. "
-
- if(failure_msg)
- alert(user,failure_msg,"Error!")
- return FALSE
-
- qdel(selected)
- selected = user.vore_organs[1]
- user.vore_selected = user.vore_organs[1]
-
- if(href_list["saveprefs"])
- if(!(user.client?.prefs))
- return FALSE
- if(!user.copy_to_prefs_vr() || !user.client.prefs.save_character())
- to_chat(user, "Belly Preferences not saved!")
- log_admin("Could not save vore prefs on USER: [user].")
- else
- to_chat(user, "Belly Preferences were saved!")
-
- if(href_list["applyprefs"])
- var/alert = alert("Are you sure you want to reload the current slot preferences? This will remove your current vore organs and eject their contents.","Confirmation","Reload","Cancel")
- if(!alert == "Reload")
- return FALSE
- if(!user.copy_from_prefs_vr())
- alert("ERROR: Vore preferences failed to apply!","Error")
- else
- to_chat(user,"Vore preferences applied from active slot!")
-
- if(href_list["setflavor"])
- var/new_flavor = html_encode(input(usr,"What your character tastes like (40ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",user.vore_taste) as text|null)
- if(!new_flavor)
- return FALSE
-
- new_flavor = readd_quotes(new_flavor)
- if(length(new_flavor) > MAX_TASTE_LEN)
- alert("Entered flavor/taste text too long. [MAX_TASTE_LEN] character limit.","Error!")
- return FALSE
- user.vore_taste = new_flavor
-
- if(href_list["toggledg"])
- var/choice = alert(user, "This button is for those who don't like being digested. It can make you undigestable to all mobs. Digesting you is currently: [(user.vore_flags & DIGESTABLE) ? "Allowed" : "Prevented"]", "", "Allow Digestion", "Cancel", "Prevent Digestion")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ if("newbelly")
+ if(host.vore_organs.len >= BELLIES_MAX)
return FALSE
- if("Allow Digestion")
- user.vore_flags |= DIGESTABLE
- user.client.prefs.vore_flags |= DIGESTABLE
- if("Prevent Digestion")
- user.vore_flags &= ~DIGESTABLE
- user.client.prefs.vore_flags &= ~DIGESTABLE
- if(href_list["toggledvor"])
- var/choice = alert(user, "This button is for those who don't like vore at all. Devouring you is currently: [(user.vore_flags & DEVOURABLE) ? "Allowed" : "Prevented"]", "", "Allow Devourment", "Cancel", "Prevent Devourment")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ var/new_name = html_encode(input(usr,"New belly's name:","New Belly") as text|null)
+
+ var/failure_msg
+ if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
+ failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
+ // else if(whatever) //Next test here.
+ else
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ if(lowertext(new_name) == lowertext(B.name))
+ failure_msg = "No duplicate belly names, please."
+ break
+
+ if(failure_msg) //Something went wrong.
+ tgui_alert_async(usr, failure_msg, "Error!")
+ return TRUE
+
+ var/obj/belly/NB = new(host)
+ NB.name = new_name
+ host.vore_selected = NB
+ unsaved_changes = TRUE
+ return TRUE
+
+ if("bellypick")
+ host.vore_selected = locate(params["bellypick"])
+ return TRUE
+ if("move_belly")
+ var/dir = text2num(params["dir"])
+ if(LAZYLEN(host.vore_organs) <= 1)
+ to_chat(usr, "You can't sort bellies with only one belly to sort...")
+ return TRUE
+
+ var/current_index = host.vore_organs.Find(host.vore_selected)
+ if(current_index)
+ var/new_index = clamp(current_index + dir, 1, LAZYLEN(host.vore_organs))
+ host.vore_organs.Swap(current_index, new_index)
+ unsaved_changes = TRUE
+ return TRUE
+
+ if("set_attribute")
+ return set_attr(usr, params)
+
+ if("saveprefs")
+ if(!host.copy_to_prefs_vr())
+ tgui_alert_async(usr, "Belly Preferences not saved!", "Error")
+ log_admin("Could not save vore prefs on USER: [usr].")
+ else
+ to_chat(usr, "Belly Preferences were saved!")
+ unsaved_changes = FALSE
+ return TRUE
+ if("reloadprefs")
+ var/alert = tgui_alert(usr, "Are you sure you want to reload character slot preferences? This will remove your current vore organs and eject their contents.","Confirmation",list("Reload","Cancel"))
+ if(alert != "Reload")
return FALSE
- if("Allow Devourment")
- user.vore_flags |= DEVOURABLE
- user.client.prefs.vore_flags |= DEVOURABLE
- if("Prevent Devourment")
- user.vore_flags &= ~DEVOURABLE
- user.client.prefs.vore_flags &= ~DEVOURABLE
-
- if(href_list["toggledfeed"])
- var/choice = alert(user, "This button is to toggle your ability to be fed to others. Feeding predators is currently: [(user.vore_flags & FEEDING) ? "Allowed" : "Prevented"]", "", "Allow Feeding", "Cancel", "Prevent Feeding")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ if(!host.copy_from_prefs_vr())
+ tgui_alert_async(usr, "ERROR: Virgo-specific preferences failed to apply!","Error")
+ else
+ to_chat(usr, "Vore preferences applied from active slot!")
+ unsaved_changes = FALSE
+ return TRUE
+ if("setflavor")
+ var/new_flavor = html_encode(input(usr,"What your character tastes like (400ch limit). This text will be printed to the pred after 'X tastes of...' so just put something like 'strawberries and cream':","Character Flavor",host.vore_taste) as text|null)
+ if(!new_flavor)
return FALSE
- if("Allow Feeding")
- user.vore_flags |= FEEDING
- user.client.prefs.vore_flags |= FEEDING
- if("Prevent Feeding")
- user.vore_flags &= ~FEEDING
- user.client.prefs.vore_flags &= ~FEEDING
- if(href_list["toggledlickable"])
- var/choice = alert(user, "This button is to toggle your ability to be licked. Being licked is currently: [(user.client.prefs.vore_flags & LICKABLE) ? "Allowed" : "Prevented"]", "", "Allow Licking", "Cancel", "Prevent Licking")
- if(!user || !user.client)
- return
- switch(choice)
- if("Cancel")
+ new_flavor = readd_quotes(new_flavor)
+ if(length(new_flavor) > FLAVOR_MAX)
+ tgui_alert_async(usr, "Entered flavor/taste text too long. [FLAVOR_MAX] character limit.","Error!")
+ return FALSE
+ host.vore_taste = new_flavor
+ unsaved_changes = TRUE
+ return TRUE
+ if("setsmell")
+ var/new_smell = html_encode(input(usr,"What your character smells like (400ch limit). This text will be printed to the pred after 'X smells of...' so just put something like 'strawberries and cream':","Character Smell",host.vore_smell) as text|null)
+ if(!new_smell)
return FALSE
- if("Allow Licking")
- user.client.prefs.vore_flags |= LICKABLE
- if("Prevent Licking")
- user.client.prefs.vore_flags &= ~LICKABLE
- //Refresh when interacted with, returning 1 makes vore_look.Topic update
- return TRUE
+ new_smell = readd_quotes(new_smell)
+ if(length(new_smell) > FLAVOR_MAX)
+ tgui_alert_async(usr, "Entered perfume/smell text too long. [FLAVOR_MAX] character limit.","Error!")
+ return FALSE
+ host.vore_smell = new_smell
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_digest")
+ TOGGLE_BITFIELD(host.vore_flags, DIGESTABLE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, DIGESTABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_devour")
+ TOGGLE_BITFIELD(host.vore_flags, DEVOURABLE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, DEVOURABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_feed")
+ TOGGLE_BITFIELD(host.vore_flags, FEEDING)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, FEEDING)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_absorbable")
+ TOGGLE_BITFIELD(host.vore_flags, ABSORBABLE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, ABSORBABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_mobvore")
+ TOGGLE_BITFIELD(host.vore_flags, MOBVORE)
+ if(host.client.prefs)
+ COPY_SPECIFIC_BITFIELDS(host.client.prefs.vore_flags, host.vore_flags, MOBVORE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_vore_sounds")
+ TOGGLE_BITFIELD(host.client.prefs.cit_toggles, EATING_NOISES)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_digestion_sounds")
+ TOGGLE_BITFIELD(host.client.prefs.cit_toggles, DIGESTION_NOISES)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_lickable")
+ TOGGLE_BITFIELD(host.vore_flags, LICKABLE)
+ unsaved_changes = TRUE
+ return TRUE
+ if("toggle_smellable")
+ TOGGLE_BITFIELD(host.vore_flags, SMELLABLE)
+ unsaved_changes = TRUE
+ return TRUE
+
+/datum/vore_look/proc/pick_from_inside(mob/user, params)
+ var/atom/movable/target = locate(params["pick"])
+ var/obj/belly/OB = locate(params["belly"])
+
+ if(!(target in OB))
+ return TRUE // Aren't here anymore, need to update menu
+
+ var/intent = "Examine"
+ if(isliving(target))
+ intent = tgui_alert(usr, "What do you want to do to them?","Query",list("Examine","Help Out","Devour"))
+
+ else if(istype(target, /obj/item))
+ intent = tgui_alert(usr, "What do you want to do to that?","Query",list("Examine","Use Hand"))
+
+ switch(intent)
+ if("Examine") //Examine a mob inside another mob
+ var/list/results = target.examine(host)
+ if(!results || !results.len)
+ results = list("You were unable to examine that. Tell a developer!")
+ to_chat(user, jointext(results, " "))
+ return TRUE
+
+ if("Use Hand")
+ if(host.stat)
+ to_chat(user, "You can't do that in your state!")
+ return TRUE
+
+ host.ClickOn(target)
+ return TRUE
+
+ if(!isliving(target))
+ return
+
+ var/mob/living/M = target
+ switch(intent)
+ if("Help Out") //Help the inside-mob out
+ if(host.stat || host.vore_flags & ABSORBED || M.vore_flags & ABSORBED)
+ to_chat(user, "You can't do that in your state!")
+ return TRUE
+
+ to_chat(user,"You begin to push [M] to freedom!")
+ to_chat(M,"[host] begins to push you to freedom!")
+ to_chat(M.loc,"Someone is trying to escape from inside you!")
+ sleep(50)
+ if(prob(33))
+ OB.release_specific_contents(M)
+ to_chat(user,"You manage to help [M] to safety!")
+ to_chat(M,"[host] pushes you free!")
+ to_chat(OB.owner,"[M] forces free of the confines of your body!")
+ else
+ to_chat(user,"[M] slips back down inside despite your efforts.")
+ to_chat(M," Even with [host]'s help, you slip back inside again.")
+ to_chat(OB.owner,"Your body efficiently shoves [M] back where they belong.")
+ return TRUE
+
+ if("Devour") //Eat the inside mob
+ if(host.stat || host.vore_flags & ABSORBED)
+ to_chat(user,"You can't do that in your state!")
+ return TRUE
+
+ if(!host.vore_selected)
+ to_chat(user,"Pick a belly on yourself first!")
+ return TRUE
+
+ var/obj/belly/TB = host.vore_selected
+ to_chat(user,"You begin to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!")
+ to_chat(M,"[host] begins to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!")
+ to_chat(OB.owner,"Someone inside you is eating someone else!")
+
+ sleep(TB.nonhuman_prey_swallow_time) //Can't do after, in a stomach, weird things abound.
+ if((host in OB) && (M in OB)) //Make sure they're still here.
+ to_chat(user,"You manage to [lowertext(TB.vore_verb)] [M] into your [lowertext(TB.name)]!")
+ to_chat(M,"[host] manages to [lowertext(TB.vore_verb)] you into their [lowertext(TB.name)]!")
+ to_chat(OB.owner,"Someone inside you has eaten someone else!")
+ TB.nom_mob(M)
+
+/datum/vore_look/proc/pick_from_outside(mob/user, params)
+ var/intent
+
+ //Handle the [All] choice. Ugh inelegant. Someone make this pretty.
+ if(params["pickall"])
+ intent = tgui_alert(usr, "Eject all, Move all?","Query",list("Eject all","Cancel","Move all"))
+ switch(intent)
+ if("Cancel")
+ return TRUE
+
+ if("Eject all")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state!")
+ return TRUE
+
+ host.vore_selected.release_all_contents()
+ return TRUE
+
+ if("Move all")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state!")
+ return TRUE
+
+ var/obj/belly/choice = tgui_input_list(usr, "Move all where?","Select Belly", host.vore_organs)
+ if(!choice)
+ return FALSE
+
+ for(var/atom/movable/target in host.vore_selected)
+ to_chat(target,"You're squished from [host]'s [lowertext(host.vore_selected)] to their [lowertext(choice.name)]!")
+ host.vore_selected.transfer_contents(target, choice, 1)
+ return TRUE
+ return
+
+ var/atom/movable/target = locate(params["pick"])
+ if(!(target in host.vore_selected))
+ return TRUE // Not in our X anymore, update UI
+ var/list/available_options = list("Examine", "Eject", "Move")
+ if(ishuman(target))
+ available_options += "Transform"
+ intent = tgui_alert(user, "What would you like to do with [target]?", "Vore Pick", available_options)
+ switch(intent)
+ if("Examine")
+ var/list/results = target.examine(host)
+ if(!results || !results.len)
+ results = list("You were unable to examine that. Tell a developer!")
+ to_chat(user, jointext(results, " "))
+ return TRUE
+
+ if("Eject")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state!")
+ return TRUE
+
+ host.vore_selected.release_specific_contents(target)
+ return TRUE
+
+ if("Move")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state!")
+ return TRUE
+
+ var/obj/belly/choice = tgui_input_list(usr, "Move [target] where?","Select Belly", host.vore_organs)
+ if(!choice || !(target in host.vore_selected))
+ return TRUE
+
+ to_chat(target,"You're squished from [host]'s [lowertext(host.vore_selected.name)] to their [lowertext(choice.name)]!")
+ host.vore_selected.transfer_contents(target, choice)
+ return TRUE
+
+ if("Transform")
+ if(host.stat)
+ to_chat(user,"You can't do that in your state!")
+ return TRUE
+
+ var/mob/living/carbon/human/H = target
+ if(!istype(H))
+ return
+
+ return TRUE
+
+/datum/vore_look/proc/set_attr(mob/user, params)
+ if(!host.vore_selected)
+ tgui_alert_async(usr, "No belly selected to modify.")
+ return FALSE
+
+ var/attr = params["attribute"]
+ switch(attr)
+ if("b_name")
+ var/new_name = html_encode(input(usr,"Belly's new name:","New Name") as text|null)
+
+ var/failure_msg
+ if(length(new_name) > BELLIES_NAME_MAX || length(new_name) < BELLIES_NAME_MIN)
+ failure_msg = "Entered belly name length invalid (must be longer than [BELLIES_NAME_MIN], no more than than [BELLIES_NAME_MAX])."
+ // else if(whatever) //Next test here.
+ else
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ if(lowertext(new_name) == lowertext(B.name))
+ failure_msg = "No duplicate belly names, please."
+ break
+
+ if(failure_msg) //Something went wrong.
+ tgui_alert_async(user,failure_msg,"Error!")
+ return FALSE
+
+ host.vore_selected.name = new_name
+ . = TRUE
+ if("b_wetness")
+ host.vore_selected.is_wet = !host.vore_selected.is_wet
+ . = TRUE
+ if("b_wetloop")
+ host.vore_selected.wet_loop = !host.vore_selected.wet_loop
+ . = TRUE
+ if("b_mode")
+ var/list/menu_list = host.vore_selected.digest_modes.Copy()
+ var/new_mode = tgui_input_list(usr, "Choose Mode (currently [host.vore_selected.digest_mode])", "Mode Choice", menu_list)
+ if(!new_mode)
+ return FALSE
+
+ host.vore_selected.digest_mode = new_mode
+ . = TRUE
+ if("b_desc")
+ var/new_desc = html_encode(input(usr,"Belly Description ([BELLIES_DESC_MAX] char limit):","New Description",host.vore_selected.desc) as message|null)
+
+ if(new_desc)
+ new_desc = readd_quotes(new_desc)
+ if(length(new_desc) > BELLIES_DESC_MAX)
+ tgui_alert_async(usr, "Entered belly desc too long. [BELLIES_DESC_MAX] character limit.","Error")
+ return FALSE
+ host.vore_selected.desc = new_desc
+ . = TRUE
+ if("b_msgs")
+ tgui_alert(user,"Setting abusive or deceptive messages will result in a ban. Consider this your warning. Max 150 characters per message (500 for idle messages), max 10 messages per topic.","Really, don't.") // Should remain tgui_alert() (blocking)
+ var/help = " Press enter twice to separate messages. '%pred' will be replaced with your name. '%prey' will be replaced with the prey's name. '%belly' will be replaced with your belly's name. '%count' will be replaced with the number of anything in your belly (will not work for absorbed examine). '%countprey' will be replaced with the number of living prey in your belly (or absorbed prey for absorbed examine)."
+ switch(params["msgtype"])
+ if("dmp")
+ var/new_message = input(user,"These are sent to prey when they expire. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Digest Message (to prey)",host.vore_selected.get_messages("dmp")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"dmp")
+
+ if("dmo")
+ var/new_message = input(user,"These are sent to you when prey expires in you. Write them in 2nd person ('you feel X'). Avoid using %pred in this type."+help,"Digest Message (to you)",host.vore_selected.get_messages("dmo")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"dmo")
+
+ if("smo")
+ var/new_message = input(user,"These are sent to those nearby when prey struggles. Write them in 3rd person ('X's Y bulges')."+help,"Struggle Message (outside)",host.vore_selected.get_messages("smo")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"smo")
+
+ if("smi")
+ var/new_message = input(user,"These are sent to prey when they struggle. Write them in 2nd person ('you feel X'). Avoid using %prey in this type."+help,"Struggle Message (inside)",host.vore_selected.get_messages("smi")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"smi")
+
+ if("em")
+ var/new_message = input(user,"These are sent to people who examine you when this belly has contents. Write them in 3rd person ('Their %belly is bulging')."+help,"Examine Message (when full)",host.vore_selected.get_messages("em")) as message
+ if(new_message)
+ host.vore_selected.set_messages(new_message,"em")
+
+ if("reset")
+ var/confirm = tgui_alert(user,"This will delete any custom messages. Are you sure?","Confirmation",list("Cancel","DELETE"))
+ if(confirm == "DELETE")
+ host.vore_selected.digest_messages_prey = initial(host.vore_selected.digest_messages_prey)
+ host.vore_selected.digest_messages_owner = initial(host.vore_selected.digest_messages_owner)
+ host.vore_selected.struggle_messages_outside = initial(host.vore_selected.struggle_messages_outside)
+ host.vore_selected.struggle_messages_inside = initial(host.vore_selected.struggle_messages_inside)
+ host.vore_selected.examine_messages = initial(host.vore_selected.examine_messages)
+ host.vore_selected.emote_lists = initial(host.vore_selected.emote_lists)
+ . = TRUE
+ if("b_verb")
+ var/new_verb = html_encode(input(usr,"New verb when eating (infinitive tense, e.g. nom or swallow):","New Verb") as text|null)
+
+ if(length(new_verb) > BELLIES_NAME_MAX || length(new_verb) < BELLIES_NAME_MIN)
+ tgui_alert_async(usr, "Entered verb length invalid (must be longer than [BELLIES_NAME_MIN], no longer than [BELLIES_NAME_MAX]).","Error")
+ return FALSE
+
+ host.vore_selected.vore_verb = new_verb
+ . = TRUE
+ if("b_release")
+ var/choice = tgui_input_list(user,"Currently set to [host.vore_selected.release_sound]","Select Sound", GLOB.pred_release_sounds)
+
+ if(!choice)
+ return FALSE
+
+ host.vore_selected.release_sound = choice
+ . = TRUE
+ if("b_releasesoundtest")
+ var/sound/releasetest = GLOB.pred_release_sounds[host.vore_selected.release_sound]
+
+ if(releasetest)
+ SEND_SOUND(user, releasetest)
+ . = FALSE //Testing sound, no changes.
+ if("b_sound")
+ var/choice = tgui_input_list(user,"Currently set to [host.vore_selected.vore_sound]","Select Sound", GLOB.prey_vore_sounds)
+
+ if(!choice)
+ return FALSE
+
+ host.vore_selected.vore_sound = choice
+ . = TRUE
+ if("b_soundtest")
+ var/sound/voretest = GLOB.prey_vore_sounds[host.vore_selected.vore_sound]
+ if(voretest)
+ SEND_SOUND(user, voretest)
+ . = FALSE //Testing sound, no changes.
+ if("b_tastes")
+ host.vore_selected.can_taste = !host.vore_selected.can_taste
+ . = TRUE
+ if("b_bulge_size")
+ var/new_bulge = input(user, "Choose the required size prey must be to show up on examine, ranging from 25% to 200% Set this to 0 for no text on examine.", "Set Belly Examine Size.") as num|null
+ if(new_bulge == null)
+ return FALSE
+ if(new_bulge == 0) //Disable.
+ host.vore_selected.bulge_size = 0
+ to_chat(user,"Your stomach will not be seen on examine.")
+ else if (!ISINRANGE(new_bulge,25,200))
+ host.vore_selected.bulge_size = 0.25 //Set it to the default.
+ to_chat(user,"Invalid size.")
+ else if(new_bulge)
+ host.vore_selected.bulge_size = (new_bulge/100)
+ . = TRUE
+ if("b_escapable")
+ if(host.vore_selected.escapable == 0) //Possibly escapable and special interactions.
+ host.vore_selected.escapable = 1
+ to_chat(usr,"Prey now have special interactions with your [lowertext(host.vore_selected.name)] depending on your settings.")
+ else if(host.vore_selected.escapable == 1) //Never escapable.
+ host.vore_selected.escapable = 0
+ to_chat(usr,"Prey will not be able to have special interactions with your [lowertext(host.vore_selected.name)].")
+ else
+ tgui_alert_async(usr, "Something went wrong. Your stomach will now not have special interactions. Press the button enable them again and tell a dev.","Error") //If they somehow have a varable that's not 0 or 1
+ host.vore_selected.escapable = 0
+ . = TRUE
+ if("b_escapechance")
+ var/escape_chance_input = input(user, "Set prey escape chance on resist (as %)", "Prey Escape Chance") as num|null
+ if(!isnull(escape_chance_input)) //These have to be 'null' because both cancel and 0 are valid, separate options
+ host.vore_selected.escapechance = sanitize_integer(escape_chance_input, 0, 100, initial(host.vore_selected.escapechance))
+ . = TRUE
+ if("b_escapetime")
+ var/escape_time_input = input(user, "Set number of seconds for prey to escape on resist (1-60)", "Prey Escape Time") as num|null
+ if(!isnull(escape_time_input))
+ host.vore_selected.escapetime = sanitize_integer(escape_time_input*10, 10, 600, initial(host.vore_selected.escapetime))
+ . = TRUE
+ if("b_transferchance")
+ var/transfer_chance_input = input(user, "Set belly transfer chance on resist (as %). You must also set the location for this to have any effect.", "Prey Escape Time") as num|null
+ if(!isnull(transfer_chance_input))
+ host.vore_selected.transferchance = sanitize_integer(transfer_chance_input, 0, 100, initial(host.vore_selected.transferchance))
+ . = TRUE
+ if("b_transferlocation")
+ var/obj/belly/choice = tgui_input_list(usr, "Where do you want your [lowertext(host.vore_selected.name)] to lead if prey resists?","Select Belly", (host.vore_organs + "None - Remove" - host.vore_selected))
+
+ if(!choice) //They cancelled, no changes
+ return FALSE
+ else if(choice == "None - Remove")
+ host.vore_selected.transferlocation = null
+ else
+ host.vore_selected.transferlocation = choice.name
+ . = TRUE
+ if("b_absorbchance")
+ var/absorb_chance_input = input(user, "Set belly absorb mode chance on resist (as %)", "Prey Absorb Chance") as num|null
+ if(!isnull(absorb_chance_input))
+ host.vore_selected.absorbchance = sanitize_integer(absorb_chance_input, 0, 100, initial(host.vore_selected.absorbchance))
+ . = TRUE
+ if("b_digestchance")
+ var/digest_chance_input = input(user, "Set belly digest mode chance on resist (as %)", "Prey Digest Chance") as num|null
+ if(!isnull(digest_chance_input))
+ host.vore_selected.digestchance = sanitize_integer(digest_chance_input, 0, 100, initial(host.vore_selected.digestchance))
+ . = TRUE
+ if("b_del")
+ var/alert = tgui_alert(usr, "Are you sure you want to delete your [lowertext(host.vore_selected.name)]?","Confirmation",list("Cancel","Delete"))
+ if(!(alert == "Delete"))
+ return FALSE
+
+ var/failure_msg = ""
+
+ var/dest_for //Check to see if it's the destination of another vore organ.
+ for(var/belly in host.vore_organs)
+ var/obj/belly/B = belly
+ if(B.transferlocation == host.vore_selected)
+ dest_for = B.name
+ failure_msg += "This is the destiantion for at least '[dest_for]' belly transfers. Remove it as the destination from any bellies before deleting it. "
+ break
+
+ if(host.vore_selected.contents.len)
+ failure_msg += "You cannot delete bellies with contents! " //These end with spaces, to be nice looking. Make sure you do the same.
+ if(host.vore_selected.immutable)
+ failure_msg += "This belly is marked as undeletable. "
+ if(host.vore_organs.len == 1)
+ failure_msg += "You must have at least one belly. "
+
+ if(failure_msg)
+ tgui_alert_async(user,failure_msg,"Error!")
+ return FALSE
+
+ qdel(host.vore_selected)
+ host.vore_selected = host.vore_organs[1]
+ . = TRUE
+
+ if(.)
+ unsaved_changes = TRUE
diff --git a/html/changelog.html b/html/changelog.html
index 00874e649c..80b12a5df9 100644
--- a/html/changelog.html
+++ b/html/changelog.html
@@ -50,6 +50,44 @@
-->
+
19 July 2021
+
Arturlang updated:
+
+
The crafting button should no longer silently make more copies of itself on reconects
+
There are no longer two copies of the crafting component.
+
There is no longer a rogue d in tracer.dm tweak: Everything in Misc was moved to Miscelanious in the crafting menu, and Misc was nuked from orbit. Nobody will miss you.
+
+
MrJWhit updated:
+
+
Fixes a memory leak, there's a good chance that it's the same one that killed kilo.
+
+
SandPoot updated:
+
+
Put back mob vore with a pref.
+
Taken some stuff from tg for tgui_alert.
+
Refactored a lot of code on vore panel.
+
+
WanderingFox95 updated:
+
+
custom plasteel kegs
+
+
YakumoChen updated:
+
+
You can now wear the suffering of others on your head with just a sheet of human skin!
+
Human skin hats
+
+
keronshb updated:
+
+
Stripping/equipping things to a conscious braindead person (AKA a human owned by a disconnected player) will now give them a message with your visible name and roughly how long ago you touched their stuff when they login again. Touching someone's pockets or adjusting their gear other than equipping/unequipping is not logged. After 5 minutes, you'll have forgotten both their name and exactly how long ago past those 5 minutes it happened. (Credit to Ryll-Ryll)
+
LAZYNULL
+
Breaking mirrors now gives you a bad omen
+
+
timothyteakettle updated:
+
+
anthros can now select the cow tail
+
new quirk that allows you to eat trash
+
+
18 July 2021
timothyteakettle updated:
diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml
index c1aaca7a0b..bf9e88d857 100644
--- a/html/changelogs/.all_changelog.yml
+++ b/html/changelogs/.all_changelog.yml
@@ -30305,3 +30305,36 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py.
timothyteakettle:
- bugfix: fixes photosynthesis stopping nutrition going past well fed from non-photosynthesis
means
+2021-07-19:
+ Arturlang:
+ - bugfix: The crafting button should no longer silently make more copies of itself
+ on reconects
+ - code_imp: There are no longer two copies of the crafting component.
+ - code_imp: 'There is no longer a rogue d in tracer.dm tweak: Everything in Misc
+ was moved to Miscelanious in the crafting menu, and Misc was nuked from orbit.
+ Nobody will miss you.'
+ MrJWhit:
+ - bugfix: Fixes a memory leak, there's a good chance that it's the same one that
+ killed kilo.
+ SandPoot:
+ - rscadd: Put back mob vore with a pref.
+ - code_imp: Taken some stuff from tg for tgui_alert.
+ - refactor: Refactored a lot of code on vore panel.
+ WanderingFox95:
+ - rscadd: custom plasteel kegs
+ YakumoChen:
+ - rscadd: You can now wear the suffering of others on your head with just a sheet
+ of human skin!
+ - imageadd: Human skin hats
+ keronshb:
+ - rscadd: Stripping/equipping things to a conscious braindead person (AKA a human
+ owned by a disconnected player) will now give them a message with your visible
+ name and roughly how long ago you touched their stuff when they login again.
+ Touching someone's pockets or adjusting their gear other than equipping/unequipping
+ is not logged. After 5 minutes, you'll have forgotten both their name and exactly
+ how long ago past those 5 minutes it happened. (Credit to Ryll-Ryll)
+ - rscadd: LAZYNULL
+ - rscadd: Breaking mirrors now gives you a bad omen
+ timothyteakettle:
+ - bugfix: anthros can now select the cow tail
+ - rscadd: new quirk that allows you to eat trash
diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi
index 806f378323..51675c8d7d 100644
Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index e5bd686603..854928f2ca 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/objects.dmi b/icons/obj/objects.dmi
index b9c891ddb9..6d29b8df8e 100644
Binary files a/icons/obj/objects.dmi and b/icons/obj/objects.dmi differ
diff --git a/modular_sand/code/datums/components/crafting/recipes/recipes_misc.dm b/modular_sand/code/datums/components/crafting/recipes/recipes_misc.dm
index 5623b6da55..8710b396e9 100644
--- a/modular_sand/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/modular_sand/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -9,7 +9,7 @@
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_WELDER)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/protolathe
name = "Protolathe Board"
@@ -21,7 +21,7 @@
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_WELDER)
time = 20
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/circuit_imprinter
name = "Circuit Imprinter"
@@ -33,7 +33,7 @@
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_WELDER)
time = 20
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/autolathe
name = "Autolathe Board"
@@ -44,7 +44,7 @@
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_WELDER)
time = 20
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/crowbar
name = "Makeshift Crowbar"
@@ -52,7 +52,7 @@
reqs = list(/obj/item/stack/rods = 1)
time = 40
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/screwdriver
name = "Makeshift Screwdriver"
@@ -61,7 +61,7 @@
tools = list(TOOL_CROWBAR)
time = 20
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/wirecutters
name = "Wirecutters"
@@ -71,7 +71,7 @@
tools = list(TOOL_CROWBAR, TOOL_SCREWDRIVER)
time = 50
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/wrench
name = "Wrench"
@@ -81,7 +81,7 @@
tools = list(TOOL_CROWBAR, TOOL_SCREWDRIVER)
time = 30
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/emergency_welder
name = "Makeshift Welder"
@@ -93,7 +93,7 @@
tools = list(TOOL_CROWBAR, TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
time = 30
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/toolbox
name = "Toolbox"
@@ -104,7 +104,7 @@
tools = list(TOOL_CROWBAR, TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_WELDER)
time = 70
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/bin
name = "Makeshift Matter Bin"
@@ -113,7 +113,7 @@
tools = list(TOOL_WIRECUTTER, TOOL_WELDER)
time = 70
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/makeshift_manipulator
name = "Makeshift Manipulator"
@@ -123,7 +123,7 @@
tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER, TOOL_WELDER)
time = 70
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/slime_core
name = "Slime Core"
@@ -133,4 +133,4 @@
tools = list(TOOL_WELDER)
time = 100
subcategory = CAT_TOOL
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
diff --git a/modular_sand/code/modules/crafting/recipes/recipes_misc.dm b/modular_sand/code/modules/crafting/recipes/recipes_misc.dm
index 44274af012..c914ffd674 100644
--- a/modular_sand/code/modules/crafting/recipes/recipes_misc.dm
+++ b/modular_sand/code/modules/crafting/recipes/recipes_misc.dm
@@ -8,7 +8,7 @@
result = /obj/machinery/shower
tools = list(TOOL_WELDER)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/a357ammospeed
name = "Speed loader (.357)"
@@ -26,7 +26,7 @@
result = /obj/item/tank/internals/emergency_oxygen/double/empty
tools = list(TOOL_WELDER)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
/datum/crafting_recipe/case_cosmos
name = "Cosmos Compartment Case"
@@ -35,4 +35,4 @@
result = /obj/item/storage/backpack/case/cosmos
tools = list(TOOL_SCREWDRIVER)
subcategory = CAT_MISCELLANEOUS
- category = CAT_MISC
+ category = CAT_MISCELLANEOUS
diff --git a/tgstation.dme b/tgstation.dme
index d28e66c9d0..2198645134 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -644,6 +644,7 @@
#include "code\datums\elements\swimming.dm"
#include "code\datums\elements\sword_point.dm"
#include "code\datums\elements\tactical.dm"
+#include "code\datums\elements\trash.dm"
#include "code\datums\elements\turf_transparency.dm"
#include "code\datums\elements\update_icon_blocker.dm"
#include "code\datums\elements\update_icon_updates_onmob.dm"
@@ -3595,6 +3596,7 @@
#include "code\modules\tgui\states\observer.dm"
#include "code\modules\tgui\states\physical.dm"
#include "code\modules\tgui\states\self.dm"
+#include "code\modules\tgui\states\vorepanel.dm"
#include "code\modules\tgui\states\zlevel.dm"
#include "code\modules\tgui_panel\audio.dm"
#include "code\modules\tgui_panel\external.dm"
diff --git a/tgui/packages/tgui/interfaces/VorePanel.js b/tgui/packages/tgui/interfaces/VorePanel.js
new file mode 100644
index 0000000000..3e29c8bff4
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/VorePanel.js
@@ -0,0 +1,591 @@
+/* eslint-disable max-len */
+import { Fragment } from 'inferno';
+import { useBackend, useLocalState } from "../backend";
+import { Box, Button, Flex, Collapsible, Icon, LabeledList, NoticeBox, Section, Tabs } from "../components";
+import { Window } from "../layouts";
+
+const stats = [
+ null,
+ 'average',
+ 'bad',
+];
+
+const digestModeToColor = {
+ "Hold": null,
+ "Dragon": "blue",
+ "Digest": "red",
+ "Absorb": "purple",
+ "Unabsorb": "purple",
+};
+
+const digestModeToPreyMode = {
+ "Hold": "being held.",
+ "Digest": "being digested.",
+ "Absorb": "being absorbed.",
+ "Unabsorb": "being unabsorbed.",
+ "Dragon": "being digested by a powerful creature.",
+};
+
+/**
+ * There are three main sections to this UI.
+ * - The Inside Panel, where all relevant data for interacting with a belly you're in is located.
+ * - The Belly Selection Panel, where you can select what belly people will go into and customize the active one.
+ * - User Preferences, where you can adjust all of your vore preferences on the fly.
+ */
+export const VorePanel = (props, context) => {
+ const { act, data } = useBackend(context);
+ return (
+
+
+ {data.unsaved_changes && (
+
+
+
+ Warning: Unsaved Changes!
+
+
+
+
+
+ ) || null}
+
+
+
+
+
+ );
+};
+
+const VoreInsidePanel = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ absorbed,
+ belly_name,
+ belly_mode,
+ desc,
+ pred,
+ contents,
+ ref,
+ } = data.inside;
+
+ if (!belly_name) {
+ return (
+
+ You aren't inside anyone.
+
+ );
+ }
+
+ return (
+
+ You are currently {absorbed ? "absorbed into" : "inside"}
+ {pred}'s
+ {belly_name}
+ and you are
+ {digestModeToPreyMode[belly_mode]}
+
+ {desc}
+
+ {contents.length && (
+
+
+
+ ) || "There is nothing else around you."}
+
+ );
+};
+
+const VoreBellySelectionAndCustomization = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ our_bellies,
+ selected,
+ } = data;
+
+ return (
+
+
+ {our_bellies.map(belly => (
+ act("bellypick", { bellypick: belly.ref })}>
+
+ {belly.name} ({belly.contents})
+
+
+ ))}
+ act("newbelly")}>
+ New
+
+
+
+ {selected && }
+
+ );
+};
+
+/**
+ * Subtemplate of VoreBellySelectionAndCustomization
+ */
+const VoreSelectedBelly = (props, context) => {
+ const { act } = useBackend(context);
+
+ const { belly } = props;
+ const {
+ belly_name,
+ is_wet,
+ wet_loop,
+ mode,
+ verb,
+ desc,
+ sound,
+ release_sound,
+ can_taste,
+ bulge_size,
+ escapable,
+ interacts,
+ contents,
+ } = belly;
+
+ const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0);
+
+ return (
+
+
+ setTabIndex(0)}>
+ Controls
+
+ setTabIndex(1)}>
+ Options
+
+ setTabIndex(2)}>
+ Contents ({contents.length})
+
+ setTabIndex(3)}>
+ Interactions
+
+
+ {tabIndex === 0 && (
+
+
+
+ }>
+ act("set_attribute", { attribute: "b_name" })}
+ content={belly_name} />
+
+
+ act("set_attribute", { attribute: "b_mode" })}
+ content={mode} />
+
+ act("set_attribute", { attribute: "b_desc" })}
+ icon="pen" />
+ }>
+ {desc}
+
+
+ act("set_attribute", { attribute: "b_verb" })}
+ content={verb} />
+
+
+ act("set_attribute", { attribute: "b_msgs", msgtype: "dmp" })}
+ content="Digest Message (to prey)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "dmo" })}
+ content="Digest Message (to you)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "smo" })}
+ content="Struggle Message (outside)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "smi" })}
+ content="Struggle Message (inside)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "em" })}
+ content="Examine Message (when full)" />
+ act("set_attribute", { attribute: "b_msgs", msgtype: "reset" })}
+ content="Reset Messages" />
+
+
+ ) || tabIndex === 1 && (
+
+
+
+
+ act("set_attribute", { attribute: "b_tastes" })}
+ icon={can_taste ? "toggle-on" : "toggle-off"}
+ selected={can_taste}
+ content={can_taste ? "Yes" : "No"} />
+
+
+ act("set_attribute", { attribute: "b_wetness" })}
+ icon={is_wet ? "toggle-on" : "toggle-off"}
+ selected={is_wet}
+ content={is_wet ? "Yes" : "No"} />
+
+
+ act("set_attribute", { attribute: "b_wetloop" })}
+ icon={wet_loop ? "toggle-on" : "toggle-off"}
+ selected={wet_loop}
+ content={wet_loop ? "Yes" : "No"} />
+
+
+
+
+
+
+ act("set_attribute", { attribute: "b_sound" })}
+ content={sound} />
+ act("set_attribute", { attribute: "b_soundtest" })}
+ icon="volume-up" />
+
+
+ act("set_attribute", { attribute: "b_release" })}
+ content={release_sound} />
+ act("set_attribute", { attribute: "b_releasesoundtest" })}
+ icon="volume-up" />
+
+
+ act("set_attribute", { attribute: "b_bulge_size" })}
+ content={bulge_size * 100 + "%"} />
+
+
+
+
+ act("set_attribute", { attribute: "b_del" })} />
+
+
+ ) || tabIndex === 2 && (
+
+ ) || tabIndex === 3 && (
+ act("set_attribute", { attribute: "b_escapable" })}
+ icon={escapable ? "toggle-on" : "toggle-off"}
+ selected={escapable}
+ content={escapable ? "Interactions On" : "Interactions Off"} />
+ }>
+ {escapable ? (
+
+
+ act("set_attribute", { attribute: "b_escapechance" })} />
+
+
+ act("set_attribute", { attribute: "b_escapetime" })} />
+
+
+
+ act("set_attribute", { attribute: "b_transferchance" })} />
+
+
+ act("set_attribute", { attribute: "b_transferlocation" })} />
+
+
+
+ act("set_attribute", { attribute: "b_absorbchance" })} />
+
+
+ act("set_attribute", { attribute: "b_digestchance" })} />
+
+
+ ) : "These options only display while interactions are turned on."}
+
+ ) || "Error"}
+
+ );
+};
+
+const VoreContentsPanel = (props, context) => {
+ const { act, data } = useBackend(context);
+ const {
+ show_pictures,
+ } = data;
+ const {
+ contents,
+ belly,
+ outside = false,
+ } = props;
+
+ return (
+
+ {outside && (
+ act("pick_from_outside", { "pickall": true })}>
+ All
+
+ ) || null}
+ {show_pictures && (
+
+ {contents.map(thing => (
+
+ act(thing.outside ? "pick_from_outside" : "pick_from_inside", {
+ "pick": thing.ref,
+ "belly": belly,
+ })}>
+
+
+ {thing.name}
+
+ ))}
+
+ ) || (
+
+ {contents.map(thing => (
+
+ act(thing.outside ? "pick_from_outside" : "pick_from_inside", {
+ "pick": thing.ref,
+ "belly": belly,
+ })}>
+ Interact
+
+
+ ))}
+
+ )}
+
+ );
+};
+
+const VoreUserPreferences = (props, context) => {
+ const { act, data } = useBackend(context);
+
+ const {
+ digestable,
+ devourable,
+ feeding,
+ absorbable,
+ allowmobvore,
+ vore_sounds,
+ digestion_sounds,
+ lickable,
+ smellable,
+ } = data.prefs;
+
+ const {
+ show_pictures,
+ } = data;
+
+ return (
+ act("show_pictures")}>
+ Contents Preference: {show_pictures ? "Show Pictures" : "Show List"}
+
+ }>
+
+
+ act("toggle_devour")}
+ icon={devourable ? "toggle-on" : "toggle-off"}
+ selected={devourable}
+ fluid
+ tooltip={"This button is to toggle your ability to be devoured by others. "
+ + (devourable ? "Click here to prevent being devoured." : "Click here to allow being devoured.")}
+ content={devourable ? "Devouring Allowed" : "No Devouring"} />
+
+
+ act("toggle_mobvore")}
+ icon={allowmobvore ? "toggle-on" : "toggle-off"}
+ selected={allowmobvore}
+ fluid
+ tooltip={"This button is for those who don't like being eaten by mobs. "
+ + (allowmobvore
+ ? "Click here to prevent being eaten by mobs."
+ : "Click here to allow being eaten by mobs.")}
+ tooltipPosition="bottom-right"
+ content={allowmobvore ? "Mobs eating you allowed" : "No Mobs eating you"} />
+
+
+ act("toggle_feed")}
+ icon={feeding ? "toggle-on" : "toggle-off"}
+ selected={feeding}
+ fluid
+ tooltip={"This button is to toggle your ability to be fed to or by others vorishly. "
+ + (feeding
+ ? "Click here to prevent being fed to/by other people."
+ : "Click here to allow being fed to/by other people.")}
+ content={feeding ? "Feeding Allowed" : "No Feeding"} />
+
+
+ act("toggle_digest")}
+ icon={digestable ? "toggle-on" : "toggle-off"}
+ selected={digestable}
+ fluid
+ tooltip={"This button is for those who don't like being digested. It can make you undigestable."
+ + (digestable ? " Click here to prevent digestion." : " Click here to allow digestion.")}
+ tooltipPosition="bottom-right"
+ content={digestable ? "Digestion Allowed" : "No Digestion"} />
+
+
+ act("toggle_absorbable")}
+ icon={absorbable ? "toggle-on" : "toggle-off"}
+ selected={absorbable}
+ fluid
+ tooltip={"This button allows preds to know whether you prefer or don't prefer to be absorbed. "
+ + (absorbable ? "Click here to disallow being absorbed." : "Click here to allow being absorbed.")}
+ content={absorbable ? "Absorption Allowed" : "No Absorption"} />
+
+
+ act("toggle_vore_sounds")}
+ icon={vore_sounds ? "volume-up" : "volume-mute"}
+ selected={vore_sounds}
+ fluid
+ tooltip={"Be able to hear vore sounds. "
+ + (vore_sounds
+ ? "Click here to turn off vore sounds."
+ : "Click here to turn on vore sounds.")}
+ content={vore_sounds ? "Vore Sounds Enabled" : "Vore Sounds Disabled"} />
+
+
+ act("toggle_digestion_sounds")}
+ icon={digestion_sounds ? "volume-up" : "volume-mute"}
+ selected={digestion_sounds}
+ fluid
+ tooltip={"Be able to hear digestion sounds. "
+ + (digestion_sounds
+ ? "Click here to turn off digestion sounds."
+ : "Click here to turn on digestion sounds.")}
+ content={digestion_sounds ? "Digestion Sounds Enabled" : "Digestion Sounds Disabled"} />
+
+
+ act("toggle_lickable")}
+ icon={lickable ? "toggle-on" : "toggle-off"}
+ selected={lickable}
+ fluid
+ tooltip={"Be able to be licked by others. "
+ + (lickable
+ ? "Click here to turn off being able to be licked."
+ : "Click here to turn on being able to be licked.")}
+ tooltipPosition="bottom-right"
+ content={lickable ? "Lickable" : "Unlickable"} />
+
+
+ act("setflavor")} />
+
+
+ act("toggle_smellable")}
+ icon={smellable ? "toggle-on" : "toggle-off"}
+ selected={smellable}
+ fluid
+ tooltip={"Be able to be smelled by others. "
+ + (smellable
+ ? "Click here to turn off being able to be smelled."
+ : "Click here to turn on being able to be smelled.")}
+ content={smellable ? "Smellable" : "Unsmellable"} />
+
+
+ act("setsmell")} />
+
+
+
+
+
+ act("saveprefs")} />
+
+
+ act("reloadprefs")} />
+
+
+
+
+ );
+};