Skip to content

Commit

Permalink
Allows players to create "flavor text" for their characters that can …
Browse files Browse the repository at this point in the history
…be seen on examine (#21925)

* yeah

ok

* chubby uopdate

* worry about ti later

* yeah

ahuh

* sure

* Revert "chubby uopdate"

This reverts commit fc3647c.

* q

e

* goodbye exodia

* this al can go

bye

* god im so good

yeah

* push it

* we're done

what sup

* goodbye low mem mode

* uhgh

* oops
  • Loading branch information
cowbot92 authored Apr 27, 2024
1 parent a059104 commit c750643
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 1 deletion.
3 changes: 3 additions & 0 deletions code/__DEFINES/say.dm
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,6 @@
#define BUBBLE_DARKSPAWN "darkspawn"
#define BUBBLE_GUARDIAN "guardian"
#define BUBBLE_BLOB "blob"


#define MAX_FLAVOR_LEN 4096 //double the maximum message length.
2 changes: 2 additions & 0 deletions code/__DEFINES/{yogs_defines}/flavor_text.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// How many characters will be displayed in the flavor text preview before we cut it off?
#define FLAVOR_PREVIEW_LIMIT 110
24 changes: 24 additions & 0 deletions code/modules/client/preferences/_preference.dm
Original file line number Diff line number Diff line change
Expand Up @@ -553,3 +553,27 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key())

/datum/preference/toggle/is_valid(value)
return value == TRUE || value == FALSE


/// A string-based preference accepting arbitrary string values entered by the user, with a maximum length.
/datum/preference/text
abstract_type = /datum/preference/text

/// What is the maximum length of the value allowed in this field?
var/maximum_value_length = 256

/// Should we strip HTML the input or simply restrict it to the maximum_value_length?
var/should_strip_html = TRUE


/datum/preference/text/deserialize(input, datum/preferences/preferences)
return should_strip_html ? STRIP_HTML_SIMPLE(input, maximum_value_length) : copytext(input, 1, maximum_value_length)

/datum/preference/text/create_default_value()
return ""

/datum/preference/text/is_valid(value)
return istext(value) && length(value) < maximum_value_length

/datum/preference/text/compile_constant_data()
return list("maximum_length" = maximum_value_length)
8 changes: 8 additions & 0 deletions code/modules/client/preferences/flavor_text.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/datum/preference/text/flavor_text
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
savefile_key = "flavor_text"
maximum_value_length = MAX_FLAVOR_LEN

/datum/preference/text/flavor_text/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
target.dna.features["flavor_text"] = value
14 changes: 14 additions & 0 deletions code/modules/mob/living/carbon/human/examine.dm
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,20 @@
. += "<span class='info'><b>Quirks:</b> [traitstring]</span><br>"
. += "</span>"


var/flavor_text_link
/// The first 1-FLAVOR_PREVIEW_LIMIT characters in the mob's "flavor_text" DNA feature. FLAVOR_PREVIEW_LIMIT is defined in flavor_defines.dm.
var/preview_text = copytext_char((dna.features["flavor_text"]), 1, FLAVOR_PREVIEW_LIMIT)
// What examine_tgui.dm uses to determine if flavor text appears as "Obscured".
var/face_obscured = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE))

if (!(face_obscured))
flavor_text_link = span_notice("[preview_text]... <a href='?src=[REF(src)];lookup_info=open_examine_panel'>\[Look closer?\]</a>")
else
flavor_text_link = span_notice("<a href='?src=[REF(src)];lookup_info=open_examine_panel'>\[Examine closely...\]</a>")
if (flavor_text_link)
. += flavor_text_link

/mob/living/proc/status_effect_examines(pronoun_replacement) //You can include this in any mob's examine() to show the examine texts of status effects!
var/list/dat = list()
if(!pronoun_replacement)
Expand Down
78 changes: 78 additions & 0 deletions code/modules/mob/living/carbon/human/examine_tgui.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/datum/examine_panel
/// Mob that the examine panel belongs to.
var/mob/living/holder
/// The screen containing the appearance of the mob
var/atom/movable/screen/map_view/examine_panel_screen/examine_panel_screen


/datum/examine_panel/ui_state(mob/user)
return GLOB.always_state


/datum/examine_panel/ui_close(mob/user)
user.client.clear_map(examine_panel_screen.assigned_map)


/atom/movable/screen/map_view/examine_panel_screen
name = "examine panel screen"


/datum/examine_panel/ui_interact(mob/user, datum/tgui/ui)
if(!examine_panel_screen)
examine_panel_screen = new
examine_panel_screen.name = "screen"
examine_panel_screen.assigned_map = "examine_panel_[REF(holder)]_map"
examine_panel_screen.del_on_map_removal = FALSE
examine_panel_screen.screen_loc = "[examine_panel_screen.assigned_map]:1,1"

var/mutable_appearance/current_mob_appearance = new(holder)
current_mob_appearance.setDir(SOUTH)
current_mob_appearance.transform = matrix() // We reset their rotation, in case they're lying down.

// In case they're pixel-shifted, we bring 'em back!
current_mob_appearance.pixel_x = 0
current_mob_appearance.pixel_y = 0

examine_panel_screen.cut_overlays()
examine_panel_screen.add_overlay(current_mob_appearance)

ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
examine_panel_screen.display_to(user)
user.client.register_map_obj(examine_panel_screen)
ui = new(user, src, "ExaminePanel")
ui.open()


/datum/examine_panel/ui_data(mob/user)
var/list/data = list()

var/flavor_text
var/obscured
var/ideal_antag_optin_status
var/current_antag_optin_status
var/headshot = ""

if(ishuman(holder))
var/mob/living/carbon/human/holder_human = holder
obscured = (holder_human.wear_mask && (holder_human.wear_mask.flags_inv & HIDEFACE)) || (holder_human.head && (holder_human.head.flags_inv & HIDEFACE))
flavor_text = obscured ? "Obscured" : holder_human.dna.features["flavor_text"]
if(!obscured)
headshot += holder_human.dna.features["headshot"]

var/name = obscured ? "Unknown" : holder.name

data["obscured"] = obscured ? TRUE : FALSE
data["character_name"] = name
data["assigned_map"] = examine_panel_screen.assigned_map
data["flavor_text"] = flavor_text
data["headshot"] = headshot

data["ideal_antag_optin_status"] = ideal_antag_optin_status
data["current_antag_optin_status"] = current_antag_optin_status
return data

/datum/examine_panel/ui_static_data(mob/user)
var/list/data = list()

return data
6 changes: 6 additions & 0 deletions code/modules/mob/living/carbon/human/human.dm
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,12 @@
clear_alert("embeddedobject")
SEND_SIGNAL(usr, COMSIG_CLEAR_MOOD_EVENT, "embedded")
return
if(href_list["lookup_info"])
switch(href_list["lookup_info"])
if("open_examine_panel")
tgui.holder = src
tgui.ui_interact(usr) //datum has a tgui component, here we open the window
return

if(href_list["item"]) //canUseTopic check for this is handled by mob/Topic()
var/slot = text2num(href_list["item"])
Expand Down
2 changes: 2 additions & 0 deletions code/modules/mob/living/carbon/human/human_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@
var/account_id
var/xylophone = 0 //For the spoooooooky xylophone cooldown
var/blood_in_hands = 0
///The Examine Panel TGUI.
var/datum/examine_panel/tgui = new() //create the datum
45 changes: 45 additions & 0 deletions tgui/packages/tgui/interfaces/ExaminePanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useBackend } from '../backend';
import { Stack, Section, ByondUi } from '../components';
import { Window } from '../layouts';

export const ExaminePanel = (props, context) => {
const { act, data } = useBackend(context);
const {
character_name,
assigned_map,
flavor_text,
} = data;
return (
<Window
title="Examine Panel"
width={900}
height={670}
theme="admin">
<Window.Content>
<Stack fill>
<Stack.Item grow>
<Section fill title="Character Preview">
<ByondUi
height="100%"
width="100%"
className="ExaminePanel__map"
params={{
id: assigned_map,
type: 'map',
}} />
</Section>
</Stack.Item>
<Stack.Item grow>
<Stack fill vertical>
<Stack.Item grow>
<Section scrollable fill title={character_name + "'s Flavor Text:"}>
{flavor_text}
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BooleanLike, classes } from "common/react";
import { ComponentType, createComponentVNode, InfernoNode } from "inferno";
import { VNodeFlags } from "inferno-vnode-flags";
import { sendAct, useBackend, useLocalState } from "../../../../backend";
import { Box, Button, Dropdown, NumberInput, Stack } from "../../../../components";
import { Box, Button, Dropdown, Input, NumberInput, Stack, TextArea } from "../../../../components";
import { createSetPreference, PreferencesMenuData } from "../../data";
import { ServerPreferencesFetcher } from "../../ServerPreferencesFetcher";

Expand Down Expand Up @@ -353,3 +353,41 @@ export const FeatureValueInput = (props: {
/>
);
};

export type FeatureShortTextData = {
maximum_length: number;
};

export const FeatureShortTextInput = (
props: FeatureValueProps<string, string, FeatureShortTextData>,
) => {
if (!props.serverData) {
return <Box>Loading...</Box>;
}

return (
<Input
width="100%"
value={props.value}
maxLength={props.serverData.maximum_length}
onChange={(_, value) => props.handleSetValue(value)}
/>
);
};

export const FeatureTextInput = (
props: FeatureValueProps<string, string, FeatureShortTextData>,
) => {
if (!props.serverData) {
return <Box>Loading...</Box>;
}

return (
<TextArea
height="100px"
value={props.value}
maxLength={props.serverData.maximum_length}
onChange={(_, value) => props.handleSetValue(value)}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// THIS IS A NOVA SECTOR UI FILE
import {
Feature,
FeatureTextInput,
} from "../base";

export const flavor_text: Feature<string> = {
name: 'Flavor Text',
description:
"Appears when your character is examined (but only if they're identifiable - try a gas mask).",
component: FeatureTextInput,
};

3 changes: 3 additions & 0 deletions yogstation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@
#include "code\__DEFINES\{yogs_defines}\darkspawn.dm"
#include "code\__DEFINES\{yogs_defines}\DNA.dm"
#include "code\__DEFINES\{yogs_defines}\flavor_misc.dm"
#include "code\__DEFINES\{yogs_defines}\flavor_text.dm"
#include "code\__DEFINES\{yogs_defines}\is_helpers.dm"
#include "code\__DEFINES\{yogs_defines}\jungle.dm"
#include "code\__DEFINES\{yogs_defines}\layers.dm"
Expand Down Expand Up @@ -2266,6 +2267,7 @@
#include "code\modules\client\preferences\donor.dm"
#include "code\modules\client\preferences\engineering_department.dm"
#include "code\modules\client\preferences\events.dm"
#include "code\modules\client\preferences\flavor_text.dm"
#include "code\modules\client\preferences\fps.dm"
#include "code\modules\client\preferences\gender.dm"
#include "code\modules\client\preferences\ghost.dm"
Expand Down Expand Up @@ -2907,6 +2909,7 @@
#include "code\modules\mob\living\carbon\human\dummy.dm"
#include "code\modules\mob\living\carbon\human\emote.dm"
#include "code\modules\mob\living\carbon\human\examine.dm"
#include "code\modules\mob\living\carbon\human\examine_tgui.dm"
#include "code\modules\mob\living\carbon\human\human.dm"
#include "code\modules\mob\living\carbon\human\human_defense.dm"
#include "code\modules\mob\living\carbon\human\human_defines.dm"
Expand Down

0 comments on commit c750643

Please sign in to comment.