Привет, Хабр! В этой статье я хочу затронуть тему аддонов. Многим чего-то не хватало в игре, и они скачивали аддоны с интернета. Когда вы понимаете, что скачанный аддон настолько безполезный, удаляете его. Я, допустим, хочу сделать аддон в minecraft bedrock. Если я сделаю достойный аддон, он может попасть на рынок. Заманчиво? Давайте попробуем сделать что-нибудь своё. Пригодится базовое знание json и в некоторых случаях javascript.

Первым делом, создадим папку myaddon по пути android/data/com.mojang.../files//games/com.mojang/behaivor_packs если у вас другое сохранение файлов, используйте games/com.mojang.../... Название папки, которую вы создали, никак не влияет на название аддона. За это отвечает manifest.json, который находится в нашей папке. Отредакиируем его

{
    "format_version": 1,
    "header": {
        "description": "Описание вашего аддона"
        "name": "Название вашего аддона",
        "uuid": "вставьте сюда уникальный id",
        "version": [ 1, 0, 5 ],
        "min_engine_version": [ 1, 17, 10 ]
    },
    "modules": [
        {
            "description": "",
            "type": "data",
            "uuid": "вставьте сюда второй уникальный id",
            "version": [1, 0, 0]
        }
    ]
}

Если что, uuid это уникальный id, который используется для сохранения аддона. Уникальный айди можно сгенерировать, например, на сайте uuidgener*tor.net, я не хочу оставлять точную ссылку поэтому вместо "а" поставил "*". Чтобы создать иконку нашему аддону, создайте в папке аддона файл pack_icon.jpg или pack_icon.png. Теперь сохраняем наши файлы, заходим в майнкрафт и проверям - теперь во вкладке создание мира в "наборы параметров" есть пустой аддон, который ещё бесполезный.

Function - так называется функция аддона, которая может выполнять несколько команд одновременно. Например, если вы в чат введёте команду /function myfunction, вам дадут и яблоко, и дерево. Так можно создавать на function даже нормальные античиты, но для этого требуется знание команд minecraft bedrock. Чтобы сделать функции в аддона, создайте в папке аддона папку functions. В ней создадите myfunction.mcfunction, ваша функция будет называться myfunction. Отредактируйте код.

#выдать всём яблоко
give @a apple

#выдать всем дерево
give @a log

#создать скорборд
scoreboard objectives add my score dummy

Функция может не отображаться при вводе /function ****. Теперь, если вы введёте /function myfunction, всем игрокам в мире дадут яблоко и дерево, а ещё создадится новый скорборд. Чтобы функция работала каждый тик непрерывно, не вводя её в чат или командный блок, создадим в папке function файл tick.json. Он будет отвечать за автоматическое выполнение функций каждый тик. Введём небольшой json код

{
  "values": [
    "myfunction"
  ]
}

теперь функция myfunction будет выполняться каждый тик. Если что, одна секунда это 20 тиков.

Идём всё дальше - чат. Вы, наверное, давно хотели оптимизировать чат, сделать ранги, антиспам. Тут всё в одном аддона - антиспам, ранги в чате и тд. Для начала нам придётся изменить manifest.json, чтобы указать в нём содержание javascript.

{

  "format_version": 2,

  "header": {

    "name": "Название аддона",

    "description": "Описание аддона",

    "uuid": "Уникальный id",

    "version": [ 1, 0, 3 ],

    "min_engine_version": [ 1, 14, 0 ]

  },

  "modules": [

    {

      "description": "made by habr @DinoZavr2",

      "type": "data",

      "uuid": "Второй уникальный id",

      "version": [ 1, 0, 0 ]

    },

    {

      "description": "",

      "language": "javascript",

      "type": "script",

      "uuid": "Третий уникальный id",

      "version": [0, 0, 1],

      "entry": "scripts/main/index.js"

    }

  ],

  "dependencies": [

    {

      "uuid": "Четвёртый уникальный id",

      "version": [ 0, 1, 0 ]

    },

    {

      "uuid": "Пятый уникальный id",
      
      "version": [ 0, 1, 0 ]

    }

  ]

}

Если хотите, пробелы можете убрать. Вы наверное заметили, что здесь используется 5 уникальных айди. Это может быть для кого-то странно. Также мы указали путь к индексу. Вы уже наверное понимаете структуру.

В папке аддона создадим папку scripts, в ней хранятся скрипты (тоже очевидно). Создадим в папке scripts папку main. В ней файл index.js, языка javascript. Это наш индекс. Введите туда этот код

import { chatrank } from './misc/chat.js'
import { world } from 'mojang-minecraft'
import { timer } from './misc/second.js'
let tick = 0, worldLoaded = false, loadTime = 0;

world.events.beforeChat.subscribe((data) => {
    chatrank(data)
})
world.events.tick.subscribe((ticks) => {
    tick++
    if (!world.getDimension("overworld").runCommand('testfor @a').error && !worldLoaded) {
        loadTime = tick
        worldLoaded = true;
        world.getDimension("overworld").runCommand(`execute @r ~~~ say §l§aМир был загружен в ${ticks} тиках. Добро пожаловать! `)
        world.getDimension("overworld").runCommand(`scoreboard objectives add chatsSent dummy`)
    }
    if(tick >= 20){
        tick = 0
        timer()
    }
})

Думаю, вы поняли, что это отображение того, сколько тиках загружался мир. Ну, базовых знаний js достаточно, чтобы тут не объяснять. Далее в папке main создадим папку misc, а в ней файл chat.js, это будет отвечать за спам и ранги.

import { world } from "mojang-minecraft"

let messages = new Map()

function chatrank(data){
    const tags = data.sender.getTags()
    data.sender.runCommand(`scoreboard players add @s chatsSent 0`)
    let score = parseInt(data.sender.runCommand(`scoreboard players test @s chatsSent *`).statusMessage.match(/-?\d+/)[0])
    let ranks = [];
    for(const tag of tags){
        if(tag.startsWith('rank:')){
            ranks.push(tag.replace('rank:', ''))
        }
    }
    if(ranks.length == 0)ranks = ["§l§aPlayer"]
    
    if(data.message.startsWith("!*")){
        data.cancel = true
        return
    }
    if(score >= 3){
        data.cancel = true
        return world.getDimension("overworld").runCommand(`ability "${data.sender.nameTag}" mute true`)
    }
    if(!messages.get(data.sender.name)){
        messages.set(data.sender.name, data.message)
    }else {
        const oldMsg = messages.get(data.sender.name)
        if(oldMsg == data.message){
            data.cancel = true
            return world.getDimension("overworld").runCommand(`tellraw "${data.sender.nameTag}" {"rawtext":[{"text":"§l§cНе пишите похожие сообщения"}]}`)
        }
    }
    let text = `§f[${ranks}§r§f] §7${data.sender.nameTag}: §f${data.message}`
    world.getDimension('overworld').runCommand(`tellraw @a {"rawtext":[{"translate":"§l§eM§r - ${JSON.stringify(text)}}]}`)
    messages.set(data.sender.name, data.message)
    data.sender.runCommand(`scoreboard players add @s chatsSent 1`)
    data.cancel = true
}
export { chatrank }

Чтобы создать себе ранг, нужно вписать tag @s add rank:ВАШ_РАНГ. Например, rank:§l§cADMIN, это будет красным ADMIN, а по умолчанию ранг зелёным Player. Но пока что всё равно ранги и анти-спам не будут работать, нужно в misc создать ещё один файл second.js, по названию всё понятно.

import { world } from 'mojang-minecraft'
let seconds = 0

export function timer(){
    seconds++
    if(seconds >= 4){
        world.getDimension("overworld").runCommand(`scoreboard players reset * chatsSent`)
        world.getDimension("overworld").runCommand(`scoreboard players set "dummy" chatsSent 1`)
        seconds = 0
        return seconds
    }
}

Это тоже небольшой js код, который импортирует секунды.Теперь заходим в майнкрафт, создаём мир с этим аддоном, в настройках мира включаем gametest и education edition. Внимание: поддерживаемая версия рангов и антиспама - 1.19.11 и выше. На других, более старых версиях, есть небольшой баг с командой tellraw.

Если что, у меня пустой ник, там вместо >> будет ":". Перед >> ник, но у меня пустой
Если что, у меня пустой ник, там вместо >> будет ":". Перед >> ник, но у меня пустой

Далее - кастомные команды в чате. Это звучит необычно, но, если вы в чат например ввели .mycommand, выполнится команда. Она будет доступна всем игрокам. Создайте в папке scripts файл commands.js

import { world } from "mojang-minecraft";

world.events.beforeChat.subscribe((eventData) => {
    var player_name = eventData.sender.name
    var player = eventData.sender
    var args = eventData.message.split(" ")
    if (args[0].charAt(0) == ".") {
        eventData.cancel = true
        if (args[0] == ".mycommand") {
            player.runCommand("здесь первая команда")
            player.runCommand("здесь другая команда. Копируйте эти строки бесконечно")
        } else {
            player.runCommand("tellraw @s {\"rawtext\":[{\"text\":\"§cНеизвестная команда\"}]}");
        }
    }
});

Вы можете указать много команд в строке player.runCommand("команда"). Далее, необходимо создать файл main.js

import 'scripts/commands.js'

Всего лишь одна строка, импортирующая скрипт. Давайте отредактируем команды в прошлом коде и в майнкрафте введём в чат .mycommand, выполнятся введённые команды.

Как поставить авторские права на аддон? Легко. Если аддон не был скачан, вы можете создать в нём файл LICENSE.md или LICENSE.txt, в них всё указать. Вот пример

this addon by @DinoZavr2
AntiSpam and chat rangs

©2022

Лицензия может быть очень длинная, всё на ваш вкус.

Текстуры

Мы только что говорили о аддонах. Теперь поговорим о текстурах. Создадим папку mytextures по пути android/data/com.mojang.../files/games/com.mojang/resource_packs, опять же название папки не влияет на название ресурспака. В папке ресурспака создадим наш manifest.json. Вот собственно его код

{
  "format_version": 1,
  "header": {
    "description": "описание вашего текстурпака",
    "name": "название вашего текстурпака",
    "uuid": "уникальный айди текстурпака",
    "version": [
      0,
      0,
      1
    ],
    "min_engine_version": [
      1,
      8,
      0
    ]
  },
  "modules": [
    {
      "description": "",
      "type": "resources",
      "uuid": "второй уникальный айди текстурпака",
      "version": [
        0,
        0,
        1
      ]
    }
  ]
}

Опять же, как и в аддоне, уникальный айди можно сгенерировать на любом сайте, например, uuidgener*tor.net. Вместо "а" я также поставил "*", чтобы ничего не нарушать. Для создания иконки создайте файл pack_icon.jpg или pack_icon.png.

Чтобы создать текстуры блоков, нужно в папке текстурпака создать папку textures. В ней папку blocks. В blocks запихиваем файл diamond_block.jpg (или .png). Теперь в игре алмазный блок будет выглядеть так, как в diamond_block.jpg.

Чтобы создать текстуры предметов, которые у вас в руках, необходимо в textures создать папку items. В неё запихивайте item.jpg, тоесть у меня например stick.jpg, в игре палка будет выглядеть по другому, тоесть так, как выглядит изображение stick.png.

Субпаки. Subpacks - это паки, использующие json код, который что-то определяет. Например, так можно сделать отображение хитбоксов игрока. Вот манифест для субпаков

{
   "format_version":2,
   "header":{
      "description":"описание текстурпака",
      "name":"название текстурпака", 
      "uuid":"уникальный id",
      "version":[1,0,0],
      "min_engine_version":[1,14,0]
   },
   "modules":[
      {
         "description":"",
         "type":"resources",
         "uuid":"второй уникальный id",
         "version":[1,0,0]
      }
   ],
   "subpacks":[
      {
         "folder_name":"no_ray",
         "name":"",
         "memory_tier":1
      },
      {
         "folder_name":"opaque_model_collision",
         "name":"",
         "memory_tier":2
      },
      {
         "folder_name":"default",
         "name":"",
         "memory_tier":3
      }
   ]
}

По манифесту видно, что будет очень сложно. Но всё таки придётся сделать отображение хитбоксов.

Создайте в папке текстурпака папка subpacks. В ней ещё три папки - default, no_ray, opaque_model_collision. Сразу говорю, если вы не знакомы с субпаками и json, ознакомьтесь с ними, потому что сдесь вам без базовых знаний о субпаках вам не будет понятно. Дальше в папке default создаём папку animations, в которой содержатся все анимации. В ней hitboxrot.json. В ней отредактируем код анимации

{
	"format_version" : "1.8.0",
	"animations" : {
		"animation.player.hitboxrot" : {
			"loop" : true,
			"bones" : {
				"hitbox" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		},
		"animation.player.rayrot" : {
			"loop" : true,
			"bones" : {
				"ray" : {
					"rotation" : [ "query.is_sneaking ? query.target_x_rotation - 1.5 : query.target_x_rotation", "query.target_y_rotation", 0.0 ],
					"position" : [ 0.0, "query.is_sneaking ? -4.25 : 0.0", 0.0 ]
				}
			}
		},
		"animation.player.axis" : {
			"loop" : true,
			"bones" : {
				"axis" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		}
	}
}

Небольшой код в 30 строк для анимаций. Теперь если игрок подвижится, текстуры будут с ним поворачиваться. Далее в subpack/default создаём папку entity, естественно, там сущности, на которых действуют текстуры. В entity необходимо создать файл player.entity.json, это сущность игрока. Вот код

{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {
      "identifier": "minecraft:player",
      "materials": {
        "default": "entity_alphatest",
        "cape": "entity_alphatest",
        "animated": "player_animated",
        "emissive": "entity_emissive_alpha_hitbox"
      },
      "textures": {
        "default": "textures/entity/steve",
        "cape": "textures/entity/cape_invisible",
        "hitbox": "textures/models/hitboxoverlay",
        "ray": "textures/models/ray"
      },
      "geometry": {
        "default": "geometry.humanoid.custom",
        "cape": "geometry.cape",
        "hitbox": "geometry.hitbox",
        "hitbox.swimming": "geometry.hitbox.swimming",
        "ray": "geometry.ray"
      },
      "scripts": {
        "scale": "0.9375",
        "initialize": [
          "variable.is_holding_right = 0.0;",
          "variable.is_blinking = 0.0;",
          "variable.last_blink_time = 0.0;",
          "variable.hand_bob = 0.0;"
        ],
        "pre_animation": [
          "variable.helmet_layer_visible = 1.0;",
          "variable.leg_layer_visible = 1.0;",
          "variable.boot_layer_visible = 1.0;",
          "variable.chest_layer_visible = 1.0;",
          "variable.attack_body_rot_y = Math.sin(360*Math.sqrt(variable.attack_time)) * 5.0;",
          "variable.tcos0 = (math.cos(query.modified_distance_moved * 38.17) * query.modified_move_speed / variable.gliding_speed_value) * 57.3;",
          "variable.first_person_rotation_factor = math.sin((1 - variable.attack_time) * 180.0);",
          "variable.hand_bob = query.life_time < 0.01 ? 0.0 : variable.hand_bob + ((query.is_on_ground && query.is_alive ? math.clamp(math.sqrt(math.pow(query.position_delta(0), 2.0) + math.pow(query.position_delta(2), 2.0)), 0.0, 0.1) : 0.0) - variable.hand_bob) * 0.02;",

          "variable.map_angle = math.clamp(1 - variable.player_x_rotation / 45.1, 0.0, 1.0);",
          "variable.item_use_normalized = query.main_hand_item_use_duration / query.main_hand_item_max_duration;"
        ],
        "animate": [
          "root",
          "hitbox_rot",
          "ray_rot",
          "axis"
        ]
      },
      "animations": {
      	"hitbox_rot": "animation.player.hitboxrot",
      	"ray_rot": "animation.player.rayrot",
      	"axis": "animation.player.axis",
        "root": "controller.animation.player.root",
        "base_controller": "controller.animation.player.base",
        "hudplayer":  "controller.animation.player.hudplayer",
        "humanoid_base_pose": "animation.humanoid.base_pose",
        "look_at_target": "controller.animation.humanoid.look_at_target",
        "look_at_target_ui": "animation.player.look_at_target.ui",
        "look_at_target_default": "animation.humanoid.look_at_target.default",
        "look_at_target_gliding": "animation.humanoid.look_at_target.gliding",
        "look_at_target_swimming": "animation.humanoid.look_at_target.swimming",
        "look_at_target_inverted": "animation.player.look_at_target.inverted",
        "cape": "animation.player.cape",
        "move.arms": "animation.player.move.arms",
        "move.legs": "animation.player.move.legs",
        "swimming": "animation.player.swim",
        "swimming.legs": "animation.player.swim.legs",
        "riding.arms": "animation.player.riding.arms",
        "riding.legs": "animation.player.riding.legs",
        "holding": "animation.player.holding",
        "brandish_spear": "animation.humanoid.brandish_spear",
        "charging": "animation.humanoid.charging",
        "attack.positions": "animation.player.attack.positions",
        "attack.rotations": "animation.player.attack.rotations",
        "sneaking": "animation.player.sneaking",
        "bob": "animation.player.bob",
        "damage_nearby_mobs": "animation.humanoid.damage_nearby_mobs",
        "bow_and_arrow": "animation.humanoid.bow_and_arrow",
        "fishing_rod": "animation.humanoid.fishing_rod",
        "use_item_progress": "animation.humanoid.use_item_progress",
        "skeleton_attack": "animation.skeleton.attack",
        "sleeping": "animation.player.sleeping",
        "first_person_base_pose": "animation.player.first_person.base_pose",
        "first_person_empty_hand": "animation.player.first_person.empty_hand",
        "first_person_swap_item": "animation.player.first_person.swap_item",
        "first_person_attack_controller": "controller.animation.player.first_person_attack",
        "first_person_attack_rotation": "animation.player.first_person.attack_rotation",
        "first_person_vr_attack_rotation": "animation.player.first_person.vr_attack_rotation",
        "first_person_walk": "animation.player.first_person.walk",
        "first_person_map_controller": "controller.animation.player.first_person_map",
        "first_person_map_hold": "animation.player.first_person.map_hold",
        "first_person_map_hold_attack": "animation.player.first_person.map_hold_attack",
        "first_person_map_hold_off_hand": "animation.player.first_person.map_hold_off_hand",
        "first_person_map_hold_main_hand": "animation.player.first_person.map_hold_main_hand",
        "first_person_crossbow_equipped": "animation.player.first_person.crossbow_equipped",
        "third_person_crossbow_equipped": "animation.player.crossbow_equipped",
        "third_person_bow_equipped": "animation.player.bow_equipped",
        "crossbow_hold": "animation.player.crossbow_hold",
        "crossbow_controller": "controller.animation.player.crossbow",
        "shield_block_main_hand": "animation.player.shield_block_main_hand",
        "shield_block_off_hand": "animation.player.shield_block_off_hand",
        "blink": "controller.animation.persona.blink"
      },
      "render_controllers": [
        { "controller.render.player.first_person": "variable.is_first_person" },
        { "controller.render.player.third_person": "!variable.is_first_person && !variable.map_face_icon" },
        { "controller.render.player.map": "variable.map_face_icon" },
        { "controller.render.player.hitbox": "!variable.is_first_person && !query.is_in_ui" },
        { "controller.render.player.ray": "!variable.is_first_person && !query.is_in_ui" }
      ],
      "enable_attachables": true
    }
  }
}

Это очень сложно понять без хотя бы базовых знаний json. Тут применяются математические примеры, рендер контроллеры, целых 118 строк. rotation'ы и всё подобное. Можете создать много сущностей для отображения хитбоксов, например, для иссушителя или волка. Но лучше не надо, а то если на многих сущностях будут действовать текстуры, будет лагать. Далее нам нужны материалы - создаём в default папку materials. В ней entity.material (без json), расширение material.

{
  "materials": {
    "version": "1.0.0",
	"entity_emissive_alpha_hitbox:entity_nocull": {
      "+defines": [
        "ALPHA_TEST",
        "USE_EMISSIVE"
      ],
      "depthFunc": "Always"
    }
  }
}

Думаю, здесь объяснять ничего не надо. Функция будет работать всегда. Дальше ещё сложнее - в default создайте папку models, в ней entity. А в entity 3 файла - первый hitbox.json

{
	"format_version": "1.10.0",
	"geometry.hitbox": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 30, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

В этом файле мы указали размер хитбокса и сами хитбоксы. Второй файл - ray.json

{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.ray",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 2,
				"visible_bounds_height": 2,
				"visible_bounds_offset": [0, 2, 0]
			},
			"bones": [
				{
					"name": "ray",
					"pivot": [0, 27, 0],
					"cubes": [
						{"origin": [-0.5, 26.5, -0.5], "size": [1, 1, -16], "inflate": -0.41, "uv": [0, 0]}
					]
				}
			]
		}
	]
}

Здесь тоже, думаю, объяснять ничего не надо - мы создали рэй. И третий файл - hitboxhitbox_swimming.json

{
	"format_version": "1.10.0",
	"geometry.hitbox.swimming": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 9, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Здесь мы указали отображение хитбоксов в режиме плавания. Это не будет выглядеть странно, как без этого файла. Если этого файла не будет, при плавании у игрока будет хитбокс, как будто он стоя ходит.

Теперь возвращаемся в папку default. В ней нужно создать папку render_controllers - в ней тоже hitbox.json и ray.json. Вот hitbox.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.hitbox": {
      "geometry": "Array.geo[query.is_swimming]",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.hitbox" ],
      "arrays": {
        "geometries": { "Array.geo": [ "geometry.hitbox", "geometry.hitbox.swimming"] }
      },
      "is_hurt_color":{},
      "on_fire_color":{}
    }
  }
}

Тут мы также не забыли указать про плавание. Теперь ray.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.ray": {
      "geometry": "Geometry.ray",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.ray" ],
      "is_hurt_color":{},
      "on_fire_color":{},
      "part_visibility": [
        { "*": "!query.is_swimming" }
      ]
    }
  }
}

Да да, опять указали swimming. Это рендер контроллеры. И создаём последнюю папку в папке default - textures. Как же без текстур хитбокса? Закидываем туда models/hitboxoverlay.png,ray.png

Вот вам hitboxoverlay

hitboxoverlay.png
hitboxoverlay.png

И вот ray.png

ray.png
ray.png

Да, они находятся в models. Вы, наверное, не видите тут модели, но они есть. На этом с папкой default мы закончили

Ну что, с папкой default закончили. В ней мы указали модели, материалы, плаванье и отображение хитбоксов, также модели хитбоксов. Теперь переходим в папку subpacks, в которой и содержался default. В subpacks создаём папку no_ray. Вы наверное уже представляете, насколько это будет долго. В no_ray создадим несколько папок - animations, entity, materials, models, render_controllers, textures.

В папке animations создайте файл hitboxrot.json

{
	"format_version" : "1.8.0",
	"animations" : {
		"animation.player.hitboxrot" : {
			"loop" : true,
			"bones" : {
				"hitbox" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		},
		"animation.player.axis" : {
			"loop" : true,
			"bones" : {
				"axis" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		}
	}
}

hitboxrot - hitboxrotation. Это небольшое сокращение, но файл называется именно hitboxrot.json. Тут есть body_rotation и всё в этом роде, тоесть анимации без ray, это подтверждает папка no_ray. Далее в no_ray в materials создайте папку entity.material, тоесть с расширением material.

{
  "materials": {
    "version": "1.0.0",
	"entity_emissive_alpha_hitbox:entity_nocull": {
      "+defines": [
        "ALPHA_TEST",
        "USE_EMISSIVE"
      ],
      "depthFunc": "Always"
    }
  }
}

Опять анимации в материалах. Ладно, дальше в models создаём папку entity, в которой hitbox_swimming.json и hitbox.json. Вот hitbox.json

{
	"format_version": "1.10.0",
	"geometry.hitbox": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 30, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Здесь размер хитбокса в json. Дальше hitbox_swimming.json - плавающий хитбокс

{
	"format_version": "1.10.0",
	"geometry.hitbox.swimming": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 9, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Здесь хитбокс в режиме плавания. Кого не смущает стоячий хитбокс плавающего игрока? Ладно, вот hitbox.json в render_controllers

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.hitbox": {
      "geometry": "Array.geo[query.is_swimming]",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.hitbox" ],
      "arrays": {
        "geometries": { "Array.geo": [ "geometry.hitbox", "geometry.hitbox.swimming"] }
      },
      "is_hurt_color":{},
      "on_fire_color":{}
    }
  }
}

Дальше в папке no_ray/textures/models вставляем hitboxoverlay.png, как же без него

hitboxoverlay.png
hitboxoverlay.png

На этом с no_ray мы закончили. Но это ещё не всё - в субпаках есть папка opaque_model_collision. Напоминаю, мы делаем текстурпак с хитбоксами только для ознакомления с субпаками и json. Теперь, в opaque_model_collision создаём папки animations, entity, models, render_controllers, textures. Давайте немного от редактируем -сначалa папка animations. В ней hitboxrot.json. Вот hitboxrot.json

{
	"format_version" : "1.8.0",
	"animations" : {
		"animation.player.hitboxrot" : {
			"loop" : true,
			"bones" : {
				"hitbox" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		},
		"animation.player.rayrot" : {
			"loop" : true,
			"bones" : {
				"ray" : {
					"rotation" : [ "query.is_sneaking ? query.target_x_rotation - 1.5 : query.target_x_rotation", "query.target_y_rotation", 0.0 ],
					"position" : [ 0.0, "query.is_sneaking ? -4.25 : 0.0", 0.0 ]
				}
			}
		},
		"animation.player.axis" : {
			"loop" : true,
			"bones" : {
				"axis" : {
					"rotation" : [ 0.0, "-query.body_y_rotation", 0.0 ]
				}
			}
		}
	}
}

Мы указали кружение объекта, луп и хитбоксы. Далее указываем сущности - entity/player.entity.json

{
  "format_version": "1.10.0",
  "minecraft:client_entity": {
    "description": {
      "identifier": "minecraft:player",
      "materials": {
        "default": "entity_alphatest",
        "cape": "entity_alphatest",
        "animated": "player_animated",
        "emissive": "entity_emissive_alpha"
      },
      "textures": {
        "default": "textures/entity/steve",
        "cape": "textures/entity/cape_invisible",
        "hitbox": "textures/models/hitboxoverlay",
        "ray": "textures/models/ray"
      },
      "geometry": {
        "default": "geometry.humanoid.custom",
        "cape": "geometry.cape",
        "hitbox": "geometry.hitbox",
        "hitbox.swimming": "geometry.hitbox.swimming",
        "ray": "geometry.ray"
      },
      "scripts": {
        "scale": "0.9375",
        "initialize": [
          "variable.is_holding_right = 0.0;",
          "variable.is_blinking = 0.0;",
          "variable.last_blink_time = 0.0;",
          "variable.hand_bob = 0.0;"
        ],
        "pre_animation": [
          "variable.helmet_layer_visible = 1.0;",
          "variable.leg_layer_visible = 1.0;",
          "variable.boot_layer_visible = 1.0;",
          "variable.chest_layer_visible = 1.0;",
          "variable.attack_body_rot_y = Math.sin(360*Math.sqrt(variable.attack_time)) * 5.0;",
          "variable.tcos0 = (math.cos(query.modified_distance_moved * 38.17) * query.modified_move_speed / variable.gliding_speed_value) * 57.3;",
          "variable.first_person_rotation_factor = math.sin((1 - variable.attack_time) * 180.0);",
          "variable.hand_bob = query.life_time < 0.01 ? 0.0 : variable.hand_bob + ((query.is_on_ground && query.is_alive ? math.clamp(math.sqrt(math.pow(query.position_delta(0), 2.0) + math.pow(query.position_delta(2), 2.0)), 0.0, 0.1) : 0.0) - variable.hand_bob) * 0.02;",

          "variable.map_angle = math.clamp(1 - variable.player_x_rotation / 45.1, 0.0, 1.0);",
          "variable.item_use_normalized = query.main_hand_item_use_duration / query.main_hand_item_max_duration;"
        ],
        "animate": [
          "root",
          "hitbox_rot",
          "ray_rot",
          "axis"
        ]
      },
      "animations": {
      	"hitbox_rot": "animation.player.hitboxrot",
      	"ray_rot": "animation.player.rayrot",
      	"axis": "animation.player.axis",
        "root": "controller.animation.player.root",
        "base_controller": "controller.animation.player.base",
        "hudplayer":  "controller.animation.player.hudplayer",
        "humanoid_base_pose": "animation.humanoid.base_pose",
        "look_at_target": "controller.animation.humanoid.look_at_target",
        "look_at_target_ui": "animation.player.look_at_target.ui",
        "look_at_target_default": "animation.humanoid.look_at_target.default",
        "look_at_target_gliding": "animation.humanoid.look_at_target.gliding",
        "look_at_target_swimming": "animation.humanoid.look_at_target.swimming",
        "look_at_target_inverted": "animation.player.look_at_target.inverted",
        "cape": "animation.player.cape",
        "move.arms": "animation.player.move.arms",
        "move.legs": "animation.player.move.legs",
        "swimming": "animation.player.swim",
        "swimming.legs": "animation.player.swim.legs",
        "riding.arms": "animation.player.riding.arms",
        "riding.legs": "animation.player.riding.legs",
        "holding": "animation.player.holding",
        "brandish_spear": "animation.humanoid.brandish_spear",
        "charging": "animation.humanoid.charging",
        "attack.positions": "animation.player.attack.positions",
        "attack.rotations": "animation.player.attack.rotations",
        "sneaking": "animation.player.sneaking",
        "bob": "animation.player.bob",
        "damage_nearby_mobs": "animation.humanoid.damage_nearby_mobs",
        "bow_and_arrow": "animation.humanoid.bow_and_arrow",
        "fishing_rod": "animation.humanoid.fishing_rod",
        "use_item_progress": "animation.humanoid.use_item_progress",
        "skeleton_attack": "animation.skeleton.attack",
        "sleeping": "animation.player.sleeping",
        "first_person_base_pose": "animation.player.first_person.base_pose",
        "first_person_empty_hand": "animation.player.first_person.empty_hand",
        "first_person_swap_item": "animation.player.first_person.swap_item",
        "first_person_attack_controller": "controller.animation.player.first_person_attack",
        "first_person_attack_rotation": "animation.player.first_person.attack_rotation",
        "first_person_vr_attack_rotation": "animation.player.first_person.vr_attack_rotation",
        "first_person_walk": "animation.player.first_person.walk",
        "first_person_map_controller": "controller.animation.player.first_person_map",
        "first_person_map_hold": "animation.player.first_person.map_hold",
        "first_person_map_hold_attack": "animation.player.first_person.map_hold_attack",
        "first_person_map_hold_off_hand": "animation.player.first_person.map_hold_off_hand",
        "first_person_map_hold_main_hand": "animation.player.first_person.map_hold_main_hand",
        "first_person_crossbow_equipped": "animation.player.first_person.crossbow_equipped",
        "third_person_crossbow_equipped": "animation.player.crossbow_equipped",
        "third_person_bow_equipped": "animation.player.bow_equipped",
        "crossbow_hold": "animation.player.crossbow_hold",
        "crossbow_controller": "controller.animation.player.crossbow",
        "shield_block_main_hand": "animation.player.shield_block_main_hand",
        "shield_block_off_hand": "animation.player.shield_block_off_hand",
        "blink": "controller.animation.persona.blink"
      },
      "render_controllers": [
        { "controller.render.player.first_person": "variable.is_first_person" },
        { "controller.render.player.third_person": "!variable.is_first_person && !variable.map_face_icon" },
        { "controller.render.player.map": "variable.map_face_icon" },
        { "controller.render.player.hitbox": "!variable.is_first_person && !query.is_in_ui" },
        { "controller.render.player.ray": "!variable.is_first_person && !query.is_in_ui" }
      ],
      "enable_attachables": true
    }
  }
}

Ууу, тоже 118 строк. Одни вариаблы и контроллеры. Ну ладно, дальше в папке models создайте папку entity. В ней hitbox.json, hitbox_swimming.json, ray.json, вот hitbox

{
	"format_version": "1.10.0",
	"geometry.hitbox": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 30, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

Указали размер. Теперь в плаванью хитбокса - hitbox_swimming.json

{
	"format_version": "1.10.0",
	"geometry.hitbox.swimming": {
		"texturewidth": 64,
		"textureheight": 64,
		"visible_bounds_width": 3,
		"visible_bounds_height": 3,
		"visible_bounds_offset": [0, 1.5, 0],
		"bones": [
			{
				"name": "hitbox",
				"pivot": [0, 0, 0],
				"cubes": [
					{ "origin": [-5, 0.2, -5], "size": [10, 9, 10], "uv": [0, 0], "inflate": 0.3 },
					{ "origin": [-4.5, 0.5, -4.5], "size": [9, 9, 9], "uv": [0, 42] }
				]
			}
		]
	}
}

ЗдесьЗдесь используется функция geometry.hitbox.swimming, остался ray.json

{
	"format_version": "1.12.0",
	"minecraft:geometry": [
		{
			"description": {
				"identifier": "geometry.ray",
				"texture_width": 16,
				"texture_height": 16,
				"visible_bounds_width": 2,
				"visible_bounds_height": 2,
				"visible_bounds_offset": [0, 2, 0]
			},
			"bones": [
				{
					"name": "ray",
					"pivot": [0, 27, 0],
					"cubes": [
						{"origin": [-0.5, 26.5, -0.5], "size": [1, 1, -16], "inflate": -0.41, "uv": [0, 0]}
					]
				}
			]
		}
	]
}

А здесь функция minecraft:geometry. Странно, но всё же так. Далее переходим в render_controllers, там 2 файла - hitbox.json и ray.json. Отредактируем hitbox.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.hitbox": {
      "geometry": "Array.geo[query.is_swimming]",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.hitbox" ],
      "arrays": {
        "geometries": { "Array.geo": [ "geometry.hitbox", "geometry.hitbox.swimming"] }
      },
      "is_hurt_color":{},
      "on_fire_color":{}
    }
  }
}

Здесь используется функция render_controllers. Дальше ray.json

{
  "format_version": "1.8.0",
  "render_controllers": {
    "controller.render.player.ray": {
      "geometry": "Geometry.ray",
      "materials": [ { "*": "Material.emissive" } ],
      "textures": [ "Texture.ray" ],
      "is_hurt_color":{},
      "on_fire_color":{},
      "part_visibility": [
        { "*": "!query.is_swimming" }
      ]
    }
  }
}

И опять функция render_controllers. Ладно, что нам осталось? Правильно, textures, в котором models. В models опять 2 файла - hitboxoverlay.png, ray.png. Вот hitboxoverlay.png

hitboxoverlay.png
hitboxoverlay.png

Прошлые картинки ray.png, смотрите на них.

На этом всё. Импортируем аддон и заходим в майнкрафт. Запускаем и проверяем - у всех игроков есть хитбоксы.

На этом всё. Теперь перевод. Чтобы в ресурспаке сделать название предметов американцу stick, а русскому палка-копалка, создайте в папке ресурспака папку texts, в ней ru_RU.lang. Далее строка

item.предмет.name=§l§aПалка копалка

На этом всё. Я рассказал про наборы параметров, ресурспаки и субпаки в текстурпаках, отображение хитбокса, перевод и анти-спам с рангами чата. Надеюсь, это статья была полезной.

Комментарии (0)