From 92fc9f79186114c4c08b8c6eb68f38cd96ce3f40 Mon Sep 17 00:00:00 2001 From: Aya <31237389+tal5@users.noreply.github.com> Date: Wed, 5 Jul 2023 07:32:39 +0200 Subject: [PATCH] Interaction entity support (#2489) * Interaction entity support * use duration instead of time for interactions --- .../denizen/objects/EntityTag.java | 89 ++++++++++++----- .../properties/entity/EntityAware.java | 98 +++++++------------ .../properties/entity/EntityHeight.java | 24 +++-- .../entity/EntityInterpolationStart.java | 4 +- .../properties/entity/EntityWidth.java | 24 +++-- .../utilities/MultiVersionHelper1_19.java | 29 +++++- 6 files changed, 162 insertions(+), 106 deletions(-) diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java b/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java index 9c1729d1be..271697bead 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/EntityTag.java @@ -1,6 +1,7 @@ package com.denizenscript.denizen.objects; import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.nms.abstracts.ProfileEditor; import com.denizenscript.denizen.nms.interfaces.EntityAnimation; import com.denizenscript.denizen.nms.interfaces.FakePlayer; @@ -13,6 +14,7 @@ import com.denizenscript.denizen.scripts.containers.core.EntityScriptContainer; import com.denizenscript.denizen.scripts.containers.core.EntityScriptHelper; import com.denizenscript.denizen.utilities.BukkitImplDeprecations; +import com.denizenscript.denizen.utilities.MultiVersionHelper1_19; import com.denizenscript.denizen.utilities.Utilities; import com.denizenscript.denizen.utilities.VanillaTagHelper; import com.denizenscript.denizen.utilities.depends.Depends; @@ -2638,8 +2640,8 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Returns an element indicating the minecraft key for the loot-table for the entity (if any). // --> registerSpawnedOnlyTag(ElementTag.class, "loot_table_id", (attribute, object) -> { - if (object.getBukkitEntity() instanceof Lootable) { - LootTable table = ((Lootable) object.getBukkitEntity()).getLootTable(); + if (object.getBukkitEntity() instanceof Lootable lootable) { + LootTable table = lootable.getLootTable(); if (table != null) { return new ElementTag(table.getKey().toString()); } @@ -2654,11 +2656,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Returns the current state of the fish hook, as any of: UNHOOKED, HOOKED_ENTITY, BOBBING (unhooked means the fishing hook is in the air or on ground). // --> registerSpawnedOnlyTag(ElementTag.class, "fish_hook_state", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof FishHook)) { + if (!(object.getBukkitEntity() instanceof FishHook fishHook)) { attribute.echoError("EntityTag.fish_hook_state is only valid for fish hooks."); return null; } - return new ElementTag(((FishHook) object.getBukkitEntity()).getState()); + return new ElementTag(fishHook.getState()); }); // <--[tag] @@ -2669,11 +2671,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Returns the remaining time before this fish hook will lure a fish. // --> registerSpawnedOnlyTag(DurationTag.class, "fish_hook_lure_time", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof FishHook)) { + if (!(object.getBukkitEntity() instanceof FishHook fishHook)) { attribute.echoError("EntityTag.fish_hook_lure_time is only valid for fish hooks."); return null; } - return new DurationTag((long) NMSHandler.fishingHelper.getLureTime((FishHook) object.getBukkitEntity())); + return new DurationTag((long) NMSHandler.fishingHelper.getLureTime(fishHook)); }); // <--[tag] @@ -2684,11 +2686,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Returns the minimum possible time before this fish hook can lure a fish. // --> registerSpawnedOnlyTag(DurationTag.class, "fish_hook_min_lure_time", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof FishHook)) { + if (!(object.getBukkitEntity() instanceof FishHook fishHook)) { attribute.echoError("EntityTag.fish_hook_min_lure_time is only valid for fish hooks."); return null; } - return new DurationTag((long) ((FishHook) object.getBukkitEntity()).getMinWaitTime()); + return new DurationTag((long) fishHook.getMinWaitTime()); }); // <--[tag] @@ -2699,11 +2701,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Returns the maximum possible time before this fish hook will lure a fish. // --> registerSpawnedOnlyTag(DurationTag.class, "fish_hook_max_lure_time", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof FishHook)) { + if (!(object.getBukkitEntity() instanceof FishHook fishHook)) { attribute.echoError("EntityTag.fish_hook_max_lure_time is only valid for fish hooks."); return null; } - return new DurationTag((long) ((FishHook) object.getBukkitEntity()).getMaxWaitTime()); + return new DurationTag((long) fishHook.getMaxWaitTime()); }); // <--[tag] @@ -2714,11 +2716,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Returns the entity this fish hook is attached to. // --> registerSpawnedOnlyTag(EntityTag.class, "fish_hook_hooked_entity", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof FishHook)) { + if (!(object.getBukkitEntity() instanceof FishHook fishHook)) { attribute.echoError("EntityTag.fish_hook_hooked_entity is only valid for fish hooks."); return null; } - Entity entity = ((FishHook) object.getBukkitEntity()).getHookedEntity(); + Entity entity = fishHook.getHookedEntity(); return entity != null ? new EntityTag(entity) : null; }); @@ -2731,11 +2733,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Every level of lure enchantment reduces lure time by 5 seconds. // --> registerSpawnedOnlyTag(ElementTag.class, "fish_hook_apply_lure", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof FishHook)) { + if (!(object.getBukkitEntity() instanceof FishHook fishHook)) { attribute.echoError("EntityTag.fish_hook_apply_lure is only valid for fish hooks."); return null; } - return new ElementTag(((FishHook) object.getBukkitEntity()).getApplyLure()); + return new ElementTag(fishHook.getApplyLure()); }); // <--[tag] @@ -2746,11 +2748,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // See <@link url https://minecraft.fandom.com/wiki/Fishing> for more info. // --> registerSpawnedOnlyTag(ElementTag.class, "fish_hook_in_open_water", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof FishHook)) { + if (!(object.getBukkitEntity() instanceof FishHook fishHook)) { attribute.echoError("EntityTag.fish_hook_in_open_water is only valid for fish hooks."); return null; } - return new ElementTag(((FishHook) object.getBukkitEntity()).isInOpenWater()); + return new ElementTag(fishHook.isInOpenWater()); }); // <--[tag] @@ -2823,11 +2825,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // Returns the amount of time that passed since the start of the attack cooldown. // --> registerSpawnedOnlyTag(DurationTag.class, "attack_cooldown_duration", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof Player)) { + if (!(object.getBukkitEntity() instanceof Player player)) { attribute.echoError("Only player-type entities can have attack_cooldowns!"); return null; } - return new DurationTag((long) NMSHandler.playerHelper.ticksPassedDuringCooldown((Player) object.getLivingEntity())); + return new DurationTag((long) NMSHandler.playerHelper.ticksPassedDuringCooldown(player)); }); // <--[tag] @@ -2841,11 +2843,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // cooldown progress. // --> registerSpawnedOnlyTag(DurationTag.class, "attack_cooldown_max_duration", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof Player)) { + if (!(object.getBukkitEntity() instanceof Player player)) { attribute.echoError("Only player-type entities can have attack_cooldowns!"); return null; } - return new DurationTag((long) NMSHandler.playerHelper.getMaxAttackCooldownTicks((Player) object.getLivingEntity())); + return new DurationTag((long) NMSHandler.playerHelper.getMaxAttackCooldownTicks(player)); }); // <--[tag] @@ -2858,11 +2860,11 @@ else if (object.getBukkitEntity() instanceof Hanging) { // NOTE: This may not match exactly with the clientside attack cooldown indicator. // --> registerSpawnedOnlyTag(ElementTag.class, "attack_cooldown_percent", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof Player)) { + if (!(object.getBukkitEntity() instanceof Player player)) { attribute.echoError("Only player-type entities can have attack_cooldowns!"); return null; } - return new ElementTag(((Player) object.getLivingEntity()).getAttackCooldown() * 100); + return new ElementTag(player.getAttackCooldown() * 100); }); // <--[tag] @@ -2873,13 +2875,52 @@ else if (object.getBukkitEntity() instanceof Hanging) { // A player's hand is raised when they are blocking with a shield, aiming a crossbow, looking through a spyglass, etc. // --> registerSpawnedOnlyTag(ElementTag.class, "is_hand_raised", (attribute, object) -> { - if (!(object.getBukkitEntity() instanceof HumanEntity)) { + if (!(object.getBukkitEntity() instanceof HumanEntity humanEntity)) { attribute.echoError("Only player-type entities can have is_hand_raised!"); return null; } - return new ElementTag(((HumanEntity) object.getLivingEntity()).isHandRaised()); + return new ElementTag(humanEntity.isHandRaised()); }); + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19)) { + + // <--[tag] + // @attribute + // @returns MapTag + // @description + // Returns an interaction entity's last attack interaction, if any. + // The returned map contains: + // - 'player' (PlayerTag): the player who interacted + // - 'duration' (DurationTag): the amount of time since the interaction. Note that this value is calculated, and may become inaccurate if the interaction entity changes worlds or the server lags. + // - 'raw_game_time' (ElementTag(Number)): the raw game time the interaction occurred at, used to calculate the time above. + // --> + registerSpawnedOnlyTag(MapTag.class, "last_attack", (attribute, object) -> { + if (!(object.getBukkitEntity() instanceof Interaction interaction)) { + attribute.echoError("'EntityTag.last_attack' is only valid for interaction entities."); + return null; + } + return MultiVersionHelper1_19.interactionToMap(interaction.getLastAttack(), interaction.getWorld()); + }); + + // <--[tag] + // @attribute + // @returns MapTag + // @description + // Returns an interaction entity's last right click interaction, if any. + // The returned map contains: + // - 'player' (PlayerTag): the player who interacted + // - 'duration' (DurationTag): the amount of time since the interaction. Note that this value is calculated, and may become inaccurate if the interaction entity changes worlds or the server lags. + // - 'raw_game_time' (ElementTag(Number)): the raw game time the interaction occurred at, used to calculate the time above. + // --> + registerSpawnedOnlyTag(MapTag.class, "last_interaction", (attribute, object) -> { + if (!(object.getBukkitEntity() instanceof Interaction interaction)) { + attribute.echoError("'EntityTag.last_interaction' is only valid for interaction entities."); + return null; + } + return MultiVersionHelper1_19.interactionToMap(interaction.getLastInteraction(), interaction.getWorld()); + }); + } + // <--[mechanism] // @object EntityTag // @name alter_uuid diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAware.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAware.java index a9c64cd126..2571d6d801 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAware.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityAware.java @@ -1,42 +1,54 @@ package com.denizenscript.denizen.objects.properties.entity; +import com.denizenscript.denizen.nms.NMSHandler; +import com.denizenscript.denizen.nms.NMSVersion; import com.denizenscript.denizen.objects.EntityTag; -import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.Mechanism; -import com.denizenscript.denizencore.objects.ObjectTag; -import com.denizenscript.denizencore.objects.properties.Property; -import com.denizenscript.denizencore.objects.properties.PropertyParser; +import com.denizenscript.denizencore.objects.core.ElementTag; +import org.bukkit.entity.Interaction; import org.bukkit.entity.Mob; -public class EntityAware implements Property { +public class EntityAware extends EntityProperty { + + // <--[property] + // @object EntityTag + // @name is_aware + // @input ElementTag(Boolean) + // @description + // For mobs (<@link tag EntityTag.is_mob>), this is whether the entity is aware of its surroundings. + // Unaware entities will not perform any actions on their own, such as pathfinding or attacking. + // Similar to <@link property EntityTag.has_ai>, except allows the entity to be moved by gravity, being pushed or attacked, etc. + // For interaction entities, this is whether interacting with them should trigger a response (arm swings, sounds, etc.). + // --> - public static boolean describes(ObjectTag entity) { - return entity instanceof EntityTag - && ((EntityTag) entity).isMobType(); + public static boolean describes(EntityTag entity) { + return entity.getBukkitEntity() instanceof Mob || (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && entity.getBukkitEntity() instanceof Interaction); } - public static EntityAware getFrom(ObjectTag entity) { - if (!describes(entity)) { - return null; - } - else { - return new EntityAware((EntityTag) entity); + @Override + public ElementTag getPropertyValue() { + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof Interaction interaction) { + return new ElementTag(interaction.isResponsive()); } + return new ElementTag(as(Mob.class).isAware()); } - public static final String[] handledMechs = new String[] { - "is_aware" - }; - - public EntityAware(EntityTag entity) { - this.entity = entity; + @Override + public boolean isDefaultValue(ElementTag value) { + // Default value is true for mobs, false for interaction entities + return value.asBoolean() == getEntity() instanceof Mob; } - EntityTag entity; - @Override - public String getPropertyString() { - return String.valueOf(getMob().isAware()); + public void setPropertyValue(ElementTag value, Mechanism mechanism) { + if (!mechanism.requireBoolean()) { + return; + } + if (NMSHandler.getVersion().isAtLeast(NMSVersion.v1_19) && getEntity() instanceof Interaction interaction) { + interaction.setResponsive(value.asBoolean()); + return; + } + as(Mob.class).setAware(value.asBoolean()); } @Override @@ -44,43 +56,7 @@ public String getPropertyId() { return "is_aware"; } - public Mob getMob() { - return (Mob) entity.getBukkitEntity(); - } - public static void register() { - - // <--[tag] - // @attribute - // @returns ElementTag(Boolean) - // @mechanism EntityTag.is_aware - // @group attributes - // @description - // Returns whether the entity is aware of its surroundings. - // Unaware entities will not perform any actions on their own, such as pathfinding or attacking. - // Similar to <@link tag EntityTag.has_ai>, except allows the entity to be moved by gravity, being pushed or attacked, etc. - // --> - PropertyParser.registerTag(EntityAware.class, ElementTag.class, "is_aware", (attribute, entity) -> { - return new ElementTag(entity.getMob().isAware()); - }); - } - - @Override - public void adjust(Mechanism mechanism) { - - // <--[mechanism] - // @object EntityTag - // @name is_aware - // @input ElementTag(Boolean) - // @description - // Sets whether the entity is aware of its surroundings. - // Unaware entities will not perform any actions on their own, such as pathfinding or attacking. - // Similar to <@link mechanism EntityTag.has_ai>, except allows the entity to be moved by gravity, being pushed or attacked, etc. - // @tags - // - // --> - if (mechanism.matches("is_aware") && mechanism.requireBoolean()) { - getMob().setAware(mechanism.getValue().asBoolean()); - } + autoRegister("is_aware", EntityAware.class, ElementTag.class, false); } } diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHeight.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHeight.java index b42c0128b0..ca8ec9cfcd 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHeight.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityHeight.java @@ -4,6 +4,7 @@ import com.denizenscript.denizencore.objects.Mechanism; import com.denizenscript.denizencore.objects.core.ElementTag; import org.bukkit.entity.Display; +import org.bukkit.entity.Interaction; public class EntityHeight extends EntityProperty { @@ -12,29 +13,38 @@ public class EntityHeight extends EntityProperty { // @name height // @input ElementTag(Decimal) // @description - // The height of a display entity's culling box. The box will span from the entity's y to the entity's y + the height. - // The default value is 0, which disables culling entirely. + // For a display entity, this is the height of it's culling box. The box will span from the entity's y to the entity's y + the height. + // The default value for these is 0, which disables culling entirely. + // For an interaction entity, this is the height of it's bounding box (the area that can be interacted with). // --> public static boolean describes(EntityTag entity) { - return entity.getBukkitEntity() instanceof Display; + return entity.getBukkitEntity() instanceof Display || entity.getBukkitEntity() instanceof Interaction; } @Override public ElementTag getPropertyValue() { - return new ElementTag(as(Display.class).getDisplayHeight()); + if (getEntity() instanceof Display display) { + return new ElementTag(display.getDisplayHeight()); + } + return new ElementTag(as(Interaction.class).getInteractionHeight()); } @Override public boolean isDefaultValue(ElementTag value) { - return value.asFloat() == 0f; + return value.asFloat() == (getEntity() instanceof Display ? 0f : 1f); } @Override public void setPropertyValue(ElementTag value, Mechanism mechanism) { - if (mechanism.requireFloat()) { - as(Display.class).setDisplayHeight(value.asFloat()); + if (!mechanism.requireFloat()) { + return; + } + if (getEntity() instanceof Display display) { + display.setDisplayHeight(value.asFloat()); + return; } + as(Interaction.class).setInteractionHeight(value.asFloat()); } @Override diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInterpolationStart.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInterpolationStart.java index d9e68c2d25..fe066b5454 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInterpolationStart.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityInterpolationStart.java @@ -12,8 +12,8 @@ public class EntityInterpolationStart extends EntityProperty { // @group Minecraft Logic // @description // Display entities can interpolate between different properties, providing a smooth transition. - // Interpolation can be started (immediately or with a delay) using <@link mechanism EntityTag.interpolation_start>, and it's duration can be set using <@link mechanism EntityTag.interpolation_duration>. - // The following properties can be interpolated: <@link mechanism EntityTag.scale>, <@link mechanism EntityTag.translation>, <@link mechanism EntityTag.shadow_radius>, <@link mechanism EntityTag.shadow_strength>, <@link mechanism EntityTag.opacity>. + // Interpolation can be started (immediately or with a delay) using <@link property EntityTag.interpolation_start>, and it's duration can be set using <@link property EntityTag.interpolation_duration>. + // The following properties can be interpolated: <@link property EntityTag.scale>, <@link property EntityTag.translation>, <@link property EntityTag.shadow_radius>, <@link property EntityTag.shadow_strength>, <@link mechanism EntityTag.opacity>, <@link property EntityTag.left_rotation>, <@link property EntityTag.right_rotation>. // --> // <--[property] diff --git a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityWidth.java b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityWidth.java index c27be4225e..c61151be41 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityWidth.java +++ b/plugin/src/main/java/com/denizenscript/denizen/objects/properties/entity/EntityWidth.java @@ -4,6 +4,7 @@ import com.denizenscript.denizencore.objects.Mechanism; import com.denizenscript.denizencore.objects.core.ElementTag; import org.bukkit.entity.Display; +import org.bukkit.entity.Interaction; public class EntityWidth extends EntityProperty { @@ -12,29 +13,38 @@ public class EntityWidth extends EntityProperty { // @name width // @input ElementTag(Decimal) // @description - // The width of a display entity's culling box. The box will span half the width in every direction from the entity's position. - // The default value is 0, which disables culling entirely. + // For a display entity, this is the width of it's culling box. The box will span half the width in every direction from the entity's position. + // The default value for these is 0, which disables culling entirely. + // For an interaction entity, this is the width of it's bounding box (the area that can be interacted with). // --> public static boolean describes(EntityTag entity) { - return entity.getBukkitEntity() instanceof Display; + return entity.getBukkitEntity() instanceof Display || entity.getBukkitEntity() instanceof Interaction; } @Override public ElementTag getPropertyValue() { - return new ElementTag(as(Display.class).getDisplayWidth()); + if (getEntity() instanceof Display display) { + return new ElementTag(display.getDisplayWidth()); + } + return new ElementTag(as(Interaction.class).getInteractionWidth()); } @Override public boolean isDefaultValue(ElementTag value) { - return value.asFloat() == 0f; + return value.asFloat() == (getEntity() instanceof Display ? 0f : 1f); } @Override public void setPropertyValue(ElementTag value, Mechanism mechanism) { - if (mechanism.requireFloat()) { - as(Display.class).setDisplayWidth(value.asFloat()); + if (!mechanism.requireFloat()) { + return; + } + if (getEntity() instanceof Display display) { + display.setDisplayWidth(value.asFloat()); + return; } + as(Interaction.class).setInteractionWidth(value.asFloat()); } @Override diff --git a/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java b/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java index d297517006..c97f8876b7 100644 --- a/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java +++ b/plugin/src/main/java/com/denizenscript/denizen/utilities/MultiVersionHelper1_19.java @@ -1,9 +1,17 @@ package com.denizenscript.denizen.utilities; +import com.denizenscript.denizen.objects.PlayerTag; import com.denizenscript.denizen.objects.properties.entity.EntityColor; import com.denizenscript.denizencore.objects.Mechanism; +import com.denizenscript.denizencore.objects.core.DurationTag; +import com.denizenscript.denizencore.objects.core.ElementTag; import com.denizenscript.denizencore.objects.core.ListTag; -import org.bukkit.entity.*; +import com.denizenscript.denizencore.objects.core.MapTag; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Frog; +import org.bukkit.entity.Interaction; public class MultiVersionHelper1_19 { @@ -12,8 +20,8 @@ public static boolean colorIsApplicable(EntityType type) { } public static String getColor(Entity entity) { - if (entity instanceof Frog) { - return ((Frog) entity).getVariant().name(); + if (entity instanceof Frog frog) { + return frog.getVariant().name(); } return null; } @@ -26,8 +34,19 @@ public static ListTag getAllowedColors(EntityType type) { } public static void setColor(Entity entity, Mechanism mech) { - if (entity instanceof Frog && mech.requireEnum(Frog.Variant.class)) { - ((Frog) entity).setVariant(Frog.Variant.valueOf(mech.getValue().asString().toUpperCase())); + if (entity instanceof Frog frog && mech.requireEnum(Frog.Variant.class)) { + frog.setVariant(mech.getValue().asEnum(Frog.Variant.class)); } } + + public static MapTag interactionToMap(Interaction.PreviousInteraction interaction, World world) { + if (interaction == null) { + return null; + } + MapTag result = new MapTag(); + result.putObject("player", new PlayerTag(interaction.getPlayer())); + result.putObject("duration", new DurationTag((world.getGameTime() - interaction.getTimestamp()) / 20d)); + result.putObject("raw_game_time", new ElementTag(interaction.getTimestamp())); + return result; + } }