Add ability to set molang tags for custom items (#4041)

* Start on custom molang tags with custom items

* geyser_custom instead of geyser item tag

* Address reviews, add custom namespace ("geyser_custom") to tags

* use isBlank() instead of isEmpty()

* More efficient item tag setting
Co-authored-by: Konicai <71294714+konicai@users.noreply.github.com>

* tags instead of temp

* Merge in master, adapt to changes in the MappingsReader, delete unused ToolBreakSpeedsUtils class

* oops

* clean diff

* Change namespace from `geyser_custom` to just `geyser`

* Don't force a namespace at all; just like blocks don't

* Tags for items are now, as blocks, NonNull. Additionally, calling the .tags() builder multiple times will not add both sets of tags, but replace the existing tag set

* Remove @NotNull usage in favor of @NonNull

* Allow setting null for tags, but ensure that .tags() is always non-null

* Fix nullable annotation on tags method in the builder interface
This commit is contained in:
chris 2023-11-09 08:44:13 +01:00 committed by GitHub
parent aa899af908
commit f40ca2004e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 85 additions and 191 deletions

View file

@ -185,7 +185,7 @@ public interface CustomBlockComponents {
Builder placeAir(boolean placeAir);
Builder tags(Set<String> tags);
Builder tags(@Nullable Set<String> tags);
CustomBlockComponents build();
}

View file

@ -29,6 +29,8 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.GeyserApi;
import java.util.Set;
/**
* This is used to store data for a custom item.
*/
@ -89,6 +91,14 @@ public interface CustomItemData {
*/
@Nullable CustomRenderOffsets renderOffsets();
/**
* Gets the item's set of tags that can be used in Molang.
* Equivalent to "tag:some_tag"
*
* @return the item's tags, if they exist
*/
@NonNull Set<String> tags();
static CustomItemData.Builder builder() {
return GeyserApi.api().provider(CustomItemData.Builder.class);
}
@ -113,6 +123,8 @@ public interface CustomItemData {
Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets);
Builder tags(@Nullable Set<String> tags);
CustomItemData build();
}
}

View file

@ -239,6 +239,9 @@ public interface NonVanillaCustomItemData extends CustomItemData {
@Override
Builder renderOffsets(@Nullable CustomRenderOffsets renderOffsets);
@Override
Builder tags(@Nullable Set<String> tags);
NonVanillaCustomItemData build();
}
}

View file

@ -28,10 +28,14 @@ package org.geysermc.geyser.item;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.item.custom.CustomItemData;
import org.geysermc.geyser.api.item.custom.CustomItemOptions;
import org.geysermc.geyser.api.item.custom.CustomRenderOffsets;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
@EqualsAndHashCode
@ToString
@ -44,6 +48,7 @@ public class GeyserCustomItemData implements CustomItemData {
private final boolean displayHandheld;
private final int textureSize;
private final CustomRenderOffsets renderOffsets;
private final Set<String> tags;
public GeyserCustomItemData(String name,
CustomItemOptions customItemOptions,
@ -52,7 +57,8 @@ public class GeyserCustomItemData implements CustomItemData {
boolean allowOffhand,
boolean displayHandheld,
int textureSize,
CustomRenderOffsets renderOffsets) {
CustomRenderOffsets renderOffsets,
Set<String> tags) {
this.name = name;
this.customItemOptions = customItemOptions;
this.displayName = displayName;
@ -61,10 +67,11 @@ public class GeyserCustomItemData implements CustomItemData {
this.displayHandheld = displayHandheld;
this.textureSize = textureSize;
this.renderOffsets = renderOffsets;
this.tags = tags;
}
@Override
public @NotNull String name() {
public @NonNull String name() {
return name;
}
@ -74,12 +81,12 @@ public class GeyserCustomItemData implements CustomItemData {
}
@Override
public @NotNull String displayName() {
public @NonNull String displayName() {
return displayName;
}
@Override
public @NotNull String icon() {
public @NonNull String icon() {
return icon;
}
@ -103,6 +110,11 @@ public class GeyserCustomItemData implements CustomItemData {
return renderOffsets;
}
@Override
public @NonNull Set<String> tags() {
return tags;
}
public static class CustomItemDataBuilder implements Builder {
protected String name = null;
protected CustomItemOptions customItemOptions = null;
@ -113,6 +125,7 @@ public class GeyserCustomItemData implements CustomItemData {
protected boolean displayHandheld = false;
protected int textureSize = 16;
protected CustomRenderOffsets renderOffsets = null;
protected Set<String> tags = new HashSet<>();
@Override
public Builder name(@NonNull String name) {
@ -162,6 +175,12 @@ public class GeyserCustomItemData implements CustomItemData {
return this;
}
@Override
public Builder tags(@Nullable Set<String> tags) {
this.tags = Objects.requireNonNullElseGet(tags, Set::of);
return this;
}
@Override
public CustomItemData build() {
if (this.name == null || this.customItemOptions == null) {
@ -174,7 +193,7 @@ public class GeyserCustomItemData implements CustomItemData {
if (this.icon == null) {
this.icon = this.name;
}
return new GeyserCustomItemData(this.name, this.customItemOptions, this.displayName, this.icon, this.allowOffhand, this.displayHandheld, this.textureSize, this.renderOffsets);
return new GeyserCustomItemData(this.name, this.customItemOptions, this.displayName, this.icon, this.allowOffhand, this.displayHandheld, this.textureSize, this.renderOffsets, this.tags);
}
}
}

View file

@ -61,7 +61,7 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
public GeyserNonVanillaCustomItemData(NonVanillaCustomItemDataBuilder builder) {
super(builder.name, builder.customItemOptions, builder.displayName, builder.icon, builder.allowOffhand,
builder.displayHandheld, builder.textureSize, builder.renderOffsets);
builder.displayHandheld, builder.textureSize, builder.renderOffsets, builder.tags);
this.identifier = builder.identifier;
this.javaId = builder.javaId;
@ -237,6 +237,11 @@ public final class GeyserNonVanillaCustomItemData extends GeyserCustomItemData i
return (NonVanillaCustomItemData.Builder) super.renderOffsets(renderOffsets);
}
@Override
public NonVanillaCustomItemData.Builder tags(@Nullable Set<String> tags) {
return (NonVanillaCustomItemData.Builder) super.tags(tags);
}
@Override
public NonVanillaCustomItemData.Builder identifier(@NonNull String identifier) {
this.identifier = identifier;

View file

@ -1,174 +0,0 @@
/*
* Copyright (c) 2019-2022 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
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.item.components;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.nbt.NbtType;
import java.util.ArrayList;
import java.util.List;
public class ToolBreakSpeedsUtils {
public static int toolTierToSpeed(String toolTier) {
ToolTier tier = ToolTier.getByName(toolTier);
if (tier != null) {
return tier.getSpeed();
}
return 0;
}
private static NbtMap createTagBreakSpeed(int speed, String... tags) {
StringBuilder builder = new StringBuilder("query.any_tag('");
builder.append(tags[0]);
for (int i = 1; i < tags.length; i++) {
builder.append("', '").append(tags[i]);
}
builder.append("')");
return NbtMap.builder()
.putCompound("block", NbtMap.builder()
.putString("tags", builder.toString())
.build())
.putCompound("on_dig", NbtMap.builder()
.putCompound("condition", NbtMap.builder()
.putString("expression", "")
.putInt("version", -1)
.build())
.putString("event", "tool_durability")
.putString("target", "self")
.build())
.putInt("speed", speed)
.build();
}
private static NbtMap createBreakSpeed(int speed, String block) {
return NbtMap.builder()
.putCompound("block", NbtMap.builder()
.putString("name", block).build())
.putCompound("on_dig", NbtMap.builder()
.putCompound("condition", NbtMap.builder()
.putString("expression", "")
.putInt("version", -1)
.build())
.putString("event", "tool_durability")
.putString("target", "self")
.build())
.putInt("speed", speed)
.build();
}
private static NbtMap createDigger(List<NbtMap> speeds) {
return NbtMap.builder()
.putList("destroy_speeds", NbtType.COMPOUND, speeds)
.putCompound("on_dig", NbtMap.builder()
.putCompound("condition", NbtMap.builder()
.putString("expression", "")
.putInt("version", -1)
.build())
.putString("event", "tool_durability")
.putString("target", "self")
.build())
.putBoolean("use_efficiency", true)
.build();
}
public static NbtMap getAxeDigger(int speed) {
List<NbtMap> speeds = new ArrayList<>();
speeds.add(createTagBreakSpeed(speed, "wood", "pumpkin", "plant"));
return createDigger(speeds);
}
public static NbtMap getPickaxeDigger(int speed, String toolTier) {
List<NbtMap> speeds = new ArrayList<>();
if (toolTier.equals(ToolTier.DIAMOND.toString()) || toolTier.equals(ToolTier.NETHERITE.toString())) {
speeds.add(createTagBreakSpeed(speed, "iron_pick_diggable", "diamond_pick_diggable"));
} else {
speeds.add(createTagBreakSpeed(speed, "iron_pick_diggable"));
}
speeds.add(createTagBreakSpeed(speed, "stone", "metal", "rail", "mob_spawner"));
return createDigger(speeds);
}
public static NbtMap getShovelDigger(int speed) {
List<NbtMap> speeds = new ArrayList<>();
speeds.add(createTagBreakSpeed(speed, "dirt", "sand", "gravel", "grass", "snow"));
return createDigger(speeds);
}
public static NbtMap getSwordDigger(int speed) {
List<NbtMap> speeds = new ArrayList<>();
speeds.add(createBreakSpeed(speed, "minecraft:web"));
speeds.add(createBreakSpeed(speed, "minecraft:bamboo"));
return createDigger(speeds);
}
public static NbtMap getHoeDigger(int speed) {
List<NbtMap> speeds = new ArrayList<>();
speeds.add(createBreakSpeed(speed, "minecraft:leaves"));
speeds.add(createBreakSpeed(speed, "minecraft:leaves2"));
speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves"));
speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves_flowered"));
speeds.add(createBreakSpeed(speed, "minecraft:sculk"));
speeds.add(createBreakSpeed(speed, "minecraft:sculk_catalyst"));
speeds.add(createBreakSpeed(speed, "minecraft:sculk_sensor"));
speeds.add(createBreakSpeed(speed, "minecraft:sculk_shrieker"));
speeds.add(createBreakSpeed(speed, "minecraft:sculk_vein"));
speeds.add(createBreakSpeed(speed, "minecraft:nether_wart_block"));
speeds.add(createBreakSpeed(speed, "minecraft:warped_wart_block"));
speeds.add(createBreakSpeed(speed, "minecraft:hay_block"));
speeds.add(createBreakSpeed(speed, "minecraft:moss_block"));
speeds.add(createBreakSpeed(speed, "minecraft:shroomlight"));
speeds.add(createBreakSpeed(speed, "minecraft:sponge"));
speeds.add(createBreakSpeed(speed, "minecraft:target"));
return createDigger(speeds);
}
public static NbtMap getShearsDigger(int speed) {
List<NbtMap> speeds = new ArrayList<>();
speeds.add(createBreakSpeed(speed, "minecraft:web"));
speeds.add(createBreakSpeed(speed, "minecraft:leaves"));
speeds.add(createBreakSpeed(speed, "minecraft:leaves2"));
speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves"));
speeds.add(createBreakSpeed(speed, "minecraft:azalea_leaves_flowered"));
speeds.add(createBreakSpeed(speed, "minecraft:wool"));
speeds.add(createBreakSpeed(speed, "minecraft:glow_lichen"));
speeds.add(createBreakSpeed(speed, "minecraft:vine"));
return createDigger(speeds);
}
}

View file

@ -31,18 +31,19 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectMaps;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import lombok.Value;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.api.block.custom.component.BoxComponent;
import org.geysermc.geyser.api.block.custom.component.CustomBlockComponents;
import org.geysermc.geyser.api.block.custom.component.GeometryComponent;
import org.geysermc.geyser.api.block.custom.component.MaterialInstance;
import org.geysermc.geyser.api.block.custom.component.PlacementConditions;
import org.geysermc.geyser.api.block.custom.component.TransformationComponent;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
@Value
public class GeyserCustomBlockComponents implements CustomBlockComponents {
@ -152,7 +153,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
}
@Override
public @NotNull Set<String> tags() {
public @NonNull Set<String> tags() {
return tags;
}
@ -170,7 +171,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
protected TransformationComponent transformation;
protected boolean unitCube = false;
protected boolean placeAir = false;
protected final Set<String> tags = new HashSet<>();
protected Set<String> tags = new HashSet<>();
private void validateBox(BoxComponent box) {
if (box == null) {
@ -217,7 +218,7 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
}
@Override
public Builder materialInstance(@NotNull String name, @NotNull MaterialInstance materialInstance) {
public Builder materialInstance(@NonNull String name, @NonNull MaterialInstance materialInstance) {
this.materialInstances.put(name, materialInstance);
return this;
}
@ -292,8 +293,8 @@ public class GeyserCustomBlockComponents implements CustomBlockComponents {
}
@Override
public Builder tags(Set<String> tags) {
this.tags.addAll(tags);
public Builder tags(@Nullable Set<String> tags) {
this.tags = Objects.requireNonNullElseGet(tags, Set::of);
return this;
}

View file

@ -198,6 +198,12 @@ public class MappingsReader_v1 extends MappingsReader {
customItemData.renderOffsets(fromJsonNode(tmpNode));
}
if (node.get("tags") instanceof ArrayNode tags) {
Set<String> tagsSet = new ObjectOpenHashSet<>();
tags.forEach(tag -> tagsSet.add(tag.asText()));
customItemData.tags(tagsSet);
}
return customItemData.build();
}

View file

@ -270,6 +270,17 @@ public class CustomItemRegistryPopulator {
.build());
componentBuilder.putCompound("minecraft:display_name", NbtMap.builder().putString("value", customItemData.displayName()).build());
// Add a Geyser tag to the item, allowing Molang queries
addItemTag(componentBuilder, "geyser:is_custom");
// Add other defined tags to the item
Set<String> tags = customItemData.tags();
for (String tag : tags) {
if (tag != null && !tag.isBlank()) {
addItemTag(componentBuilder, tag);
}
}
itemProperties.putBoolean("allow_off_hand", customItemData.allowOffhand());
itemProperties.putBoolean("hand_equipped", displayHandheld);
itemProperties.putInt("max_stack_size", stackSize);
@ -313,7 +324,7 @@ public class CustomItemRegistryPopulator {
.build()
));
componentBuilder.putCompound("minecraft:digger",
componentBuilder.putCompound("minecraft:digger",
NbtMap.builder()
.putList("destroy_speeds", NbtType.COMPOUND, speed)
.putCompound("on_dig", NbtMap.builder()
@ -506,8 +517,19 @@ public class CustomItemRegistryPopulator {
return List.of(xyz.x(), xyz.y(), xyz.z());
}
private static void setItemTag(NbtMapBuilder builder, String tag) {
builder.putList("item_tags", NbtType.STRING, List.of("minecraft:is_" + tag));
@SuppressWarnings("unchecked")
private static void addItemTag(NbtMapBuilder builder, String tag) {
List<String> tagList = (List<String>) builder.get("item_tags");
if (tagList == null) {
builder.putList("item_tags", NbtType.STRING, tag);
} else {
// NbtList is immutable
if (!tagList.contains(tag)) {
tagList = new ArrayList<>(tagList);
tagList.add(tag);
builder.putList("item_tags", NbtType.STRING, tagList);
}
}
}
private static NbtMap xyzToScaleList(float x, float y, float z) {