Skip to content

Instantly share code, notes, and snippets.

@Lerycide
Last active April 6, 2025 21:43
Show Gist options
  • Save Lerycide/5a76891729fbbcf7a0de7521a2c0e093 to your computer and use it in GitHub Desktop.
Save Lerycide/5a76891729fbbcf7a0de7521a2c0e093 to your computer and use it in GitHub Desktop.

The CBPastes Formatting Specification

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

Introduction

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.

Formatting

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

Header

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.

Identifier

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

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

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.

Key

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.

Modifier

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.

Value

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.

Text Color

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

Regular Expressions

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.

Detecting Headers

(?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.

Capturing Header Data

^(\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.

Capturing Identifiers

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.

Capturing Moves Only

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.

Capturing Moves with Attributes

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.


Converting Pastes into Tapes

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.

Move Conversion

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 Conversion

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment