UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

1,237 lines (1,236 loc) 48.4 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. Object.defineProperty(exports, "__esModule", { value: true }); /** * Entity trait signature definitions. */ const ENTITY_TRAIT_SIGNATURES = { // Body types — these are primarily about the visual model and animation rig, // not easily detectable from behavior components alone. We check type_family // for clues but these are intentionally conservative. humanoid: { optionalComponents: ["minecraft:can_climb", "minecraft:type_family"], validator: (components) => { // Humanoid detection requires strong signals from type_family const typeFamily = components["minecraft:type_family"]; if (typeFamily) { const families = typeFamily.family || []; const humanoidFamilies = ["zombie", "skeleton", "humanoid", "piglin", "villager", "illager", "witch", "player"]; if (families.some((f) => humanoidFamilies.includes(f.toLowerCase()))) return 0.9; } // Without type_family signals, no confident detection return 0; }, }, quadruped: { optionalComponents: ["minecraft:behavior.follow_parent", "minecraft:type_family"], validator: (components) => { // Quadruped detection uses type_family animal clues + follow_parent pattern const typeFamily = components["minecraft:type_family"]; const hasFollowParent = "minecraft:behavior.follow_parent" in components; if (typeFamily) { const families = typeFamily.family || []; const quadrupedFamilies = [ "cow", "pig", "sheep", "horse", "donkey", "mule", "llama", "goat", "wolf", "fox", "cat", "ocelot", "animal", ]; if (families.some((f) => quadrupedFamilies.includes(f.toLowerCase()))) return 0.9; } // follow_parent + breedable suggests a land animal but not enough for body type const hasBreedable = "minecraft:breedable" in components; if (hasFollowParent && hasBreedable) return 0.5; return 0; }, }, quadruped_small: { optionalComponents: ["minecraft:scale"], validator: (components) => { const scale = components["minecraft:scale"]?.value; if (scale && scale < 0.7) return 0.7; return 0; }, }, flying: { requiredComponents: ["minecraft:navigation.fly"], optionalComponents: ["minecraft:can_fly", "minecraft:behavior.float"], validator: (components) => { const hasFlyNav = "minecraft:navigation.fly" in components; const hasCanFly = "minecraft:can_fly" in components; if (hasFlyNav && hasCanFly) return 1.0; if (hasFlyNav) return 0.8; if (hasCanFly) return 0.6; return 0; }, }, aquatic: { requiredComponents: ["minecraft:navigation.swim"], optionalComponents: ["minecraft:underwater_movement", "minecraft:breathable"], validator: (components) => { const hasSwimNav = "minecraft:navigation.swim" in components; const hasUnderwaterMovement = "minecraft:underwater_movement" in components; const breathable = components["minecraft:breathable"]; const breathesWater = breathable?.breathes_water === true; if (hasSwimNav && breathesWater) return 1.0; if (hasSwimNav && hasUnderwaterMovement) return 0.9; if (hasSwimNav) return 0.7; return 0; }, }, arthropod: { optionalComponents: ["minecraft:can_climb", "minecraft:mark_variant"], validator: (components) => { // Arthropods typically have climbing ability and multiple variants const hasClimb = "minecraft:can_climb" in components; const hasVariant = "minecraft:mark_variant" in components; return hasClimb && hasVariant ? 0.6 : 0; }, }, slime: { optionalComponents: ["minecraft:movement.sway"], validator: (components) => { return "minecraft:movement.sway" in components ? 0.8 : 0; }, }, // Behavior archetypes hostile: { optionalComponents: ["minecraft:behavior.nearest_attackable_target", "minecraft:behavior.hurt_by_target"], conflictsWith: ["passive"], validator: (components) => { const hasTargeting = "minecraft:behavior.nearest_attackable_target" in components; const hasHurtBy = "minecraft:behavior.hurt_by_target" in components; const hasAttack = "minecraft:attack" in components || "minecraft:behavior.melee_attack" in components; // Check if targeting players const targeting = components["minecraft:behavior.nearest_attackable_target"]; const targetsPlayers = targeting ? JSON.stringify(targeting).includes("player") : false; if (hasTargeting && targetsPlayers && hasAttack) return 1.0; if (hasTargeting && hasAttack) return 0.8; if (hasHurtBy && hasAttack) return 0.7; return 0; }, }, passive: { requiredComponents: ["minecraft:behavior.panic"], conflictsWith: ["hostile"], validator: (components) => { const hasPanic = "minecraft:behavior.panic" in components; const hasNoAttack = !("minecraft:attack" in components) && !("minecraft:behavior.melee_attack" in components); if (hasPanic && hasNoAttack) return 1.0; if (hasPanic) return 0.7; return 0; }, }, neutral: { optionalComponents: ["minecraft:behavior.hurt_by_target"], conflictsWith: ["hostile", "passive"], validator: (components) => { const hurtBy = components["minecraft:behavior.hurt_by_target"]; const alertsSameType = hurtBy?.alert_same_type === true; const hasPanic = "minecraft:behavior.panic" in components; const hasAttack = "minecraft:attack" in components; // Neutral mobs retaliate but don't seek targets if (alertsSameType && !("minecraft:behavior.nearest_attackable_target" in components)) return 0.9; if (hurtBy && hasAttack && hasPanic) return 0.7; return 0; }, }, boss: { requiredComponents: ["minecraft:boss"], validator: (components) => { return "minecraft:boss" in components ? 1.0 : 0; }, }, // Combat styles melee_attacker: { requiredComponents: ["minecraft:behavior.melee_attack"], optionalComponents: ["minecraft:attack"], validator: (components) => { const hasMelee = "minecraft:behavior.melee_attack" in components; const hasAttack = "minecraft:attack" in components; if (hasMelee && hasAttack) return 1.0; if (hasMelee) return 0.8; return 0; }, }, ranged_attacker: { requiredComponents: ["minecraft:behavior.ranged_attack"], optionalComponents: ["minecraft:shooter"], validator: (components) => { const hasRanged = "minecraft:behavior.ranged_attack" in components; const hasShooter = "minecraft:shooter" in components; if (hasRanged && hasShooter) return 1.0; if (hasRanged) return 0.8; return 0; }, }, exploder: { requiredComponents: ["minecraft:explode"], validator: (components) => { return "minecraft:explode" in components ? 1.0 : 0; }, }, // Interaction patterns trader: { requiredComponents: ["minecraft:trade_table"], optionalComponents: ["minecraft:behavior.trade_with_player"], validator: (components) => { const hasTradeTable = "minecraft:trade_table" in components; const hasTradeBehavior = "minecraft:behavior.trade_with_player" in components; if (hasTradeTable && hasTradeBehavior) return 1.0; if (hasTradeTable) return 0.9; return 0; }, }, tameable: { requiredComponents: ["minecraft:tameable"], optionalComponents: ["minecraft:is_tamed"], validator: (components, componentGroups) => { // Check both direct components and component groups const hasTameable = "minecraft:tameable" in components || (componentGroups && Object.values(componentGroups).some((g) => "minecraft:tameable" in g)); const hasIsTamed = "minecraft:is_tamed" in components || (componentGroups && Object.values(componentGroups).some((g) => "minecraft:is_tamed" in g)); if (hasTameable) return 1.0; if (hasIsTamed) return 0.7; return 0; }, }, rideable: { requiredComponents: ["minecraft:rideable"], optionalComponents: ["minecraft:input_ground_controlled"], validator: (components, componentGroups) => { // Check both direct components and component groups const hasRideable = "minecraft:rideable" in components || (componentGroups && Object.values(componentGroups).some((g) => "minecraft:rideable" in g)); const hasInput = "minecraft:input_ground_controlled" in components || (componentGroups && Object.values(componentGroups).some((g) => "minecraft:input_ground_controlled" in g)); if (hasRideable && hasInput) return 1.0; if (hasRideable) return 0.9; return 0; }, }, breedable: { requiredComponents: ["minecraft:breedable"], optionalComponents: ["minecraft:behavior.breed"], validator: (components, componentGroups) => { const hasBreedable = "minecraft:breedable" in components || (componentGroups && Object.values(componentGroups).some((g) => "minecraft:breedable" in g)); const hasBreedBehavior = "minecraft:behavior.breed" in components; if (hasBreedable && hasBreedBehavior) return 1.0; if (hasBreedable) return 0.9; return 0; }, }, leasable: { requiredComponents: ["minecraft:leashable"], validator: (components) => { return "minecraft:leashable" in components ? 1.0 : 0; }, }, // Special behaviors undead: { optionalComponents: ["minecraft:burns_in_daylight", "minecraft:type_family"], validator: (components) => { const burnsDaylight = "minecraft:burns_in_daylight" in components; const typeFamily = components["minecraft:type_family"]; const isUndeadFamily = typeFamily?.family?.includes("undead") || (typeFamily ? JSON.stringify(typeFamily).includes("undead") : false); if (burnsDaylight && isUndeadFamily) return 1.0; if (burnsDaylight) return 0.9; if (isUndeadFamily) return 0.7; return 0; }, }, illager: { optionalComponents: ["minecraft:behavior.raid_garden", "minecraft:type_family"], validator: (components) => { const hasRaidGarden = "minecraft:behavior.raid_garden" in components; const typeFamily = components["minecraft:type_family"]; const isIllagerFamily = typeFamily ? JSON.stringify(typeFamily).includes("illager") : false; if (hasRaidGarden && isIllagerFamily) return 1.0; if (isIllagerFamily) return 0.8; return 0; }, }, aquatic_only: { optionalComponents: ["minecraft:breathable"], validator: (components) => { const breathable = components["minecraft:breathable"]; const breathesWater = breathable?.breathes_water === true; const breathesAir = breathable?.breathes_air !== false; if (breathesWater && !breathesAir) return 1.0; return 0; }, }, baby_variant: { optionalComponents: ["minecraft:is_baby", "minecraft:ageable"], validator: (components, componentGroups) => { // Check component groups for baby state const hasIsBaby = "minecraft:is_baby" in components || (componentGroups && Object.values(componentGroups).some((g) => "minecraft:is_baby" in g)); const hasAgeable = "minecraft:ageable" in components; if (hasIsBaby && hasAgeable) return 1.0; if (hasAgeable) return 0.8; if (hasIsBaby) return 0.7; return 0; }, }, wanders: { requiredComponents: ["minecraft:behavior.random_stroll"], optionalComponents: ["minecraft:behavior.random_look_around"], validator: (components) => { const hasStroll = "minecraft:behavior.random_stroll" in components; const hasLookAround = "minecraft:behavior.random_look_around" in components; if (hasStroll && hasLookAround) return 1.0; if (hasStroll) return 0.9; return 0; }, }, patrols: { requiredComponents: ["minecraft:behavior.move_to_poi"], validator: (components) => { return "minecraft:behavior.move_to_poi" in components ? 0.9 : 0; }, }, guards: { requiredComponents: ["minecraft:behavior.defend_village_target"], validator: (components) => { return "minecraft:behavior.defend_village_target" in components ? 1.0 : 0; }, }, flees_daylight: { requiredComponents: ["minecraft:behavior.flee_sun"], validator: (components) => { return "minecraft:behavior.flee_sun" in components ? 1.0 : 0; }, }, teleporter: { optionalComponents: ["minecraft:teleport"], validator: (components) => { return "minecraft:teleport" in components ? 1.0 : 0; }, }, }; // ============================================================================ // BEHAVIOR PRESET SIGNATURES // ============================================================================ const BEHAVIOR_PRESET_SIGNATURES = { // Movement wander: { requiredComponents: ["minecraft:behavior.random_stroll"], validator: (c) => ("minecraft:behavior.random_stroll" in c ? 1.0 : 0), }, swim: { requiredComponents: ["minecraft:behavior.swim_idle"], validator: (c) => ("minecraft:behavior.swim_idle" in c || "minecraft:navigation.swim" in c ? 0.8 : 0), }, fly_around: { requiredComponents: ["minecraft:behavior.fly_idle"], validator: (c) => ("minecraft:behavior.fly_idle" in c ? 1.0 : 0), }, float: { requiredComponents: ["minecraft:behavior.float"], validator: (c) => ("minecraft:behavior.float" in c ? 1.0 : 0), }, climb: { requiredComponents: ["minecraft:can_climb"], validator: (c) => ("minecraft:can_climb" in c ? 1.0 : 0), }, // Combat melee_attack: { requiredComponents: ["minecraft:behavior.melee_attack"], validator: (c) => ("minecraft:behavior.melee_attack" in c ? 1.0 : 0), }, ranged_attack: { requiredComponents: ["minecraft:behavior.ranged_attack"], validator: (c) => ("minecraft:behavior.ranged_attack" in c ? 1.0 : 0), }, target_players: { requiredComponents: ["minecraft:behavior.nearest_attackable_target"], validator: (c) => { const targeting = c["minecraft:behavior.nearest_attackable_target"]; return targeting && JSON.stringify(targeting).includes("player") ? 1.0 : 0; }, }, target_monsters: { requiredComponents: ["minecraft:behavior.nearest_attackable_target"], validator: (c) => { const targeting = c["minecraft:behavior.nearest_attackable_target"]; return targeting && JSON.stringify(targeting).includes("monster") ? 1.0 : 0; }, }, flee_when_hurt: { requiredComponents: ["minecraft:behavior.panic"], validator: (c) => ("minecraft:behavior.panic" in c ? 1.0 : 0), }, retaliate: { requiredComponents: ["minecraft:behavior.hurt_by_target"], validator: (c) => ("minecraft:behavior.hurt_by_target" in c ? 1.0 : 0), }, // Social follow_owner: { requiredComponents: ["minecraft:behavior.follow_owner"], validator: (c) => ("minecraft:behavior.follow_owner" in c ? 1.0 : 0), }, follow_parent: { requiredComponents: ["minecraft:behavior.follow_parent"], validator: (c) => ("minecraft:behavior.follow_parent" in c ? 1.0 : 0), }, herd: { requiredComponents: ["minecraft:behavior.move_towards_dwelling_restriction"], validator: (c) => ("minecraft:behavior.move_towards_dwelling_restriction" in c ? 0.7 : 0), }, avoid_players: { requiredComponents: ["minecraft:behavior.avoid_mob_type"], validator: (c) => { const avoid = c["minecraft:behavior.avoid_mob_type"]; return avoid && JSON.stringify(avoid).includes("player") ? 1.0 : 0; }, }, // Interaction look_at_player: { requiredComponents: ["minecraft:behavior.look_at_player"], validator: (c) => ("minecraft:behavior.look_at_player" in c ? 1.0 : 0), }, beg: { requiredComponents: ["minecraft:behavior.beg"], validator: (c) => ("minecraft:behavior.beg" in c ? 1.0 : 0), }, tempt: { requiredComponents: ["minecraft:behavior.tempt"], validator: (c) => ("minecraft:behavior.tempt" in c ? 1.0 : 0), }, sit_command: { requiredComponents: ["minecraft:behavior.sit"], validator: (c) => ("minecraft:behavior.sit" in c ? 1.0 : 0), }, // Actions eat_grass: { requiredComponents: ["minecraft:behavior.eat_block"], validator: (c) => ("minecraft:behavior.eat_block" in c ? 1.0 : 0), }, break_doors: { requiredComponents: ["minecraft:behavior.break_door"], validator: (c) => ("minecraft:behavior.break_door" in c ? 1.0 : 0), }, open_doors: { requiredComponents: ["minecraft:behavior.open_door"], validator: (c) => ("minecraft:behavior.open_door" in c ? 1.0 : 0), }, pick_up_items: { requiredComponents: ["minecraft:behavior.pickup_items"], validator: (c) => ("minecraft:behavior.pickup_items" in c ? 1.0 : 0), }, sleep_in_bed: { requiredComponents: ["minecraft:behavior.sleep"], validator: (c) => ("minecraft:behavior.sleep" in c ? 1.0 : 0), }, // Environment hide_from_sun: { requiredComponents: ["minecraft:behavior.flee_sun"], validator: (c) => ("minecraft:behavior.flee_sun" in c ? 1.0 : 0), }, go_home_at_night: { requiredComponents: ["minecraft:behavior.go_home"], validator: (c) => ("minecraft:behavior.go_home" in c ? 1.0 : 0), }, seek_water: { requiredComponents: ["minecraft:behavior.find_water"], validator: (c) => ("minecraft:behavior.find_water" in c ? 1.0 : 0), }, seek_land: { requiredComponents: ["minecraft:behavior.stroll_towards_village"], validator: (c) => ("minecraft:behavior.stroll_towards_village" in c ? 0.6 : 0), }, }; // ============================================================================ // BLOCK TRAIT SIGNATURES // ============================================================================ /** * Block trait signature definitions. */ const BLOCK_TRAIT_SIGNATURES = { // Basic types solid: { validator: (components) => { // Solid blocks have standard collision and no transparency const hasCollision = !("minecraft:collision_box" in components) || components["minecraft:collision_box"] !== false; const isDestructible = "minecraft:destructible_by_mining" in components; if (hasCollision && isDestructible) return 0.7; return 0; }, }, transparent: { optionalComponents: ["minecraft:light_dampening"], validator: (components) => { const lightDamp = components["minecraft:light_dampening"]; if (lightDamp !== undefined && lightDamp < 15) return 0.8; return 0; }, }, leaves: { optionalComponents: ["minecraft:destructible_by_mining"], validator: (components) => { // Leaves have fast destroy time and specific properties const destructible = components["minecraft:destructible_by_mining"]; if (destructible?.seconds_to_destroy !== undefined && destructible.seconds_to_destroy < 0.5) { // Check for transparency const lightDamp = components["minecraft:light_dampening"]; if (lightDamp !== undefined && lightDamp < 5) return 0.9; return 0.5; } return 0; }, }, log: { optionalComponents: ["minecraft:transformation"], validator: (components) => { // Logs often have pillar rotation and medium hardness const hasTransformation = "minecraft:transformation" in components; const destructible = components["minecraft:destructible_by_mining"]; const hasWoodHardness = destructible?.seconds_to_destroy >= 2 && destructible?.seconds_to_destroy <= 3; if (hasTransformation || hasWoodHardness) return 0.7; return 0; }, }, slab: { optionalComponents: ["minecraft:geometry"], validator: (components) => { // Slabs have half-height geometry or specific states const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("slab")) return 1.0; // Check for vertical_half state handling const collision = components["minecraft:collision_box"]; if (collision?.size && collision.size[1] === 8) return 0.8; return 0; }, }, stairs: { optionalComponents: ["minecraft:geometry"], validator: (components) => { const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("stairs")) return 1.0; return 0; }, }, fence: { optionalComponents: ["minecraft:support", "minecraft:connection_rule", "minecraft:geometry"], validator: (components) => { const support = components["minecraft:support"]; if (support?.shape === "fence") return 1.0; if ("minecraft:connection_rule" in components) return 0.7; const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("fence")) return 0.8; const collision = components["minecraft:collision_box"]; if (collision?.size && collision.size[0] < 16) return 0.6; return 0; }, }, wall: { optionalComponents: ["minecraft:geometry"], validator: (components) => { const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("wall")) return 1.0; return 0; }, }, door: { optionalComponents: ["minecraft:on_interact"], validator: (components) => { const hasInteract = "minecraft:on_interact" in components; const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("door")) return 1.0; if (hasInteract) return 0.6; return 0; }, }, trapdoor: { optionalComponents: ["minecraft:on_interact"], validator: (components) => { const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("trapdoor")) return 1.0; return 0; }, }, // Functional workstation: { optionalComponents: ["minecraft:crafting_table"], validator: (components) => { const hasCrafting = "minecraft:crafting_table" in components; const hasInteract = "minecraft:on_interact" in components; if (hasCrafting) return 1.0; if (hasInteract) return 0.5; return 0; }, }, light_source: { requiredComponents: ["minecraft:light_emission"], validator: (components) => { const light = components["minecraft:light_emission"]; if (light !== undefined && light > 0) return 1.0; return 0; }, }, gravity: { optionalComponents: ["minecraft:gravity"], validator: (components) => { // Blocks affected by gravity (sand, gravel, etc.) return "minecraft:gravity" in components ? 1.0 : 0; }, }, liquid: { optionalComponents: ["minecraft:liquid"], validator: (components) => { return "minecraft:liquid" in components ? 1.0 : 0; }, }, // Redstone redstone_signal: { optionalComponents: ["minecraft:redstone_conductivity", "minecraft:redstone_producer"], validator: (components) => { if ("minecraft:redstone_producer" in components) return 1.0; const redstone = components["minecraft:redstone_conductivity"]; if (redstone?.emits_redstone) return 1.0; return 0; }, }, redstone_receiver: { optionalComponents: ["minecraft:redstone_conductivity"], validator: (components) => { const redstone = components["minecraft:redstone_conductivity"]; if (redstone && !redstone.emits_redstone) return 0.8; return 0; }, }, button: { optionalComponents: ["minecraft:on_interact"], validator: (components) => { const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("button")) return 1.0; // Buttons are small interactable blocks const collision = components["minecraft:collision_box"]; const hasInteract = "minecraft:on_interact" in components; if (hasInteract && collision?.size && collision.size[1] < 4) return 0.8; return 0; }, }, lever: { optionalComponents: ["minecraft:on_interact"], validator: (components) => { const geometry = components["minecraft:geometry"]; if (geometry && JSON.stringify(geometry).includes("lever")) return 1.0; return 0; }, }, pressure_plate: { optionalComponents: ["minecraft:on_step_on", "minecraft:on_step_off"], validator: (components) => { const hasStepOn = "minecraft:on_step_on" in components; const hasStepOff = "minecraft:on_step_off" in components; if (hasStepOn || hasStepOff) return 1.0; // Pressure plates are flat const collision = components["minecraft:collision_box"]; if (collision?.size && collision.size[1] <= 1) return 0.6; return 0; }, }, // Properties flammable: { optionalComponents: ["minecraft:flammable"], validator: (components) => { return "minecraft:flammable" in components ? 1.0 : 0; }, }, explosion_resistant: { optionalComponents: ["minecraft:destructible_by_explosion"], validator: (components) => { const explosionData = components["minecraft:destructible_by_explosion"]; if (explosionData === false) return 1.0; if (explosionData?.explosion_resistance !== undefined && explosionData.explosion_resistance >= 1000) return 1.0; return 0; }, }, slippery: { optionalComponents: ["minecraft:friction"], validator: (components) => { const friction = components["minecraft:friction"]; if (typeof friction === "number" && friction < 0.3) return 1.0; if (typeof friction === "number" && friction < 0.4) return 0.6; return 0; }, }, }; // ============================================================================ // ITEM TRAIT SIGNATURES // ============================================================================ /** * Item trait signature definitions. */ const ITEM_TRAIT_SIGNATURES = { // Tools sword: { optionalComponents: ["minecraft:damage", "minecraft:weapon"], validator: (components) => { const hasDamage = "minecraft:damage" in components; const hasWeapon = "minecraft:weapon" in components; const hasDigger = "minecraft:digger" in components; // Swords have damage but aren't mining tools if (hasDamage && !hasDigger) return 0.9; if (hasWeapon && !hasDigger) return 0.8; return 0; }, }, pickaxe: { requiredComponents: ["minecraft:digger"], validator: (components) => { const digger = components["minecraft:digger"]; if (!digger) return 0; const rules = JSON.stringify(digger); if (rules.includes("stone") || rules.includes("ore") || rules.includes("pickaxe")) return 1.0; return 0; }, }, axe: { requiredComponents: ["minecraft:digger"], validator: (components) => { const digger = components["minecraft:digger"]; if (!digger) return 0; const rules = JSON.stringify(digger); if (rules.includes("wood") || rules.includes("log") || rules.includes("axe")) return 1.0; return 0; }, }, shovel: { requiredComponents: ["minecraft:digger"], validator: (components) => { const digger = components["minecraft:digger"]; if (!digger) return 0; const rules = JSON.stringify(digger); if (rules.includes("dirt") || rules.includes("sand") || rules.includes("shovel")) return 1.0; return 0; }, }, hoe: { requiredComponents: ["minecraft:digger"], validator: (components) => { const digger = components["minecraft:digger"]; if (!digger) return 0; const rules = JSON.stringify(digger); if (rules.includes("hoe") || rules.includes("leaves") || rules.includes("hay")) return 1.0; return 0; }, }, bow: { optionalComponents: ["minecraft:shooter", "minecraft:use_modifiers"], validator: (components) => { const hasShooter = "minecraft:shooter" in components; const useModifiers = components["minecraft:use_modifiers"]; const hasChargeTime = useModifiers?.use_duration !== undefined; if (hasShooter && hasChargeTime) return 1.0; if (hasShooter) return 0.8; return 0; }, }, crossbow: { optionalComponents: ["minecraft:shooter"], validator: (components) => { const shooter = components["minecraft:shooter"]; if (shooter && JSON.stringify(shooter).includes("crossbow")) return 1.0; return 0; }, }, food: { requiredComponents: ["minecraft:food"], validator: (components) => { return "minecraft:food" in components ? 1.0 : 0; }, }, // Armor armor_helmet: { requiredComponents: ["minecraft:wearable"], validator: (components) => { const wearable = components["minecraft:wearable"]; if (wearable?.slot === "slot.armor.head") return 1.0; return 0; }, }, armor_chestplate: { requiredComponents: ["minecraft:wearable"], validator: (components) => { const wearable = components["minecraft:wearable"]; if (wearable?.slot === "slot.armor.chest") return 1.0; return 0; }, }, armor_leggings: { requiredComponents: ["minecraft:wearable"], validator: (components) => { const wearable = components["minecraft:wearable"]; if (wearable?.slot === "slot.armor.legs") return 1.0; return 0; }, }, armor_boots: { requiredComponents: ["minecraft:wearable"], validator: (components) => { const wearable = components["minecraft:wearable"]; if (wearable?.slot === "slot.armor.feet") return 1.0; return 0; }, }, throwable: { optionalComponents: ["minecraft:throwable", "minecraft:projectile"], validator: (components) => { const hasThrowable = "minecraft:throwable" in components; const hasProjectile = "minecraft:projectile" in components; if (hasThrowable || hasProjectile) return 1.0; return 0; }, }, placeable: { optionalComponents: ["minecraft:block_placer", "minecraft:entity_placer"], validator: (components) => { const hasBlockPlacer = "minecraft:block_placer" in components; const hasEntityPlacer = "minecraft:entity_placer" in components; if (hasBlockPlacer || hasEntityPlacer) return 1.0; return 0; }, }, }; // ============================================================================ // TRAIT DETECTOR CLASS // ============================================================================ /** * TraitDetector - Detects traits from native Minecraft components. */ class TraitDetector { /** Minimum confidence threshold for including a trait */ static DEFAULT_MIN_CONFIDENCE = 0.6; /** * Detect entity traits from components. */ static detectEntityTraits(components, componentGroups, minConfidence = TraitDetector.DEFAULT_MIN_CONFIDENCE) { const results = []; // Merge component group components for detection const allComponents = { ...components }; if (componentGroups) { for (const group of Object.values(componentGroups)) { Object.assign(allComponents, group); } } for (const [traitId, signature] of Object.entries(ENTITY_TRAIT_SIGNATURES)) { let confidence = 0; const matchedComponents = []; // Check required components if (signature.requiredComponents) { const hasAllRequired = signature.requiredComponents.every((comp) => { if (comp in allComponents) { matchedComponents.push(comp); return true; } return false; }); if (!hasAllRequired) continue; // Skip if missing required confidence = 0.5; } // Check optional components if (signature.optionalComponents) { for (const comp of signature.optionalComponents) { if (comp in allComponents) { matchedComponents.push(comp); confidence += 0.1; } } } // Run custom validator if (signature.validator) { const validatorScore = signature.validator(allComponents, componentGroups); confidence = Math.max(confidence, validatorScore); } if (confidence >= minConfidence) { results.push({ traitId: traitId, confidence, matchedComponents, }); } } // Handle conflicts return TraitDetector.resolveConflicts(results, ENTITY_TRAIT_SIGNATURES); } /** * Detect behavior presets from components. */ static detectBehaviorPresets(components, minConfidence = TraitDetector.DEFAULT_MIN_CONFIDENCE) { const results = []; for (const [presetId, signature] of Object.entries(BEHAVIOR_PRESET_SIGNATURES)) { let confidence = 0; const matchedComponents = []; if (signature.requiredComponents) { const hasAllRequired = signature.requiredComponents.every((comp) => { if (comp in components) { matchedComponents.push(comp); return true; } return false; }); if (!hasAllRequired) continue; confidence = 0.7; } if (signature.validator) { const validatorScore = signature.validator(components); confidence = Math.max(confidence, validatorScore); } if (confidence >= minConfidence) { results.push({ traitId: presetId, confidence, matchedComponents, }); } } return results; } /** * Detect block traits from components. */ static detectBlockTraits(components, minConfidence = TraitDetector.DEFAULT_MIN_CONFIDENCE) { const results = []; for (const [traitId, signature] of Object.entries(BLOCK_TRAIT_SIGNATURES)) { let confidence = 0; const matchedComponents = []; // Check required components if (signature.requiredComponents) { const hasAllRequired = signature.requiredComponents.every((comp) => { if (comp in components) { matchedComponents.push(comp); return true; } return false; }); if (!hasAllRequired) continue; confidence = 0.5; } // Check optional components if (signature.optionalComponents) { for (const comp of signature.optionalComponents) { if (comp in components) { matchedComponents.push(comp); confidence += 0.1; } } } // Run custom validator if (signature.validator) { const validatorScore = signature.validator(components); confidence = Math.max(confidence, validatorScore); } if (confidence >= minConfidence) { results.push({ traitId: traitId, confidence, matchedComponents, }); } } // Resolve conflicts (some block traits are mutually exclusive) return TraitDetector.resolveConflicts(results, BLOCK_TRAIT_SIGNATURES); } /** * Detect item traits from components. */ static detectItemTraits(components, minConfidence = TraitDetector.DEFAULT_MIN_CONFIDENCE) { const results = []; for (const [traitId, signature] of Object.entries(ITEM_TRAIT_SIGNATURES)) { let confidence = 0; const matchedComponents = []; // Check required components if (signature.requiredComponents) { const hasAllRequired = signature.requiredComponents.every((comp) => { if (comp in components) { matchedComponents.push(comp); return true; } return false; }); if (!hasAllRequired) continue; confidence = 0.5; } // Check optional components if (signature.optionalComponents) { for (const comp of signature.optionalComponents) { if (comp in components) { matchedComponents.push(comp); confidence += 0.1; } } } // Run custom validator if (signature.validator) { const validatorScore = signature.validator(components); confidence = Math.max(confidence, validatorScore); } if (confidence >= minConfidence) { results.push({ traitId: traitId, confidence, matchedComponents, }); } } // Resolve conflicts (e.g., can't be both sword and pickaxe) return TraitDetector.resolveConflicts(results, ITEM_TRAIT_SIGNATURES); } /** * Extract simplified properties from entity components. */ static extractEntityProperties(components) { const props = {}; // Health const health = components["minecraft:health"]; if (health) { props.health = health.max ?? health.value; } // Attack damage const attack = components["minecraft:attack"]; if (attack?.damage !== undefined) { props.attackDamage = attack.damage; } // Movement speed const movement = components["minecraft:movement"]; if (movement?.value !== undefined) { props.movementSpeed = movement.value; } // Scale const scale = components["minecraft:scale"]; if (scale?.value !== undefined && scale.value !== 1.0) { props.scale = scale.value; } // Follow range (from targeting behaviors) const targeting = components["minecraft:behavior.nearest_attackable_target"]; if (targeting?.entity_types?.[0]?.max_dist) { props.followRange = targeting.entity_types[0].max_dist; } // Knockback resistance const knockback = components["minecraft:knockback_resistance"]; if (knockback?.value !== undefined) { props.knockbackResistance = knockback.value; } // Collision box const collision = components["minecraft:collision_box"]; if (collision) { props.collisionWidth = collision.width; props.collisionHeight = collision.height; } // Type family const family = components["minecraft:type_family"]; if (family?.family) { props.families = Array.isArray(family.family) ? family.family : [family.family]; } return props; } /** * Extract simplified properties from block components. */ static extractBlockProperties(components) { const props = {}; // Destroy time const destructible = components["minecraft:destructible_by_mining"]; if (destructible?.seconds_to_destroy !== undefined) { props.destroyTime = destructible.seconds_to_destroy; } // Explosion resistance const explosion = components["minecraft:destructible_by_explosion"]; if (explosion?.explosion_resistance !== undefined) { props.explosionResistance = explosion.explosion_resistance; } // Light emission const light = components["minecraft:light_emission"]; if (light !== undefined) { props.lightEmission = typeof light === "number" ? light : light.emission; } // Light dampening const dampen = components["minecraft:light_dampening"]; if (dampen !== undefined) { props.lightDampening = typeof dampen === "number" ? dampen : dampen.light_dampening; } // Friction const friction = components["minecraft:friction"]; if (friction !== undefined) { props.friction = typeof friction === "number" ? friction : friction.value; } // Map color const mapColor = components["minecraft:map_color"]; if (mapColor) { props.mapColor = typeof mapColor === "string" ? mapColor : mapColor.color; } return props; } /** * Extract simplified properties from item components. */ static extractItemProperties(components) { const props = {}; // Max stack size const stack = components["minecraft:max_stack_size"]; if (stack !== undefined) { props.maxStackSize = typeof stack === "number" ? stack : stack.max_stack_size; } // Durability const durability = components["minecraft:durability"]; if (durability?.max_durability !== undefined) { props.durability = durability.max_durability; } // Damage (for weapons) const damage = components["minecraft:damage"]; if (damage?.value !== undefined) { props.damage = damage.value; } // Food properties const food = components["minecraft:food"]; if (food) { props.nutrition = food.nutrition; props.saturation = food.saturation_modifier; } return props; } /** * Resolve conflicting traits by keeping higher confidence ones. */ static resolveConflicts(results, signatures) { const resolved = []; const excluded = new Set(); // Sort by confidence descending const sorted = [...results].sort((a, b) => b.confidence - a.confidence); for (const result of sorted) { if (excluded.has(result.traitId)) continue; resolved.push(result); // Mark conflicts as excluded const sig = signatures[result.traitId]; if (sig?.conflictsWith) { for (const conflict of sig.conflictsWith) { excluded.add(conflict); } } } return resolved; } /** * Get components that are NOT explained by any detected trait. * These should be included as explicit components in the schema. */ static getUnexplainedComponents(allComponents, detectedTraits) { const explainedComponents = new Set(); for (const trait of detectedTraits) { for (const comp of trait.matchedComponents) { explainedComponents.add(comp); } } return Object.keys(allComponents).filter((comp) => !explainedComponents.has(comp)); } } exports.default = TraitDetector;