Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,18 @@

package org.geysermc.geyser.item.hashing.data;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

// Ordered and named by Java ID
@RequiredArgsConstructor
@Getter
public enum FireworkExplosionShape {
SMALL_BALL,
LARGE_BALL,
STAR,
CREEPER,
BURST
SMALL_BALL("Small ball"),
LARGE_BALL("Large Ball"),
STAR("Star-shaped"),
CREEPER("Creeper-shaped"),
BURST("Burst");

private final String bedrockName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@

import it.unimi.dsi.fastutil.ints.IntArrays;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtList;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtMapBuilder;
import org.cloudburstmc.nbt.NbtType;
import org.geysermc.geyser.item.TooltipOptions;
import org.geysermc.geyser.item.hashing.data.FireworkExplosionShape;
import org.geysermc.geyser.level.FireworkColor;
import org.geysermc.geyser.registry.type.ItemMapping;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
Expand All @@ -49,13 +52,16 @@ public FireworkRocketItem(String javaIdentifier, Builder builder) {
}

@Override
public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
super.translateComponentsToBedrock(session, components, tooltip, builder);
public void translateComponentsToBedrock(@Nullable GeyserSession session, @NonNull DataComponents components, @NonNull TooltipOptions tooltip, @NonNull BedrockItemBuilder builder) {
if (session != null) {
super.translateComponentsToBedrock(session, components, tooltip, builder);
}

Fireworks fireworks = components.get(DataComponentTypes.FIREWORKS);
if (fireworks == null) {
return;
}
// We still need to translate the explosion so this is still correct, and can be reverse translate in translateNbtToJava.
NbtMapBuilder fireworksNbt = NbtMap.builder();
fireworksNbt.putByte("Flight", (byte) fireworks.getFlightDuration());

Expand All @@ -71,14 +77,55 @@ public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNul
fireworksNbt.put("Explosions", NbtList.EMPTY);
}
builder.putCompound("Fireworks", fireworksNbt.build());

// Then we translate everything into lore since the explosion tag and everything is not visible anymore due to this being a custom item.
List<String> lore = builder.getOrCreateLore();
lore.add(ChatColor.RESET + ChatColor.WHITE + "Flight Duration: " + fireworks.getFlightDuration());

for (Fireworks.FireworkExplosion explosion : explosions) {
lore.add(ChatColor.RESET + ChatColor.WHITE + " " + FireworkExplosionShape.values()[explosion.getShapeId()].getBedrockName());

final StringBuilder colorBuilder = new StringBuilder();
for (int color : explosion.getColors()) {
FireworkColor fireworkColor = FireworkColor.values()[FireworkColor.fromJavaRGB(color)];
colorBuilder.append(fireworkColor.getBedrockName()).append(" ");
}
if (!colorBuilder.isEmpty()) {
lore.add(ChatColor.RESET + ChatColor.WHITE + " " + colorBuilder);
}

final StringBuilder fadeColorBuilder = new StringBuilder();
for (int color : explosion.getFadeColors()) {
FireworkColor fireworkColor = FireworkColor.values()[FireworkColor.fromJavaRGB(color)];
fadeColorBuilder.append(fireworkColor.getBedrockName()).append(" ");
}
if (!fadeColorBuilder.isEmpty()) {
lore.add(ChatColor.RESET + ChatColor.WHITE + " Fade to " + fadeColorBuilder);
}

if (explosion.isHasTrail()) {
lore.add(ChatColor.RESET + ChatColor.WHITE + " Trail");
}

if (explosion.isHasTwinkle()) {
lore.add(ChatColor.RESET + ChatColor.WHITE + " Twinkle");
}
}
}

@Override
public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
super.translateNbtToJava(session, bedrockTag, components, mapping);
public void translateNbtToJava(@Nullable GeyserSession session, @NonNull NbtMap bedrockTag, @NonNull DataComponents components, @NonNull ItemMapping mapping) {
if (session != null) {
super.translateNbtToJava(session, bedrockTag, components, mapping);
}

NbtMap fireworksTag = bedrockTag.getCompound("Fireworks");
if (!fireworksTag.isEmpty()) {
int flightDuration = 1;
if (fireworksTag.containsKey("Flight")) {
flightDuration = fireworksTag.getByte("Flight");
}

List<NbtMap> explosions = fireworksTag.getList("Explosions", NbtType.COMPOUND);
if (!explosions.isEmpty()) {
List<Fireworks.FireworkExplosion> javaExplosions = new ArrayList<>();
Expand All @@ -88,7 +135,9 @@ public void translateNbtToJava(@NonNull GeyserSession session, @NonNull NbtMap b
javaExplosions.add(javaExplosion);
}
}
components.put(DataComponentTypes.FIREWORKS, new Fireworks(1, javaExplosions));
components.put(DataComponentTypes.FIREWORKS, new Fireworks(flightDuration, javaExplosions));
} else {
components.put(DataComponentTypes.FIREWORKS, new Fireworks(flightDuration, List.of()));
}
}
}
Expand Down
40 changes: 22 additions & 18 deletions core/src/main/java/org/geysermc/geyser/level/FireworkColor.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2022 GeyserMC. http://geysermc.org
* Copyright (c) 2019-2025 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -25,34 +25,38 @@

package org.geysermc.geyser.level;

import lombok.Getter;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.util.HSVLike;
import org.checkerframework.checker.nullness.qual.NonNull;

public enum FireworkColor {
BLACK(1973019),
RED(11743532),
GREEN(3887386),
BROWN(5320730),
BLUE(2437522),
PURPLE(8073150),
CYAN(2651799),
LIGHT_GRAY(11250603),
GRAY(4408131),
PINK(14188952),
LIME(4312372),
YELLOW(14602026),
LIGHT_BLUE(6719955),
MAGENTA(12801229),
ORANGE(15435844),
WHITE(15790320);
BLACK(1973019, "Black"),
RED(11743532, "Red"),
GREEN(3887386, "Green"),
BROWN(5320730, "Brown"),
BLUE(2437522, "Blue"),
PURPLE(8073150, "Purple"),
CYAN(2651799, "Cyan"),
LIGHT_GRAY(11250603, "Silver"),
GRAY(4408131, "Gray"),
PINK(14188952, "Pink"),
LIME(4312372, "Lime"),
YELLOW(14602026, "Yellow"),
LIGHT_BLUE(6719955, "Light Blue"),
MAGENTA(12801229, "Magenta"),
ORANGE(15435844, "Orange"),
WHITE(15790320, "White");

private static final FireworkColor[] VALUES = values();

private final TextColor color;
@Getter
private final String bedrockName;

FireworkColor(int rgbValue) {
FireworkColor(int rgbValue, String bedrockName) {
this.color = TextColor.color(rgbValue);
this.bedrockName = bedrockName;
}

private static HSVLike toHSV(int rgbValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.geysermc.geyser.inventory.item.StoredItemMappings;
import org.geysermc.geyser.item.GeyserCustomMappingData;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.item.TooltipOptions;
import org.geysermc.geyser.item.type.BlockItem;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.level.block.property.Properties;
Expand All @@ -80,6 +81,8 @@
import org.geysermc.geyser.registry.type.ItemMappings;
import org.geysermc.geyser.registry.type.NonVanillaItemRegistration;
import org.geysermc.geyser.registry.type.PaletteItem;
import org.geysermc.geyser.translator.item.BedrockItemBuilder;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;

import java.io.InputStream;
import java.util.ArrayList;
Expand Down Expand Up @@ -179,6 +182,8 @@ public static void populate() {
CustomItemRegistryPopulator.populate(items, customItems, nonVanillaCustomItems);
}

ItemDefinition oldFireworkDefinition = null;

// We can reduce some operations as Java information is the same across all palette versions
boolean firstMappingsPass = true;

Expand Down Expand Up @@ -218,6 +223,10 @@ public static void populate() {
ItemDefinition definition = new SimpleItemDefinition(entry.getName().intern(), id, ItemVersion.from(entry.getVersion()), entry.isComponentBased(), components);
definitions.put(entry.getName(), definition);
registry.put(definition.getRuntimeId(), definition);

if (definition.getIdentifier().contains("firework_rocket")) {
oldFireworkDefinition = definition;
}
}

Object2ObjectMap<String, BlockDefinition> bedrockBlockIdOverrides = new Object2ObjectOpenHashMap<>();
Expand Down Expand Up @@ -578,14 +587,14 @@ public static void populate() {
if (customItemsAllowed) {
// Add furnace minecart
int furnaceMinecartId = nextFreeBedrockId++;
ItemDefinition definition = new SimpleItemDefinition("geysermc:furnace_minecart", furnaceMinecartId, ItemVersion.DATA_DRIVEN, true, registerFurnaceMinecart(furnaceMinecartId));
definitions.put("geysermc:furnace_minecart", definition);
registry.put(definition.getRuntimeId(), definition);
ItemDefinition furnaceMinecartDefinition = new SimpleItemDefinition("geysermc:furnace_minecart", furnaceMinecartId, ItemVersion.DATA_DRIVEN, true, registerFurnaceMinecart(furnaceMinecartId));
definitions.put("geysermc:furnace_minecart", furnaceMinecartDefinition);
registry.put(furnaceMinecartDefinition.getRuntimeId(), furnaceMinecartDefinition);

mappings.set(Items.FURNACE_MINECART.javaId(), ItemMapping.builder()
.javaItem(Items.FURNACE_MINECART)
.bedrockIdentifier("geysermc:furnace_minecart")
.bedrockDefinition(definition)
.bedrockDefinition(furnaceMinecartDefinition)
.bedrockData(0)
.bedrockBlockDefinition(null)
.customItemOptions(Collections.emptyList()) // TODO check for custom items with furnace minecart
Expand All @@ -594,10 +603,56 @@ public static void populate() {
creativeItems.add(new CreativeItemData(ItemData.builder()
.usingNetId(true)
.netId(creativeNetId.incrementAndGet())
.definition(definition)
.definition(furnaceMinecartDefinition)
.count(1)
.build(), creativeNetId.get(), 99)); // todo do not hardcode!

// Register a custom fireworks item to workaround client-sided boosting.
if (oldFireworkDefinition != null) {
int fireworkRocketId = nextFreeBedrockId++;
ItemDefinition fireworkRocketDefinition = new SimpleItemDefinition("geysermc:firework_rocket", fireworkRocketId, ItemVersion.DATA_DRIVEN, true, registerFireworkRocket(fireworkRocketId));
registry.put(fireworkRocketDefinition.getRuntimeId(), fireworkRocketDefinition);
definitions.put(oldFireworkDefinition.getIdentifier(), fireworkRocketDefinition);

ItemMapping mapping = ItemMapping.builder()
.javaItem(Items.FIREWORK_ROCKET)
.bedrockIdentifier("geysermc:firework_rocket")
.bedrockDefinition(fireworkRocketDefinition)
.bedrockData(0)
.bedrockBlockDefinition(null)
.customItemOptions(Collections.emptyList())
.build();

mappings.set(Items.FIREWORK_ROCKET.javaId(), mapping);

// We have to replace all the real firework rocket in the creative menu with our fake fireworks rocket.
for (int i = 0; i < creativeItems.size(); i++) {
CreativeItemData data = creativeItems.get(i);
if (data.getItem().getDefinition().getRuntimeId() != oldFireworkDefinition.getRuntimeId()) {
continue;
}

// Since the fireworks tag is now useless due to this is being a custom item, we have to translate it to lore ourselves.
NbtMap tag = null;
if (data.getItem().getTag() != null) {
final DataComponents components = new DataComponents(new HashMap<>());
Items.FIREWORK_ROCKET.translateNbtToJava(null, data.getItem().getTag(), components, mapping);
final BedrockItemBuilder builder = new BedrockItemBuilder();
Items.FIREWORK_ROCKET.translateComponentsToBedrock(null, components, TooltipOptions.ALL_SHOWN, builder);

tag = builder.build();
}

creativeItems.set(i, new CreativeItemData(ItemData.builder()
.usingNetId(true)
.netId(data.getItem().getNetId())
.definition(fireworkRocketDefinition)
.tag(tag)
.count(data.getItem().getCount())
.build(), data.getNetId(), data.getGroupId()));
}
}

// Register any completely custom items given to us
IntSet registeredJavaIds = new IntOpenHashSet(); // Used to check for duplicate item java ids
for (NonVanillaCustomItemData customItem : nonVanillaCustomItems) {
Expand Down Expand Up @@ -688,6 +743,33 @@ public static void populate() {
}
}

private static NbtMap registerFireworkRocket(int nextFreeBedrockId) {
NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", "geysermc:firework_rocket").putInt("id", nextFreeBedrockId);

NbtMapBuilder itemProperties = NbtMap.builder();

NbtMapBuilder componentBuilder = NbtMap.builder();
NbtMap iconMap = NbtMap.builder()
.putCompound("textures", NbtMap.builder()
.putString("default", "fireworks")
.build())
.build();
itemProperties.putCompound("minecraft:icon", iconMap);
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", "item.fireworks.name").build());

// We always want to allow offhand usage when we can - matches Java Edition
itemProperties.putBoolean("allow_off_hand", true);
itemProperties.putBoolean("hand_equipped", false);
itemProperties.putInt("max_stack_size", 64);
itemProperties.putString("creative_group", "itemGroup.name.firework");
itemProperties.putInt("creative_category", 4); // 4 - "Items"

componentBuilder.putCompound("item_properties", itemProperties.build());
builder.putCompound("components", componentBuilder.build());
return builder.build();
}

private static NbtMap registerFurnaceMinecart(int nextFreeBedrockId) {
NbtMapBuilder builder = NbtMap.builder();
builder.putString("name", "geysermc:furnace_minecart")
Expand Down