Привет, Хабр! В этой статье я хочу затронуть тему аддонов. Многим чего-то не хватало в игре, и они скачивали аддоны с интернета. Когда вы понимаете, что скачанный аддон настолько безполезный, удаляете его. Я, допустим, хочу сделать аддон в 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
И вот 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, как же без него
На этом с 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
Прошлые картинки ray.png, смотрите на них.
На этом всё. Импортируем аддон и заходим в майнкрафт. Запускаем и проверяем - у всех игроков есть хитбоксы.
На этом всё. Теперь перевод. Чтобы в ресурспаке сделать название предметов американцу stick, а русскому палка-копалка, создайте в папке ресурспака папку texts, в ней ru_RU.lang. Далее строка
item.предмет.name=§l§aПалка копалка
На этом всё. Я рассказал про наборы параметров, ресурспаки и субпаки в текстурпаках, отображение хитбокса, перевод и анти-спам с рангами чата. Надеюсь, это статья была полезной.