Skip to content

Commit

Permalink
[PORT] [SEMI-MODULAR] [de-janked] Ports the lovely Character Director…
Browse files Browse the repository at this point in the history
…y which was ported from Coyote Bayou (#183)

* I'm not cooking this anymore

* obscurity pref time

* THE NUT MACHINE

* CONTEXT REMOVAL, EVERYONE IS CONFUSED, ME TOO SO I FALL ASLEEP

* Updates Character directory

* Epic fix!

* obliterates straights AND gays

* stupid ticker

* More gloop

* Reorders gender and attraction

* resize mooore

* Pass 1 - code improvement

* Starting to implement portrait

* Very early WIP redesign of the character view menu, with portrait

* Update character_directory.dm

* Redesign courtesy of LT3

* Some updates

* Update NovaCharacterDirectory.jsx

* Attempts to fix black screen bug, clean up the refs better

* Update character_directory.dm

* Update character_directory.dm

* New color scheme + minor refactor + cyborg previews are functional

* Updates the lists, rephrasing

* Does away with obscurity examine pref

* adds hypnosis 2 directory

* Update borg preview when they change models

* Functional sort buttons search function

* Update NovaCharacterDirectory.jsx

* Adds color code toggle button, some mild unloudening of colors

* Fixes state resetting

* Update NovaCharacterDirectory.jsx

* Update NovaCharacterDirectory.jsx

* Improved character previews

* Fixed color palette (courtesy of LT3)

* Revert "Improved character previews"

This reverts commit 92d9f0fd08f309807be78452b9b7a482dd56f0be.

* Adds better feedback to headshot error

* Update ticker.dm

* Ghost role will have previews

* Adds link to directory when examining mobs who have an ad

* Adds character directory to the lobby

* Obscured face = don't show link

* Documentation, fixes a bug with the 'show in directory' pref

* opening a char ad from examine autofills the search bar with the examined mob's name

* Fixes preview view

* Fixes another bug with the views, whoops

* Update modular_nova/modules/character_directory/code/character_directory.dm

* Last minute optimizations

* We don't actually use exploitables

* Update character_directory.dm

---------

Co-authored-by: Mal <[email protected]>
  • Loading branch information
2 people authored and StealsThePRs committed Jun 18, 2024
1 parent f4bfb58 commit 4b06b11
Show file tree
Hide file tree
Showing 16 changed files with 869 additions and 1 deletion.
1 change: 1 addition & 0 deletions code/datums/records/record.dm
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
species_type = locked_dna.species.type

GLOB.manifest.locked += src
GLOB.name_to_appearance[name] = character_appearance // NOVA EDIT ADDITION - Cache these for Character Directory

/datum/record/locked/Destroy()
GLOB.manifest.locked -= src
Expand Down
2 changes: 2 additions & 0 deletions code/modules/mob/living/carbon/human/examine.dm
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,8 @@
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
if (!face_obscured && !HAS_TRAIT(src, TRAIT_UNKNOWN) && client?.prefs.read_preference(/datum/preference/text/character_ad))
. += span_notice("[t_He] [t_has] an ad in the character directory... <a href='?src=[REF(src)];lookup_info=open_character_ad'>\[Open directory?\]</a>")

//Temporary flavor text addition:
if(temporary_flavor_text)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

find_index = findtext(value, link_regex)
if(find_index != 9)
to_chat(usr, span_warning("The image must be hosted on one of the following sites: 'Gyazo, Byond, Imgbox'"))
to_chat(usr, span_warning("The image must be hosted on one of the following sites: 'Gyazo (i.gyazo.com), Byond (files.byondhome.com), Imgbox (images2.imgbox.com)'"))
return

apply_headshot(value)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/mob/dead/new_player/transfer_character()
if(iscyborg(new_character))
var/mutable_appearance/character_appearance = new(new_character.appearance)
GLOB.name_to_appearance[new_character.real_name] = character_appearance // Cache this for Character Directory
return ..()
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,10 @@
color = "#ffffffc2"
pixel_y = -8
layer = ABOVE_MOB_LAYER

// Update the borg's model appearance when they change models
/obj/item/robot_model/do_transform_animation()
. = ..()
var/mob/living/silicon/robot/cyborg = loc
var/mutable_appearance/character_appearance = new(cyborg.appearance)
GLOB.name_to_appearance[cyborg.real_name] = character_appearance // Cache this for Character Directory
3 changes: 3 additions & 0 deletions modular_nova/master_files/code/modules/mob_spawn/mob_spawn.dm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
else if (!isnull(spawned_human))
equip(spawned_human)

var/mutable_appearance/character_appearance = new(spawned_human.appearance)
GLOB.name_to_appearance[spawned_human.real_name] = character_appearance // Cache this for Character Directory

return spawned_mob

/// This edit would cause somewhat ugly diffs, so I'm just replacing it.
Expand Down
295 changes: 295 additions & 0 deletions modular_nova/modules/character_directory/code/character_directory.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
GLOBAL_DATUM(character_directory, /datum/character_directory)
GLOBAL_LIST_EMPTY(name_to_appearance)
#define READ_PREFS(target, pref) (target.client?.prefs?.read_preference(/datum/preference/pref))

// We want players to be able to decide whether they show up in the directory or not
/datum/preference/toggle/show_in_directory
category = PREFERENCE_CATEGORY_GAME_PREFERENCES
default_value = TRUE
savefile_key = "show_in_directory"
savefile_identifier = PREFERENCE_PLAYER

// The advertisement that you show to people looking through the directory
/datum/preference/text/character_ad
savefile_key = "character_ad"
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER
maximum_value_length = MAX_FLAVOR_LEN

// TGUI gets angry if you don't define a default on text preferences
/datum/preference/text/character_ad/create_default_value()
return ""

// Any text preference needs this for some reason
/datum/preference/text/character_ad/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
return FALSE

/datum/preference/choiced/attraction
savefile_key = "attraction"
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER

/datum/preference/choiced/attraction/init_possible_values()
return list("Gay", "Lesbian", "Straight", "Skolio", "Bi", "Pan", "Poly", "Omni", "Ace", "Aro", "Aro/Ace", "Unset", "Check OOC")

/datum/preference/choiced/attraction/create_default_value()
return "Unset"

/datum/preference/choiced/attraction/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
return FALSE

/datum/preference/choiced/display_gender
savefile_key = "display_gender"
category = PREFERENCE_CATEGORY_NON_CONTEXTUAL
savefile_identifier = PREFERENCE_CHARACTER

/datum/preference/choiced/display_gender/init_possible_values()
return list("Male", "Female", "Null", "Plural", "Nonbinary", "Omni", "Trans", "Andro", "Gyno", "Fluid", "Unset", "Check OOC")

/datum/preference/choiced/display_gender/create_default_value()
return "Unset"

/datum/preference/choiced/display_gender/apply_to_human(mob/living/carbon/human/target, value, datum/preferences/preferences)
return FALSE

// Add a cooldown for the character directory to the client, primarily to stop server lag from refresh spam
/client
COOLDOWN_DECLARE(char_directory_cooldown)

/// Opens character directory UI for a specific user
/client/verb/show_character_directory(specific_ad as text|null)
set name = "Character Directory"
set category = "OOC"
set desc = "Shows a listing of all active characters, along with their associated OOC notes, flavor text, and more."

if(is_character_directory_on_cooldown())
return

// Check if there's not already a character directory open; open a new one if one is not present
if(!GLOB.character_directory)
GLOB.character_directory = new

// So we start opening their page right away. There really isn't any other good way to pass this to tgui unfortunately...
if(specific_ad)
var/sanitized_name = trim(specific_ad, MAX_NAME_LEN)
GLOB.character_directory.start_viewing_ad[ckey] = sanitized_name

GLOB.character_directory.ui_interact(mob)

/// Returns TRUE if it's on cooldown, FALSE otherwise. This is primarily to stop malicious users from trying to lag the server by spamming this verb
/client/proc/is_character_directory_on_cooldown()
// This is primarily to stop malicious users from trying to lag the server by spamming this verb
if(!COOLDOWN_FINISHED(src, char_directory_cooldown))
to_chat(src, span_alert("Hold your horses! It's still refreshing!"))
return TRUE
COOLDOWN_START(src, char_directory_cooldown, 10)
return FALSE

// This is a global singleton. Keep in mind that all operations should occur on user, not src.
/datum/character_directory
/// The character preview views for the UI.
var/list/atom/movable/screen/map_view/char_preview/character_preview_views = list()
/// For when a character starts off viewing a specific character's ad
var/list/start_viewing_ad = list()

/datum/character_directory/Destroy(force)
for(var/ckey in character_preview_views)
var/atom/movable/screen/map_view/char_preview/preview = character_preview_views[ckey]
var/mob/user = get_mob_by_ckey(ckey)
if(user)
user.client?.screen_maps -= preview
qdel(preview)
return ..()

/// Makes a managed character preview view for a specific user
/datum/character_directory/proc/create_character_preview_view(mob/user)
var/assigned_view = "preview_[user.ckey]_[REF(src)]_directory"

// sometimes--e.g. if you have a ui open and you observe--you can end up with a stuck map_view, which leads to subsequent previews not rendering.
// let's clear those out, we always want a new one when calling this proc anyway.
var/old_view = user.client?.screen_maps[assigned_view]
if(old_view)
character_preview_views -= old_view
user.client.screen_maps -= old_view
qdel(old_view)

var/atom/movable/screen/map_view/char_preview/new_view = new(null)
new_view.generate_view(assigned_view)
new_view.display_to(user)
return new_view

/// Takes a record and updates the character preview view to match it.
/datum/character_directory/proc/update_preview(mob/user, assigned_view, mutable_appearance/appearance)
var/mutable_appearance/preview = new(appearance)
preview.transform = matrix() // This is so scaled mobs aren't just getting cut off for being too big

var/atom/movable/screen/map_view/char_preview/old_view = user.client?.screen_maps[assigned_view]?[1]
if(!old_view)
return

old_view.appearance = preview.appearance

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

/datum/character_directory/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
character_preview_views[user.ckey] = create_character_preview_view(user)
ui = new(user, src, "NovaCharacterDirectory", "Character Directory")
ui.set_autoupdate(FALSE)
ui.open()

/datum/character_directory/ui_close(mob/user)
var/atom/movable/screen/map_view/char_preview/old_preview = character_preview_views[user.ckey]
user.client?.screen_maps -= old_preview
character_preview_views -= user.ckey
qdel(old_preview)

// We want this information to update any time the player updates their preferences, not just when the panel is refreshed
/datum/character_directory/ui_data(mob/user)
. = ..()
var/list/data = .

// Collect the user's own preferences for the top of the UI
if (user?.client?.prefs)
data["personalVisibility"] = READ_PREFS(user, toggle/show_in_directory)
data["personalAttraction"] = READ_PREFS(user, choiced/attraction)
data["personalGender"] = READ_PREFS(user, choiced/display_gender)
data["personalErpTag"] = READ_PREFS(user, choiced/erp_status)
data["personalVoreTag"] = READ_PREFS(user, choiced/erp_status_v)
data["personalNonconTag"] = READ_PREFS(user, choiced/erp_status_nc)
data["personalHypnoTag"] = READ_PREFS(user, choiced/erp_status_hypno)
data["prefsOnly"] = TRUE

data["assignedView"] = "preview_[user.ckey]_[REF(src)]_directory"
data["canOrbit"] = isobserver(user)
// for when we want to start off with a search term filled in automatically
var/autofill_search_term = start_viewing_ad[user.ckey]
if(autofill_search_term)
data["startViewing"] = autofill_search_term
start_viewing_ad -= user.ckey

return data

/datum/character_directory/ui_static_data(mob/user)
. = ..()
var/list/data = .

// These are the variables we're trying to display in the directory
var/list/directory_mobs = list()
var/name
var/species
var/ooc_notes
var/flavor_text
var/attraction
var/gender
var/erp
var/vore
var/noncon
var/hypno
var/character_ad
var/headshot
var/ref

// We want the directory to display only alive players, not observers or people in the lobby
for(var/mob/mob in GLOB.alive_player_list)
// Skip people who are opted out
if(!READ_PREFS(mob, toggle/show_in_directory))
continue
// Just in case ?
if(QDELETED(mob))
continue

ref = REF(mob)

// Different approach for humans and silicons
if(ishuman(mob))
var/mob/living/carbon/human/human = mob
//If someone is obscured without flavor text visible, we don't want them on the Directory.
if((human.wear_mask && (human.wear_mask.flags_inv & HIDEFACE)) || (human.head && (human.head.flags_inv & HIDEFACE)) || (HAS_TRAIT(human, TRAIT_UNKNOWN)))
continue
//Display custom species, otherwise show base species instead
species = (READ_PREFS(human, text/custom_species)) || "Unset"
if(species == "Unset")
species = "[human.dna.species.name]"
//Load standard flavor text preference
flavor_text = READ_PREFS(human, text/flavor_text) || ""
headshot = human.dna.features["headshot"] || ""
else if(issilicon(mob))
var/mob/living/silicon/silicon = mob
//If the target is a silicon, we want it to show its brain as its species
species = READ_PREFS(silicon, choiced/brain_type)
//Load silicon flavor text in place of normal flavor text
flavor_text = READ_PREFS(silicon, text/silicon_flavor_text) || ""
headshot = READ_PREFS(silicon, text/headshot) || ""
// Don't show if they are not a human or a silicon
else
continue

// List of all the shown ERP preferences in the Directory. If there is none, return "Unset"
attraction = READ_PREFS(mob, choiced/attraction) || "Unspecified"
gender = READ_PREFS(mob, choiced/display_gender) || "Unset"
if(gender == "Unset")
gender = capitalize(mob.gender)
erp = READ_PREFS(mob, choiced/erp_status) || "Ask"
vore = READ_PREFS(mob, choiced/erp_status_v) || "Ask"
noncon = READ_PREFS(mob, choiced/erp_status_nc) || "Ask"
hypno = READ_PREFS(mob, choiced/erp_status_hypno) || "Ask"
character_ad = READ_PREFS(mob, text/character_ad) || ""
ooc_notes = READ_PREFS(mob, text/ooc_notes) || ""
// And finally, we want to get the mob's name, taking into account disguised names.
name = mob.real_name ? mob.name : mob.real_name

directory_mobs.Add(list(list(
"name" = name,
"appearance_name" = mob.real_name,
"species" = species,
"ooc_notes" = ooc_notes,
"attraction" = attraction,
"gender" = gender,
"erp" = erp,
"vore" = vore,
"noncon" = noncon,
"hypno" = hypno,
"character_ad" = character_ad,
"flavor_text" = flavor_text,
"headshot" = headshot,
"ref" = ref
)))

data["directory"] = directory_mobs

return data

/datum/character_directory/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()

if(.)
return

var/mob/user = usr
if(!user)
return

switch(action)
if("refresh")
// This is primarily to stop malicious users from trying to lag the server by spamming this verb
if(!COOLDOWN_FINISHED(user.client, char_directory_cooldown))
to_chat(user, "<span class='warning'>Please wait before refreshing the directory again.</span>")
return
COOLDOWN_START(user.client, char_directory_cooldown, 10)
update_static_data(user, ui)
return TRUE
if("orbit")
var/ref = params["ref"]
var/mob/dead/observer/ghost = user
var/atom/movable/poi = (locate(ref) in GLOB.mob_list)
if (poi == null)
return TRUE
ghost.ManualFollow(poi)
ghost.reset_perspective(null)
return TRUE
if("view_character")
update_preview(usr, params["assigned_view"], GLOB.name_to_appearance[params["name"]])
return TRUE
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
to_chat(usr, span_notice("[jointext(line, "\n")]"))
if("open_examine_panel")
mob_examine_panel.ui_interact(usr) //datum has a examine_panel component, here we open the window
if("open_character_ad")
usr.client?.show_character_directory(specific_ad = real_name)

/mob/living/carbon/human/species/vox
race = /datum/species/vox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
if (flavor_text_link)
. += flavor_text_link

if (client?.prefs.read_preference(/datum/preference/text/character_ad))
. += span_notice("They have an ad in the character directory... <a href='?src=[REF(src)];lookup_info=open_character_ad'>\[Open directory?\]</a>")

if(client)
var/erp_status_pref = client.prefs.read_preference(/datum/preference/choiced/erp_status)
if(erp_status_pref && !CONFIG_GET(flag/disable_erp_preferences))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
mob_examine_panel.ui_interact(usr) //datum has a examine_panel datum, here we open the window
if(href_list["temporary_flavor"]) // we need this here because tg code doesnt call parent in /mob/living/silicon/Topic()
show_temp_ftext(usr)
if(href_list["lookup_info"] == "open_character_ad")
usr.client?.show_character_directory(specific_ad = name)
5 changes: 5 additions & 0 deletions modular_nova/modules/title_screen/code/new_player.dm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
ViewManifest()
return

if(href_list["view_directory"])
play_lobby_button_sound()
client?.show_character_directory()
return

if(href_list["toggle_antag"])
play_lobby_button_sound()
var/datum/preferences/preferences = client.prefs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ GLOBAL_LIST_EMPTY(startup_messages)
dat += {"
<a class="menu_button" href='?src=[text_ref(src)];late_join=1'>JOIN GAME</a>
<a class="menu_button" href='?src=[text_ref(src)];view_manifest=1'>CREW MANIFEST</a>
<a class="menu_button" href='?src=[text_ref(src)];view_directory=1'>CHARACTER DIRECTORY</a>
"}

dat += {"<a class="menu_button" href='?src=[text_ref(src)];observe=1'>OBSERVE</a>"}
Expand Down
Loading

0 comments on commit 4b06b11

Please sign in to comment.