diff --git a/modular_bandastation/_defines220/code/defines/preferenced.dm b/modular_bandastation/_defines220/code/defines/preferenced.dm new file mode 100644 index 0000000000000..29eb79057088a --- /dev/null +++ b/modular_bandastation/_defines220/code/defines/preferenced.dm @@ -0,0 +1 @@ +#define PREFERENCE_TAB_BODY_MODIFICATIONS_PREFERENCES 4 diff --git a/modular_bandastation/augmentation_preferences/_augmentation_preferences.dm b/modular_bandastation/augmentation_preferences/_augmentation_preferences.dm new file mode 100644 index 0000000000000..755f833b1b5da --- /dev/null +++ b/modular_bandastation/augmentation_preferences/_augmentation_preferences.dm @@ -0,0 +1,4 @@ +/datum/modpack/augmentation_preferences + name = "Augmentation Preferences" + desc = "Расширенное меню для аугментаций, имплантов, протезов и ампутаций" + author = "gaxeer" diff --git a/modular_bandastation/augmentation_preferences/_augmentation_preferences.dme b/modular_bandastation/augmentation_preferences/_augmentation_preferences.dme new file mode 100644 index 0000000000000..f898dc4d3a1be --- /dev/null +++ b/modular_bandastation/augmentation_preferences/_augmentation_preferences.dme @@ -0,0 +1,8 @@ +#include "_augmentation_preferences.dm" + +#include "code/body_modifications/body_part/amputations.dm" +#include "code/body_modifications/body_part/prosthetics.dm" +#include "code/body_modifications/_body_modifications_datum.dm" +#include "code/body_modifications/_body_modifications_datum.dm" +#include "code/preferences/body_modification_preference_middleware.dm" +#include "code/preferences/body_modification_preference.dm" diff --git a/modular_bandastation/augmentation_preferences/code/body_modifications/_body_modifications_datum.dm b/modular_bandastation/augmentation_preferences/code/body_modifications/_body_modifications_datum.dm new file mode 100644 index 0000000000000..1a3d7a25dc250 --- /dev/null +++ b/modular_bandastation/augmentation_preferences/code/body_modifications/_body_modifications_datum.dm @@ -0,0 +1,62 @@ +GLOBAL_LIST_INIT_TYPED(body_modifications, /datum/body_modification, init_body_modifications()) + +/datum/body_modification + /// The abstract type of this body modification + var/abstract_type = /datum/body_modification + /// The key used to identify this body modification + var/key = null + /// The name of this body modification + var/name = null + /// Cost in quirk points of thisbody modification + var/cost = 0 + /// The list of body modifications incompatible with this body modification + var/list/incompatible_body_modifications = list() + +/datum/body_modification/New() + ..() + if(isnull(key)) + stack_trace("body modification without key: [type]") + + if(abstract_type == type) + stack_trace("abstract body modification attempted to be instantiated: [type]") + qdel(src) + +/// Apply this set of body modifications to the given mob +/datum/body_modification/proc/apply_to_human(mob/living/carbon/target) + SHOULD_CALL_PARENT(TRUE) + + return can_be_applied(target) + +/// Returns TRUE if this body modification can be applied +/datum/body_modification/proc/can_be_applied(mob/living/carbon/target) + SHOULD_CALL_PARENT(TRUE) + + if(isnull(target)) + return FALSE + + var/list/applied_body_modifications = target.client?.prefs?.read_preference(/datum/preference/body_modifications) + if(length(applied_body_modifications) == 0) + return TRUE + + for(var/incompatible_body_modification in incompatible_body_modifications) + if(incompatible_body_modification in applied_body_modifications) + return FALSE + + return TRUE + +/// Returns the list of body modifications incompatible with this body modification +/datum/body_modification/proc/get_conflicting_body_modifications(mob/living/carbon/target) + return incompatible_body_modifications && target.client?.prefs?.read_preference(/datum/preference/body_modifications) + +/datum/body_modification/proc/get_description() + return "No description yet" + +/proc/init_body_modifications() + var/list/body_modifications = list() + for(var/datum/body_modification/body_modification_type as anything in subtypesof(/datum/body_modification)) + if(body_modification_type == body_modification_type::abstract_type) + continue + + body_modifications[body_modification_type::key] = new body_modification_type() + + return body_modifications diff --git a/modular_bandastation/augmentation_preferences/code/body_modifications/body_part/amputations.dm b/modular_bandastation/augmentation_preferences/code/body_modifications/body_part/amputations.dm new file mode 100644 index 0000000000000..1ad66267dfe9b --- /dev/null +++ b/modular_bandastation/augmentation_preferences/code/body_modifications/body_part/amputations.dm @@ -0,0 +1,42 @@ +/datum/body_modification/limb_amputation + name = "Body Part Amputation" + abstract_type = /datum/body_modification/limb_amputation + var/limb_body_zone = null + +/datum/body_modification/limb_amputation/apply_to_human(mob/living/carbon/target) + . = ..() + if(!.) + return + + var/obj/item/bodypart/limb_to_remove = target.get_bodypart(limb_body_zone) + if(!limb_to_remove) + return FALSE + + limb_to_remove.drop_limb(special = TRUE) + return TRUE + +/datum/body_modification/limb_amputation/arm + abstract_type = /datum/body_modification/limb_amputation/arm + +/datum/body_modification/limb_amputation/arm/left + key = "left_arm_amputation" + name = "Ампутация левой руки" + limb_body_zone = BODY_ZONE_L_ARM + +/datum/body_modification/limb_amputation/arm/right + key = "right_arm_amputation" + name = "Ампутация правой руки" + limb_body_zone = BODY_ZONE_R_ARM + +/datum/body_modification/limb_amputation/leg + abstract_type = /datum/body_modification/limb_amputation/leg + +/datum/body_modification/limb_amputation/leg/left + key = "left_leg_amputation" + name = "Ампутация левой ноги" + limb_body_zone = BODY_ZONE_L_LEG + +/datum/body_modification/limb_amputation/leg/right + key = "right_leg_amputation" + name = "Ампутация правой ноги" + limb_body_zone = BODY_ZONE_R_LEG diff --git a/modular_bandastation/augmentation_preferences/code/body_modifications/body_part/prosthetics.dm b/modular_bandastation/augmentation_preferences/code/body_modifications/body_part/prosthetics.dm new file mode 100644 index 0000000000000..d5d1166bfcc2a --- /dev/null +++ b/modular_bandastation/augmentation_preferences/code/body_modifications/body_part/prosthetics.dm @@ -0,0 +1,39 @@ +/datum/body_modification/bodypart_prosthesis + name = "Body Part Prosthesis" + abstract_type = /datum/body_modification/bodypart_prosthesis + var/replacement_bodypart_type = null + +/datum/body_modification/bodypart_prosthesis/apply_to_human(mob/living/carbon/target) + . = ..() + if(!.) + return + + var/obj/item/bodypart/replacement_bodypart = new replacement_bodypart_type() + replacement_bodypart.replace_limb(target, TRUE) + return TRUE + +/datum/body_modification/bodypart_prosthesis/arm + abstract_type = /datum/body_modification/bodypart_prosthesis/arm + +/datum/body_modification/bodypart_prosthesis/arm/left + key = "left_arm_prosthetic" + name = "Протез левой руки" + replacement_bodypart_type = /obj/item/bodypart/arm/left/robot + +/datum/body_modification/bodypart_prosthesis/arm/right + key = "right_arm_prosthesis" + name = "Протез правой руки" + replacement_bodypart_type = /obj/item/bodypart/arm/right/robot + +/datum/body_modification/bodypart_prosthesis/leg + abstract_type = /datum/body_modification/bodypart_prosthesis/leg + +/datum/body_modification/bodypart_prosthesis/leg/left + key = "left_leg_prosthesis" + name = "Протез левой ноги" + replacement_bodypart_type = /obj/item/bodypart/leg/left/robot + +/datum/body_modification/bodypart_prosthesis/leg/right + key = "right_leg_prosthesis" + name = "Протез правой ноги" + replacement_bodypart_type = /obj/item/bodypart/leg/right/robot diff --git a/modular_bandastation/augmentation_preferences/code/body_modifications/implants.dm b/modular_bandastation/augmentation_preferences/code/body_modifications/implants.dm new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/modular_bandastation/augmentation_preferences/code/preferences/body_modification_preference.dm b/modular_bandastation/augmentation_preferences/code/preferences/body_modification_preference.dm new file mode 100644 index 0000000000000..c55009e9b9160 --- /dev/null +++ b/modular_bandastation/augmentation_preferences/code/preferences/body_modification_preference.dm @@ -0,0 +1,55 @@ +/datum/preference/body_modifications + savefile_key = "body_modifications" + savefile_identifier = PREFERENCE_CHARACTER + priority = PREFERENCE_PRIORITY_BODYPARTS + can_randomize = FALSE + +/datum/preference/body_modifications/is_valid(value) + if(!islist(value)) + return FALSE + + var/list/values = value + for(var/body_modification_key in values) + if(isnull(GLOB.body_modifications[body_modification_key])) + return FALSE + + return TRUE + +/datum/preference/body_modifications/apply_to_human(mob/living/carbon/human/target, value) + if(!islist(value)) + return + + var/list/body_modifications = value + for(var/body_modification_key in body_modifications) + GLOB.body_modifications[body_modification_key].apply_to_human(target) + +/datum/preference/body_modifications/deserialize(input, datum/preferences/preferences) + if(!islist(input)) + return list() + + var/list/body_modifications = input + var/list/valid_body_modification_keys = list() + for(var/body_modification_key in body_modifications) + if(!GLOB.body_modifications[body_modification_key]) + continue + + valid_body_modification_keys[body_modification_key] = TRUE + + return valid_body_modification_keys + +/datum/preference/body_modifications/serialize(input) + if(!islist(input)) + return list() + + var/list/body_modifications = input + var/list/valid_body_modification_keys = list() + for(var/body_modification_key in body_modifications) + if(!GLOB.body_modifications[body_modification_key]) + continue + + valid_body_modification_keys |= body_modification_key + + return valid_body_modification_keys + +/datum/preference/body_modifications/create_default_value() + return list() diff --git a/modular_bandastation/augmentation_preferences/code/preferences/body_modification_preference_middleware.dm b/modular_bandastation/augmentation_preferences/code/preferences/body_modification_preference_middleware.dm new file mode 100644 index 0000000000000..a402092706069 --- /dev/null +++ b/modular_bandastation/augmentation_preferences/code/preferences/body_modification_preference_middleware.dm @@ -0,0 +1,80 @@ +/datum/preference_middleware/body_modifications + action_delegations = list( + "apply_body_modification" = PROC_REF(apply_body_modification), + "remove_body_modification" = PROC_REF(remove_body_modification), + ) + +/// Append all of these into ui_data +/datum/preference_middleware/body_modifications/get_ui_data(mob/user) + var/list/data = list() + data["applied_body_modifications"] = get_applied_body_modifications() + data["incomptable_body_modifications"] = get_incomptable_body_modifications(user) + return data + +/// Append all of these into ui_static_data +/datum/preference_middleware/body_modifications/get_constant_data(mob/user) + var/list/data = list() + for(var/body_modification_key in GLOB.body_modifications) + var/datum/body_modification/body_modification = GLOB.body_modifications[body_modification_key] + data += list( + list( + "key" = body_modification.key, + "name" = body_modification.name, + "description" = body_modification.get_description(), + "cost" = body_modification.cost + ) + ) + + return data + +/datum/preference_middleware/body_modifications/proc/get_applied_body_modifications() + PRIVATE_PROC(TRUE) + + var/list/applied_body_modifications = preferences.read_preference(/datum/preference/body_modifications) + var/list/modifications = list() + for(var/body_modification_key in applied_body_modifications) + modifications += body_modification_key + + return modifications + +/datum/preference_middleware/body_modifications/proc/get_incomptable_body_modifications(mob/user) + PRIVATE_PROC(TRUE) + + var/list/incompatible_body_modifications = list() + for(var/body_modification_key in GLOB.body_modifications) + if(GLOB.body_modifications[body_modification_key].can_be_applied(user)) + continue + + incompatible_body_modifications += body_modification_key + + return incompatible_body_modifications + +/datum/preference_middleware/body_modifications/proc/apply_body_modification(list/params, mob/user) + var/body_modification_key = params["body_modification_key"] + if(!body_modification_key) + return FALSE + + var/datum/body_modification/body_modification_prototype = GLOB.body_modifications[body_modification_key] + if(isnull(body_modification_prototype) || !body_modification_prototype.can_be_applied(user)) + return FALSE + + var/list/body_modifications = preferences.read_preference(/datum/preference/body_modifications) + if(body_modifications[body_modification_key]) + return FALSE + + body_modifications[body_modification_key] = TRUE + preferences.update_preference(GLOB.preference_entries[/datum/preference/body_modifications], body_modifications) + return TRUE + +/datum/preference_middleware/body_modifications/proc/remove_body_modification(list/params, mob/user) + var/body_modification_key = params["body_modification_key"] + if(!body_modification_key) + return FALSE + + var/list/body_modifications = preferences.read_preference(/datum/preference/body_modifications) + if(!body_modifications[body_modification_key]) + return FALSE + + body_modifications -= body_modification_key + preferences.update_preference(GLOB.preference_entries[/datum/preference/body_modifications], body_modifications) + return TRUE diff --git a/modular_bandastation/modular_bandastation.dme b/modular_bandastation/modular_bandastation.dme index 057aa3a0243dd..8f51f07c5c97d 100644 --- a/modular_bandastation/modular_bandastation.dme +++ b/modular_bandastation/modular_bandastation.dme @@ -12,6 +12,7 @@ #include "aesthetics_sounds/_aesthetics_sounds.dme" #include "automapper/_automapper.dme" #include "ai_laws/_ai_laws.dme" +#include "augmentation_preferences/_augmentation_preferences.dme" #include "announcers/_announcers.dme" #include "autohiss/_autohiss.dme" #include "balance/_balance.dme" diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/BodyModificationsPage.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/BodyModificationsPage.tsx new file mode 100644 index 0000000000000..d83c3d02ef5d1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/BodyModificationsPage.tsx @@ -0,0 +1,68 @@ +import { useBackend } from '../../backend'; +import { Button, NoticeBox, Stack } from '../../components'; +import { BodyModification, PreferencesMenuData, ServerData } from './data'; +import { ServerPreferencesFetcher } from './ServerPreferencesFetcher'; + +export const BodyModificationsPage = () => { + return ( + { + if (!serverData) { + return Loading...; + } + return ( + + ); + }} + /> + ); +}; + +const BodyModificationsPageInner = (props: { + bodyModification: BodyModification[]; +}) => { + return ( + + {props.bodyModification.map((bodyModification) => ( + + + + ))} + + ); +}; + +const BodyModificationRow = (props: { bodyModification: BodyModification }) => { + const { act, data } = useBackend(); + const { applied_body_modifications } = data; + return ( + + {props.bodyModification.name} + + {props.bodyModification.key in applied_body_modifications ? ( + + ) : ( + + )} + + + ); +}; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx index a8a3404cd802b..0367164df20f1 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx @@ -5,6 +5,7 @@ import { exhaustiveCheck } from 'tgui-core/exhaustive'; import { useBackend } from '../../backend'; import { Window } from '../../layouts'; import { AntagsPage } from './AntagsPage'; +import { BodyModificationsPage } from './BodyModificationsPage'; // BANDASTATION EDIT ADD - TTS import { PreferencesMenuData } from './data'; import { JobsPage } from './JobsPage'; import { LoadoutPage } from './loadout/index'; @@ -21,8 +22,10 @@ enum Page { Species, Quirks, Loadout, - // BANDASTATION EDIT ADD - TTS + // BANDASTATION ADD START Voice, + BodyModifications, + // BANDASTATION ADD END } const CharacterProfiles = (props: { @@ -83,10 +86,14 @@ export const CharacterPreferenceWindow = (props) => { case Page.Loadout: pageContents = ; break; - // BANDASTATION EDIT ADD - TTS + // BANDASTATION ADD START case Page.Voice: pageContents = ; break; + case Page.BodyModifications: + pageContents = ; + break; + // BANDASTATION ADD END default: exhaustiveCheck(currentPage); } @@ -169,6 +176,16 @@ export const CharacterPreferenceWindow = (props) => { + + + Body Modifications + + + {Boolean(data.tts_enabled) && ( // BANDASTATION EDIT - TTS ; jobs: Record;