Skip to content

Commit

Permalink
Added basic custom entity translation
Browse files Browse the repository at this point in the history
  • Loading branch information
RaphiMC committed Sep 4, 2024
1 parent e0e2565 commit ef38887
Show file tree
Hide file tree
Showing 14 changed files with 522 additions and 29 deletions.
11 changes: 11 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ repositories {
name = "Minecraft Libraries"
url "https://libraries.minecraft.net"
}
maven {
name = "Jitpack"
url "https://jitpack.io"

content {
includeGroup "com.github.Oryxel"
}
}
}

dependencies {
Expand Down Expand Up @@ -63,6 +71,9 @@ dependencies {
exclude group: "com.google.guava"
}
api "org.lz4:lz4-pure-java:1.8.0"
api("com.github.Oryxel:CubeConverter:b382f23e8a") {
transitive = false
}
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock
* Copyright (C) 2023-2024 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viabedrock.api.model.entity;

import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.minecraft.Vector3f;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_20_5;
import com.viaversion.viaversion.api.minecraft.entitydata.EntityData;
import com.viaversion.viaversion.api.minecraft.item.StructuredItem;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.api.type.types.version.Types1_21;
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundPackets1_21;
import com.viaversion.viaversion.util.Key;
import net.raphimc.viabedrock.api.model.resourcepack.EntityDefinitions;
import net.raphimc.viabedrock.api.util.MathUtil;
import net.raphimc.viabedrock.protocol.BedrockProtocol;
import net.raphimc.viabedrock.protocol.data.ProtocolConstants;
import net.raphimc.viabedrock.protocol.model.Position3f;
import net.raphimc.viabedrock.protocol.rewriter.resourcepack.CustomEntityResourceRewriter;
import net.raphimc.viabedrock.protocol.storage.EntityTracker;
import net.raphimc.viabedrock.protocol.storage.ResourcePacksStorage;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class CustomEntity extends Entity {

private final EntityDefinitions.EntityDefinition entityDefinition;
private final List<ItemDisplayEntity> partEntities = new ArrayList<>();
private boolean spawned;

public CustomEntity(final UserConnection user, final long uniqueId, final long runtimeId, final int javaId, final EntityDefinitions.EntityDefinition entityDefinition) {
super(user, uniqueId, runtimeId, javaId, UUID.randomUUID(), EntityTypes1_20_5.INTERACTION);
this.entityDefinition = entityDefinition;
}

@Override
public void setPosition(final Position3f position) {
super.setPosition(position);

if (!this.spawned) {
this.spawned = true;
this.spawn();
} else {
this.partEntities.forEach(ItemDisplayEntity::updatePositionAndRotation);
}
}

@Override
public void setRotation(final Position3f rotation) {
super.setRotation(rotation);

if (!this.spawned) {
this.spawned = true;
this.spawn();
} else {
this.partEntities.forEach(ItemDisplayEntity::updatePositionAndRotation);
}
}

@Override
public void remove() {
super.remove();

final int[] entityIds = new int[partEntities.size()];
for (int i = 0; i < partEntities.size(); i++) {
entityIds[i] = partEntities.get(i).javaId;
}
final PacketWrapper removeEntities = PacketWrapper.create(ClientboundPackets1_21.REMOVE_ENTITIES, this.user);
removeEntities.write(Types.VAR_INT_ARRAY_PRIMITIVE, entityIds); // entity ids
removeEntities.send(BedrockProtocol.class);
}

private void spawn() {
final EntityTracker entityTracker = user.get(EntityTracker.class);
final ResourcePacksStorage resourcePacksStorage = user.get(ResourcePacksStorage.class);
final int parts = (int) resourcePacksStorage.getConverterData().get("ce_" + this.entityDefinition.identifier() + "_default");
for (int i = 0; i < parts; i++) {
final ItemDisplayEntity partEntity = new ItemDisplayEntity(entityTracker.getNextJavaEntityId());
this.partEntities.add(partEntity);
final List<EntityData> javaEntityData = new ArrayList<>();

final StructuredDataContainer data = ProtocolConstants.createStructuredDataContainer();
data.set(StructuredDataKey.CUSTOM_MODEL_DATA, CustomEntityResourceRewriter.getCustomModelData(this.entityDefinition.identifier() + "_default_" + i));
final StructuredItem item = new StructuredItem(BedrockProtocol.MAPPINGS.getJavaItems().get(Key.namespaced(CustomEntityResourceRewriter.ITEM)), 1, data);
javaEntityData.add(new EntityData(partEntity.getJavaEntityDataIndex("ITEM_STACK"), Types1_21.ENTITY_DATA_TYPES.itemType, item));

final float scale = (float) resourcePacksStorage.getConverterData().get("ce_" + this.entityDefinition.identifier() + "_default_" + i + "_scale");
javaEntityData.add(new EntityData(partEntity.getJavaEntityDataIndex("SCALE"), Types1_21.ENTITY_DATA_TYPES.vector3FType, new Vector3f(scale, scale, scale)));

final PacketWrapper addEntity = PacketWrapper.create(ClientboundPackets1_21.ADD_ENTITY, user);
addEntity.write(Types.VAR_INT, partEntity.javaId()); // entity id
addEntity.write(Types.UUID, partEntity.javaUuid()); // uuid
addEntity.write(Types.VAR_INT, partEntity.type().getId()); // type id
addEntity.write(Types.DOUBLE, (double) this.position.x()); // x
addEntity.write(Types.DOUBLE, (double) this.position.y()); // y
addEntity.write(Types.DOUBLE, (double) this.position.z()); // z
addEntity.write(Types.BYTE, MathUtil.float2Byte(this.rotation.x())); // pitch
addEntity.write(Types.BYTE, MathUtil.float2Byte(this.rotation.y())); // yaw
addEntity.write(Types.BYTE, MathUtil.float2Byte(this.rotation.z())); // head yaw
addEntity.write(Types.VAR_INT, 0); // data
addEntity.write(Types.SHORT, (short) 0); // velocity x
addEntity.write(Types.SHORT, (short) 0); // velocity y
addEntity.write(Types.SHORT, (short) 0); // velocity z
addEntity.send(BedrockProtocol.class);

final PacketWrapper setEntityData = PacketWrapper.create(ClientboundPackets1_21.SET_ENTITY_DATA, user);
setEntityData.write(Types.VAR_INT, partEntity.javaId()); // entity id
setEntityData.write(Types1_21.ENTITY_DATA_LIST, javaEntityData); // entity data
setEntityData.send(BedrockProtocol.class);
}
}

private class ItemDisplayEntity extends Entity {

public ItemDisplayEntity(final int javaId) {
super(CustomEntity.this.user, 0L, 0L, javaId, UUID.randomUUID(), EntityTypes1_20_5.ITEM_DISPLAY);
}

public void updatePositionAndRotation() {
final PacketWrapper teleportEntity = PacketWrapper.create(ClientboundPackets1_21.TELEPORT_ENTITY, this.user);
teleportEntity.write(Types.VAR_INT, this.javaId()); // entity id
teleportEntity.write(Types.DOUBLE, (double) CustomEntity.this.position.x()); // x
teleportEntity.write(Types.DOUBLE, (double) CustomEntity.this.position.y()); // y
teleportEntity.write(Types.DOUBLE, (double) CustomEntity.this.position.z()); // z
teleportEntity.write(Types.BYTE, MathUtil.float2Byte(CustomEntity.this.rotation.y())); // yaw
teleportEntity.write(Types.BYTE, MathUtil.float2Byte(CustomEntity.this.rotation.x())); // pitch
teleportEntity.write(Types.BOOLEAN, CustomEntity.this.onGround); // on ground
teleportEntity.send(BedrockProtocol.class);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public void tick() {
this.age++;
}

public void remove() {
}

public float eyeOffset() {
return 0F;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ public final void deleteTeam() {
setPlayerTeam.send(BedrockProtocol.class);
}

@Override
public void remove() {
super.remove();
this.deleteTeam();
}

@Override
public float eyeOffset() {
return 1.62F;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock
* Copyright (C) 2023-2024 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viabedrock.api.model.resourcepack;

import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.libs.gson.JsonObject;
import com.viaversion.viaversion.util.Key;
import net.raphimc.viabedrock.ViaBedrock;
import net.raphimc.viabedrock.protocol.storage.ResourcePacksStorage;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

// https://wiki.bedrock.dev/entities/entity-intro-rp.html
public class EntityDefinitions {

private final Map<String, EntityDefinition> entities = new HashMap<>();

public EntityDefinitions(final ResourcePacksStorage resourcePacksStorage) {
for (ResourcePack pack : resourcePacksStorage.getPackStackBottomToTop()) {
for (String entityPath : pack.content().getFilesDeep("entity/", ".json")) {
try {
final JsonObject description = pack.content().getJson(entityPath).getAsJsonObject("minecraft:client_entity").getAsJsonObject("description");
final String identifier = Key.namespaced(description.get("identifier").getAsString());
final EntityDefinition entityDefinition = new EntityDefinition(identifier, pack.content().getString(entityPath));
if (description.has("geometry")) {
final JsonObject geometry = description.getAsJsonObject("geometry");
for (Map.Entry<String, JsonElement> entry : geometry.entrySet()) {
entityDefinition.models.put(entry.getKey(), entry.getValue().getAsString());
}
}
if (description.has("textures")) {
final JsonObject textures = description.getAsJsonObject("textures");
for (Map.Entry<String, JsonElement> entry : textures.entrySet()) {
entityDefinition.textures.put(entry.getKey(), entry.getValue().getAsString());
}
}
this.entities.put(identifier, entityDefinition);
} catch (Throwable e) {
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Failed to parse entity definition " + entityPath + " in pack " + pack.packId(), e);
}
}
}
}

public EntityDefinition get(final String identifier) {
return this.entities.get(identifier);
}

public Map<String, EntityDefinition> entities() {
return Collections.unmodifiableMap(this.entities);
}

public static class EntityDefinition {

private final String identifier;
private final Map<String, String> models = new HashMap<>();
private final Map<String, String> textures = new HashMap<>();
private final String jsonForCubeConverter;

public EntityDefinition(final String identifier, final String jsonForCubeConverter) {
this.identifier = identifier;
this.jsonForCubeConverter = jsonForCubeConverter;
}

public String identifier() {
return this.identifier;
}

public Map<String, String> models() {
return Collections.unmodifiableMap(this.models);
}

public Map<String, String> textures() {
return Collections.unmodifiableMap(this.textures);
}

public String jsonForCubeConverter() {
return this.jsonForCubeConverter;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* This file is part of ViaBedrock - https://github.com/RaphiMC/ViaBedrock
* Copyright (C) 2023-2024 RK_01/RaphiMC and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.raphimc.viabedrock.api.model.resourcepack;

import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.libs.gson.JsonObject;
import net.raphimc.viabedrock.ViaBedrock;
import net.raphimc.viabedrock.protocol.storage.ResourcePacksStorage;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

public class ModelDefinitions {

private final Map<String, ModelDefinition> entityModels = new HashMap<>();

public ModelDefinitions(final ResourcePacksStorage resourcePacksStorage) {
for (ResourcePack pack : resourcePacksStorage.getPackStackBottomToTop()) {
for (String modelPath : pack.content().getFilesDeep("models/", ".json")) {
try {
for (JsonElement geometryElement : pack.content().getJson(modelPath).getAsJsonArray("minecraft:geometry")) {
final JsonObject description = geometryElement.getAsJsonObject().getAsJsonObject("description");
final String name = description.get("identifier").getAsString();
final ModelDefinition modelDefinition = new ModelDefinition(name, pack.content().getString(modelPath));
if (modelPath.startsWith("models/entity/")) {
this.entityModels.put(name, modelDefinition);
}
}
} catch (Throwable e) {
ViaBedrock.getPlatform().getLogger().log(Level.WARNING, "Failed to parse model definition " + modelPath + " in pack " + pack.packId(), e);
}
}
}
}

public ModelDefinition getEntityModel(final String name) {
return this.entityModels.get(name);
}

public Map<String, ModelDefinition> entityModels() {
return Collections.unmodifiableMap(this.entityModels);
}

public static class ModelDefinition {

private final String name;
private final String jsonForCubeConverter;

public ModelDefinition(final String name, final String jsonForCubeConverter) {
this.name = name;
this.jsonForCubeConverter = jsonForCubeConverter;
}

public String name() {
return this.name;
}

public String jsonForCubeConverter() {
return this.jsonForCubeConverter;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ public boolean putJson(final String path, final JsonObject json) {
return this.putString(path, GsonUtil.getGson().toJson(json));
}

public BufferedImage getImageLenient(final String path) {
public BufferedImage getShortnameImage(final String path) {
if (this.contains(path)) {
return this.getImage(path);
} else if (this.contains(path + ".png")) {
Expand Down
Loading

0 comments on commit ef38887

Please sign in to comment.