From 5d5aad0028cfe6fe30d8cda942a8736dad48b6de Mon Sep 17 00:00:00 2001 From: Florian ERNST Date: Wed, 25 Nov 2020 09:06:24 +0100 Subject: [PATCH 1/2] Improved documentation & renamed mcfunction to MCFunction --- .eslintrc.js | 4 + package.json | 4 +- .../resources/AdvancementTriggers.ts | 126 +++++++++++++++-- .../arguments/resources/advancement.ts | 38 +++-- .../resources/criteria/LocationCriterion.ts | 20 ++- .../arguments/resources/lootTables.ts | 112 +++++++++++++-- .../arguments/resources/predicate.ts | 80 +++++++++-- src/_internals/arguments/resources/recipe.ts | 45 +++--- .../commands/implementations/Loot.ts | 2 +- src/_internals/datapack/BasePath.ts | 75 ++++++++-- src/_internals/datapack/Datapack.ts | 12 +- src/_internals/datapack/saveDatapack.ts | 2 +- src/_internals/flow/Flow.ts | 34 +++-- src/_internals/resources/Advancement.ts | 66 --------- src/_internals/resources/AdvancementClass.ts | 93 ++++++++++++ src/_internals/resources/LootTable.ts | 55 ++++++-- .../{McFunction.ts => MCFunctionClass.ts} | 6 +- src/_internals/resources/Predicate.ts | 33 ++--- src/_internals/resources/Recipe.ts | 18 +-- src/_internals/resources/Resource.ts | 28 ++++ src/_internals/resources/Tag.ts | 22 ++- src/_internals/resources/index.ts | 5 +- src/_internals/variables/NBTs.ts | 37 ++++- src/core.ts | 2 +- tests/menu.ts | 4 +- tests/test.ts | 133 ++++++++++++++++-- tests/test2.ts | 6 +- tests/test3.ts | 10 ++ yarn.lock | 25 +++- 29 files changed, 842 insertions(+), 255 deletions(-) delete mode 100644 src/_internals/resources/Advancement.ts create mode 100644 src/_internals/resources/AdvancementClass.ts rename src/_internals/resources/{McFunction.ts => MCFunctionClass.ts} (97%) create mode 100644 src/_internals/resources/Resource.ts create mode 100644 tests/test3.ts diff --git a/.eslintrc.js b/.eslintrc.js index 5a075ec9..949466a5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -99,5 +99,9 @@ module.exports = { // Allow use after define 'no-use-before-define': 'off', + + 'multiline-comment-style': ['error', 'starred-block'], + + 'spaced-comment': ['error', 'always'], }, } diff --git a/package.json b/package.json index f30ccd45..16dd2fc5 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "url": "git+https://github.com/TheMrZZ/sandstone.git" }, "scripts": { - "build:watch": "ttsc --watch --incremental --pretty", + "build:watch": "nodemon --watch src --watch tests -e js,ts --exec ttsc --pretty", "build": "node scripts/clean.js && ttsc && node scripts/setupPackage.js", "test": "node dist/tests/test.js", "test:watch": "nodemon -q --unhandled-rejections=strict --trace-warnings dist/tests/test.js", @@ -45,7 +45,7 @@ "nodemon": "^2.0.4", "prettier": "^2.0.5", "ttypescript": "^1.5.12", - "typescript": "^4.1.0-dev.20200930", + "typescript": "^4.1.2", "typescript-transform-paths": "^2.0.1" }, "dependencies": { diff --git a/src/_internals/arguments/resources/AdvancementTriggers.ts b/src/_internals/arguments/resources/AdvancementTriggers.ts index a5f77124..d42e3f31 100644 --- a/src/_internals/arguments/resources/AdvancementTriggers.ts +++ b/src/_internals/arguments/resources/AdvancementTriggers.ts @@ -13,13 +13,109 @@ import type { ObjectOrArray } from './predicate' // The advancement triggers type Trigger> = { - /** The trigger for this advancement; specifies what the game should check for the advancement. */ + /** + * The trigger for this advancement; specifies what the game should check for the advancement. + * + * One of: + * - `minecraft:bee_nest_destroyed`: Triggers when the player breaks a bee nest or beehive. + * + * - `minecraft:bred_animals`: Triggers after the player breeds 2 animals. + * + * - `minecraft:brewed_potion`: Triggers after the player takes any item out of a brewing stand. + * + * - `minecraft:changed_dimension`: Triggers after the player travels between two dimensions. + * + * - `minecraft:channeled_lightning`: Triggers after the player successfully uses the Channeling enchantment on an entity. + * + * - `minecraft:construct_beacon`: Triggers after the player changes the structure of a beacon. (When the beacon updates itself). + * + * - `minecraft:consume_item`: Triggers when the player consumes an item. + * + * - `minecraft:cured_zombie_villager`: Triggers when the player cures a zombie villager. + * + * - `minecraft:effects_changed`: Triggers after the player gets a status effect applied or taken from them. + * + * - `minecraft:enchanted_item`: Triggers after the player enchants an item through an enchanting table (does not get triggered through an anvil, or through commands). + * + * - `minecraft:enter_block`: Triggers when the player stands in a block. + * Checks every tick and will try to trigger for each successful match (up to 8 times, the maximum amount of blocks a player can stand in), + * which only works if the advancement is revoked from within the advancement using a function reward. + * + * - `minecraft:entity_hurt_player`: Triggers after a player gets hurt. + * + * - `minecraft:entity_killed_player`: Triggers after an entity kills a player. + * + * - `minecraft:filled_bucket`: Triggers after the player fills a bucket. + * + * - `minecraft:fishing_rod_hooked`: Triggers after the player successfully catches an item with a fishing rod or pulls an entity with a fishing rod. + * + * - `minecraft:hero_of_the_village`: Triggers when the player defeats a raid and checks where the player is. + * + * - `minecraft:impossible`: Triggers only using commands. + * + * - `minecraft:inventory_changed`: Triggers after any changes happen to the player's inventory. + * + * - `minecraft:item_durability_changed`: Triggers after any item in the inventory has been damaged in any form. + * + * - `minecraft:item_used_on_block`: Triggers when the player uses their hand or an item on a block. + * + * - `minecraft:killed_by_crossbow`: Triggers after the player kills a mob or player using a crossbow in ranged combat. + * + * - `minecraft:levitation`: Triggers when the player has the levitation status effect. + * + * - `minecraft:location`: Triggers every 20 ticks (1 second) and checks where the player is. + * + * - `minecraft:nether_travel`: Triggers when the player travels to the Nether and then returns to the Overworld. + * + * - `minecraft:placed_block`: Triggers when the player places a block. + * + * - `minecraft:player_generates_container_loot`: Triggers when the player generates the contents of a container with a loot table set. + * + * - `minecraft:player_hurt_entity`: Triggers after the player hurts a mob or player. + * + * - `minecraft:player_interacted_with_entity`: Triggers when the player interacts with an entity. + * + * - `minecraft:player_killed_entity`: Triggers after a player is the source of a mob or player being killed. + * + * - `minecraft:recipe_unlocked`: Triggers after the player unlocks a recipe (using a knowledge book for example). + * + * - `minecraft:shot_crossbow`: Triggers when the player shoots a crossbow. + * + * - `minecraft:slept_in_bed`: Triggers when the player enters a bed. + * + * - `minecraft:slide_down_block`: Triggers when the player slides down a block. + * + * - `minecraft:summoned_entity`: Triggers after an entity has been summoned. + * Works with iron golems (pumpkin and iron blocks), snow golems (pumpkin and snow blocks), the ender dragon (end crystals) + * and the wither (wither skulls and soul sand/soul soil). + * Using dispensers to place the wither skulls or pumpkins will still activate this trigger. + * Spawn eggs, commands and mob spawners will not work however. + * + * - `minecraft:tame_animal`: Triggers after the player tames an animal. + * + * - `minecraft:target_hit`: Triggers when the player shoots a target block. + * + * - `minecraft:thrown_item_picked_up_by_entity`: Triggers after the player throws an item and another entity picks it up. + * + * - `minecraft:tick`: Triggers every tick (20 times a second). + * + * - `minecraft:used_ender_eye`: Triggers when the player uses an eye of ender (in a world where strongholds generate). + * + * - `minecraft:used_totem`: Triggers when the player uses a totem. + * + * - `minecraft:villager_trade`: Triggers after the player trades with a villager or a wandering trader. + * + * - `minecraft:voluntary_exile`: Triggers when the player causes a raid and checks where the player is. + * + */ trigger: NAME /** All the conditions that need to be met when the trigger gets activated. */ conditions: Partial & { - /** A list of loot table conditions that must pass in order for the trigger to activate. - * The checks are applied to the player that would get the advancement. */ + /** + * A list of loot table conditions that must pass in order for the trigger to activate. + * The checks are applied to the player that would get the advancement. + */ player?: PlayerCriterion } } @@ -50,10 +146,12 @@ export type AdvancementTriggers = ( to: DimensionCriterion }> | Trigger<'minecraft:channeled_lightning', { - /** The victims hit by the lightning summoned by the Channeling enchantment. + /** + * The victims hit by the lightning summoned by the Channeling enchantment. * All entities in this list must be hit. * Each entry may also be a list of loot table conditions that must pass in order for the trigger to activate. - * The checks are applied to the victim hit by the enchanted trident. */ + * The checks are applied to the victim hit by the enchanted trident. + */ victims: EntityCriterion[] }> | Trigger<'minecraft:construct_beacon', { @@ -67,13 +165,17 @@ export type AdvancementTriggers = ( /** The item that was consumed. */ item: ItemCriterion }> | Trigger<'minecraft:cured_zombie_villager', { - /** The villager that is the result of the conversion. + /** + * The villager that is the result of the conversion. * The 'type' tag is redundant since it will always be "villager". - * May also be a list of loot table conditions that must pass in order for the trigger to activate. */ + * May also be a list of loot table conditions that must pass in order for the trigger to activate. + */ villager: EntityCriterion - /** The zombie villager right before the conversion is complete (not when it is initiated). + /** + * The zombie villager right before the conversion is complete (not when it is initiated). * The `type` tag is redundant since it will always be `zombie_villager`. - * May also be a list of loot table conditions that must pass in order for the trigger to activate. */ + * May also be a list of loot table conditions that must pass in order for the trigger to activate. + */ zombie: EntityCriterion }> | Trigger<'minecraft:effects_changed', { @@ -90,8 +192,10 @@ export type AdvancementTriggers = ( /** Checks the damage done to the player. */ damage: DamageCriterion }> | Trigger<'minecraft:entity_killed_player', { - /** Checks the entity that was the source of the damage that killed the player (for example: The skeleton that shot the arrow). - * May also be a list of loot table conditions that must pass in order for the trigger to activate. */ + /** + * Checks the entity that was the source of the damage that killed the player (for example: The skeleton that shot the arrow). + * May also be a list of loot table conditions that must pass in order for the trigger to activate. + */ entity: EntityCriterion /** Checks the type of damage that killed the player. Missing corresponding list of loot table conditions for the direct entity. */ killing_blow: DamageCriterion diff --git a/src/_internals/arguments/resources/advancement.ts b/src/_internals/arguments/resources/advancement.ts index b2ecde0c..060ab0a8 100644 --- a/src/_internals/arguments/resources/advancement.ts +++ b/src/_internals/arguments/resources/advancement.ts @@ -2,9 +2,11 @@ import type { LiteralUnion } from '@/generalTypes' import type { ITEMS, JsonTextComponent, NBT } from '@arguments' import type { McFunctionReturn } from '@datapack/Datapack' +import type { AdvancementClass } from '@resources' import type { AdvancementTriggers } from './AdvancementTriggers' -export type AdvancementType = { +/** A representation of a Minecraft advancement. */ +export interface AdvancementType { /** The optional display data. */ display?: { /** The data for the icon. */ @@ -12,21 +14,23 @@ export type AdvancementType = { /** The item id. */ item: LiteralUnion - /** The nbt data of the item. */ - nbt?: NBT + /** The nbt data of the item. Must be a string. */ + nbt?: string } /** The title for this advancement. */ title: JsonTextComponent - /** The optional type of frame for the icon. + /** + * The optional type of frame for the icon. * `challenge` for a tile with a more fancy spiked border as it is used for the kill all mobs advancement, * `goal` for a tile with a rounded border as it is used for the full beacon advancement, - * `task` for a normal tile (default.) */ + * `task` for a normal tile (default.) + */ frame?: 'challenge' | 'goal' | 'task' /** The description of the advancement. */ - description?: JsonTextComponent + description: JsonTextComponent /** Whether or not to show the toast pop up after completing this advancement. Defaults to `true`. */ show_toast?: boolean @@ -34,10 +38,12 @@ export type AdvancementType = { /** Whether or not to announce in the chat when this advancement has been completed. Defaults to `true`. */ announce_to_chat?: boolean - /** Whether or not to hide this advancement and all its children from the advancement screen, + /** + * Whether or not to hide this advancement and all its children from the advancement screen, * until this advancement have been completed. * Has no effect on root advancements themselves, but still affects all their children. - * Defaults to `false`. */ + * Defaults to `false`. + */ hidden?: boolean /** The optional directory for the background to use in this advancement tab (used only for the root advancement). */ @@ -62,12 +68,14 @@ export type AdvancementType = { */ criteria: Record - /** An optional list of requirements (all the ). + /** + * An optional list of requirements (all the ). * If all criteria are required, this may be omitted. * With multiple criteria: requirements contains a list of lists with criteria (all criteria need to be mentioned). * If all of the lists each have any criteria met, the advancement is complete. - * (basically AND grouping of OR groups) */ - requirements?: CRITERIA_NAMES[] | CRITERIA_NAMES[][] + * (basically AND grouping of OR groups) + */ + requirements?: (keyof this['criteria'])[] | (keyof this['criteria'])[][] /** An optional object representing the rewards provided when this advancement is obtained. */ rewards?: { @@ -84,8 +92,10 @@ export type AdvancementType = { function?: string | McFunctionReturn<[]> } - /** The optional parent advancement directory of this advancement. + /** + * The optional parent advancement directory of this advancement. * If this field is absent, this advancement is a root advancement. - * Circular references cause a loading failure. */ - parent?: string + * Circular references cause a loading failure. + */ + parent?: string | AdvancementClass } diff --git a/src/_internals/arguments/resources/criteria/LocationCriterion.ts b/src/_internals/arguments/resources/criteria/LocationCriterion.ts index 5b156b0f..f4d8342d 100644 --- a/src/_internals/arguments/resources/criteria/LocationCriterion.ts +++ b/src/_internals/arguments/resources/criteria/LocationCriterion.ts @@ -30,7 +30,25 @@ export type LocationCriterion = Partial<{ /** Name of a structure. */ feature: LiteralUnion - /** The fluid at the location. */ + /** + * The fluid at the location. + * + * Must be an object defining the fluid. + * + * @example + * { + * fluid: { + * fluid: 'minecraft:lava' + * } + * } + * + * { + * fluid: { + * tag: '#custom:fluids' + * } + * } + * + */ fluid: Partial<{ /** The fluid ID. */ fluid: LiteralUnion diff --git a/src/_internals/arguments/resources/lootTables.ts b/src/_internals/arguments/resources/lootTables.ts index 879463bc..dfb2b020 100644 --- a/src/_internals/arguments/resources/lootTables.ts +++ b/src/_internals/arguments/resources/lootTables.ts @@ -265,11 +265,31 @@ type EntryType> = { } & VALUES type LootTableEntry = { - /** Determines conditions for this entry to be used. If multiple conditions are specified, all must pass. */ + /** + * Determines conditions for this entry to be used. If multiple conditions are specified, all must pass. + * + * @example + * conditions: [ + * { + * condition: '', + * ... + * } + * ] + */ conditions?: PredicateCondition[] - /** Applies functions to the item stack or item stacks being produced. - * Functions are applied in order, so for example looting_enchant must be after set_count to work correctly. */ + /** + * Applies functions to the item stack or item stacks being produced. + * Functions are applied in order, so for example looting_enchant must be after set_count to work correctly. + * + * @example + * functions: [ + * { + * type: '', + * ..., + * } + * ] + */ functions?: LootTableFunction[] /** Determines how often this entry is chosen out of all the entries in the pool. @@ -316,15 +336,35 @@ type LootTableEntry = { ) type LootTablePoll = { - /** Determines conditions for this pool to be used. If multiple conditions are specified, all must pass. */ + /** + * Determines conditions for this pool to be used. If multiple conditions are specified, all must pass. + * + * @example + * conditions: [ + * { + * condition: '', + * ... + * } + * ] + */ conditions?: PredicateCondition[] - /** Applies functions to all item stacks produced by this pool. - * Functions are applied in order, so for example looting_enchant must be after set_count to work correctly. */ + /** + * Applies functions to all item stacks produced by this pool. + * Functions are applied in order, so for example `looting_enchant` must be after `set_count` to work correctly. + * + * @example + * functions: [ + * { + * type: '', + * ..., + * } + * ] + */ functions?: LootTableFunction[] /** Specifies the number of rolls on the pool. Can be either an exact number, a uniform range, or a binomial distribution. */ - rolls?: number | { + rolls: number | { /** * Type of the distribution. Must be one of: * - "uniform" (default), for a uniform probability between a min and a max @@ -347,8 +387,24 @@ type LootTablePoll = { max: number } - /** A list of all things that can be produced by this pool. - * One entry is chosen per roll as a weighted random selection from all entries without failing conditions. */ + /** + * A list of all things that can be produced by this pool. + * One entry is chosen per roll as a weighted random selection from all entries without failing conditions. + * + * @example + * entries: [ + * // First entry + * { + * type: '', + * ... + * }, + * // Second entry + * { + * type: '', + * ... + * }, + * ] + */ entries: LootTableEntry[] } @@ -378,11 +434,41 @@ export type LootTableType = { 'advancement_entity' | 'generic' ), - /** Applies functions to all item stacks produced by this table. - * Functions are applied in order, so for example looting_enchant must be after set_count to work correctly. */ + /** + * Applies functions to all item stacks produced by this table. + * Functions are applied in order, so for example looting_enchant must be after set_count to work correctly. + * + * @example + * functions: [ + * { + * type: '', + * ..., + * } + * ] + */ functions?: LootTableFunction[] - /** A list of all pools for this loot table. - * Each pool used generates items from its list of items based on the number of rolls. Pools are applied in order. */ + /** + * A list of all pools for this loot table. + * Each pool used generates items from its list of items based on the number of rolls. + * + * Pools are applied in order. + * + * @example + * pools: [ + * // First pool + * { + * entries: [{...}], + * conditions: [{...}], + * ... + * }, + * // Second pool + * { + * entries: [{...}], + * conditions: [{...}], + * ... + * }, + * ] + */ pools?: LootTablePoll[] } diff --git a/src/_internals/arguments/resources/predicate.ts b/src/_internals/arguments/resources/predicate.ts index fd704022..89febe03 100644 --- a/src/_internals/arguments/resources/predicate.ts +++ b/src/_internals/arguments/resources/predicate.ts @@ -6,7 +6,44 @@ import type { } from './criteria' type PredicateKind> = { - /** The condition's ID. */ + /** + * The condition's ID. + * + * One of: + * - `alternative`: Joins conditions from parameter terms with "or". + * + * - `block_state_property`: Check properties of a block state. + * + * - `damage_source_properties`: Check properties of damage source. + * + * - `entity_properties`: Test properties of an entity. + * + * - `entity_scores`: Test the scoreboard scores of an entity. + * + * - `inverted`: Inverts condition from parameter term. + * + * - `killed_by_player`: Test if a `killer_player` entity is available. + * + * - `location_check`: Checks if the current location matches. + * + * - `match_tool`: Checks tool. + * + * - `random_chance`: Test if a random number 0.0–1.0 is less than a specified value. + * + * - `random_chance_with_looting`: Test if a random number 0.0–1.0 is less than a specified value, affected by the level of Looting on the `killer` entity. + * + * - `reference`: Test if another referred condition (predicate) passes. + * + * - `survives_explosion`: Returns true with 1/explosion radius probability. + * + * - `table_bonus`: Passes with probability picked from table, indexed by enchantment level. + * + * - `time_check`: Checks the current time + * + * - `weather_check `: Checks for a current weather state + * + * - `value_check`: Checks for range of value + */ condition: NAME } & VALUES @@ -14,8 +51,12 @@ export type ObjectOrArray = T | T[] export type PredicateCondition = ( PredicateKind<'minecraft:alternative', { - /** A list of conditions to join using `or`. */ - terms: PredicateType[] + /** + * A list of conditions to join using `or`. + * @example + * terms: [{ }] + */ + terms: PredicateCondition[] }> | PredicateKind<'minecraft:block_state_property', { /** A block ID. The test fails if the block doesn't match. */ block: LiteralUnion @@ -25,23 +66,38 @@ export type PredicateCondition = ( /** Predicate applied to the damage source. */ predicate: DamageCriterion }> | PredicateKind<'minecraft:entity_properties', { - /** Specifies the entity to check for the condition. + /** + * Specifies the entity to check for the condition. * Set to `this` to use the entity that died or the player that gained the advancement, opened the container or broke the block. * `killer` for the killer. - * `killer_player` for a killer that is a player. */ + * `killer_player` for a killer that is a player. + */ entity: 'this' | 'killer' | 'killer_player' /** Predicate applied to entity. */ predicate: EntityCriterion }> | PredicateKind<'minecraft:entity_scores', { - /** Specifies the entity to check for the condition. + /** + * Specifies the entity to check for the condition. * Set to `this` to use the entity that died or the player that gained the advancement, opened the container or broke the block. * `killer` for the killer. - * `killer_player` for a killer that is a player. */ + * `killer_player` for a killer that is a player. + */ entity: 'this' | 'killer' | 'killer_player' - /** Scores to check. All specified scores must pass for the condition to pass. - * Key name are the objectives, while the value are the required score. */ + /** + * Scores to check. All specified scores must pass for the condition to pass. + * Key name are the objectives, while the value are the required score. + * + * @example + * scores: { + * 'myscore': 0, + * 'myscore2': { + * min: 5, + * max: 20, + * } + * } + */ scores: Record }> | PredicateKind<'minecraft:inverted', { /** The condition to be negated. */ @@ -86,9 +142,11 @@ export type PredicateCondition = ( /** The time value in ticks. */ value: NumberOrMinMax - /** If present, time gets modulo-divided by this value. + /** + * If present, time gets modulo-divided by this value. * - * For example, if set to 24000, value operates on a time period of days. */ + * For example, if set to 24000, value operates on a time period of days. + */ period?: number }> | PredicateKind<'minecraft:weather_check', { /** If true, the condition evaluates to true only if it's raining. */ diff --git a/src/_internals/arguments/resources/recipe.ts b/src/_internals/arguments/resources/recipe.ts index b9512175..ab04de98 100644 --- a/src/_internals/arguments/resources/recipe.ts +++ b/src/_internals/arguments/resources/recipe.ts @@ -79,8 +79,10 @@ type RecipeKind | un type: NAME } & VALUES & ( HAS_GROUP extends true ? { - /** Used to group multiple recipes together in the recipe book. - * Example: group all boats recipes. */ + /** + * Used to group multiple recipes together in the recipe book. + * Example: group all boats recipes. + */ group: string } : unknown ) @@ -118,26 +120,29 @@ export type RecipeType * key: {C: {item: 'minecraft:cobblestone'}, R: {item: 'minecraft:redstone'}}, * result: {item: 'minecraft:dropper'} * } - */ + */ pattern: [ StringSmallerThan4, StringSmallerThan4?, StringSmallerThan4?, ] } & ({ - // Note: you might notice a little trick here. - // As you can see, the object here is: - // { pattern: [] } & ({recipe: foo, result: bar} | never ) - // - // This (theoretically) should resolve to { pattern: [], recipe: foo, result: bar }. - // So why not use the latter, both more simple & more obvious? - // Because of a small bug in VSCode, who doesn't autocomplete generics-derived types until - // all properties of the object has been defined. Here, you won't get this keys autocompletion, - // until you define the `result` property. - // - // This little trick forces VSCode to autocomplete properly the keys. - - /** All keys used for this shaped crafting recipe. + /* + * Note: you might notice a little trick here. + * As you can see, the object here is: + * { pattern: [] } & ({recipe: foo, result: bar} | never ) + * + * This (theoretically) should resolve to { pattern: [], recipe: foo, result: bar }. + * So why not use the latter, both more simple & more obvious? + * Because of a small bug in VSCode, who doesn't autocomplete generics-derived types until + * all properties of the object has been defined. Here, you won't get this keys autocompletion, + * until you define the `result` property. + * + * This little trick forces VSCode to autocomplete properly the keys. + */ + + /** + * All keys used for this shaped crafting recipe. * * Each key corresponds to a character present in `pattern`. * Their values are either an ingredient, or a list of possible ingredients. @@ -154,13 +159,15 @@ export type RecipeType */ key: KeysIngredients<[P1, P2, P3]> - /** The output item of the recipe. Not optional. */ + /** The output item of the recipe. */ result: { /** The resulting item. */ item: LiteralUnion - /** Optional. The amount of the item. - * @default 1 */ + /** + * Optional. The amount of the item. + * @default 1 + */ count?: number } } | never) diff --git a/src/_internals/commands/implementations/Loot.ts b/src/_internals/commands/implementations/Loot.ts index 6483300a..4ef3450e 100644 --- a/src/_internals/commands/implementations/Loot.ts +++ b/src/_internals/commands/implementations/Loot.ts @@ -94,7 +94,7 @@ export class Loot extends Command { } /** - * Distributes items to blocks. + * Replace an entity slot with the items. * * @param targetPos Specifies the position of a block. * diff --git a/src/_internals/datapack/BasePath.ts b/src/_internals/datapack/BasePath.ts index 217cfd6d..259cd00b 100644 --- a/src/_internals/datapack/BasePath.ts +++ b/src/_internals/datapack/BasePath.ts @@ -4,7 +4,7 @@ import type { import type { Datapack } from '@datapack' import type { HintedTagStringType, McFunctionOptions } from '@resources' import { - Advancement, LootTable, McFunction, Predicate, Recipe, Tag, + AdvancementClass, LootTable, MCFunctionClass, Predicate, Recipe, Tag, } from '@resources' import type { McFunctionReturn } from './Datapack' import type { TagSingleValue } from './resourcesTree' @@ -28,7 +28,7 @@ function pathToArray(path?: string): string[] { } /** Changes the base namespace & directory of nested resources. */ -export class BasePath { +export class BasePathClass { protected datapack: Datapack namespace?: string @@ -83,6 +83,12 @@ export class BasePath { * . (Period) * - (Hyphen/minus) */ + if (!path.length) { + throw new Error( + 'Empty name is not allowed.', + ) + } + if (!path.match(/^[0-9a-z_\-/.]+$/)) { throw new Error( `Resources names can only contain numbers, lowercase letters, underscores, forward slash, period, and hyphens: got "${path}"`, @@ -113,7 +119,7 @@ export class BasePath { const newDirectory = pathToArray(trimSlashes(childPath.directory)) const oldDirectory = pathToArray(this.directory) - return new BasePath(this.datapack, { + return new BasePathClass(this.datapack, { namespace: this.namespace, directory: [...oldDirectory, ...newDirectory].join('/'), }) @@ -125,12 +131,12 @@ export class BasePath { * @param name The name of the function. * @param callback A callback containing the commands you want in the Minecraft Function. */ - mcfunction = ( + MCFunction = ( name: string, callback: (...args: T) => void, options?: McFunctionOptions, ): McFunctionReturn => { - const mcfunction = new McFunction(this.datapack, this.getName(name), callback, options ?? {}) + const mcfunction = new MCFunctionClass(this.datapack, this.getName(name), callback, options ?? {}) - this.datapack.rootFunctions.add(mcfunction as McFunction) + this.datapack.rootFunctions.add(mcfunction as MCFunctionClass) const returnFunction: any = mcfunction.call returnFunction.schedule = mcfunction.schedule @@ -146,18 +152,63 @@ export class BasePath { * @param name The name of the function. * @param callback A callback containing the commands you want in the Minecraft Function. */ - Function = this.mcfunction + Function = this.MCFunction - /** Create an advancement. */ - Advancement = (name: string, advancement: AdvancementType) => new Advancement(this.datapack.commandsRoot, this.getName(name), advancement) + /** + * Create an advancement. + * + * @param advancement The actual advancement. You must provide at least a `criteria` for it to be valid. + * + * @example + * + * Advancement('bred_two_cows', { + * criteria: { + * 'bred_cows': { + * trigger: 'minecraft:bred_animals', + * conditions: { + * child: { type: 'minecraft:cow' } + * } + * } + * } + * }) + */ + Advancement = (name: string, advancement: AdvancementType) => new AdvancementClass(this.datapack, this.getName(name), advancement) - /** Create a predicate. */ - Predicate = (name: string, predicate: PredicateType) => new Predicate(this.datapack.commandsRoot, this.getName(name), predicate) + /** + * Create a predicate. + * + * @param predicate The actual predicate. You must provide at least a `condition` for it to be valid. + * + * @example + * + * Predicate('is_raining', { + * condition: 'minecraft:weather_check', + * raining: true, + * }) + */ + Predicate = (name: string, predicate: PredicateType) => new Predicate(this.datapack, this.getName(name), predicate) /** Create a tag. */ Tag = (type: T, name: string, values: TagSingleValue>[], replace?: boolean) => new Tag(this.datapack, type, this.getName(name), values, replace) - /** Create a loot table. */ + /** + * Create a loot table. + * + * @param lootTable The actual loot table. Each pool must provide a number of `rolls` and a list of `entries` to be valid. + * Each entry must at least provide its `type` and the type-dependant required properties. + * + * @example + * + * LootTable('give_diamond', { + * pools: [{ + * rolls: 1, + * entries: [{ + * type: 'item', + * name: 'minecraft:diamond', + * }], + * }], + * }) + */ LootTable = (name: string, lootTable: LootTableType) => new LootTable(this.datapack, this.getName(name), lootTable) /** Create a recipe. */ diff --git a/src/_internals/datapack/Datapack.ts b/src/_internals/datapack/Datapack.ts index 5dd09037..cd0ad0b7 100644 --- a/src/_internals/datapack/Datapack.ts +++ b/src/_internals/datapack/Datapack.ts @@ -2,12 +2,12 @@ import type { LiteralUnion } from '@/generalTypes' import type { JsonTextComponent, OBJECTIVE_CRITERION } from '@arguments' import { CommandsRoot } from '@commands' import { Flow } from '@flow' -import type { McFunction } from '@resources' +import type { MCFunctionClass } from '@resources' import type { ObjectiveClass } from '@variables' import { Objective, SelectorCreator } from '@variables' import chalk from 'chalk' import type { BasePathOptions } from './BasePath' -import { BasePath } from './BasePath' +import { BasePathClass } from './BasePath' import type { CommandArgs } from './minecraft' import { toMcFunctionName } from './minecraft' import type { @@ -28,7 +28,7 @@ export interface McFunctionReturn { } export default class Datapack { - basePath: BasePath + basePath: BasePathClass defaultNamespace: string @@ -42,7 +42,7 @@ export default class Datapack { constants: Set - rootFunctions: Set> + rootFunctions: Set> static anonymousScoreId = 0 @@ -51,7 +51,7 @@ export default class Datapack { initCommands: CommandArgs[] constructor(namespace: string) { - this.basePath = new BasePath(this, {}) + this.basePath = new BasePathClass(this, { namespace }) this.defaultNamespace = namespace this.currentFunction = null this.resources = new ResourcesTree() @@ -334,7 +334,7 @@ export default class Datapack { Selector = SelectorCreator.bind(this) /** A BasePath changes the base namespace & directory of nested resources. */ - BasePath = (basePath: BasePathOptions) => new BasePath(this, basePath) + BasePath = (basePath: BasePathOptions) => new BasePathClass(this, basePath) addResource = (name: string, type: T, resource: Omit) => { this.resources.addResource(type, { diff --git a/src/_internals/datapack/saveDatapack.ts b/src/_internals/datapack/saveDatapack.ts index 99cc3504..7d9417ec 100644 --- a/src/_internals/datapack/saveDatapack.ts +++ b/src/_internals/datapack/saveDatapack.ts @@ -165,7 +165,7 @@ function saveResource( */ export async function saveDatapack(resources: ResourcesTree, name: string, options: SaveOptions) { // This ensure the function is async, and can be await - const writeFileToDisk = async (info: SaveFileObject) => { + const writeFileToDisk = async (info: SaveFileObject) => { const func = options?.customFileHandler ?? writeFile return func(info) } diff --git a/src/_internals/flow/Flow.ts b/src/_internals/flow/Flow.ts index 3f12268d..c345990c 100644 --- a/src/_internals/flow/Flow.ts +++ b/src/_internals/flow/Flow.ts @@ -131,8 +131,10 @@ export class Flow { /** Flow statements */ flowStatement = (callback: () => void, config: FlowStatementConfig) => { - // Sometimes, there are a few arguments left inside the commandsRoot (for execute.run mostly). - // Keep them aside, & register them after. + /* + * Sometimes, there are a few arguments left inside the commandsRoot (for execute.run mostly). + * Keep them aside, & register them after. + */ const previousArguments = this.commandsRoot.arguments const previousInExecute = this.commandsRoot.inExecute this.commandsRoot.reset() @@ -172,9 +174,11 @@ export class Flow { && callbackMcFunction.commands.length <= 1 && callbackMcFunction.commands?.[0]?.[0] !== 'execute' ) { - // If our callback only has 1 command, inline this command. We CANNOT inline executes, for complicated reasons. - // If you want to understand the reasons, see @vdvman1#9510 explanation => - // https://discordapp.com/channels/154777837382008833/154777837382008833/754985742706409492 + /* + * If our callback only has 1 command, inline this command. We CANNOT inline executes, for complicated reasons. + * If you want to understand the reasons, see @vdvman1#9510 explanation => + * https://discordapp.com/channels/154777837382008833/154777837382008833/754985742706409492 + */ this.commandsRoot.Datapack.resources.deleteResource(callbackMcFunction.path, 'functions') if (callbackMcFunction.commands.length) { @@ -318,16 +322,20 @@ export class Flow { const _ = this - // For all iterations above the maximum, - // just do a while loop that calls `maximum` times the callback, - // until there is less than `maximum` iterations + /* + * For all iterations above the maximum, + * just do a while loop that calls `maximum` times the callback, + * until there is less than `maximum` iterations + */ _.while(iterations.lowerThan(maximum), () => { callback(maximum) iterations.remove(maximum) }) - // There is now less iterations than the allowed MAXIMUM - // Start the binary part + /* + * There is now less iterations than the allowed MAXIMUM + * Start the binary part + */ for (let i = 1; i < maximum; i *= 2) { _.if(iterations.moduloBy(2).equalTo(1), () => { callback(i) @@ -354,8 +362,10 @@ export class Flow { const scoreTracker = from instanceof PlayerScore ? from : this.commandsRoot.Datapack.Variable(from) - // If the callback does not use the "score" argument, we can directly set the real score - // `scoreTracker` to the end (instead of spamming `scoreboard players add anonymous sand_ano 1`). + /* + * If the callback does not use the "score" argument, we can directly set the real score + * `scoreTracker` to the end (instead of spamming `scoreboard players add anonymous sand_ano 1`). + */ if (callback.length === 0) { // Typescript has a bug, and will not recognize `scoreTracker.set(to)` as a valid expression. So I have to use this condition. if (typeof to === 'number') { diff --git a/src/_internals/resources/Advancement.ts b/src/_internals/resources/Advancement.ts deleted file mode 100644 index da48c5d8..00000000 --- a/src/_internals/resources/Advancement.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { AdvancementType, MultiplePlayersArgument } from '@arguments' -import type { CommandsRoot } from '@commands' -import { toMcFunctionName } from '@datapack/minecraft' - -export class Advancement { - private commandsRoot - - private path - - private advancementJson - - constructor(commandsRoot: CommandsRoot, name: string, advancement: AdvancementType) { - this.commandsRoot = commandsRoot - this.advancementJson = advancement - - this.path = this.commandsRoot.Datapack.getResourcePath(name) - - this.commandsRoot.Datapack.addResource(name, 'advancements', { advancement }) - } - - get name(): string { - return toMcFunctionName(this.path.fullPathWithNamespace) - } - - /** - * Grant this achievement to the players. - */ - grant(players: MultiplePlayersArgument, criterion?: CriteriaNames) { - this.commandsRoot.advancement.grant(players).only(this.name, criterion) - } - - /** - * Revoke this achievement from the players. - */ - revoke(players: MultiplePlayersArgument, criterion?: CriteriaNames) { - this.commandsRoot.advancement.revoke(players).only(this.name, criterion) - } - - /** - * Grant all achievements until this one to the players. - */ - grantUntilThis(players: MultiplePlayersArgument) { - this.commandsRoot.advancement.grant(players).until(this.name) - } - - /** - * Revoke all achievements until this one from the players. - */ - revokeUntilThis(players: MultiplePlayersArgument) { - this.commandsRoot.advancement.revoke(players).until(this.name) - } - - /** - * Grant all achievements from this one to the players. - */ - grantFromThis(players: MultiplePlayersArgument) { - this.commandsRoot.advancement.grant(players).from(this.name) - } - - /** - * Revoke all achievements from this one from the players. - */ - revokeFromThis(players: MultiplePlayersArgument) { - this.commandsRoot.advancement.revoke(players).from(this.name) - } -} diff --git a/src/_internals/resources/AdvancementClass.ts b/src/_internals/resources/AdvancementClass.ts new file mode 100644 index 00000000..274637f0 --- /dev/null +++ b/src/_internals/resources/AdvancementClass.ts @@ -0,0 +1,93 @@ +import type { AdvancementType, MultiplePlayersArgument } from '@arguments' +import type { Datapack } from '@datapack' +import { Resource } from './Resource' + +export class AdvancementClass extends Resource { + advancementJson + + constructor(datapack: Datapack, name: string, advancement: AdvancementType) { + super(datapack, name) + + this.advancementJson = advancement + + this.datapack.addResource(name, 'advancements', { advancement }) + } + + /** + * Grant this advancement to the players. + */ + grant(players: MultiplePlayersArgument, criterion?: CriteriaNames) { + this.commandsRoot.advancement.grant(players).only(this.name, criterion) + } + + /** + * Revoke this advancement from the players. + */ + revoke(players: MultiplePlayersArgument, criterion?: CriteriaNames) { + this.commandsRoot.advancement.revoke(players).only(this.name, criterion) + } + + /** + * Grant this advancement and all its parent advancements to the players. + * Think of specifying everything from the start *until* that advancement. + * + * The exact order the operation is carried out in is: `parent > parent's parent > ... > root > this advancement.` + */ + grantUntilThis(players: MultiplePlayersArgument) { + this.commandsRoot.advancement.grant(players).until(this.name) + } + + /** + * Revoke this advancement and all its parent advancements from the players. + * Think of specifying everything from the start *until* that advancement. + * + * The exact order the operation is carried out in is: `parent > parent's parent > ... > root > this advancement.` + */ + revokeUntilThis(players: MultiplePlayersArgument) { + this.commandsRoot.advancement.revoke(players).until(this.name) + } + + /** + * Grant this advancement and all its children advancements to the players. + * Think of specifying everything *from* that advancement to the end. + * + * The exact order the operation is carried out in is specified `advancement > child > child's child > ...`. + * When it operates on a child that branches, it iterates through all its children before continuing. + */ + grantFromThis(players: MultiplePlayersArgument) { + this.commandsRoot.advancement.grant(players).from(this.name) + } + + /** + * Revoke this advancement and all its children advancements from the players. + * Think of specifying everything *from* that advancement to the end. + * + * The exact order the operation is carried out in is specified `advancement > child > child's child > ...`. + * When it operates on a child that branches, it iterates through all its children before continuing. + */ + revokeFromThis(players: MultiplePlayersArgument) { + this.commandsRoot.advancement.revoke(players).from(this.name) + } + + /** + * Grant this advancement, all its parent advancements, and all its children advancements to the players. + * Think of specifying everything through the specified advancement, going both backwards and forwards. + * + * The exact order the operation is as if the command were executed with `until` specified, then with `from` specified: + * `parent > parent's parent > ... > root > specified advancement > child > child's child > ...` + */ + grantThroughThis(players: MultiplePlayersArgument) { + this.commandsRoot.advancement.grant(players).through(this.name) + } + + /** + * Revoke this advancement, all its parent advancements, and all its children advancements from the players. + * Think of specifying everything through the specified advancement, going both backwards and forwards. + * + * The exact order the operation is as if the command were executed with `until` specified, then with `from` specified: + * `parent > parent's parent > ... > root > specified advancement > child > child's child > ...` + */ + revokeThroughThis(players: MultiplePlayersArgument) { + this.commandsRoot.advancement.revoke(players).through(this.name) + } +} diff --git a/src/_internals/resources/LootTable.ts b/src/_internals/resources/LootTable.ts index 04bcf0a5..0708d1b7 100644 --- a/src/_internals/resources/LootTable.ts +++ b/src/_internals/resources/LootTable.ts @@ -4,33 +4,60 @@ import type { } from '@arguments' import type { Datapack } from '@datapack' import { toMcFunctionName } from '@datapack/minecraft' +import { Resource } from './Resource' -export class LootTable { - lootTable - - private path - - private datapack +export class LootTable extends Resource { + lootTableJson constructor(datapack: Datapack, name: string, lootTable: LootTableType) { - this.lootTable = lootTable - - this.path = datapack.getResourcePath(name) + super(datapack, name) - this.datapack = datapack + this.lootTableJson = lootTable datapack.addResource(name, 'loot_tables', { lootTable }) } - get name() { - return toMcFunctionName(this.path.fullPathWithNamespace) - } - + /** Gives items to players, ignoring empty item stacks. */ give = (players: MultiplePlayersArgument) => this.datapack.commandsRoot.loot.give(players).loot(this.name) + /** + * Distributes items to a container block. + * @param targetPos Specifies the position of a block. + */ insert = (targetPos: Coordinates) => this.datapack.commandsRoot.loot.insert(targetPos).loot(this.name) + /** + * Replace a container block slot with the items. + * @param targetPos Specifies the position of a block. + * + * @param slot Specifies the inventory slot to be modified. + * Must be must be `container.` where `` is replaced with a number specifying the slot. + * + * - Chests, dispensers, droppers, hoppers, and trapped chests are numbered 0 for the top-left slot and then increase + * first horizontally, then vertically (so, for example, a chest's top row slots are numbered 0 to 8 from left to right). + * Double chests and double trapped chests are treated as two single container blocks. + * - A brewing stand's bottom slots are numbered 0 to 2 from left to right, its top slot is 3 and the fuel slot is 4. + * - A furnace's slots are numbered 0 for the input slot, 1 for the fuel slot, and 2 for the output slot. + * + * @param count Specifies the number of consecutive slots to be filled. Must be between 0 and 2147483647 (inclusive). + */ replaceBlock = (targetPos: Coordinates, slot: string, count?: number) => this.datapack.commandsRoot.loot.replaceBlock(targetPos, slot, count).loot(this.name) + /** + * Replace an entity slot with the items. + * + * @param targetPos Specifies the position of a block. + * + * @param slot Specifies the inventory slot to be modified. + * Must be must be `container.` where `` is replaced with a number specifying the slot. + * + * - Chests, dispensers, droppers, hoppers, and trapped chests are numbered 0 for the top-left slot and then increase + * first horizontally, then vertically (so, for example, a chest's top row slots are numbered 0 to 8 from left to right). + * Double chests and double trapped chests are treated as two single container blocks. + * - A brewing stand's bottom slots are numbered 0 to 2 from left to right, its top slot is 3 and the fuel slot is 4. + * - A furnace's slots are numbered 0 for the input slot, 1 for the fuel slot, and 2 for the output slot. + * + * @param count Specifies the number of consecutive slots to be filled. Must be between 0 and 2147483647 (inclusive). + */ replaceEntity = (entities: MultipleEntitiesArgument, slot: LiteralUnion, count?: number) => this.datapack.commandsRoot.loot.replaceEntity(entities, slot, count).loot(this.name) } diff --git a/src/_internals/resources/McFunction.ts b/src/_internals/resources/MCFunctionClass.ts similarity index 97% rename from src/_internals/resources/McFunction.ts rename to src/_internals/resources/MCFunctionClass.ts index 6e275fdf..9bbfb852 100644 --- a/src/_internals/resources/McFunction.ts +++ b/src/_internals/resources/MCFunctionClass.ts @@ -1,8 +1,8 @@ import type { LiteralUnion } from '@/generalTypes' import type { Datapack } from '@datapack' import hash from 'object-hash' -import { toMcFunctionName } from '../datapack/minecraft' -import type { FunctionResource } from '../datapack/resourcesTree' +import { toMcFunctionName } from '@datapack/minecraft' +import type { FunctionResource } from '@datapack/resourcesTree' export type McFunctionOptions = { /** @@ -35,7 +35,7 @@ export type McFunctionOptions = { tags?: readonly string[] } -export class McFunction { +export class MCFunctionClass { name: string options: McFunctionOptions diff --git a/src/_internals/resources/Predicate.ts b/src/_internals/resources/Predicate.ts index 949ce940..c93a3f4d 100644 --- a/src/_internals/resources/Predicate.ts +++ b/src/_internals/resources/Predicate.ts @@ -1,36 +1,25 @@ -import type { AdvancementType, MultiplePlayersArgument, PredicateType } from '@arguments' -import type { CommandsRoot } from '@commands' -import { toMcFunctionName as pathToResourceName } from '@datapack/minecraft' -import { ConditionClass } from '@variables' +import type { PredicateType } from '@arguments' +import type { Datapack } from '@datapack' +import type { ConditionClass } from '@variables' +import { Resource } from './Resource' -export class Predicate extends ConditionClass { - private commandsRoot +export class Predicate extends Resource implements ConditionClass { + predicateJson - private path + constructor(datapack: Datapack, name: string, predicate: PredicateType) { + super(datapack, name) - private predicateJson - - constructor(commandsRoot: CommandsRoot, name: string, predicate: PredicateType) { - super() - - this.commandsRoot = commandsRoot this.predicateJson = predicate - this.path = this.commandsRoot.Datapack.getResourcePath(name) this.commandsRoot.Datapack.addResource(name, 'predicates', { predicate }) } - get name(): string { - return pathToResourceName(this.path.fullPathWithNamespace) - } - + /** + * @internal + */ _toMinecraftCondition(): {value: any[]} { return { value: ['predicate', this.name], } } - - toString() { - return this.name - } } diff --git a/src/_internals/resources/Recipe.ts b/src/_internals/resources/Recipe.ts index 1560109c..6f274d06 100644 --- a/src/_internals/resources/Recipe.ts +++ b/src/_internals/resources/Recipe.ts @@ -1,26 +1,18 @@ import type { MultiplePlayersArgument, RecipeType } from '@arguments' import type { Datapack } from '@datapack' +import { Resource } from './Resource' -export class Recipe { - private datapack - - private path - - recipe +export class Recipe extends Resource { + recipeJson constructor(datapack: Datapack, name: string, recipe: RecipeType) { - this.datapack = datapack - this.recipe = recipe + super(datapack, name) - this.path = datapack.getResourcePath(name) + this.recipeJson = recipe this.datapack.addResource(name, 'recipes', { recipe }) } - get name() { - return this.path.name - } - /** Give this recipe to the given players. */ give = (targets: MultiplePlayersArgument) => { this.datapack.commandsRoot.recipe.give(targets, this.name) diff --git a/src/_internals/resources/Resource.ts b/src/_internals/resources/Resource.ts new file mode 100644 index 00000000..88cedeea --- /dev/null +++ b/src/_internals/resources/Resource.ts @@ -0,0 +1,28 @@ +import type { Datapack } from '@datapack' +import { toMcFunctionName } from '@datapack/minecraft' + +export class Resource { + protected datapack + + protected commandsRoot + + protected path + + constructor(datapack: Datapack, name: string) { + this.datapack = datapack + + this.commandsRoot = datapack.commandsRoot + + this.path = datapack.getResourcePath(name) + } + + get name(): string { + return toMcFunctionName(this.path.fullPathWithNamespace) + } + + toString() { + return this.name + } + + toJSON() { return this.toString() } +} diff --git a/src/_internals/resources/Tag.ts b/src/_internals/resources/Tag.ts index 5bee3227..a5074c69 100644 --- a/src/_internals/resources/Tag.ts +++ b/src/_internals/resources/Tag.ts @@ -6,6 +6,7 @@ import type { Datapack } from '@datapack' import type { McFunctionReturn } from '@datapack/Datapack' import { toMcFunctionName } from '@datapack/minecraft' import type { TagSingleValue } from '@datapack/resourcesTree' +import { Resource } from './Resource' export type HintedTagStringType = ( T extends 'blocks' ? LiteralUnion : @@ -24,18 +25,14 @@ function isTagObject(v: TagSingleValue): v is Exclude, T return typeof v === 'object' } -export class Tag { +export class Tag extends Resource { readonly type readonly values: TagSingleValue[] - readonly name - - readonly datapack - - private readonly paths - constructor(datapack: Datapack, type: TYPE, name: string, values: readonly TagSingleValue>[], replace?: boolean) { + super(datapack, name) + this.type = type this.values = values.map((v) => { @@ -51,21 +48,22 @@ export class Tag { return v as string | TagSingleValue }) - this.name = name this.datapack = datapack - this.paths = datapack.getResourcePath(name) - datapack.resources.addResource('tags', { children: new Map(), isResource: true, - path: [this.paths.namespace, type, ...this.paths.fullPath], + path: [this.path.namespace, type, ...this.path.fullPath], values: this.values, replace, }) } + get name() { + return `#${toMcFunctionName(this.path.fullPathWithNamespace)}` + } + toString() { - return `#${toMcFunctionName(this.paths.fullPathWithNamespace)}` + return this.name } } diff --git a/src/_internals/resources/index.ts b/src/_internals/resources/index.ts index e8d17c90..800cb842 100644 --- a/src/_internals/resources/index.ts +++ b/src/_internals/resources/index.ts @@ -1,6 +1,7 @@ -export * from './Advancement' -export * from './McFunction' +export * from './AdvancementClass' +export * from './MCFunctionClass' export * from './Predicate' export * from './LootTable' export * from './Tag' +export * from './Resource' export * from './Recipe' diff --git a/src/_internals/variables/NBTs.ts b/src/_internals/variables/NBTs.ts index f19a93e1..97e85746 100644 --- a/src/_internals/variables/NBTs.ts +++ b/src/_internals/variables/NBTs.ts @@ -12,6 +12,12 @@ function customUnit(num: number, unit: string): NBTCustomObject { }() } +function customUnitArray(numbers: number[], unit: string): NBTCustomObject { + return new class { + [util.inspect.custom] = () => `[I; ${numbers.join(', ')}]` + }() +} + function customNumber(num: number | number[], unit: string): NBTCustomObject | NBTCustomObject[] { if (Array.isArray(num)) { return num.map((n) => customUnit(n, unit)) @@ -24,7 +30,7 @@ interface NBTInterface { toString: (nbt: NBTObj) => string /** - * Transform a number into a Minecraft NBT float number. + * Transform a number into a Minecraft NBT float number. * * @param floatNumber The number to transform. * @@ -142,6 +148,31 @@ interface NBTInterface { * summon(..., { Test: NBT.long([0, 1, 2]) }) // => { Test: [0l, 1l, 2l] } */ long(longNumbers: number[]): NBTCustomObject[] + + /** + * Transforms an array into an Integer array. + * + * @param intNumbers The numbers to transform. + * + * @example + * summon(..., { Test: [0, 1, 2] }) // => { Test: [0, 1, 2] } + * + * summon(..., { Test: NBT.integerArray([0, 1, 2]) }) // => { Test: [I; 0, 1, 2] } + */ + integerArray(intNumbers: number[]): NBTCustomObject, + + /** + * Transforms an array into a Long array. + * + * @param longNumbers The numbers to transform. + * + * @example + * summon(..., { Test: [0, 1, 2] }) // => { Test: [0, 1, 2] } + * + * summon(..., { Test: NBT.longArray([0, 1, 2]) }) // => { Test: [L; 0, 1, 2] } + */ + longArray(longNumbers: number[]): NBTCustomObject, + } export const NBT: NBTInterface = { @@ -157,4 +188,8 @@ export const NBT: NBTInterface = { short: (num: number | number[]): any => customNumber(num, 's'), long: (num: number | number[]): any => customNumber(num, 'l'), + + integerArray: (numbers: number[]): any => customUnitArray(numbers, 'I'), + + longArray: (numbers: number[]): any => customUnitArray(numbers, 'L'), } diff --git a/src/core.ts b/src/core.ts index c962821d..40a867cc 100644 --- a/src/core.ts +++ b/src/core.ts @@ -5,7 +5,7 @@ export const { } = datapack export const { - mcfunction, Advancement, Predicate, Tag, LootTable, Recipe, + MCFunction, Advancement, Predicate, Tag, LootTable, Recipe, } = datapack.basePath export { _ } from './_internals' diff --git a/tests/menu.ts b/tests/menu.ts index 0d0e8a46..c0ea6436 100644 --- a/tests/menu.ts +++ b/tests/menu.ts @@ -4,7 +4,7 @@ import type { import { clear, execute, replaceitem, } from '../src/commands' -import { mcfunction } from '../src/core' +import { MCFunction } from '../src/core' import type { PlayerScore } from '../src/variables' import { createObjective, Variable } from '../src/variables' import { nbtParser, SelectorCreator, _ } from '../src/_internals' @@ -164,7 +164,7 @@ export class Menu { } } - mcfunction: typeof mcfunction = (name, callback, options?) => mcfunction(`__inventory__/${this.menuId}/${name}`, callback, options) + mcfunction: typeof MCFunction = (name, callback, options?) => MCFunction(`__inventory__/${this.menuId}/${name}`, callback, options) executeCallback = this.mcfunction('callback', (playerSelectedSlot: PlayerScore) => { const playerSubMenu = currentSubMenuObjective.ScoreHolder('@s') diff --git a/tests/test.ts b/tests/test.ts index 3a2476b7..98fec03a 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -1,20 +1,20 @@ /** eslint-disable */ import { - effect, give, summon, tellraw, + effect, give, say, summon, tellraw, } from '../src/commands' import { Advancement, - BasePath, mcfunction, savePack, + BasePath, LootTable, MCFunction, Predicate, Recipe, savePack, } from '../src/core' import { NBT, rel, Selector } from '../src/variables' -import { datapack } from '../src/_internals' +import { datapack, _ } from '../src/_internals' import { ResourcesTree } from '../src/_internals/datapack/resourcesTree' import { Menu } from './menu' datapack.resources = new ResourcesTree() // Advancement granted to survival players breeding 2 cows -Advancement('bred_cows', { +const breedCowsAdvancement = Advancement('breed_cows', { criteria: { bred_two_cows: { trigger: 'minecraft:bred_animals', @@ -24,6 +24,28 @@ Advancement('bred_cows', { }, }, }, + + display: { + icon: { + item: 'minecraft:player_head', + nbt: NBT.toString({ + display: { Name: JSON.stringify([{ text: 'Cow' }]) }, + SkullOwner: { + Id: [210170708, 1898725698, -1548776749, -180478570], + Properties: { + textures: [{ + Value: 'eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzVhOWNkNThkNGM2N2JjY2M4ZmIxZjVmNzU2YTJkMzgxYzlmZmFjMjkyNGI3ZjRjYjcxYWE5ZmExM2ZiNWMifX19', + }], + }, + }, + }), + }, + description: [{ text: 'Breed two cows together.' }], + title: ['Breed two cows'], + announce_to_chat: true, + show_toast: true, + background: 'minecraft:dirt', + }, }) const myBasePath = BasePath({ @@ -31,7 +53,7 @@ const myBasePath = BasePath({ directory: 'path/to/', }) -myBasePath.Predicate('test_if_sprinting', { +const myPred = myBasePath.Predicate('test_if_sprinting', { condition: 'minecraft:entity_properties', entity: 'this', predicate: { @@ -54,7 +76,13 @@ myBasePath.child({ directory: 'hiss' }).Function('boost_skeletons', () => { }) }) -mcfunction('test', () => { +MCFunction('pred', () => { + _.if(myPred, () => { + say('hi') + }) +}) + +MCFunction('test', () => { const menu = new Menu({ // A menu giving the strength effect '0': ['minecraft:golden_sword', { @@ -77,9 +105,98 @@ mcfunction('test', () => { menu.start('@p') }) +MCFunction('go', () => { + give('@a', `minecraft:player_head${NBT.toString({ + display: { Name: JSON.stringify([{ text: 'Cow' }]) }, + SkullOwner: { + Id: NBT.integerArray([210170708, 1898725698, -1548776749, -180478570]), + Properties: { + textures: [{ + Value: 'eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzVhOWNkNThkNGM2N2JjY2M4ZmIxZjVmNzU2YTJkMzgxYzlmZmFjMjkyNGI3ZjRjYjcxYWE5ZmExM2ZiNWMifX19', + }], + }, + }, + })}`, 1) +}) + +/** Give 1 diamond if the weather is clear, 2 if it is raining. */ +const l = LootTable('give_diamond', { + pools: [{ + rolls: 1, + entries: [{ + type: 'item', + name: 'minecraft:diamond', + }], + }], +}) + +Advancement('breed_chickens', { + criteria: { + bred_chickens: { + trigger: 'minecraft:bred_animals', + conditions: { + child: { type: 'minecraft:chicken' }, + }, + }, + }, + parent: breedCowsAdvancement, +}) + +/** Check if the location is inside swamp water. */ +Predicate('in_swamp_water', { + condition: 'minecraft:location_check', + predicate: { + biome: 'minecraft:swamp', + fluid: { + fluid: 'minecraft:water', + }, + }, +}) + +// Cooking cookies now gives coal. +Recipe('burning_cookies', { + type: 'smelting', + ingredient: { + item: 'minecraft:cookie', + }, + experience: 0, + result: 'minecraft:coal', + cookingtime: 50, +}) + savePack('My Datapack', { - formatVersion: 7, - description: 'gi', world: 'Crea1_15', + description: 'hi', verbose: true, }) + +MCFunction('my_func', () => { + say('Hi!') + tellraw('@a', [{ text: 'I love ' }, { selector: '@a', color: 'aqua' }]) +}) + +const playerWhoBredCows = Selector('@a', { + advancements: { + [breedCowsAdvancement.name]: { + bred_cows: true, + bred_chickens: false, + }, + }, +}) + +const breedAnimalsAdvancement = Advancement('breed_cows_and_chicken', { + criteria: { + bred_cows: { + trigger: 'minecraft:bred_animals', + conditions: { + child: { type: 'minecraft:cow' }, + }, + }, + bred_chicken: { + trigger: 'minecraft:bred_animals', + conditions: { + child: { type: 'minecraft:chicken' }, + }, + }, + }, +}) diff --git a/tests/test2.ts b/tests/test2.ts index f71fdc13..515dda6e 100644 --- a/tests/test2.ts +++ b/tests/test2.ts @@ -2,7 +2,7 @@ import { effect, give, say, tellraw, } from '../src/commands' import { - Advancement, mcfunction, Predicate, savePack, _, + Advancement, MCFunction, Predicate, savePack, _, } from '../src/core' import { Menu } from './menu' @@ -19,7 +19,7 @@ const pred = Predicate('mypredicate', { chance: 0.8, }) -mcfunction('myfunc', () => { +MCFunction('myfunc', () => { _ .if(pred, () => { test.grant('@s') @@ -32,7 +32,7 @@ mcfunction('myfunc', () => { }) }) -mcfunction('test', () => { +MCFunction('test', () => { const menu = new Menu({ // A menu giving the strength effect '0': ['minecraft:golden_sword', { diff --git a/tests/test3.ts b/tests/test3.ts new file mode 100644 index 00000000..fa70c751 --- /dev/null +++ b/tests/test3.ts @@ -0,0 +1,10 @@ +import { kill, say } from 'src/commands' +import { MCFunction } from 'src/core' +import { Selector } from 'src/variables' + +// Make a function that kills skeletons +MCFunction('kill_skeletons', () => { + say('I hate skeletons!') + const skeletons = Selector('@e', { type: 'minecraft:skeleton' }) + kill(skeletons) +}) diff --git a/yarn.lock b/yarn.lock index e747cb11..61c4ce01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1137,6 +1137,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-core-module@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" + integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA== + dependencies: + has "^1.0.3" + is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" @@ -1698,7 +1705,15 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@>=1.9.0, resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0: +resolve@>=1.9.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + +resolve@^1.10.0, resolve@^1.13.1, resolve@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -2000,10 +2015,10 @@ typescript-transform-paths@^2.0.1: resolved "https://registry.yarnpkg.com/typescript-transform-paths/-/typescript-transform-paths-2.0.1.tgz#1b4e53df3e07f400bf113cd0be3a8c14e40eac37" integrity sha512-B54vhxIP6g3ESXI+SyrKAYeiQ9cRJ/IKp0kDZy9xYcR8HyndY7AdKFK5C5bIygccCutYIBXvBnjbhnXeqauRcw== -typescript@^4.1.0-dev.20200930: - version "4.1.0-dev.20200930" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.0-dev.20200930.tgz#2eaea4a6b741d69f650c12fcc7cee759ef20fca1" - integrity sha512-zEzEGftnYtgdaY10tVgfWC/mqQG3e1j3dhBWgayPtl52LlwmTRYRIJ+++hXIFVTs/gjvlFSpzOlrTQ98FJqBwA== +typescript@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" + integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== undefsafe@^2.0.2: version "2.0.3" From ca69d2dd4b1fe72328751cb37a5987283edd00ec Mon Sep 17 00:00:00 2001 From: Florian ERNST Date: Wed, 25 Nov 2020 09:11:34 +0100 Subject: [PATCH 2/2] Bumped minor version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 16dd2fc5..df9a699d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sandstone", - "version": "0.5.4", + "version": "0.6.0", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", "license": "MIT",