diff --git a/GainStation13/code/mechanics/transformation/food.dm b/GainStation13/code/mechanics/transformation/food.dm new file mode 100644 index 0000000000..a84dae4f94 --- /dev/null +++ b/GainStation13/code/mechanics/transformation/food.dm @@ -0,0 +1,16 @@ +/obj/item/reagent_containers/food/snacks/proc/handle_tf(mob/living/eater) + var/datum/component/transformation_item/transformation_component = GetComponent(/datum/component/transformation_item) + if(!istype(transformation_component) || transformation_component.transfer_to_vore || (transformation_component.transformed_mob != src)) + return FALSE + + var/mob/living/food_mob = transformation_component.transformed_mob + if(!istype(food_mob) || !(food_mob?.vore_flags & DEVOURABLE) || (eater?.vore_flags & NO_VORE) || !istype(eater.vore_selected)) + return FALSE + + qdel(transformation_component) + var/obj/belly/vore_belly = eater.vore_selected + if(!vore_belly.nom_mob(food_mob,eater)) + return FALSE + + return TRUE + diff --git a/GainStation13/code/mechanics/transformation/mob_procs.dm b/GainStation13/code/mechanics/transformation/mob_procs.dm new file mode 100644 index 0000000000..7b4fa13c5d --- /dev/null +++ b/GainStation13/code/mechanics/transformation/mob_procs.dm @@ -0,0 +1,36 @@ +/mob/living/proc/get_tf_component() + var/obj/item/item_loc = loc + if(!istype(item_loc)) + return FALSE + + var/datum/component/transformation_item/transformation_component = item_loc.GetComponent(/datum/component/transformation_item) + if(!istype(transformation_component) || (transformation_component.transformed_mob != src)) + return FALSE + + return transformation_component + +/mob/living/proc/handle_transformation_ooc_escape() + var/datum/component/transformation_item/transformation_component = get_tf_component() + if(!transformation_component) + return FALSE + + qdel(transformation_component) + return TRUE + +/mob/living/proc/attempt_to_escape_tf() + var/datum/component/transformation_item/transformation_component = get_tf_component() + if(!transformation_component) + return FALSE + + if(!transformation_component.able_to_struggle_out) + to_chat(src, span_warning("You are unable to struggle out.")) + return FALSE + + to_chat(src, span_notice("You attempt to escape your transformation.")) + if(!do_after(src, 60 SECONDS)) + to_chat(src, span_warning("You fail to escape.")) + return FALSE + + qdel(transformation_component) + return TRUE + diff --git a/GainStation13/code/mechanics/transformation/transformation_component.dm b/GainStation13/code/mechanics/transformation/transformation_component.dm new file mode 100644 index 0000000000..2a7462b38c --- /dev/null +++ b/GainStation13/code/mechanics/transformation/transformation_component.dm @@ -0,0 +1,105 @@ +/datum/component/transformation_item + /// What mob do we currently have stuck in us? + var/mob/living/transformed_mob + /// Do we release the mob when the parent item is destroyed? + var/release_mob = TRUE + /// Is the mob able to speak as the item? + var/able_to_speak = FALSE + /// Is the mob able to emote as the item? + var/able_to_emote = FALSE + /// Does the object scale with the sprite size of the mob inside of it? + var/scale_object = TRUE + /// Show custom description. + var/show_that_object_is_tf = TRUE + /// Stored real name + var/stored_real_name = "" + /// Stored name + var/stored_name = "" + /// Is the component able to be removed? + var/stuck_on_item = FALSE + /// Is the person able to struggle out? + var/able_to_struggle_out = TRUE + /// Transfer to vore belly when eaten + var/transfer_to_vore = TRUE + +/datum/component/transformation_item/Initialize() + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(examine)) + RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(remove_mob)) + +/datum/component/transformation_item/Destroy(force, silent) + remove_mob() + return ..() + +/datum/component/transformation_item/proc/examine(datum/source, mob/user, list/examine_list) + if(show_that_object_is_tf) + examine_list += span_notice("Something about [source] seems lifelike.") + +/datum/component/transformation_item/proc/register_mob(mob/living/mob_to_register) + var/atom/parent_atom = parent + if(!istype(mob_to_register)) + return FALSE + + transformed_mob = mob_to_register + stored_real_name = mob_to_register.real_name + stored_name = mob_to_register.name + + // This is really dumb, but if it works, then maybe it is not dumb. + mob_to_register.real_name = parent_atom.name + mob_to_register.name = parent_atom.name + + ADD_TRAIT(mob_to_register, TRAIT_TRANSFORMED, src) + ADD_TRAIT(mob_to_register, TRAIT_RESISTCOLD, src) + ADD_TRAIT(mob_to_register, TRAIT_RESISTLOWPRESSURE, src) + ADD_TRAIT(mob_to_register, TRAIT_LOWPRESSURECOOLING, src) + + if(!able_to_speak) + ADD_TRAIT(mob_to_register, TRAIT_MUTE, src) + + if(!able_to_emote) + ADD_TRAIT(mob_to_register, TRAIT_EMOTEMUTE, src) + + // need to stop them from using radio headsets. + var/mob/living/carbon/human/human_mob = mob_to_register + if(istype(human_mob)) + ADD_TRAIT(mob_to_register, TRAIT_PARALYSIS_L_ARM, src) + ADD_TRAIT(mob_to_register, TRAIT_PARALYSIS_R_ARM, src) + human_mob.update_disabled_bodyparts() + + mob_to_register.forceMove(parent_atom) + if(scale_object) + var/target_size = mob_to_register.size_multiplier + var/matrix/new_matrix = new + + new_matrix.Scale(target_size) + new_matrix.Translate(0,16 * (target_size-1)) + parent_atom.transform = new_matrix + +/datum/component/transformation_item/proc/remove_mob() + if(!release_mob || !transformed_mob) + return FALSE + + transformed_mob.real_name = stored_real_name + transformed_mob.name = stored_name + + if(!able_to_speak) + REMOVE_TRAIT(transformed_mob, TRAIT_MUTE, src) + + if(!able_to_emote) + REMOVE_TRAIT(transformed_mob, TRAIT_EMOTEMUTE, src) + + REMOVE_TRAIT(transformed_mob, TRAIT_TRANSFORMED, src) + REMOVE_TRAIT(transformed_mob, TRAIT_RESISTCOLD, src) + REMOVE_TRAIT(transformed_mob, TRAIT_RESISTLOWPRESSURE, src) + REMOVE_TRAIT(transformed_mob, TRAIT_LOWPRESSURECOOLING, src) + + var/mob/living/carbon/human/human_mob = transformed_mob + if(istype(human_mob)) + REMOVE_TRAIT(human_mob, TRAIT_PARALYSIS_L_ARM, src) + REMOVE_TRAIT(human_mob, TRAIT_PARALYSIS_R_ARM, src) + human_mob.update_disabled_bodyparts() + + var/atom/parent_atom = parent + transformed_mob.forceMove(parent_atom.loc) + if(scale_object) + parent_atom.transform = null + diff --git a/GainStation13/code/mechanics/transformation/transformation_item.dm b/GainStation13/code/mechanics/transformation/transformation_item.dm new file mode 100644 index 0000000000..14475e89d7 --- /dev/null +++ b/GainStation13/code/mechanics/transformation/transformation_item.dm @@ -0,0 +1,116 @@ +/obj/item/transformation_item + name = "Handheld Transmogrifier" + desc = "a handheld device that is mysteriously able to turn people into objects. It can also be used to remove said transformations." + icon = 'icons/obj/abductor.dmi' + icon_state = "gizmo_scan" + item_state = "silencer" + lefthand_file = 'icons/mob/inhands/antag/abductor_lefthand.dmi' + righthand_file = 'icons/mob/inhands/antag/abductor_righthand.dmi' + + /// What item are we wanting to TF people into? + var/obj/item/target_item + /// Do we want our transformed person to be able to speak as the object? + var/able_to_speak = TRUE + /// Do we want our transformed person to be able to emote as the object? + var/able_to_emote = TRUE + /// Do we want the item to scale? + var/scale_object = FALSE + /// Do we want to show that the object was once a person? + var/show_that_object_is_tf = TRUE + /// Is our captured person able to struggle out? + var/able_to_struggle_out = TRUE + /// Do we have any items we can't turn people into? + var/list/object_blacklist = list() + +/obj/item/transformation_item/afterattack(atom/target, mob/user, proximity_flag, click_parameters) + . = ..() + var/obj/item/attacked_item = target + if(!proximity_flag || !istype(attacked_item)) + return FALSE + + var/datum/component/transformation_item/transformation_component = attacked_item.GetComponent(/datum/component/transformation_item) + if(istype(transformation_component) && !transformation_component.stuck_on_item) + qdel(transformation_component) + to_chat(user, span_notice("You dispel the current transformation for [attacked_item].")) + return + + target_item = attacked_item + to_chat(user, span_notice("The next time someone is transformed, they will be transformed into [attacked_item].")) + return + +/obj/item/transformation_item/attack(mob/living/M, mob/living/user) + if(!target_item) + to_chat(user, span_warning("You need to have an item linked to transform someone.")) + return + + perform_transfomration(M, user) + return + +/obj/item/transformation_item/proc/perform_transfomration(mob/living/target_mob, mob/living/user) + if(!istype(target_mob)) + return FALSE + + if(!target_item.Adjacent(target_mob)) + to_chat(user, span_warning("The [target_item] isn't close enough to [target_mob]")) + return FALSE + + if(target_item in target_mob.get_contents()) + return FALSE // Don't TF someone into something they are holding. + + if(!target_mob?.client?.prefs?.object_tf) + to_chat(user, span_warning("It seems like [target_mob] does not want to be transformed.")) + return FALSE + + var/datum/component/transformation_item/transformation_component = target_item.AddComponent(/datum/component/transformation_item) + // Make sure that we apply our variables before we actually put the mob in the item. + transformation_component.able_to_speak = able_to_speak + transformation_component.able_to_emote = able_to_emote + transformation_component.scale_object = scale_object + transformation_component.show_that_object_is_tf = show_that_object_is_tf + transformation_component.able_to_struggle_out = able_to_struggle_out + + transformation_component.register_mob(target_mob) + target_item = null + return TRUE + +/obj/item/transformation_item/ui_data(mob/user) + var/list/data = list() + data["able_to_speak"] = able_to_speak + data["able_to_emote"] = able_to_emote + data["scale_object"] = scale_object + data["show_that_object_is_tf"] = show_that_object_is_tf + data["linked_item_name"] = target_item + data["able_to_struggle_out"] = able_to_struggle_out + + return data + +/obj/item/transformation_item/ui_act(action, params) + if(..()) + return + + switch(action) + if("set_speaking") + able_to_speak = !able_to_speak + . = TRUE + + if("set_emote") + able_to_emote = !able_to_emote + . = TRUE + + if("set_scale") + scale_object = !scale_object + . = TRUE + + if("toggle_struggle") + able_to_struggle_out = !able_to_struggle_out + . = TRUE + + if("set_show_desc") + show_that_object_is_tf = !show_that_object_is_tf + . = TRUE + +/obj/item/transformation_item/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TransformTool", name) + ui.open() diff --git a/GainStation13/code/modules/client/preferences/preferences.dm b/GainStation13/code/modules/client/preferences/preferences.dm index 38206706a8..28bc61c2a1 100644 --- a/GainStation13/code/modules/client/preferences/preferences.dm +++ b/GainStation13/code/modules/client/preferences/preferences.dm @@ -31,6 +31,8 @@ var/fatness_vulnerable = FALSE /// Similar to fatness_vulnerable, but with more extreme effects such as transformation/hypno. var/extreme_fatness_vulnerable = FALSE + /// Can the person be transformed into an object? + var/object_tf // Helplessness, a set of prefs that make things extra tough at higher weights. If set to FALSE, they won't do anything. ///What fatness level disables movement? diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 2bc634bb61..f2c3c13848 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -295,6 +295,7 @@ #define TRAIT_HEAT "heat" #define TRAIT_DISTANT "headpat_hater" #define TRAIT_CUM_PLUS "cum_plus" +#define TRAIT_TRANSFORMED "transformed" // 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/modules/client/preferences.dm b/code/modules/client/preferences.dm index 375346009f..797796b3e8 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -1409,6 +1409,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "This preference functions similar to the one before but allows for items with more drastic effects. Do not enable this if you aren't okay with more drastic things happening to your character.
" dat += "Extreme Fatness Vulnerability:[extreme_fatness_vulnerable == TRUE ? "Enabled" : "Disabled"]
" dat += "

" + dat += "Object TF:[object_tf == TRUE ? "Enabled" : "Disabled"]
" + dat += "

" dat += "Extreme Weight Gain (Sprite Size scales with weight):[weight_gain_extreme == TRUE ? "Enabled" : "Disabled"]
" dat += "Persistent Fat (endround/cryo weight becomes your new start weight):[weight_gain_persistent == TRUE ? "Enabled" : "Disabled"]
" dat += "Permanent Weight (hard to remove and persistent weight):[weight_gain_permanent == TRUE ? "Enabled" : "Disabled"]
" @@ -3422,6 +3424,9 @@ GLOBAL_LIST_EMPTY(preferences_datums) if("extreme_fatness_vulnerable") extreme_fatness_vulnerable = !extreme_fatness_vulnerable + if("object_tf") + object_tf = !object_tf + if("blueberry_inflation") blueberry_inflation = !blueberry_inflation if("max_fatness") diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 21545d1dcd..025cf1f63a 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -951,6 +951,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["chair_breakage"] >> chair_breakage S["fatness_vulnerable"] >> fatness_vulnerable S["extreme_fatness_vulnerable"] >> extreme_fatness_vulnerable + S["object_tf"] >> object_tf S["blueberry_inflation"] >> blueberry_inflation S["feature_breasts_fluid"] >> features["breasts_fluid"] @@ -1236,6 +1237,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["chair_breakage"], chair_breakage) WRITE_FILE(S["fatness_vulnerable"], fatness_vulnerable) WRITE_FILE(S["extreme_fatness_vulnerable"], extreme_fatness_vulnerable) + WRITE_FILE(S["object_tf"], object_tf) WRITE_FILE(S["blueberry_inflation"], blueberry_inflation) WRITE_FILE(S["feature_breasts_fluid"], features["breasts_fluid"]) diff --git a/code/modules/food_and_drinks/food/snacks.dm b/code/modules/food_and_drinks/food/snacks.dm index 600a1e91ff..fbc8403bee 100644 --- a/code/modules/food_and_drinks/food/snacks.dm +++ b/code/modules/food_and_drinks/food/snacks.dm @@ -89,6 +89,7 @@ All foods are distributed among various categories. Use common sense. if(!reagents.total_volume) var/mob/living/location = loc var/obj/item/trash_item = generate_trash(location) + handle_tf()//GS13 EDIT qdel(src) if(istype(location)) location.put_in_hands(trash_item) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index cd69b5ee52..863b087361 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -820,6 +820,10 @@ //Sure, give clickdelay for anti spam. shouldn't be combat voring anyways. return TRUE + // GS13 Escape Transformation + if(attempt_to_escape_tf()) + return TRUE + //Breaking out of a container (Locker, sleeper, cryo...) if(isobj(loc)) var/obj/C = loc diff --git a/code/modules/vending/kinkmate.dm b/code/modules/vending/kinkmate.dm index 02f3f30e4b..5939b48af0 100644 --- a/code/modules/vending/kinkmate.dm +++ b/code/modules/vending/kinkmate.dm @@ -22,6 +22,7 @@ /obj/item/clothing/under/misc/gear_harness = 10, /obj/item/dildo/custom = 5, /obj/item/electropack/shockcollar = 3, + /obj/item/transformation_item = 3, // GS13 EDIT /obj/item/assembly/signaler = 3, /obj/item/clothing/under/shorts/polychromic/pantsu = 3, /obj/item/clothing/under/misc/poly_bottomless = 3, diff --git a/code/modules/vore/eating/living.dm b/code/modules/vore/eating/living.dm index d5a3627684..96aff93e7f 100644 --- a/code/modules/vore/eating/living.dm +++ b/code/modules/vore/eating/living.dm @@ -221,7 +221,12 @@ // /mob/living/proc/escapeOOC() set name = "OOC Escape" - set category = "Vore" + set category = "ERP" // GS13 Change + + //GS13 EDIT START + handle_transformation_ooc_escape() + //GS13 EDIT END + //You're in a belly! if(isbelly(loc)) diff --git a/tgstation.dme b/tgstation.dme index 7ba4d39826..563af60324 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3972,6 +3972,10 @@ #include "GainStation13\code\mechanics\web_weaving.dm" #include "GainStation13\code\mechanics\den abductors\console.dm" #include "GainStation13\code\mechanics\den abductors\purchaseble_goodies.dm" +#include "GainStation13\code\mechanics\transformation\food.dm" +#include "GainStation13\code\mechanics\transformation\mob_procs.dm" +#include "GainStation13\code\mechanics\transformation\transformation_component.dm" +#include "GainStation13\code\mechanics\transformation\transformation_item.dm" #include "GainStation13\code\mobs\cakegolem.dm" #include "GainStation13\code\mobs\chocoslime.dm" #include "GainStation13\code\mobs\eat_trash.dm" diff --git a/tgui/packages/tgui/interfaces/TransformTool.js b/tgui/packages/tgui/interfaces/TransformTool.js new file mode 100644 index 0000000000..fb06cc0c29 --- /dev/null +++ b/tgui/packages/tgui/interfaces/TransformTool.js @@ -0,0 +1,66 @@ +import { useBackend } from '../backend'; +import { Box, Button, LabeledList, NumberInput, Section } from '../components'; +import { Window } from '../layouts'; + +export const TransformTool = (props, context) => { + const { act, data } = useBackend(context); + const { + able_to_speak, + able_to_emote, + scale_object, + show_that_object_is_tf, + linked_item_name, + able_to_struggle_out, + } = data; + + return ( + + +
+ + Current item: {linked_item_name ? linked_item_name : "None"} + +
+ + + + + + + + + + + +
+
+
+ ); +};