UNPKG

bedrock-development

Version:

APIs for creating and editing files related to Minecraft Bedrock development.

323 lines (279 loc) 19.8 kB
import { Option } from "commander"; import { ClientItemTexture, ServerItem, LangFile, ClientAnimationController, ClientAnimation, ClientAnimationName, ClientAttachable, ClientGeometryAttachable, ClientAttachableArmorHelmet, ClientGeometryArmor, ClientAttachableArmorBoots, ClientAttachableArmorLeggings, ClientAttachableArmorChestplate } from "../../types/index.js"; import { Directories, File, copySourceFile, setFiles } from "../../file_manager.js"; import { NameData, currentFormatVersion, implementConfig } from "../../utils.js"; import { CommandMap } from "../command_map.js"; export interface NewItemOptions { lang: boolean; stack: number; cooldown: number|undefined; override: boolean; type: ServerItemOptions; } enum ServerItemOptions { basic="basic", attachable="attachable", food="food", armor_set="armor_set", helmet="helmet", chestplate="chestplate", leggings="leggings", boots="boots" }; CommandMap.addCommand<string[], NewItemOptions>("root.new.item", { parent: CommandMap.getCommandEntry("root.new")?.command, commandOptions(command) { command .name("item") .description("creates new bedrock items") .argument("<names...>", 'item names as "namespace:item"') .option("--no-lang", "do not add lang file") .addOption(new Option("-s, --stack <stack_size>", "max stack size").default(64).argParser(parseInt)) .option("-c, --cooldown <cooldown_duration>", "cooldown duration") .option("-o, --override", "override existing files") .addOption( new Option("-t, --type <item_type>", "item type").choices( Object.keys(ServerItemOptions) ).default("basic") ); }, commandAction: triggerCreateNewItem, }); async function triggerCreateNewItem(names: string[], options: NewItemOptions) { implementConfig(); names.forEach((name) => { const nameData = new NameData(name); const files: File[] = createFileTemplates[options.type](nameData, options); if ([ServerItemOptions.armor_set, ServerItemOptions.helmet, ServerItemOptions.chestplate, ServerItemOptions.leggings, ServerItemOptions.boots].some(itemType => itemType === options.type)) { files.push(ClientGeometryArmor.createFromTemplate(nameData).toFile()); copySourceFile('images/armor_uv_texture.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'models/armor/' + nameData.directory + nameData.shortname + '.png'); } if (options.override) files.forEach(file => file.handleExisting = "overwrite"); setFiles(files); }); } const createFileTemplates: Record<ServerItemOptions, (nameData: NameData, options: NewItemOptions) => File[]> = { basic: function (nameData: NameData, options: NewItemOptions) { const item = ServerItem.createFromTemplate(nameData); item.setDisplayData(nameData); item.setStackSize(options.stack); if (options.cooldown) item.setCooldown(options.cooldown); const files: File[] = [item.toFile()]; if (options.lang) { files.push(...LangFile.addToAllLangs('item names', `item.${nameData.fullname}.name=${nameData.display}`).files); } copySourceFile('images/sprite.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + '.png'); files.push(ClientItemTexture.fileWithAddedTextures({name: nameData.shortname, texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname})); return files; }, boots: function (nameData: NameData, options: NewItemOptions) { const item = ServerItem.createFromTemplate(nameData); item.setDisplayData(nameData); item.setStackSize(1); if (options.cooldown) item.setCooldown(options.cooldown); item.setWearable("slot.armor.feet", 3); const files: File[] = [item.toFile()]; if (options.lang) { files.push(...LangFile.addToAllLangs('item names', `item.${nameData.fullname}.name=${nameData.display}`).files); } files.push(ClientAttachableArmorBoots.createFromTemplate(nameData).toFile()); copySourceFile('images/sprite.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + '.png'); files.push(ClientItemTexture.fileWithAddedTextures({name: nameData.shortname, texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname})); return files; }, leggings: function (nameData: NameData, options: NewItemOptions) { const item = ServerItem.createFromTemplate(nameData); item.setDisplayData(nameData); item.setStackSize(1); if (options.cooldown) item.setCooldown(options.cooldown); item.setWearable("slot.armor.legs", 6); const files: File[] = [item.toFile()]; if (options.lang) { files.push(...LangFile.addToAllLangs('item names', `item.${nameData.fullname}.name=${nameData.display}`).files); } files.push(ClientAttachableArmorLeggings.createFromTemplate(nameData).toFile()); copySourceFile('images/sprite.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + '.png'); files.push(ClientItemTexture.fileWithAddedTextures({name: nameData.shortname, texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname})); return files; }, chestplate: function (nameData: NameData, options: NewItemOptions) { const item = ServerItem.createFromTemplate(nameData); item.setDisplayData(nameData); item.setStackSize(1); if (options.cooldown) item.setCooldown(options.cooldown); item.setWearable("slot.armor.chest", 8); const files: File[] = [item.toFile()]; if (options.lang) { files.push(...LangFile.addToAllLangs('item names', `item.${nameData.fullname}.name=${nameData.display}`).files); } files.push(ClientAttachableArmorChestplate.createFromTemplate(nameData).toFile()); copySourceFile('images/sprite.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + '.png'); files.push(ClientItemTexture.fileWithAddedTextures({name: nameData.shortname, texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname})); return files; }, helmet: function (nameData: NameData, options: NewItemOptions) { const item = ServerItem.createFromTemplate(nameData); item.setDisplayData(nameData); item.setStackSize(1); if (options.cooldown) item.setCooldown(options.cooldown); item.setWearable("slot.armor.head", 3); const files: File[] = [item.toFile()]; if (options.lang) { files.push(...LangFile.addToAllLangs('item names', `item.${nameData.fullname}.name=${nameData.display}`).files); } files.push(ClientAttachableArmorHelmet.createFromTemplate(nameData).toFile()); copySourceFile('images/sprite.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + '.png'); files.push(ClientItemTexture.fileWithAddedTextures({name: nameData.shortname, texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname})); return files; }, armor_set: function (nameData: NameData, options: NewItemOptions) { const files: File[] = []; const originalLang = options.lang; options.lang = false; files.push(...createFileTemplates.boots(new NameData(nameData.original + "_boots"), options)); files.push(...createFileTemplates.leggings(new NameData(nameData.original + "_leggings"), options)); files.push(...createFileTemplates.chestplate(new NameData(nameData.original + "_chestplate"), options)); files.push(...createFileTemplates.helmet(new NameData(nameData.original + "_helmet"), options)); if (originalLang) { const lang = new LangFile('*.lang'); lang.addToCategory('item names', `item.${nameData.fullname}_boots.name=${nameData.display} Boots`, `item.${nameData.fullname}_leggings.name=${nameData.display} Leggings`, `item.${nameData.fullname}_chestplate.name=${nameData.display} Chestplate`, `item.${nameData.fullname}_helmet.name=${nameData.display} Helmet`, ); files.push(...lang.files); } files.push(ClientItemTexture.fileWithAddedTextures( {name: nameData.shortname + "_boots", texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + "_boots"}, {name: nameData.shortname + "_leggings", texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + "_leggings"}, {name: nameData.shortname + "_chestplate", texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + "_chestplate"}, {name: nameData.shortname + "_helmet", texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + "_helmet"}, )); return files; }, attachable: function (nameData: NameData, options: NewItemOptions) { const item = ServerItem.createFromTemplate(nameData); item.setDisplayData(nameData); item.setStackSize(options.stack); if (options.cooldown) item.setCooldown(options.cooldown); item.setModifiers(); const files: File[] = [item.toFile()]; if (options.lang) { const langs = new LangFile('*.lang'); langs.addToCategory('item names', `item.${nameData.fullname}.name=${nameData.display}`); files.push(...langs.files); } copySourceFile('images/uv_medium_texture.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'attachables/' + nameData.directory + nameData.shortname + '.png'); // animation controller const controller = new ClientAnimationController(ClientAnimationController.createFilePath(nameData), { format_version: currentFormatVersion, animation_controllers: { [`controller.animation.${nameData.namespace}.item.custom_items.${nameData.shortname}`]: { initial_state: 'idle', states: { idle: { animations: [ { [`${nameData.shortname}.idle.first_person`]: "v.is_first_person" }, { [`${nameData.shortname}.idle.third_person`]: "!v.is_first_person" } ], transitions: [ { attack: "query.is_using_item || query.is_name_any('§a§t§t§a§c§k§e§r')" } ], blend_transition: 0.2 }, attack: { animations: [ { [`${nameData.shortname}.attack.first_person`]: "v.is_first_person" }, { [`${nameData.shortname}.attack.third_person`]: "!v.is_first_person" } ], transitions: [ { idle: "q.any_animation_finished && !query.is_using_item" }, { escape_attack: "q.any_animation_finished && query.is_using_item" } ], blend_transition: 0.2 }, escape_attack: { animations: [ { [`${nameData.shortname}.idle.first_person`]: "v.is_first_person" }, { [`${nameData.shortname}.idle.third_person`]: "!v.is_first_person" } ], transitions: [ { idle: "!query.is_using_item" } ], blend_transition: 0.2 } } } } }); files.push(controller.toFile()); // animation const animation = new ClientAnimation(ClientAnimation.createFilePath(nameData), { format_version: currentFormatVersion, animations: { [`animation.${nameData.namespace}.${nameData.shortname}.blockbench_fix` as ClientAnimationName]: { loop: true, bones: { root: { rotation: [0, 0, 0], position: [7, -15, 1] } } }, [`animation.${nameData.namespace}.player.${nameData.shortname}.idle.first_person` as ClientAnimationName]: {loop:true,override_previous_animation:true,blend_weight:`v.is_first_person && q.is_item_name_any('slot.weapon.mainhand', 0, '${nameData.fullname}')`,bones:{first_person_fix:{rotation:[-40,60,-40],position:[-3,0,0]},rightarm:{position:[13.5,-10.0,12.0],rotation:["95.0+variable.is_using_vr*7.5","-45.0+variable.is_using_vr*7.5","115.0+variable.is_using_vr*-2.5"]}}}, [`animation.${nameData.namespace}.player.${nameData.shortname}.idle.third_person` as ClientAnimationName]: {loop:true,override_previous_animation:true,blend_weight:`!v.is_first_person && q.is_item_name_any('slot.weapon.mainhand', 0, '${nameData.fullname}')`,bones:{rightArm:{rotation:[-30,0,0]}}}, [`animation.${nameData.namespace}.player.${nameData.shortname}.attack.first_person` as ClientAnimationName]: {loop:"hold_on_last_frame",animation_length:0.5,override_previous_animation:true,blend_weight:`v.is_first_person && q.is_item_name_any('slot.weapon.mainhand', 0, '${nameData.fullname}')`,bones:{first_person_fix:{rotation:[-40,60,-40],position:[-3,0,0]},rightarm:{position:[13.5,-10.0,12.0],rotation:["95.0+variable.is_using_vr*7.5","-45.0+variable.is_using_vr*7.5","115.0+variable.is_using_vr*-2.5"]},[nameData.shortname]:{rotation:{0.0:{post:[0,0,0],lerp_mode:"catmullrom"},0.1:{post:[-50,-40,60],lerp_mode:"catmullrom"},0.2:[0,0,-10],0.3:[11.2376,4.58608,-83.35255],0.4:[0,0,-80],0.5:{pre:[0,0,0],post:[0,0,0],lerp_mode:"catmullrom"}},position:{0.0:{post:[0,0,0],lerp_mode:"catmullrom"},0.1:{post:[-7,1,-11],lerp_mode:"catmullrom"},0.2:[20,-10,10],0.3:[23.43926,-13.01119,15.34095],0.4:[20,-10,10],0.5:{pre:[0,0,0],post:[0,0,0],lerp_mode:"catmullrom"}}}},timeline:{0.0:"v.playing_custom_attack = 1;",0.5:"v.playing_custom_attack = 0;"}}, [`animation.${nameData.namespace}.player.${nameData.shortname}.attack.third_person` as ClientAnimationName]: {loop:"hold_on_last_frame",override_previous_animation:true,blend_weight:`!v.is_first_person && q.is_item_name_any('slot.weapon.mainhand', 0, '${nameData.fullname}')`,animation_length:0.3,timeline:{"0":"v.playing_custom_attack = 1;",0.3:"v.playing_custom_attack = 0;"},bones:{rightArm:{rotation:{0.0:[-90,0,0],0.1:[-100,20,0],0.2:[-100,-20,0],0.3:[-90,0,0]}}}}, [`animation.${nameData.namespace}.item.${nameData.shortname}.idle.first_person` as ClientAnimationName]: {loop:true,bones:{first_person_fix:{rotation:[-40,60,-40],position:[-3,0,0]}}}, [`animation.${nameData.namespace}.item.${nameData.shortname}.idle.third_person` as ClientAnimationName]: {}, [`animation.${nameData.namespace}.item.${nameData.shortname}.attack.first_person` as ClientAnimationName]: {loop:"hold_on_last_frame",animation_length:0.5,bones:{first_person_fix:{rotation:[-40,60,-40],position:[-3,0,0]},[nameData.shortname]:{rotation:{0.0:{post:[0,0,0],lerp_mode:"catmullrom"},0.1:{post:[-50,-40,60],lerp_mode:"catmullrom"},0.2:[0,0,-10],0.3:[11.2376,4.58608,-83.35255],0.4:[0,0,-80],0.5:{pre:[0,0,0],post:[0,0,0],lerp_mode:"catmullrom"}},position:{0.0:{post:[0,0,0],lerp_mode:"catmullrom"},0.1:{post:[-7,1,-11],lerp_mode:"catmullrom"},0.2:[20,-10,10],0.3:[23.43926,-13.01119,15.34095],0.4:[20,-10,10],0.5:{pre:[0,0,0],post:[0,0,0],lerp_mode:"catmullrom"}}}},timeline:{0.0:"v.playing_custom_attack = 1;",0.5:"v.playing_custom_attack = 0;"}}, [`animation.${nameData.namespace}.item.${nameData.shortname}.attack.third_person` as ClientAnimationName]: {}, } }); files.push(animation.toFile()); // attachable const attachable = ClientAttachable.createFromTemplate(nameData); attachable.addAnimation( {name: `ctrl.${nameData.shortname}`, reference: `controller.animation.${nameData.namespace}.item.custom_items.${nameData.shortname}`}, {name: `${nameData.shortname}.idle.first_person`, reference: `animation.${nameData.namespace}.item.${nameData.shortname}.idle.first_person`}, {name: `${nameData.shortname}.idle.third_person`, reference: `animation.${nameData.namespace}.item.${nameData.shortname}.idle.third_person`}, {name: `${nameData.shortname}.attack.first_person`, reference: `animation.${nameData.namespace}.item.${nameData.shortname}.attack.first_person`}, {name: `${nameData.shortname}.attack.third_person`, reference: `animation.${nameData.namespace}.item.${nameData.shortname}.attack.third_person`} ); attachable.addAnimateScript(`ctrl.${nameData.shortname}`); files.push(attachable.toFile()); // geometry const geometry = ClientGeometryAttachable.createFromTemplate(nameData); files.push(geometry.toFile()); copySourceFile('images/sprite.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + '.png'); files.push(ClientItemTexture.fileWithAddedTextures({name: nameData.shortname, texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname})); return files; }, food: function (nameData: NameData, options: NewItemOptions) { const item = ServerItem.createFromTemplate(nameData); item.setDisplayData(nameData); item.setStackSize(options.stack); if (options.cooldown) item.setCooldown(options.cooldown); item.setFood(); const files: File[] = [item.toFile()]; if (options.lang) { files.push(...LangFile.addToAllLangs('item names', `item.${nameData.fullname}.name=${nameData.display}`).files); } copySourceFile('images/sprite.png', Directories.RESOURCE_PATH + 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname + '.png'); files.push(ClientItemTexture.fileWithAddedTextures({name: nameData.shortname, texture: 'textures/' + Directories.ADDON_PATH + 'items/' + nameData.directory + nameData.shortname})); return files; } }