Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Needs Testmerge] Guestbook, from Mojave Sun #2770

Merged
merged 47 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
08810cb
https://github.com/Mojave-Sun/mojave-sun-13/pull/2415
meemofcourse Feb 26, 2024
76c1258
https://github.com/Mojave-Sun/mojave-sun-13/pull/2433
meemofcourse Feb 26, 2024
4a9e27b
fixes runtime
meemofcourse Feb 26, 2024
a7e6bba
Update guestbook.dm
meemofcourse Feb 26, 2024
3246dc0
wtf
meemofcourse Feb 26, 2024
4d479a4
prettier
meemofcourse Feb 26, 2024
4921804
look at those linters.
meemofcourse Feb 26, 2024
a4f13bb
Update human_helpers.dm
meemofcourse Feb 26, 2024
fe2bdbb
insanity
meemofcourse Feb 26, 2024
3a8b7ed
sigh
meemofcourse Feb 26, 2024
df7ed0c
i Love Mulch
meemofcourse Feb 26, 2024
c8f23db
hopefully makes it so guestbook saves to character slots
meemofcourse Feb 26, 2024
b44d680
ert know each other
meemofcourse Feb 27, 2024
5fdfdf3
know your crew
meemofcourse Feb 27, 2024
774a156
Merge branch 'master' into guestbook
meemofcourse Feb 27, 2024
c7a0458
obliterate persistence, damage control
meemofcourse Feb 29, 2024
1f241e4
no middle aged positronics
meemofcourse Feb 29, 2024
b4c47cc
observer check
meemofcourse Feb 29, 2024
f704f30
know your crewmembers (for real this time)
meemofcourse Feb 29, 2024
d7d2808
admin observer check, adding guest defaults to visible name
meemofcourse Feb 29, 2024
6fd4699
ipc adjectives
meemofcourse Feb 29, 2024
11a9d99
changes how ID cards work
meemofcourse Mar 1, 2024
ba75ae0
Update preference_adjectives.txt
meemofcourse Mar 1, 2024
3790d13
no voice adjective
meemofcourse Mar 1, 2024
5c68711
Update admin.dm
meemofcourse Mar 1, 2024
409f205
Makes orbit meanu use real name only
MarkSuckerberg Mar 1, 2024
924b617
optimizes adding to crew guestbook
MarkSuckerberg Mar 1, 2024
8ec47d0
makes LOOC show visible name
MarkSuckerberg Mar 1, 2024
04c9c1b
tries to clear up emotes and fixes pai
MarkSuckerberg Mar 1, 2024
1aa430c
moves species name to actual name
MarkSuckerberg Mar 1, 2024
da61481
Integrated Positronic Chassis to Positronic
meemofcourse Mar 2, 2024
85da003
show your id card to people. also phorids
meemofcourse Mar 2, 2024
34c0924
wow isn't this so readable
meemofcourse Mar 2, 2024
be1f46b
Update strings/ipc_preference_adjectives.txt
meemofcourse Mar 4, 2024
520fd87
Update strings/preference_adjectives.txt
meemofcourse Mar 4, 2024
aecef4f
Merge remote-tracking branch 'upstream/master' into guestbook
meemofcourse Mar 4, 2024
09bd607
alphabetical order preference_adjectives.txt
meemofcourse Mar 7, 2024
3ca118a
Update ipc_preference_adjectives.txt
meemofcourse Mar 7, 2024
6b12201
chameleon
meemofcourse Mar 9, 2024
dd96324
Merge remote-tracking branch 'upstream/master' into guestbook
meemofcourse Mar 9, 2024
5f98914
Revert "Makes orbit meanu use real name only"
meemofcourse Mar 9, 2024
e4a99d8
Merge remote-tracking branch 'upstream/master' into guestbook
meemofcourse Mar 12, 2024
aa6a240
Merge branch 'master' into guestbook
MarkSuckerberg Mar 16, 2024
6f3d9dd
Merge remote-tracking branch 'upstream/master' into guestbook
meemofcourse Mar 16, 2024
5efa768
Merge branch 'guestbook' of https://github.com/meemofcourse/Shiptest …
meemofcourse Mar 16, 2024
a27aeb5
Merge remote-tracking branch 'upstream/master' into guestbook
meemofcourse Mar 27, 2024
995316e
Merge remote-tracking branch 'upstream/master' into guestbook
meemofcourse Mar 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 22 additions & 12 deletions code/__DEFINES/dcs/signals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -224,19 +224,29 @@
#define COMSIG_LIVING_GET_PULLED "living_start_pulled"

/////////////////

#define COMSIG_ENTER_AREA "enter_area" //from base of area/Entered(): (/area)
#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area)

#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user)
#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob)
#define COMPONENT_ALLOW_EXAMINATE 1 //Allows the user to examinate regardless of client.eye.
#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob)
#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob)
#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob)
#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user)
//from base of area/Entered(): (/area)
#define COMSIG_ENTER_AREA "enter_area"
//from base of area/Exited(): (/area)
#define COMSIG_EXIT_AREA "exit_area"
//from base of atom/Click(): (location, control, params, mob/user)
#define COMSIG_CLICK "atom_click"
//from base of atom/ShiftClick(): (/mob)
#define COMSIG_CLICK_SHIFT "shift_click"
//Allows the user to examinate regardless of client.eye.
#define COMPONENT_ALLOW_EXAMINATE 1
//from base of atom/CtrlClickOn(): (/mob)
#define COMSIG_CLICK_CTRL "ctrl_click"
//from base of atom/AltClick(): (/mob)
#define COMSIG_CLICK_ALT "alt_click"
//from base of atom/CtrlShiftClick(/mob)
#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click"
///from base of atom/CtrlShiftRightClick(/mob)
#define COMSIG_CLICK_CTRL_SHIFT_RIGHT "ctrl_shift_right_click"
//from base of atom/MouseDrop(): (/atom/over, /mob/user)
#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto"
#define COMPONENT_NO_MOUSEDROP 1
#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user)
//from base of atom/MouseDrop_T: (/atom/from, /mob/user)
#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto"

///from base of area/proc/power_change(): ()
#define COMSIG_AREA_POWER_CHANGE "area_power_change"
Expand Down
2 changes: 1 addition & 1 deletion code/__HELPERS/_lists.dm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#define LAZYCLEARLIST(L) if(L) L.Cut()
#define SANITIZE_LIST(L) (islist(L) ? L : list())
#define reverseList(L) reverseRange(L.Copy())
#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += list(V);
#define LAZYADDASSOC(L, K, V) if(!L) { L = list(); } L[K] += V;
#define LAZYADDASSOCLIST(L, K, V) if(!L) { L = list(); } L[K] += list(V);
#define LAZYREMOVEASSOC(L, K, V) if(L) { if(L[K]) { L[K] -= V; if(!length(L[K])) L -= K; } if(!length(L)) L = null; }
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
Expand Down
1 change: 1 addition & 0 deletions code/_globalvars/lists/names.dm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt"))
GLOBAL_LIST_INIT(ing_verbs, world.file2list("strings/names/ing_verbs.txt"))
GLOBAL_LIST_INIT(adverbs, world.file2list("strings/names/adverbs.txt"))
GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt"))
GLOBAL_LIST_INIT(preference_adjectives, world.file2list("strings/preference_adjectives.txt"))
GLOBAL_LIST_INIT(dream_strings, world.file2list("strings/dreamstrings.txt"))
//loaded on startup because of "
//would include in rsc if ' was used
Expand Down
1 change: 0 additions & 1 deletion code/_onclick/click.dm
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@

/**
* Control+Shift click
* Unused except for AI
*/
/mob/proc/CtrlShiftClickOn(atom/A)
A.CtrlShiftClick(src)
Expand Down
13 changes: 13 additions & 0 deletions code/controllers/subsystem/persistence.dm
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ SUBSYSTEM_DEF(persistence)
SavePaintings()
save_custom_outfits()
SavePanicBunker()
SaveGuestBooks()

/datum/controller/subsystem/persistence/proc/GetPhotoAlbums()
var/album_path = file("data/photo_albums.json")
Expand Down Expand Up @@ -255,6 +256,18 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file)
WRITE_FILE(json_file, json_encode(paintings))

/datum/controller/subsystem/persistence/proc/SaveGuestBooks()
for(var/player in GLOB.joined_player_list)
var/mob/living/carbon/human/guy = get_mob_by_ckey(player)
if(!istype(guy) || !guy.mind || !guy.mind.guestbook || !guy.client)
continue

var/mob/living/carbon/human/guy_spawned_as = guy.mind.original_character
if(!(guy == guy_spawned_as))
return
guy.client.prefs.guestbook_names = guy.mind.guestbook.known_names
guy.client.prefs.save_character()

/datum/controller/subsystem/persistence/proc/load_custom_outfits()
var/file = file("data/custom_outfits.json")
if(!fexists(file))
Expand Down
2 changes: 1 addition & 1 deletion code/datums/chatmessage.dm
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@
message.maptext = complete_text

// View the message
LAZYADDASSOC(owned_by.seen_messages, message_loc, src)
LAZYADDASSOCLIST(owned_by.seen_messages, message_loc, src)
owned_by.images |= message
animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME)

Expand Down
148 changes: 148 additions & 0 deletions code/datums/guestbook.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* THE GUESTBOOK DATUM // ripped straight from mojave.
*
* Essentially, this datum handles the people that a given human knows,
* to handle getting the correct names on examine and saycode.
*/
/datum/guestbook
/// Associative list of known guests, real_name = known_name
var/list/known_names

/datum/guestbook/Destroy(force)
known_names = null
return ..()

/datum/guestbook/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "Guestbook", "[user.real_name]'s Guestbook")
ui.set_autoupdate(FALSE)
ui.open()

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

/datum/guestbook/ui_data(mob/user)
var/list/data = list()
var/list/names = list()
for(var/real_name in known_names)
var/given_name = LAZYACCESS(known_names, real_name)
names += list(list("real_name" = real_name, "given_name" = given_name))
data["names"] = names
return data

/datum/guestbook/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return .
switch(action)
if("rename_guest")
var/real_name = params["real_name"]
var/new_name = params["new_name"]
new_name = reject_bad_name(new_name, max_length = 42)
if(!new_name)
to_chat(usr, span_warning("That's a pretty terrible name. <i>You can do better</i>."))
return FALSE
if(!rename_guest(usr, null, real_name, new_name, silent = FALSE))
return FALSE
return TRUE
if("delete_guest")
var/real_name = params["real_name"]
if(!remove_guest(usr, null, real_name, silent = FALSE))
return FALSE
return TRUE

/datum/guestbook/proc/try_add_guest(mob/user, mob/living/carbon/human/guest, silent = FALSE)
if(user == guest)
if(!silent)
to_chat(user, span_warning("That's you! You already know yourself plenty."))
return FALSE
if(!visibility_checks(user, guest, silent))
return FALSE
var/given_name = input(user, "What name do you want to give to [guest]?", "Guestbook Name", "")
if(!given_name)
if(!silent)
to_chat(user, span_warning("Nevermind."))
return FALSE
given_name = reject_bad_name(given_name)
if(!given_name)
if(!silent)
to_chat(user, span_warning("That's a pretty terrible name. You can do better."))
return FALSE
if(!visibility_checks(user, guest, silent))
return FALSE
var/face_name = guest.get_face_name("ForgetMeNot")
if(LAZYACCESS(known_names, face_name))
if(!rename_guest(user, guest, face_name, given_name, silent))
return FALSE
else
if(!add_guest(user, guest, face_name, given_name, silent))
return FALSE
return TRUE

/datum/guestbook/proc/add_guest(mob/user, mob/living/carbon/guest, real_name, given_name, silent = TRUE)
//Already exists, should be handled by rename_guest()
var/existing_name = LAZYACCESS(known_names, real_name)
if(existing_name)
if(!silent)
to_chat(user, span_warning("You already know them as \"[existing_name]\"."))
return FALSE
LAZYADDASSOC(known_names, real_name, given_name)
if(!silent)
to_chat(user, span_notice("You memorize the face of [guest] as \"[given_name]\"."))
return TRUE

/datum/guestbook/proc/rename_guest(mob/user, mob/living/carbon/guest, real_name, given_name, silent = TRUE)
var/old_name = LAZYACCESS(known_names, real_name)
if(!old_name)
return FALSE
known_names[real_name] = given_name
if(!silent)
to_chat(user, span_notice("You re-memorize the face of \"[old_name]\" as \"[given_name]\"."))
return TRUE

/datum/guestbook/proc/try_remove_guest(mob/user, mob/living/carbon/human/guest, silent = FALSE)
if(user == guest)
if(!silent)
to_chat(user, span_warning("That's you! You'll never forget yourself."))
return
if(!visibility_checks(user, guest, silent))
return FALSE
var/face_name = guest.get_face_name("ForgetMeNot")
if(!remove_guest(user, guest, face_name, silent))
return FALSE
return TRUE

/datum/guestbook/proc/remove_guest(mob/user, mob/living/carbon/guest, real_name, silent = TRUE)
//Already exists, should be handled by rename_guest()
var/existing_name = LAZYACCESS(known_names, real_name)
if(!existing_name)
if(!silent)
to_chat(user, span_warning("You don't know them in the first place."))
return FALSE
LAZYREMOVE(known_names, real_name)
if(!silent)
to_chat(user, span_notice("You forget the face of \"[existing_name]\"."))
return TRUE

/datum/guestbook/proc/get_known_name(mob/user, mob/living/carbon/guest, real_name)
if(user == guest)
return real_name
return LAZYACCESS(known_names, real_name)

/datum/guestbook/proc/visibility_checks(mob/user, mob/living/carbon/human/guest, silent = FALSE)
if(QDELETED(guest))
if(!silent)
to_chat(user, span_warning("What?"))
return FALSE
var/visible_name = guest.get_visible_name("")
var/face_name = guest.get_face_name("")
if(!visible_name || !face_name)
if(!silent)
to_chat(user, span_warning("You can't see their face very well!"))
return FALSE
if(get_dist(user, guest) > 4)
if(!silent)
to_chat(user, span_warning("You need to take a closer look at them!"))
return FALSE
return TRUE
5 changes: 5 additions & 0 deletions code/datums/mind.dm
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@
/// The index for our current scar slot, so we don't have to constantly check the savefile (unlike the slots themselves, this index is independent of selected char slot, and increments whenever a valid char is joined with)
var/current_scar_slot_index

/// Guestbook datum, in case we actually make use of the guestbook mechanics
var/datum/guestbook/guestbook

///Skill multiplier, adjusts how much xp you get/loose from adjust_xp. Dont override it directly, add your reason to experience_multiplier_reasons and use that as a key to put your value in there.
var/experience_multiplier = 1
///Skill multiplier list, just slap your multiplier change onto this with the type it is coming from as key.
Expand All @@ -95,13 +98,15 @@
key = _key
soulOwner = src
martial_art = default_martial_art
guestbook = new()
init_known_skills()

/datum/mind/Destroy()
SSticker.minds -= src
if(islist(antag_datums))
QDEL_LIST(antag_datums)
QDEL_NULL(language_holder)
QDEL_NULL(guestbook)
set_current(null)
soulOwner = null
return ..()
Expand Down
2 changes: 1 addition & 1 deletion code/datums/progressbar.dm
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
bar.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
user = User

LAZYADDASSOC(user.progressbars, bar_loc, src)
LAZYADDASSOCLIST(user.progressbars, bar_loc, src)
var/list/bars = user.progressbars[bar_loc]
listindex = bars.len

Expand Down
6 changes: 5 additions & 1 deletion code/game/atoms.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,11 @@
active_hud.screentip_text.maptext = ""
else
//We inline a MAPTEXT() here, because there's no good way to statically add to a string like this
active_hud.screentip_text.maptext = "<span class='maptext' style='text-align: center; font-size: 32px; color: [user.client.prefs.screentip_color]'>[name]</span>"
active_hud.screentip_text.maptext = "<span class='maptext' style='text-align: center; font-size: 32px; color: [user.client.prefs.screentip_color]'>[get_screentip_name(client)]</span>"

/// Returns the atom name that should be used on screentip
/atom/proc/get_screentip_name(client/hovering_client)
return name

///Called whenever a player is spawned on the same turf as this atom.
/atom/proc/join_player_here(mob/M)
Expand Down
4 changes: 2 additions & 2 deletions code/game/atoms_movable.dm
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@
if(previous_virtual_z)
LAZYREMOVEASSOC(SSmobs.players_by_virtual_z, "[previous_virtual_z]", src)
if(new_virtual_z)
LAZYADDASSOC(SSmobs.players_by_virtual_z, "[new_virtual_z]", src)
LAZYADDASSOCLIST(SSmobs.players_by_virtual_z, "[new_virtual_z]", src)
SSidlenpcpool.try_wakeup_virtual_z(new_virtual_z)

/mob/dead/on_virtual_z_change(new_virtual_z, previous_virtual_z)
Expand All @@ -570,7 +570,7 @@
if(previous_virtual_z)
LAZYREMOVEASSOC(SSmobs.dead_players_by_virtual_z, "[previous_virtual_z]", src)
if(new_virtual_z)
LAZYADDASSOC(SSmobs.dead_players_by_virtual_z, "[new_virtual_z]", src)
LAZYADDASSOCLIST(SSmobs.dead_players_by_virtual_z, "[new_virtual_z]", src)

// Make sure you know what you're doing if you call this, this is intended to only be called by byond directly.
// You probably want CanPass()
Expand Down
2 changes: 1 addition & 1 deletion code/game/machinery/navbeacon.dm
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
if(previous_virtual_z)
LAZYREMOVEASSOC(GLOB.navbeacons, "[previous_virtual_z]", src)
if(new_virtual_z)
LAZYADDASSOC(GLOB.navbeacons, "[new_virtual_z]", src)
LAZYADDASSOCLIST(GLOB.navbeacons, "[new_virtual_z]", src)
..()

// set the transponder codes assoc list from codes_txt
Expand Down
2 changes: 1 addition & 1 deletion code/game/objects/effects/landmarks.dm
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/landmark)
. = ..()
GLOB.start_landmarks_list += src
if(jobspawn_override)
LAZYADDASSOC(GLOB.jobspawn_overrides, name, src)
LAZYADDASSOCLIST(GLOB.jobspawn_overrides, name, src)
if(name != "start")
tag = "start*[name]"

Expand Down
2 changes: 1 addition & 1 deletion code/game/objects/items/toys.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1404,7 +1404,7 @@
say(message, language)
return NOPASS

/obj/item/toy/dummy/GetVoice()
/obj/item/toy/dummy/GetVoice(if_no_voice = "Unknown")
return doll_name

/obj/item/toy/seashell
Expand Down
34 changes: 26 additions & 8 deletions code/game/say.dm
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,28 @@ GLOBAL_LIST_INIT(freqcolor, list())
//Radio freq/name display
var/freqpart = radio_freq ? "\[[get_radio_name(radio_freq)]\] " : ""
//Speaker name
var/namepart = "[speaker.GetVoice()][speaker.get_alt_name()]"
if(face_name && ishuman(speaker))
var/mob/living/carbon/human/H = speaker
namepart = "[H.get_face_name()]" //So "fake" speaking like in hallucinations does not give the speaker away if disguised

var/namepart = speaker.GetVoice()
var/atom/movable/reliable_narrator = speaker
if(istype(speaker, /atom/movable/virtualspeaker)) //ugh
var/atom/movable/virtualspeaker/fakespeaker = speaker
reliable_narrator = fakespeaker.source
if(ishuman(reliable_narrator))
//So "fake" speaking like in hallucinations does not give the speaker away if disguised
if(face_name)
var/mob/living/carbon/human/human_narrator = reliable_narrator
namepart = human_narrator.name
//otherwise, do guestbook handling
else if(ismob(src))
var/mob/mob_source = src
if(mob_source.mind?.guestbook)
var/known_name = mob_source.mind.guestbook.get_known_name(src, reliable_narrator, namepart)
if(known_name)
namepart = "[known_name]"
else
var/mob/living/carbon/human/human_narrator = reliable_narrator
namepart = "[human_narrator.get_generic_name(prefixed = TRUE, lowercase = FALSE)]"

//End name span.
var/endspanpart = "</span>"

Expand All @@ -66,9 +84,9 @@ GLOBAL_LIST_INIT(freqcolor, list())
else
messagepart = lang_treat(speaker, message_language, raw_message, spans, message_mods)

var/datum/language/D = GLOB.language_datum_instances[message_language]
if(istype(D) && D.display_icon(src))
languageicon = "[D.get_icon()] "
var/datum/language/language = GLOB.language_datum_instances[message_language]
if(istype(language) && language.display_icon(src))
languageicon = "[language.get_icon()] "

messagepart = " <span class='message'>[say_emphasis(messagepart)]</span></span>"

Expand Down Expand Up @@ -176,7 +194,7 @@ GLOBAL_LIST_INIT(freqcolor, list())
return "2"
return "0"

/atom/movable/proc/GetVoice()
/atom/movable/proc/GetVoice(if_no_voice = "Unknown")
return "[src]" //Returns the atom's name, prepended with 'The' if it's not a proper noun

/atom/movable/proc/IsVocal()
Expand Down
Loading
Loading