A more human-readable text standard for sharing tape builds in Cassette Beasts
by Lerycide
v2.0.1: Fixed an issue with move parsing
v2.0: Added support for attributes
v1.0.1: Made minor changes to the string conversion parsing.
v1.0: First version; attributes are not supported yet
As someone who has been in the community for over a year now, I have seen countless builds being shared by screenshotting the tape preview. While this is fine and all, a text format version is sorely needed to make searching builds easier. In-game limitations also means that one screenshot can only show the attributes of the highlighted sticker, so multiple screenshots or an accompanying text would be necessary to convey all of this information.
A JSON format, meanwhile, is used both by the game and several mods to store this data in text form. However, since it's designed to be used by a machine, it's a bit harder for humans to read. For instance, most of the data is stored as paths, like res://data/monster_forms/traffikrab.tres
for the Traffikrab species, and res://data/battle_moves/bad_joke.tres
for Bad Joke. They essentially have really long prefixes that make it less legible, plus some move names may not be obvious since the move's file name does not always correspond to the actual move's name.
This new formatting standard was created to fill in the gap of having a human-readable way of sharing pastes, while also being convertible to a real tape using regular expressions as a way to test out sample teams shared by other people. It's inspired by the paste standard used by PokePaste and Pokémon Showdown's, but with some changes introduced to better suit this game.
If you want to see this in action, check out the CBPastes mod I made that demos both tape export and import features.
The most general example of a paste format looks like this:
Traffikrab (Beast)
Nickname: Krabs
Grade: 1
- Smack
- Wallop
- <Empty Slot>
- AP Starter
- AP Refund
If the tape is not a bootleg, the (Beast)
part can be dropped entirely. Similarly, Nickname
is optional. If Grade
is not specified, it is assumed to be at 5 stars. The -
is used to indicate moves, and is not optional. Empty slots are optional and are assumed to be there if there aren't enough moves listed. If no move is specified, it is assumed that the tape does not contain any stickers.
This format is case-insensitive, since the parser will have to convert them to lowercase characters anyway, but species names and moves should be written as they appear in-game. Diacrictics are also removed so that, except for the nickname, the paste only uses ASCII characters.
Due to technical limitations, pastes are in English only, so that they can be imported into the game. This standard works regardless of which spelling variation you use. For example, Criticise
and Criticize
should give the same result when parsed through. However, as a general rule of thumb the output paste should use UK English since that's what the game uses.
Pastes can hold multiple tapes at once; when displayed, these should be separated with a break in-between to maximize readability, though this is not necessary for parsing:
Traffikrab (Beast)
Nickname: Krabs
Grade: 4
- Smack
- Wallop
- <Empty Slot>
- AP Starter
- AP Refund
Carniviper
- Spit
The most important part of the paste is the header, it contains information for the species name and bootleg type if applicable. This is the only mandatory part of the paste for obvious reasons. It follows the following naming convention:
Species Name (BootlegType)
This indicates the tape's monster species name in English, written as a proper noun, and bootleg type. If the tape is not a bootleg, the header is simply the species name:
Species Name
Here, it is assumed that the type is whatever the default type is for this monster.
Identifiers are all optional entries in the paste, formatted as such:
Identifier: Value
The Identifier
is a key word signifying the type of information present, such as the Nickname
or Grade
. The colon :
is mandatory for this to count as an Identifier. This formatting standard currently supports the following identifiers:
Nickname
, which indicates the tape's nickname.Grade
, which is the number of stars the tape has. Defaults to 5 if not specified.
Moves are also optional; not specifying a move means that the tape has no stickers on it. All moves are prefixed by a hyphen -
. The space between the hyphen and the move name is optional:
- Move Name
The move name is English and written as a proper noun as it appears in-game, though this format is case-insensitive and can even accept spelling variations such as Criticize
and Criticise
.
Empty slots are optional, but if you need to insert an empty slot between moves, format it as <Empty Slot>
:
- <Empty Slot>
This is also case-insensitive.
Attributes use a keyword system to quickly convey all the necessary information in a concise but readable manner. Immediately the move name, we specify attributes by enclosing them in square brackets []
as such:
- Move Name [Key1 Modifier1 Value1 | Key2 Modifier2 Value2 | Key3 Modifier3 Value3]
If the sticker has less than 3 attributes, we can simply omit them as such:
- Move Name [Key1 Modifier1 Value1]
- Move Name [Key2 Modifier2 Value2 | Key3 Modifier3 Value3]
For Markdown code blocks, it is recommended to format attributes as a separate line with a tab character or 4 spaces indent, as such:
Pombomb
- Multi Shot
[Alt RAtk | +Hit]
- Multicopy
[Shared]
- AP Starter
[Passive Speed 10+ | Passive MaxHP | +Slot]
Doing so makes it more readable, though keeping everything inline for the sake of compactness is also acceptable:
Pombomb
- Multi Shot [Alt RAtk | +Hit]
- Multicopy [Shared]
- AP Starter [Passive Speed 10+ | Passive MaxHP | +Slot]
For the rest of this section we'll focus on one attribute, which can have up to 3 parameters: the key
, modifier
, and value
, in that order.
The key
parameter is mandatory and specifies the attribute type, or group of attributes if a modifier
parameter is also present. The key is formatted in PascalCase: no spaces (since we're using that to tell the parameters apart), and the first letter of each word is capitalized. For example, DestroysWalls
is used to identify the Destroys walls attribute. This convention is mainly for human readability, since the key is case-insensitive and thus can be handled by the importer regardless of capitalization.
A table of all attributes is shown below, under the Modifier
heading.
The modifier is only applicable for certain keys, and is used to be more specific in the type of attribute, or if it's the +x% Chance to apply buff/debuff attribute, the status effect it applies. Whether or not the attribute scales based on number of empty slots is handled in the value
parameter.
Similar to key
, the modifier
parameter is written in PascalCase, with dashes removed. This is mainly for readability, as when imported these parameters can be case-insensitive.
The following table lists all the keys, modifiers if applicable, and the corresponding attribute description they represent.
Key | Modifier | Attribute / Attribute Group |
---|---|---|
Alt | MaxHP | Damage based on Max HP if higher |
MAtk | Damage based on Melee Attack if higher | |
MDef | Damage based on Melee Defence if higher | |
RAtk | Damage based on Ranged Attack if higher | |
RDef | Damage based on Ranged Defence if higher | |
Speed | Damage based on Speed if higher | |
+Hit | +1 extra hit | |
+Duration | +1 duration | |
+Slot | +1 slot | |
Heal | Heals x% chance of Max HP | |
Damage | +x% Damage | |
Exp | +x% Exp Points | |
CritDamage | +x% Critical damage | |
CritChance | +x% Critical chance | |
MoveAcc | +x% Accuracy | |
EffectChance | +x% Effect chance | |
Splash | +x% Splash damage | |
Priority | +x% Priority chance | |
CritAdvantage | Critical hit on type advantage | |
Refund1AP | +x% Chance to refund 1 AP after usage | |
RefundAllAP | +x% Chance to refund all AP after usage | |
AutoUse | Smack | +x% Chance to use Smack after attack |
Spit | +x% Chance to use Spit after attack | |
Start | +x% Chance to use at the start of battles | |
End | +x% Chance to use at the end of a round | |
Attack | +x% Chance to use after attacks | |
Hit | +x% Chance to use when hit | |
Twice | +x% Chance to use twice | |
Random | +x% Chance to use a random move | |
Buff | CottonedOn | +x% Chance to apply Cottoned On to user |
ParryStance | +x% Chance to apply Parry Stance to user | |
LockedOn | +x% Chance to apply Locked On to user | |
Multistrike | +x% Chance to apply Multistrike to user | |
AccUp | +x% Chance to apply Accuracy Up to user | |
EvasUp | +x% Chance to apply Evasion Up to user | |
MAtkUp | +x% Chance to apply Melee Attack Up to user | |
MDefUp | +x% Chance to apply Melee Defence Up to user | |
RAtkUp | +x% Chance to apply Ranged Attack Up to user | |
RDefUp | +x% Chance to apply Ranged Defence Up to user | |
SpeedUp | +x% Chance to apply Speed Up to user | |
APBoost | +x% Chance to apply AP Boost to user | |
Multitarget | +x% Chance to apply Multitarget to user | |
HealingLeaf | +x% Chance to apply Healing Leaf to user | |
HealingSteam | +x% Chance to apply Healing Steam to user | |
MindMeld | +x% Chance to apply Mind-Meld to user | |
Debuff | Bomb | +x% Chance to apply Bomb to target |
Flinch | +x% Chance to apply Flinch to target | |
Resonance | +x% Chance to apply Resonance to target | |
Confused | +x% Chance to apply Confused to target | |
GlassBonds | +x% Chance to apply Glass Bonds to target | |
Leeched | +x% Chance to apply Leeched to target | |
Poisoned | +x% Chance to apply Poisoned to target | |
Shrapnel | +x% Chance to apply Shrapnel to target | |
Sleep | +x% Chance to apply Sleep to target | |
AccDown | +x% Chance to apply Accuracy Down to target | |
EvasDown | +x% Chance to apply Evasion Down to target | |
MAtkDown | +x% Chance to apply Melee Attack Down to target | |
MDefDown | +x% Chance to apply Melee Defence Down to target | |
RAtkDown | +x% Chance to apply Ranged Attack Down to target | |
RDefDown | +x% Chance to apply Ranged Defence Down to target | |
SpeedDown | +x% Chance to apply Speed Down to target | |
Berserk | +x% Chance to apply Berserk to target | |
APDrain | +x% Chance to apply AP Drain to target | |
Burned | +x% Chance to apply Burned to target | |
Conductive | +x% Chance to apply Conductive to target | |
Petrified | +x% Chance to apply Petrified to target | |
Unitarget | +x% Chance to apply Unitarget to target | |
Wall | +x% Chance to summon wall | |
ContactDmg | +x% Chance to apply Contact Dmg | |
AllCompat | Compatible with any tape | |
DestroysWalls | Destroys walls | |
IgnoresWalls | Ignores walls | |
TargetTeam | +x% Chance to target whole team | |
Shared | Shared with allies | |
Passive | Max HP | Passive: +x% Max HP |
MAtk | Passive: +x% Melee Attack | |
MDef | Passive: +x% Melee Defence | |
RAtk | Passive: +x% Ranged Attack | |
RDef | Passive: +x% Ranged Defence | |
Speed | Passive: +x% Speed |
For some attributes, specifying the key is enough, such as IgnoresWalls
. For some, a modifier is also present, such as Alt MaxHP
. As an example, consider the following Spit sticker on a tape:
- Spit [Alt Speed | +Slot]
This means it has both the Damage based on Speed if higher and +1 Slot attributes.
The value specifies the chance or attribute value for attributes that require it. It's simply written as a number, and if it's the per empty slot variant, a +
is included at the end. As an example, Heal 5
is Heals 5% of Max HP, while Heal 5+
is Heals 5% of Max HP per empty slot. For example,
- Double Smack [CritChance 30+ | Buff ParryStance 50 | AutoUse Start 90]
is a Double Smack sticker with the +30% Critical chance per empty slot, +50% Chance to apply Parry Stance, and +90% Chance to use at the start of battles attributes.
A percent symbol can be included for clarity, such as using Heal 5%
or Heal 5%+
, but the %
is ignored when parsed by the importer.
This formatting standard is intended for use in plain text and as such this section is merely a recommendation to anyone who wants to incorporate colors in the paste.
The color format presented here keeps the species name with a default color, and lines relating to identifiers will use a gray color. Bootleg types specified in the header will use the type's associated color, but the parentheses should remain a default color.
Moves will also follow suit, being colored based on their type. For typeless moves, they are colored based on the tape's type, to reflect how it works in-game. If this is too cumbersome to implement, an alternative is to set them to the default color.
Attributes are colored based on rarity, while the brackets and commas will stick to the default color.
The following colors are recommended to use for formatting, although it is intended to be viewed on a light background:
Type | Hex Color |
---|---|
Default | #000000 |
Identifiers | #777777 |
Uncommon Attribute | #225d31 |
Rare Attribute | #35379d |
Air | #206454 |
Astral | #373f67 |
Beast | #757157 |
Earth | #6f3945 |
Fire | #d04d2f |
Glass | #9dacc3 |
Glitter | #c355c1 |
Ice | #3471b2 |
Lightning | #d98a30 |
Metal | #78668a |
Plant | #308245 |
Plastic | #b12031 |
Poison | #7629db |
Water | #4648ce |
This section covers how to parse through a paste using regular expressions to extract important information.
Regular Expressions (RegEx) have become a standard format in identifying particular patterns in a given text. RegEx will be used here to serve as a transition from our human-readable format to something usable for a machine. The following regular expressions follow the PCRE2 Specification.
(?m)^\w[^:\n]*$
This is useful for dealing with pastes containing multiple tapes, as you can extract a substring separated by the headers to have all the information for a given tape, and then capture the relevant data from there using more regexes. This Regex specifically excludes colons to avoid identifiers, and newlines to prevent unnecessarily capturing empty lines.
^(\w[\w _-]*?)\s*(?:\((\w*?)\))? *$
This RegEx puts the species name in the first capturing group, and optionally the bootleg type in the second capturing group.
Since we only have a very small selection of identifiers to deal with, we can just do a manual search to locate them:
(?mi)^Nickname\s*:(?: *)?(.*)$
(?mi)^Grade\s*:(?: *)?([0-9])
This RegEx uses case-insensitive flags (i
) so you're not forced to use sentence case for identifier names. As a limitation, leading spaces in a tape's nickname are excluded. This was done to still allow the flexibility of having arbitrary spaces after the colon.
The regex for Grade
does not assert an end of the line so that it can still capture the grade even if it is followed by Stars
, i.e. Grade: 4 Stars
will still be captured correctly.
For cases where the paste will not contain any attributes, moves can be captured with this much simpler RegEx:
^(?:- *)(\w[\w-]+(?: +[\w-]+)*)\s*$
Note that this accepts multiple white spaces between words, so Double Slice
is still valid. The importer can then trim down the spaces as necessary, though if this is not a desirable feature, the RegEx can be modified to be strict with spacing:
^(?:- *)(\w[\w-]+(?: [\w-]+)*)\s*$
Both expression will still accept arbitrary spaces after the last word in the name.
To also capture attributes, or when working with pastes that can or will have attributes included, use this instead:
(?m)^(?:- *)(\w[\w-]+(?: +[\w-]+)*)\s*(?:\[\s*([\w+][^|]*?)(?:\s*?\|\s*?([\w+][^|]*?)(?: *?\|\s*?([\w+][^|]*?)\s*?)?)?\])?$
Here, the m
flag asserts that this is multiline. The 1st capturing group takes the move. The first attribute (if present) is in the 2nd capturing group, the second attribute (if present) in the 3rd capturing group, and the third attribute (if present) in the 4th capturing group.
When it comes to processing attributes, however, it is a lot more complicated. Refer to Attribute Conversion in the next section, Converting Pastes into Tapes, on how this is done in practice.
This section will cover how this specification can be parsed through using regular expressions to generate a MonsterTape
resource, which is used by the game to hold data for the actual tape.
For this to work, we need to convert the species name, type, and moves into lowercase, converting any spaces and dashes -
into underscores _
as well. It is rather straightforward for species names and types, but moves are a different story. For those, we have to perform a dictionary conversion since this lowercase name doesn't always correspond to the actual name in the game files. For instance, the move Life Absorb
is actually named as hp_absorb
. We also want to account for spelling variations such as Hypnotise
and Hypnotize
, extra spaces in some words incorrectly split as two words like Bonbon Blast
and Bon Bon Blast
, moves that get confused with status names like Spring-Load
and Spring-loaded
, common names from in-game files such as Camouflage Fire
and Fireproof
, as well as some optional shorthand notations such as CS
for Custom Starter
, Beast Camo
for Beast Camouflage
, and so on.
The following example dictionary shows such a conversion:
const MOVE_NAME_CONVERSIONS: Dictionary = {
"<empty_slot>": "_empty_slot",
"empty_slot": "_empty_slot",
"empty_slot_-": "_empty_slot",
"empty_slot-": "_empty_slot",
"<_empty_slot_>": "_empty_slot",
"-empty_slot-": "_empty_slot",
"-_empty_slot_-": "_empty_slot",
"emptyslot": "_empty_slot",
"be_random!": "be_random",
"be_random!!": "be_random",
"be_random!!!": "be_random",
"bish_bash_bosh": "bishbashbosh",
"bush_fire": "bushfire",
"clock_work_mouse": "clockwork_mouse",
"cm": "critical_mass",
"complement": "compliment",
"copy_cat": "copycat",
"copythat": "copy_that",
"cottoned_on": "cotton_on",
"crit_ap": "critical_ap",
"criticise": "criticize",
"cs": "custom_starter",
"deja_vu": "dejavu",
"de_ja_vu": "dejavu",
"djinn_toxicate": "djinntoxicate",
"echolocate": "echolocation",
"gem_stone_wall": "gemstone_wall",
"hypnotise": "hypnotize",
"ionized_air": "ionised_air",
"iron_fillings": "iron_filings",
"jagged_edges": "jagged_edge",
"jumpscare": "jump_scare",
"life_absorb": "hp_absorb",
"lift_off": "liftoff",
"mc": "machine_curse",
"multi_copy": "multicopy",
"multishot": "multi_shot",
"multismack": "multi_smack",
"neutralize": "neutralise",
"polevault_assault": "pole_vault_assault",
"pre_emptive_strike": "preemptive_strike",
"qs": "quick_smack",
"rs": "random_starter",
"rf": "rapid_fire",
"sand_storm": "sandstorm",
"selfdestruct": "self_destruct",
"sharp_edge": "sharp_edges",
"sheer_luck": "starter2_passive",
"shear_luck": "starter2_passive",
"spring_loaded": "spring_load",
"sitd": "stab_in_the_dark",
"status_res": "status_resistance",
"bonbon_blast": "starter1_attack",
"bon_bon_blast": "starter1_attack",
"battering_ram": "starter2_attack",
"sugar_rush": "starter1_passive",
"super_heated_fist": "superheated_fist",
"sturdy_armor": "sturdy_armour",
"old_1-2": "the_old_1_2",
"old_1_2": "the_old_1_2",
"trapjaw": "trap_jaw",
"trip_wire": "tripwire",
"twoheads": "two_heads",
"tower_defence": "tower_defense",
"water_works": "waterworks",
"wonderful_seven": "wonderful_7",
"w7": "wonderful_7",
"wood_cutter": "woodcutter",
"air_camouflage" : "camouflage_air",
"air_camo" : "camouflage_air",
"astral_camouflage" : "camouflage_astral",
"astral_camo" : "camouflage_astral",
"beast_camouflage" : "camouflage_beast",
"beast_camo" : "camouflage_beast",
"earth_camouflage" : "camouflage_earth",
"earth_camo" : "camouflage_earth",
"fire_camouflage" : "camouflage_fire",
"fire_camo" : "camouflage_fire",
"glass_camouflage" : "camouflage_glass",
"glass_camo" : "camouflage_glass",
"ice_camouflage" : "camouflage_ice",
"ice_camo" : "camouflage_ice",
"lightning_camouflage" : "camouflage_lightning",
"lightning_camo" : "camouflage_lightning",
"metal_camouflage" : "camouflage_metal",
"metal_camo" : "camouflage_metal",
"plant_camouflage" : "camouflage_plant",
"plant_camo" : "camouflage_plant",
"plastic_camouflage" : "camouflage_plastic",
"plastic_camo" : "camouflage_plastic",
"poison_camouflage" : "camouflage:poison",
"poison_camo" : "camouflage:poison",
"water_camouflage" : "camouflage:water",
"water_camo" : "camouflage:water",
"air_coating" : "coating_air",
"astral_coating" : "coating_astral",
"beast_coating" : "coating_beast",
"earth_coating" : "coating_earth",
"elemental_coating" : "coating_elemental",
"fire_coating" : "coating_fire",
"glass_coating" : "coating_glass",
"ice_coating" : "coating_ice",
"lightning_coating" : "coating_lightning",
"metal_coating" : "coating_metal",
"plant_coating" : "coating_plant",
"plastic_coating" : "coating_plastic",
"poison_coating" : "coating_poison",
"water_coating" : "coating_water",
"air_resistance" : "resistance_air",
"astral_resistance" : "resistance_astral",
"beast_resistance" : "resistance_beast",
"earth_resistance" : "resistance_earth",
"fire_resistance" : "resistance_fire",
"fireproof" : "resistance_fire",
"fire_proof" : "resistance_fire",
"glass_resistance" : "resistance_glass",
"glitter_resistance" : "resistance_glitter",
"ice_resistance" : "resistance_ice",
"lightning_resistance" : "resistance_lightning",
"grounded" : "resistance_lightning",
"metal_resistance" : "resistance_metal",
"plant_resistance" : "resistance_plant",
"plastic_resistance" : "resistance_plastic",
"poison_resistance" : "resistance_poison",
"water_resistance" : "resistance_water",
"waterproof" : "resistance_water",
"water_proof" : "resistance_water",
}
Next, we do one last special case check for empty slots since _empty_slot
is just a placeholder name that doesn't represent any kind of move. If this is the move string, simply ignore it and go continue on with the next move. After that, we simply check this from the dictionary of all valid moves in the game, which can be generated using the Datatables
class present in the decompiled copy of Cassette Beasts.
Attribute data can have anywhere from 1 to 3 parameters, with only the key
being specified at minimum, and both the key
, modifier
, and value
parameters specified at the maximum. It is, however, possible for an attribute to not need a modifier
or a value
which is omitted.
We will always assume that the first parameter is the key
. Then we check the last parameter and see if it is a value
type parameter, by checking if it is a nonnegative number, after removing the +
or the %
if present, then keep track of if this should be the per empty slot variant (if +
is present) or if it is the regular variant.
If there are 3 parameters, then the second parameter must be the modifier
.
With that out of the way, we then use a lookup table or dictionary for the key
. One example of this implementation would be the following:
const ATTRIBUTES: Dictionary = {
"alt": {
"maxhp": preload("res://data/sticker_attributes/alt_attack_max_hp.tres"),
"matk": preload("res://data/sticker_attributes/alt_attack_matk.tres"),
"mdef": preload("res://data/sticker_attributes/alt_attack_mdef.tres"),
"ratk": preload("res://data/sticker_attributes/alt_attack_ratk.tres"),
"rdef": preload("res://data/sticker_attributes/alt_attack_rdef.tres"),
"speed": preload("res://data/sticker_attributes/alt_attack_speed.tres")
},
"+hit": preload("res://data/sticker_attributes/extra_hit.tres"),
"+duration": preload("res://data/sticker_attributes/status_duration_boost.tres"),
"+slot": preload("res://data/sticker_attributes/extra_slot.tres"),
"heal": {
"regular": preload("res://data/sticker_attributes/heal.tres"),
"empty": preload("res://data/sticker_attributes/specialization_heal.tres"),
},
"damage": {
"regular": preload("res://data/sticker_attributes/stat_damage.tres"),
"empty": preload("res://data/sticker_attributes/specialization_damage.tres"),
},
"exp": {
"regular": preload("res://data/sticker_attributes/stat_exp.tres"),
"empty": preload("res://data/sticker_attributes/specialization_exp.tres"),
},
"critdamage": {
"regular": preload("res://data/sticker_attributes/stat_move_crit_damage.tres"),
"empty": preload("res://data/sticker_attributes/specialization_move_crit_damage.tres")
},
"critchance": {
"regular": preload("res://data/sticker_attributes/stat_move_crit_rate.tres"),
"empty": preload("res://data/sticker_attributes/specialization_move_crit_rate.tres"),
},
"moveacc": {
"regular": preload("res://data/sticker_attributes/stat_move_accuracy.tres"),
"empty": preload("res://data/sticker_attributes/stat_move_accuracy.tres"),
},
"effectchance": {
"regular": preload("res://data/sticker_attributes/stat_move_effect_chance.tres"),
"empty": preload("res://data/sticker_attributes/specialization_move_effect_chance.tres"),
},
"splash": {
"regular": preload("res://data/sticker_attributes/stat_move_splash_damage.tres"),
"empty": preload("res://data/sticker_attributes/specialization_move_splash_damage.tres"),
},
"priority": {
"regular": preload("res://data/sticker_attributes/stat_priority_chance.tres"),
"empty": preload("res://data/sticker_attributes/specialization_priority_chance.tres"),
},
"critadvantage": preload("res://data/sticker_attributes/type_advantage_crit.tres"),
"refund1ap": preload("res://data/sticker_attributes/ap_refund_1.tres"),
"refundallap": preload("res://data/sticker_attributes/ap_refund_all.tres"),
"autouse": {
"smack": preload("res://data/sticker_attributes/attack_after_use_smack.tres"),
"spit": preload("res://data/sticker_attributes/attack_after_use_spit.tres"),
"start": preload("res://data/sticker_attributes/auto_use_battle_start.tres"),
"end": preload("res://data/sticker_attributes/auto_use_round_ending.tres"),
"attack": preload("res://data/sticker_attributes/auto_use_user_attack.tres"),
"hit": preload("res://data/sticker_attributes/auto_use_user_hit.tres"),
"twice": preload("res://data/sticker_attributes/use_again.tres"),
"random": preload("res://data/sticker_attributes/use_random.tres"),
},
"buff": preload("res://data/sticker_attributes/buff_user.tres"),
"debuff": preload("res://data/sticker_attributes/debuff_target.tres"),
"wall": preload("res://data/sticker_attributes/buff_user_wall.tres"),
"contactdmg": preload("res://data/sticker_attributes/buff_user_contactdmg.tres"),
"allcompat": preload("res://data/sticker_attributes/compatibility.tres"),
"destroyswalls": preload("res://data/sticker_attributes/destroys_walls.tres"),
"ignoreswalls": preload("res://data/sticker_attributes/ignores_walls.tres"),
"targetteam": preload("res://data/sticker_attributes/multitarget.tres"),
"shared": preload("res://data/sticker_attributes/shared.tres"),
"passive": {
"maxhp": {
"regular": preload("res://data/sticker_attributes/stat_passive_max_hp.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_max_hp.tres"),
},
"matk": {
"regular": preload("res://data/sticker_attributes/stat_passive_melee_attack.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_melee_attack.tres"),
},
"mdef": {
"regular": preload("res://data/sticker_attributes/stat_passive_melee_defense.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_melee_defense.tres"),
},
"ratk": {
"regular": preload("res://data/sticker_attributes/stat_passive_ranged_attack.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_ranged_attack.tres"),
},
"rdef": {
"regular": preload("res://data/sticker_attributes/stat_passive_ranged_defense.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_ranged_defense.tres"),
},
"speed": {
"regular": preload("res://data/sticker_attributes/stat_passive_speed.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_speed.tres"),
},
"acc": {
"regular": preload("res://data/sticker_attributes/stat_passive_accuracy.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_accuracy.tres"),
},
"evas": {
"regular": preload("res://data/sticker_attributes/stat_passive_evasion.tres"),
"empty": preload("res://data/sticker_attributes/specialization_passive_evasion.tres"),
},
},
}
If the retrieved value is an attribute, then we can just generate that and use the value
and apply it if possible. If the retrieved value is a Dictionary
, then check if it has "regular"
as a key. If it does, then get the "regular"
value if it doesn't scale per empty slot, or "empty"
if it scales per empty slots, and use the value
to apply it if possible. Otherwise, use the modifier
key to narrow it down, and if we get an attribute generate the value as before, or if not do the per empty slot check then apply the value.
Note, however, that some attributes use the stat_value
to store the value, while some use chance
, so first check if the attribute has either property before setting the values.
Both the Buff
and Debuff
attribute keys have to be handled separately. Here, the lookup should be a sticker attribute. So we can just manually check if the key
is a Buff
, and use the modifier
to specify the buff
and the value
the value, or if it is a Debuff
, and do the same for the debuff
property.
An example of this in practice can be found by decompiling my CBPastes mod. There should be a script named PasteParser.gd
in it showing how you can use regular expressions and string conversion to parse pastes.