mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-13 03:02:38 +00:00
## About The Pull Request Fixes #82440 This PR just creates a new macro, `LOWER_TEXT()` (yes the irony is not lost on me) to wrap around all calls of `lowertext()` and ensure that whatever we input into that proc will be stringified using the `"[]"` (or `tostring()` for the nerds) operator. very simple. I also added a linter to enforce this (and prevent all forms of regression) because I think that machines should do the menial work and we shouldn't expect maintainers to remember this, let me know if you disagree. if there is a time when it should be opted out for some reason, the linter does respect it if you wrap your input with the `UNLINT()` function.
425 lines
14 KiB
Plaintext
425 lines
14 KiB
Plaintext
/**
|
|
* Little tidbits of past events generated by the player doing things.
|
|
*
|
|
* Can be used in engravings, dreams, and changeling succs.
|
|
*
|
|
* All of those things are supposed to be taken vaguely -
|
|
* Engravings crossround and should not include names, dreams and succs are memory goop.
|
|
* As such, the generated text of the memory is vague.
|
|
*
|
|
* Don't hold any references in this, it's not necessary.
|
|
*/
|
|
/datum/memory
|
|
/// The name of the MEMORY that the user sees.
|
|
/// Something like "The time the Clown did a sweet flip".
|
|
var/name
|
|
/// Job of the person memorizing the event
|
|
var/memorizer
|
|
/// Mind of who is memorizing the event
|
|
var/datum/mind/memorizer_mind
|
|
/// The value of the mood in it's worth as a story.
|
|
/// Defines how beautiful art from it can be, and whether or not it stays in persistence.
|
|
var/story_value = STORY_VALUE_NONE
|
|
/// Flags of any special behavior for the memory
|
|
var/memory_flags = NONE
|
|
/// If this memory changes based on mood, this is the verb it uses.
|
|
var/mood_verb
|
|
|
|
// Below are common infobits passed to memories, placed into strings.
|
|
// You can add your own to subtypes of memories to add more infobits.
|
|
// Just remember to override New() with your additional arguments
|
|
// (And call parent at the end of your override, not the beginning).
|
|
|
|
/// The main character of the memory.
|
|
var/protagonist_name
|
|
/// The side character of the memory.
|
|
var/deuteragonist_name
|
|
/// The main villain of the memory.
|
|
var/antagonist_name
|
|
/// Where this memory took place.
|
|
var/where
|
|
|
|
/datum/memory/New(
|
|
datum/mind/memorizer_mind,
|
|
atom/protagonist,
|
|
atom/deuteragonist,
|
|
atom/antagonist,
|
|
...
|
|
)
|
|
|
|
// If we weren't supplied a protag, use our memorizer by default.
|
|
if(isnull(protagonist))
|
|
protagonist = memorizer_mind.current
|
|
|
|
src.memorizer_mind = memorizer_mind
|
|
src.memorizer = build_story_character(memorizer_mind)
|
|
src.protagonist_name = build_story_character(protagonist)
|
|
src.deuteragonist_name = build_story_character(deuteragonist)
|
|
src.antagonist_name = build_story_character(antagonist)
|
|
|
|
if(!src.where && isatom(protagonist) && !(memory_flags & MEMORY_FLAG_NOLOCATION))
|
|
src.where = get_area_name(protagonist)
|
|
|
|
if(!(memory_flags & MEMORY_FLAG_NOMOOD))
|
|
var/story_mood = MOODLESS_MEMORY
|
|
if(isliving(protagonist))
|
|
var/mob/living/the_main_character = protagonist
|
|
story_mood = the_main_character.mob_mood?.mood_level || MOODLESS_MEMORY
|
|
|
|
select_mood_verb(story_mood)
|
|
|
|
// This happens after everything's all set, remember this for New overrides
|
|
generate_memory_name()
|
|
|
|
/datum/memory/Destroy(force)
|
|
memorizer_mind = null
|
|
return ..()
|
|
|
|
/datum/memory/serialize_list(list/options, list/semvers)
|
|
. = ..()
|
|
|
|
.["name"] = name
|
|
.["memorizer"] = memorizer
|
|
.["story_value"] = story_value
|
|
.["memory_flags"] = memory_flags
|
|
.["mood_verb"] = mood_verb
|
|
.["protagonist_name"] = protagonist_name
|
|
.["deuteragonist_name"] = deuteragonist_name
|
|
.["antagonist_name"] = antagonist_name
|
|
.["where"] = where
|
|
|
|
SET_SERIALIZATION_SEMVER(semvers, "1.0.0")
|
|
return .
|
|
|
|
/**
|
|
* Generates a name for the memory.
|
|
*/
|
|
/datum/memory/proc/generate_memory_name()
|
|
var/list/potential_names = get_names()
|
|
if(!length(potential_names))
|
|
// Someone forgot to implement get_names - it will stack trace, so we just need to return
|
|
name = "Erroneous memory - This is a bug"
|
|
story_value = STORY_VALUE_SHIT
|
|
memory_flags |= (MEMORY_FLAG_NOPERSISTENCE|MEMORY_NO_STORY)
|
|
return
|
|
|
|
name = capitalize(pick(potential_names))
|
|
|
|
/**
|
|
* Selects a mood related verb for the memory.
|
|
*
|
|
* Arguments
|
|
* * story_mood - What mood level should we use to select a verb from?
|
|
*/
|
|
/datum/memory/proc/select_mood_verb(story_mood)
|
|
if(story_mood == MOODLESS_MEMORY)
|
|
// The protagonist didn't end up having a mood, so just continue on
|
|
memory_flags |= MEMORY_FLAG_NOMOOD
|
|
return
|
|
|
|
var/list/possible_verbs
|
|
switch(story_mood)
|
|
if(MOOD_SAD4 to MOOD_SAD2)
|
|
possible_verbs = get_sad_moods()
|
|
|
|
if(MOOD_SAD2 to MOOD_HAPPY2)
|
|
possible_verbs = get_neutral_moods()
|
|
|
|
if(MOOD_HAPPY2 to MOOD_HAPPY4)
|
|
possible_verbs = get_happy_moods()
|
|
|
|
if(!length(possible_verbs))
|
|
return
|
|
|
|
mood_verb = pick(possible_verbs)
|
|
|
|
/**
|
|
* Returns a list of names for [proc/select_mood_verb] to select from.
|
|
*
|
|
* This is necessary to implement. Names should be at-a-glance summaries of what the memory entails.
|
|
*
|
|
* For example: "The time the Clown did a sweet flip.".
|
|
* You can use any information tidbits in your names to fill them out.
|
|
* Your names should be puncuated.
|
|
*/
|
|
/datum/memory/proc/get_names()
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
stack_trace("[type] didn't have any names setup, these are necessary for all memories!")
|
|
return list()
|
|
|
|
/**
|
|
* Returns a list of story starts for the memory.
|
|
*
|
|
* Starts are necessary if [MEMORY_FLAG_NOSTORY] is not set. They are used in generating stories out of memories.
|
|
*
|
|
* For example: "The Clown cracks his hands and honks his horn as he prepares to do a backflip".
|
|
* You can use any information tidbits in your names to fill them out.
|
|
* If the memory is not [MEMORY_FLAG_NOMOOD], your starts should NOT be puncuated, as a mood phrase will follow.
|
|
* They should also be in the present tense.
|
|
*/
|
|
/datum/memory/proc/get_starts()
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
stack_trace("[type] didn't have any starts setup, these are necessary if the MEMORY_FLAG_NOSTORY is not set!")
|
|
return list()
|
|
|
|
/**
|
|
* Returns a list of mood phrases for the memory.
|
|
*
|
|
* Mood phrases are necessary if [MEMORY_FLAG_NOMOOD] is not set. They are used in making stories out of memories.
|
|
* These are phrases that change in their verbage depending on the mood of the PROTAGONIST at the time of the memory.
|
|
*
|
|
* For example: "The clown grins (this is the mood verb) at the audience.".
|
|
* You can use any information tidbits in your names to fill them out.
|
|
* Mood phrases should be punctated, as they are their own independent clause.
|
|
* Mood phrases should always include the [mood_verb] var, as well.
|
|
*/
|
|
/datum/memory/proc/get_moods()
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
stack_trace("[type] didn't have any mood phrases, these are necessary if the MEMORY_FLAG_NOMOOD is not set!")
|
|
return list()
|
|
|
|
/**
|
|
* Used to select a mood verb if the protagonist is happy for memories that do not have [MEMORY_FLAG_NOMOOD] set.
|
|
*/
|
|
/datum/memory/proc/get_happy_moods()
|
|
return list(
|
|
"chuckles",
|
|
"has a huge grin",
|
|
"has a twisted grin like a maniac",
|
|
"is silently working away like a pro",
|
|
"looks determined",
|
|
"seems cheerful about it all",
|
|
"seems confident",
|
|
"whistles to themselves",
|
|
)
|
|
|
|
/**
|
|
* Used to select a mood verb if the protagonist is neither happy or sad for memories that do not have [MEMORY_FLAG_NOMOOD] set.
|
|
*/
|
|
/datum/memory/proc/get_neutral_moods()
|
|
return list(
|
|
"appears clueless",
|
|
"is darting their eyes around",
|
|
"is impatient",
|
|
"is uninterested",
|
|
"looks around cautiously",
|
|
"seems a bit sleepy",
|
|
"seems okay",
|
|
"works diligently",
|
|
)
|
|
|
|
|
|
/**
|
|
* Used to select a mood verb if the protagonist is sad for memories that do not have [MEMORY_FLAG_NOMOOD] set.
|
|
*/
|
|
/datum/memory/proc/get_sad_moods()
|
|
return list(
|
|
"appears crushed",
|
|
"has dried tears on their face",
|
|
"is complaining loudly",
|
|
"is having a temper tantrum",
|
|
"is whiney about it all",
|
|
"looks angry",
|
|
"seems sad",
|
|
)
|
|
|
|
/**
|
|
* Returns a list of locations for use in stories which do not have [MEMORY_FLAG_NOLOCATION] set.
|
|
*/
|
|
/datum/memory/proc/get_locations()
|
|
return list(
|
|
"in [where].",
|
|
"while in [where]."
|
|
)
|
|
|
|
/**
|
|
* Generates a story based on this memory.
|
|
*
|
|
* Arguments
|
|
* * story_type - for used in grabbing phrases from memories.json involving specific types of stories.
|
|
* * story_flags - any additional flags involving the story
|
|
*/
|
|
/datum/memory/proc/generate_story(story_type, story_flags)
|
|
|
|
//entirely independent vars (not related to the action or story type)
|
|
|
|
var/static/list/something_pool = list(
|
|
/mob/living/basic/bat,
|
|
/mob/living/basic/bear,
|
|
/mob/living/basic/blob_minion/blobbernaut,
|
|
/mob/living/basic/butterfly,
|
|
/mob/living/basic/carp,
|
|
/mob/living/basic/carp/magic,
|
|
/mob/living/basic/carp/magic/chaos,
|
|
/mob/living/basic/chick,
|
|
/mob/living/basic/chicken,
|
|
/mob/living/basic/cow,
|
|
/mob/living/basic/cow/wisdom,
|
|
/mob/living/basic/crab,
|
|
/mob/living/basic/goat,
|
|
/mob/living/basic/gorilla,
|
|
/mob/living/basic/headslug,
|
|
/mob/living/basic/killer_tomato,
|
|
/mob/living/basic/lizard,
|
|
/mob/living/basic/mining/goliath,
|
|
/mob/living/basic/mining/watcher,
|
|
/mob/living/basic/morph,
|
|
/mob/living/basic/mouse,
|
|
/mob/living/basic/mushroom,
|
|
/mob/living/basic/parrot,
|
|
/mob/living/basic/pet/cat,
|
|
/mob/living/basic/pet/cat/cak,
|
|
/mob/living/basic/pet/dog/breaddog,
|
|
/mob/living/basic/pet/dog/corgi,
|
|
/mob/living/basic/pet/dog/pug,
|
|
/mob/living/basic/pet/fox,
|
|
/mob/living/basic/spider/giant,
|
|
/mob/living/basic/spider/giant/hunter,
|
|
/mob/living/basic/statue,
|
|
/mob/living/basic/stickman,
|
|
/mob/living/basic/stickman/dog,
|
|
/mob/living/simple_animal/hostile/megafauna/dragon/lesser,
|
|
/obj/item/food/sausage/american,
|
|
/obj/item/skub,
|
|
)
|
|
|
|
// These are picked from the json
|
|
var/list/forewords = strings(MEMORY_FILE, story_type + "_forewords")
|
|
var/list/somethings = strings(MEMORY_FILE, story_type + "_somethings")
|
|
var/list/styles
|
|
if(!(story_flags & STORY_FLAG_NO_STYLE))
|
|
styles = strings(MEMORY_FILE, "styles")
|
|
if("[story_type]_styles" in GLOB.string_cache[MEMORY_FILE])
|
|
styles += strings(MEMORY_FILE, story_type + "_styles")
|
|
|
|
// These are picked from the datum
|
|
var/list/wheres = (memory_flags & MEMORY_FLAG_NOLOCATION) ? null : get_locations()
|
|
var/list/story_starts = get_starts()
|
|
var/list/story_mood_sentences = (memory_flags & MEMORY_FLAG_NOMOOD) ? null : get_moods()
|
|
var/mob/living/crew_member
|
|
var/atom/something = pick(something_pool) //Pick a something for the potential something line
|
|
|
|
// This can be signalized or something in the future
|
|
var/datum/antagonist/obsessed/creeper = memorizer_mind.has_antag_datum(/datum/antagonist/obsessed)
|
|
if(creeper && creeper.trauma.obsession)
|
|
crew_member = creeper.trauma.obsession //ALWAYS ENGRAVE MY OBSESSION!
|
|
|
|
// Get a random crewmember
|
|
else
|
|
var/list/crew_members = list()
|
|
for(var/mob/living/carbon/human/potential_crew_member as anything in GLOB.human_list)
|
|
if(potential_crew_member.mind?.assigned_role.job_flags & JOB_CREW_MEMBER)
|
|
crew_members += potential_crew_member
|
|
|
|
crew_member = length(crew_members) ? pick(crew_members) : "an unknown crewmember"
|
|
|
|
//storybuilding
|
|
var/list/story_pieces = list()
|
|
|
|
//The forewords for this specific type of story (E.g. This engraving depicts)
|
|
story_pieces += pick(forewords)
|
|
//The story start for this specific action. (E.g. The Chef carving into The Clown)
|
|
story_pieces += pick(story_starts)
|
|
//The location it happend, which isn't always included, but commonly is. (E.g. in Space, while in the Bar)
|
|
if(length(wheres))
|
|
story_pieces += pick(wheres)
|
|
//Shows how the protagonist felt about it all (E.g. The Chef is looking sad as they tear into The Clown.)
|
|
if(length(story_mood_sentences))
|
|
story_pieces += pick(story_mood_sentences)
|
|
//A nonsensical addition, using the memorizer, protagonist or even random crew / things (E.g. in the meantime, the Clown is being arrested, clutching a skub.")
|
|
if(prob(75))
|
|
var/chosen_addition = pick(somethings)
|
|
chosen_addition = replacetext(chosen_addition, "%MEMORIZER", "[memorizer]")
|
|
chosen_addition = replacetext(chosen_addition, "%PROTAGONIST", protagonist_name)
|
|
chosen_addition = replacetext(chosen_addition, "%SOMETHING", initial(something.name))
|
|
chosen_addition = replacetext(chosen_addition, "%CREWMEMBER", build_story_character(crew_member))
|
|
chosen_addition = replacetext(chosen_addition, "%STORY_TYPE", story_type)
|
|
|
|
story_pieces += chosen_addition
|
|
//Explains any unique styling the art has. e.g. (The engraving has a cubist style.)
|
|
if(length(styles) && prob(75))
|
|
story_pieces += pick(styles)
|
|
|
|
var/parsed_story = ""
|
|
|
|
var/capitalize_next_line = FALSE
|
|
|
|
for(var/line in story_pieces)
|
|
if(capitalize_next_line)
|
|
line = capitalize(line)
|
|
capitalize_next_line = FALSE
|
|
|
|
if(line[length(line)] == ".")//End of sentence, next sentence needs to start with a capital.'
|
|
capitalize_next_line = TRUE
|
|
|
|
if(line != story_pieces[story_pieces.len]) //not the last line
|
|
parsed_story += "[line] "
|
|
|
|
//after replacement section for performance
|
|
if(story_flags & STORY_FLAG_DATED)
|
|
if(memory_flags & MEMORY_FLAG_NOSTATIONNAME)
|
|
parsed_story += "This took place in [time2text(world.realtime, "Month")] of [CURRENT_STATION_YEAR]."
|
|
else
|
|
parsed_story += "This took place in [time2text(world.realtime, "Month")] of [CURRENT_STATION_YEAR] on [station_name()]."
|
|
|
|
parsed_story = trim_right(parsed_story)
|
|
|
|
return parsed_story
|
|
|
|
/**
|
|
* When passed a "character", returns the name of the character formatted for stories
|
|
*
|
|
* If character is a string, it will just return it back.
|
|
*
|
|
* Otherwise, it will try to generate a title based on the mob's assigned role.
|
|
*
|
|
* If the character has no mind or no assigned role, it'll just return their name.
|
|
*/
|
|
/datum/memory/proc/build_story_character(character)
|
|
if(isnull(character))
|
|
return
|
|
if(istext(character))
|
|
return character
|
|
|
|
if(isliving(character))
|
|
var/mob/living/living_character = character
|
|
if(living_character.mind && !is_unassigned_job(living_character.mind.assigned_role))
|
|
character = living_character.mind
|
|
|
|
else if(ishuman(character))
|
|
// This can slip into memories involving monkey humans.
|
|
return "the unfamiliar person"
|
|
|
|
if(istype(character, /datum/mind))
|
|
var/datum/mind/character_mind = character
|
|
return "\the [LOWER_TEXT(initial(character_mind.assigned_role.title))]"
|
|
|
|
// Generic result - mobs get "the guy", objs / turfs get "a thing"
|
|
return ismob(character) ? "\the [character]" : "\a [character]"
|
|
|
|
/**
|
|
* Creates a "quick copy" of the memory for another mind,
|
|
* copying just basic memory information (name, major charactecrs) over.
|
|
*
|
|
* The copied memory cannot be used for stories or anything.
|
|
* They should generally only be used to give a new mind an idea of another mind's memories.
|
|
*/
|
|
/datum/memory/proc/quick_copy_memory(datum/mind/new_memorizer)
|
|
var/datum/memory/copy/new_copy = new(new_memorizer, protagonist_name, deuteragonist_name, antagonist_name, where, memory_flags)
|
|
new_copy.name = name
|
|
new_copy.story_value = story_value
|
|
return new_copy
|
|
|
|
// To only be used by quick copies of memories
|
|
/datum/memory/copy
|
|
memory_flags = MEMORY_NO_STORY
|
|
|
|
/datum/memory/copy/New(datum/mind/memorizer_mind, atom/protagonist, atom/deuteragonist, atom/antagonist, where, new_memory_flags)
|
|
src.where = where
|
|
src.memory_flags |= new_memory_flags
|
|
return ..()
|
|
|
|
/datum/memory/copy/generate_memory_name()
|
|
// We just copy the original memory's name anyways
|
|
return
|