Files
Bubberstation/code/controllers/subsystem/tcgsetup.dm
LemonInTheDark 12a90800c5 Adds tooltips to /tg/c keywords. Adds support for chat embedded tgui components (#65383)
* Refactors tcg code a bit. Adds support for "keywords"

Idea is to allow card makers to embed tooltips in their card
descriptions.
These tooltips are defined in the keywords.json file
They can be referenced using {$keyword_name}

I've also done some refactoring to move more logic and state onto the
subsystem, and implemented a few keywords from the wiki

* New keywords, applies the old ones to the second card set

* Adds support for embedding react components in tgui chat

This is done by adding the data-component attribute to an html element
The value of that attibute is the component you want to use.
New components can be added by modifying the TGUI_CHAT_COMPONENTS list
in tgui-panel/chat/renderer.js.

Props can also be passed in in a limited capacity.
Any props you wish to pass must be added to
TGUI_CHAT_ATTRIBUTES_TO_PROPS.
This is due to a style restriction of html attributes, they cannot
contain an upper case char.
Use this list to convert between attibute compatible text and the prop's
name.

Props support 3 datatypes.
true and false can be passed by wrapping them in ""s. (Note to self add
a special char here to prevent colison with people just passing the
string true.
Numbers are supported in a limited capacity. Whitespace is not allowed,
but floats and ints are fair game.
And of course, strings are fully supported.

I've currently added support for Tooltip, since that's what I'm using
this for. Also added some tooltip html styles to the chat css.

* Implements the embedded component system to make tcg cards have nice pretty tooltips so people don't need to have the wiki open on one screen

* Adds documentation for embedding tgui components in chat, adds some protection against accidentially sending true as a bool

* Adds italitcs to the tooltips, moves the span stuff to a macro

* tGUI -> tgui, thank fikou

Co-authored-by: Fikou <23585223+Fikou@users.noreply.github.com>

* Style suggestions

Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>

* Removes unneeded key from the components list

Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>

* Removes needless span

* Actually adds the tooltip, oops

Co-authored-by: Fikou <23585223+Fikou@users.noreply.github.com>
Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
2022-03-15 11:36:31 +02:00

189 lines
8.1 KiB
Plaintext

SUBSYSTEM_DEF(trading_card_game)
name = "Trading Card Game"
flags = SS_NO_FIRE
init_order = INIT_ORDER_TCG
/// Base directory for all related string files
var/card_directory = "strings/tcg"
/// List of card files to load
var/list/card_files = list("set_one.json", "set_two.json")
/// List of keyword files
/// These allow you to add on hovor logic to parts of a card's text, displaying extra info
var/list/keyword_files = list("keywords.json")
/// What cardpack types to load
var/card_packs = list(/obj/item/cardpack/series_one, /obj/item/cardpack/resin)
var/list/cached_guar_rarity = list()
var/list/cached_rarity_table = list()
/// List of all cards by series, with cards cached by rarity to make those lookups faster
var/list/cached_cards = list()
/// List of loaded keywords matched with their hovor text
var/list/keywords = list()
var/loaded = FALSE
//Let's load the cards before the map fires, so we can load cards on the map safely
/datum/controller/subsystem/trading_card_game/Initialize()
reloadAllCardFiles()
return ..()
///Loads all the card files
/datum/controller/subsystem/trading_card_game/proc/loadAllCardFiles()
for(var/keyword_file in keyword_files)
loadKeywordFile(keyword_file, card_directory)
styleKeywords()
for(var/card_file in card_files)
loadCardFile(card_file, card_directory)
///Empty the rarity cache so we can safely add new cards
/datum/controller/subsystem/trading_card_game/proc/clearCards()
loaded = FALSE
cached_cards = list()
keywords = list()
///Reloads all card files
/datum/controller/subsystem/trading_card_game/proc/reloadAllCardFiles()
clearCards()
loadAllCardFiles()
loaded = TRUE
///Loads the contents of a json file into our global card list
/datum/controller/subsystem/trading_card_game/proc/loadKeywordFile(filename, directory = "strings/tcg")
var/list/keyword_data = json_decode(file2text("[directory]/[filename]"))
for(var/keyword in keyword_data)
if(keywords[keyword])
stack_trace("Dupe detected, [keyword] was defined by [directory]/[filename] after it already had a value!")
continue
keywords[keyword] = keyword_data[keyword]
///Styles our keywords, converting them from just the raw text to the output we want
/datum/controller/subsystem/trading_card_game/proc/styleKeywords()
// Add the tooltip component to our text, make it pretty
for(var/keyword in keywords)
var/tooltip_text = keywords[keyword]
keywords[keyword] = span_tooltip(tooltip_text, keyword)
///Takes a string as input. Searches it for keywords in the pattern {$keyword}, and replaces them with their expanded form, generated above
/datum/controller/subsystem/trading_card_game/proc/resolve_keywords(search_through)
var/starting_text = search_through
while(TRUE)
var/fragment_start = findtext(search_through, "{$")
if(!fragment_start)
break
var/fragment_end = findtext(search_through, "}")
if(!fragment_end)
CRASH("[starting_text] contains a {$ that denotes the start of a keyword replacement, but not a closing }!")
///Gets the keyword this string wants to use
///We offset the start by two indexes to account for
var/keyword = copytext(search_through, fragment_start + 2, fragment_end)
var/replacement = keywords[keyword]
if(!replacement)
CRASH("[starting_text] contains a non-existent keyword! \[[keyword]\]")
search_through = replacetext(search_through, "{$[keyword]}", replacement)
return search_through
///Loads the contents of a json file into our global card list
/datum/controller/subsystem/trading_card_game/proc/loadCardFile(filename, directory = "strings/tcg")
var/list/json = json_decode(file2text("[directory]/[filename]"))
var/list/cards = json["cards"]
var/list/templates = list()
for(var/list/data in json["templates"])
templates[data["template"]] = data
for(var/list/data in cards)
var/datum/card/card = new(data, templates)
//Lets cache the id by rarity, for top speed lookup later
if(!cached_cards[card.series])
cached_cards[card.series] = list()
cached_cards[card.series]["ALL"] = list()
if(!cached_cards[card.series][card.rarity])
cached_cards[card.series][card.rarity] = list()
cached_cards[card.series][card.rarity] += card.id
//Let's actually store the datum here
cached_cards[card.series]["ALL"][card.id] = card
///Because old me wanted to keep memory costs down, each cardpack type shares a rarity list
///We do the spooky stuff in here to ensure we don't have too many lists lying around
/datum/controller/subsystem/trading_card_game/proc/get_rarity_table(type, list/sample_table)
//Pass by refrance moment
//This lets us only have one rarity table per pack, badmins beware
//Yes this is horribly overengineered. No I am not sorry
if(!cached_rarity_table[type])
cached_rarity_table[type] = sample_table
return cached_rarity_table[type]
///See above
/datum/controller/subsystem/trading_card_game/proc/get_guarenteed_rarity_table(type, list/sample_table)
if(!cached_guar_rarity[type])
cached_guar_rarity[type] = sample_table
return cached_guar_rarity[type]
///Prints all the cards names
/datum/controller/subsystem/trading_card_game/proc/printAllCards()
for(var/card_set in cached_cards)
message_admins("Printing the [card_set] set")
for(var/card in cached_cards[card_set]["ALL"])
var/datum/card/toPrint = cached_cards[card_set]["ALL"][card]
message_admins(toPrint.name)
///Checks the passed type list for missing raritys, or raritys out of bounds
/datum/controller/subsystem/trading_card_game/proc/checkCardpacks(cardPackList)
var/toReturn = ""
for(var/cardPack in cardPackList)
var/obj/item/cardpack/pack = new cardPack()
//Lets see if someone made a type yeah?
if(!cached_cards[pack.series])
toReturn += "[pack.series] does not have any cards in it\n"
continue
for(var/card in cached_cards[pack.series]["ALL"])
var/datum/card/template = cached_cards[pack.series]["ALL"][card]
if(template.rarity == "ALL")
toReturn += "[pack.type] has a rarity [template.rarity] on the card [template.id] that needs to be changed to something that isn't \"ALL\"\n"
continue
if(!(template.rarity in pack.rarity_table))
toReturn += "[pack.type] has a rarity [template.rarity] on the card [template.id] that does not exist\n"
continue
//Lets run a check to see if all the rarities exist that we want to exist exist
for(var/pack_rarity in pack.rarity_table)
if(!cached_cards[pack.series][pack_rarity])
toReturn += "[pack.type] does not have the required rarity [pack_rarity]\n"
qdel(pack)
return toReturn
///Checks the global card list for cards that don't override all the default values of the card datum
/datum/controller/subsystem/trading_card_game/proc/checkCardDatums()
var/toReturn = ""
var/datum/thing = new()
for(var/series in cached_cards)
var/cards = cached_cards[series]["ALL"]
for(var/card in cards)
var/datum/card/target = cached_cards[series]["ALL"][card]
var/toAdd = "The card [target.id] in [series] has the following default variables:"
var/shouldAdd = FALSE
for(var/current_var in (target.vars ^ thing.vars))
if(current_var == "icon" && target.vars[current_var] == DEFAULT_TCG_DMI)
continue
if(target.vars[current_var] == initial(target.vars[current_var]))
shouldAdd = TRUE
toAdd += "\n[current_var] with a value of [target.vars[current_var]]"
if(shouldAdd)
toReturn += toAdd
qdel(thing)
return toReturn
///Used to test open a large amount of cardpacks
/datum/controller/subsystem/trading_card_game/proc/checkCardDistribution(cardPack, batchSize, batchCount, guaranteed)
var/totalCards = 0
//Gotta make this look like an associated list so the implicit "does this exist" checks work proper later
var/list/cardsByCount = list("" = 0)
var/obj/item/cardpack/pack = new cardPack()
for(var/index in 1 to batchCount)
var/list/cards = pack.buildCardListWithRarity(batchSize, guaranteed)
for(var/id in cards)
totalCards++
cardsByCount[id] += 1
var/toSend = "Out of [totalCards] cards"
for(var/id in sort_list(cardsByCount, /proc/cmp_num_string_asc))
if(id)
var/datum/card/template = cached_cards[pack.series]["ALL"][id]
toSend += "\nID:[id] [template.name] [(cardsByCount[id] * 100) / totalCards]% Total:[cardsByCount[id]]"
message_admins(toSend)
qdel(pack)