From f3c9a54e9fef313985f9b9526a7f338ca7ba6399 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 17:58:19 -0800 Subject: [PATCH 01/31] Add particles --- .../elements/effects/EffectHandler.java | 2 + .../elements/effects/world/EffParticle.java | 119 +++++++++++ .../plugin/elements/sections/SecParticle.java | 190 ++++++++++++++++++ .../elements/sections/SectionHandler.java | 1 + .../elements/types/TypesAssetStore.java | 3 + 5 files changed, 315 insertions(+) create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java index 6ae285a..b2fadd2 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java @@ -29,6 +29,7 @@ import com.github.skriptdev.skript.plugin.elements.effects.world.EffChunkRegenerate; import com.github.skriptdev.skript.plugin.elements.effects.world.EffExplosion; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffInteraction; +import com.github.skriptdev.skript.plugin.elements.effects.world.EffParticle; public class EffectHandler { @@ -72,6 +73,7 @@ public static void register(SkriptRegistration registration) { EffChunkLoadAsync.register(registration); EffChunkRegenerate.register(registration); EffExplosion.register(registration); + EffParticle.register(registration); } } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java new file mode 100644 index 0000000..98499f5 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java @@ -0,0 +1,119 @@ +package com.github.skriptdev.skript.plugin.elements.effects.world; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Location; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.github.syst3ms.skriptparser.lang.Effect; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import it.unimi.dsi.fastutil.objects.ObjectList; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class EffParticle extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffParticle.class, "(draw|spawn) particle %particle% at %location%", + "(draw|spawn) particle %particle% at %location% (to|for) %players/playerrefs%") + .name("Particle Spawn") + .description("Draw a particle at a location.", + "If no receivers are specified, it will default to players in a radius of 75 of the location.", + "If you would like more options, use the particle spawn section.") + .examples("draw particle Want_Food_Corn at {_loc} for player", + "draw particle Splash at location of player ~ vector3d(0, 0.5, 0)") + .since("INSERT VERSION") + .register(); + } + + private Expression particle; + private Expression location; + private Expression receivers; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.particle = (Expression) expressions[0]; + this.location = (Expression) expressions[1]; + if (matchedPattern == 1) { + this.receivers = expressions[2]; + } + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + ParticleSystem particleSystem = this.particle.getSingle(ctx).orElse(null); + if (particleSystem == null) return; + + String particleId = particleSystem.getId(); + + Location location = this.location.getSingle(ctx).orElse(null); + if (location == null) { + return; + } + + Vector3d pos = location.getPosition(); + Vector3f rotation = location.getRotation(); + float yaw = rotation.getYaw(); + float pitch = rotation.getPitch(); + float roll = rotation.getRoll(); + if (Float.isNaN(yaw)) yaw = 0; + if (Float.isNaN(pitch)) pitch = 0; + if (Float.isNaN(roll)) roll = 0; + + List> receivers = new ArrayList<>(); + if (this.receivers != null) { + for (Object o : this.receivers.getArray(ctx)) { + if (o instanceof Player player) { + receivers.add(player.getReference()); + } else if (o instanceof PlayerRef playerRef) { + receivers.add(playerRef.getReference()); + } + } + } + + World world = Universe.get().getWorld(location.getWorld()); + if (world == null) return; + + Store store = world.getEntityStore().getStore(); + + // If no receivers were specified, use players in a radius of 75 of location + if (receivers.isEmpty()) { + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> playerRefs = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(pos, 75.0F, playerRefs); + receivers.addAll(playerRefs); + } + + ParticleUtil.spawnParticleEffect(particleId, + pos.getX(), pos.getY(), pos.getZ(), + yaw, pitch, roll, + 1.0f, + null, + null, + receivers, + store); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String receives = this.receivers != null ? " to " + this.receivers.toString(ctx, debug) : ""; + return "draw particle " + this.particle.toString(ctx, debug) + " at " + this.location.toString(ctx, debug) + receives; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java new file mode 100644 index 0000000..b28700c --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java @@ -0,0 +1,190 @@ +package com.github.skriptdev.skript.plugin.elements.sections; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Location; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.math.vector.Vector3f; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.ParticleUtil; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.lang.CodeSection; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.Statement; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.entries.SectionConfiguration; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.parsing.ParserState; +import io.github.syst3ms.skriptparser.util.color.Color; +import it.unimi.dsi.fastutil.objects.ObjectList; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class SecParticle extends CodeSection { + + public static void register(SkriptRegistration reg) { + reg.newSection(SecParticle.class, + "(draw|spawn) particle %particle% at %location%") + .name("Particle Spawn") + .description("Draw a particle at a location.", + "If you don't need the entries, you can use the particle spawn effect.", + "**Entries**:", + " - `rotation` = The rotation of the particle [optional: vector3f].", + " - `scale` = The size of the particle [optional: number, default = 1].", + " - `color` = The color of the particle [optional: color/string(hex)].", + " - `source` = Who sent the particle (they wont see it) [optional: entity/player].", + " - `receivers` = Who will receive the particle [optional: players/playerRefs, " + + "default: players in a radius of 75 of the location].") + .examples("draw particle Totem_Heal_AoE at {_loc}:", + "\trotation: vector3f(0.5, 1, 1)", + "\tcolor: red", + "\tscale: 0.5") + .since("INSERT VERSION") + .register(); + } + + private Expression particle; + private Expression location; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.particle = (Expression) expressions[0]; + this.location = (Expression) expressions[1]; + return true; + } + + SectionConfiguration config = new SectionConfiguration.Builder() + .addOptionalExpression("rotation", Vector3f.class, false) + .addOptionalExpression("scale", Number.class, false) + .addOptionalExpression("color", Object.class, false) + .addOptionalExpression("source", Entity.class, false) + .addOptionalExpression("receivers", Object.class, true) + .build(); + + @Override + public boolean loadSection(@NotNull FileSection section, @NotNull ParserState parserState, @NotNull SkriptLogger logger) { + return this.config.loadConfiguration(null, section, parserState, logger); + } + + @Override + public Optional walk(@NotNull TriggerContext ctx) { + Optional nextStatement = getNext(); + + ParticleSystem particleSystem = this.particle.getSingle(ctx).orElse(null); + if (particleSystem == null) return nextStatement; + + String particleId = particleSystem.getId(); + + Location location = this.location.getSingle(ctx).orElse(null); + if (location == null) { + return nextStatement; + } + + Vector3d pos = location.getPosition(); + Vector3f rotation = location.getRotation(); + + Expression vecExpr = this.config.getExpression("rotation", Vector3f.class).orElse(null); + if (vecExpr != null) { + Vector3f vec = vecExpr.getSingle(ctx).orElse(null); + if (vec != null) rotation = vec; + } + + float scale = 1.0f; + Expression scaleExpr = this.config.getExpression("scale", Number.class).orElse(null); + if (scaleExpr != null) { + Number number = scaleExpr.getSingle(ctx).orElse(null); + if (number != null) scale = number.floatValue(); + } + + Color color = null; + Expression colorExpr = this.config.getExpression("color", Object.class).orElse(null); + if (colorExpr != null) { + Object o = colorExpr.getSingle(ctx).orElse(null); + if (o instanceof Color c) { + color = c; + } else if (o instanceof String s) { + color = Color.ofHex(s).orElse(null); + } + } + + Ref source = null; + Expression sourceExpr = this.config.getExpression("source", Entity.class).orElse(null); + if (sourceExpr != null) { + Entity entity = sourceExpr.getSingle(ctx).orElse(null); + if (entity != null) source = entity.getReference(); + } + + List> receivers = new ArrayList<>(); + Optional> receiversExpr = this.config.getExpression("receivers", Object.class); + if (receiversExpr.isPresent()) { + for (Object o : receiversExpr.get().getArray(ctx)) { + if (o instanceof Player player) { + receivers.add(player.getReference()); + } else if (o instanceof PlayerRef playerRef) { + receivers.add(playerRef.getReference()); + } + } + } + + World world = Universe.get().getWorld(location.getWorld()); + if (world == null) return nextStatement; + + Store store = world.getEntityStore().getStore(); + + // If no receivers were specified, use players in a radius of 75 of location + if (receivers.isEmpty()) { + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + ObjectList> playerRefs = SpatialResource.getThreadLocalReferenceList(); + playerSpatialResource.getSpatialStructure().collect(pos, 75.0F, playerRefs); + receivers.addAll(playerRefs); + } + + float yaw = rotation.getYaw(); + float pitch = rotation.getPitch(); + float roll = rotation.getRoll(); + if (Float.isNaN(yaw)) yaw = 0; + if (Float.isNaN(pitch)) pitch = 0; + if (Float.isNaN(roll)) roll = 0; + + ParticleUtil.spawnParticleEffect(particleId, + pos.getX(), pos.getY(), pos.getZ(), + yaw, pitch, roll, + scale, + getHytaleColor(color), + source, + receivers, + store); + + return nextStatement; + } + + private com.hypixel.hytale.protocol.Color getHytaleColor(@Nullable Color color) { + if (color == null) return null; + return new com.hypixel.hytale.protocol.Color( + (byte) color.getRed(), + (byte) color.getGreen(), + (byte) color.getBlue() + ); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "draw particle " + this.particle.toString(ctx, debug) + " at " + this.location.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java index efe504c..55bb30e 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java @@ -8,6 +8,7 @@ public static void register(SkriptRegistration registration) { SecApplyStatModifier.register(registration); SecDropItem.register(registration); SecExecuteInWorld.register(registration); + SecParticle.register(registration); SecPlaySound.register(registration); SecSendNotification.register(registration); SecSendTitle.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java index 7522560..1259220 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java @@ -16,6 +16,7 @@ import com.hypixel.hytale.server.core.asset.type.item.config.Item; import com.hypixel.hytale.server.core.asset.type.item.config.ItemQuality; import com.hypixel.hytale.server.core.asset.type.item.config.ResourceType; +import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; import com.hypixel.hytale.server.core.asset.type.weather.config.Weather; @@ -125,6 +126,8 @@ public Item deserialize(@NotNull Gson gson, @NotNull JsonElement element) { .since("1.1.0") .toStringFunction(ItemQuality::getId) .register(); + reg.newAssetStoreType(ParticleSystem.class, ParticleSystem.getAssetMap(), "particle", "particle@s") + .register(); reg.newAssetStoreType(Projectile.class, Projectile.getAssetMap(), "projectile", "projectile@s") .name("Projectile") .description("Represents the types of projectiles in the game.") From c4991fb32ae4810c088eb7cf257618cd63646eae Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 18:15:56 -0800 Subject: [PATCH 02/31] Checkstyle... --- .../skript/plugin/elements/effects/world/EffParticle.java | 3 ++- .../skriptdev/skript/plugin/elements/sections/SecParticle.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java index 98499f5..4e535cf 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffParticle.java @@ -94,7 +94,8 @@ protected void execute(@NotNull TriggerContext ctx) { // If no receivers were specified, use players in a radius of 75 of location if (receivers.isEmpty()) { - SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get() + .getPlayerSpatialResourceType()); ObjectList> playerRefs = SpatialResource.getThreadLocalReferenceList(); playerSpatialResource.getSpatialStructure().collect(pos, 75.0F, playerRefs); receivers.addAll(playerRefs); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java index b28700c..7b14d4d 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecParticle.java @@ -148,7 +148,8 @@ public Optional walk(@NotNull TriggerContext ctx) { // If no receivers were specified, use players in a radius of 75 of location if (receivers.isEmpty()) { - SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get() + .getPlayerSpatialResourceType()); ObjectList> playerRefs = SpatialResource.getThreadLocalReferenceList(); playerSpatialResource.getSpatialStructure().collect(pos, 75.0F, playerRefs); receivers.addAll(playerRefs); From add92b406c14027bef24b7f695e1475f4419e83c Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 19:11:23 -0800 Subject: [PATCH 03/31] PlayerUtils - add util to get players in radius/cuboid --- .../skript/api/hytale/utils/PlayerUtils.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java index 26fc50c..623af4a 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java @@ -2,11 +2,17 @@ import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Location; +import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.Universe; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import it.unimi.dsi.fastutil.objects.ObjectList; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -96,4 +102,59 @@ public static List getPlayerRefs(@Nullable World world) { return store.getComponent(reference, Player.getComponentType()); } + /** + * Get all players within a radius of a location. + * + * @param location Location to get players from + * @param radius Radius to get players from + * @return List of players within radius of location + */ + public static @NotNull List getPlayersInRadius(Location location, double radius) { + World world = Universe.get().getWorld(location.getWorld()); + if (world == null) return List.of(); + + Store store = world.getEntityStore().getStore(); + if (store == null) return List.of(); + + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collect(location.getPosition(), (float) radius, results); + + List players = new ArrayList<>(); + for (Ref result : results) { + Player player = store.getComponent(result, Player.getComponentType()); + if (player != null) players.add(player); + } + return players; + } + + /** + * Get all players within a cuboid. + * + * @param loc1 First location of cuboid + * @param loc2 Second location of cuboid + * @return List of players within cuboid + */ + public static @NotNull List getPlayersInCuboid(Location loc1, Location loc2) { + World world = Universe.get().getWorld(loc1.getWorld()); + if (world == null) return List.of(); + + Store store = world.getEntityStore().getStore(); + if (store == null) return List.of(); + + Vector3d min = Vector3d.min(loc1.getPosition(), loc2.getPosition()); + Vector3d max = Vector3d.max(loc1.getPosition(), loc2.getPosition()); + + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + playerSpatialResource.getSpatialStructure().collectBox(min, max, results); + + List players = new ArrayList<>(); + for (Ref result : results) { + Player player = store.getComponent(result, Player.getComponentType()); + if (player != null) players.add(player); + } + return players; + } + } From 30ae613dd0a728336d41124c66cef4258129fb16 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 19:12:00 -0800 Subject: [PATCH 04/31] PlayerUtils - checkstyle --- .../skriptdev/skript/api/hytale/utils/PlayerUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java index 623af4a..9adb7b7 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/PlayerUtils.java @@ -117,7 +117,8 @@ public static List getPlayerRefs(@Nullable World world) { if (store == null) return List.of(); ObjectList> results = SpatialResource.getThreadLocalReferenceList(); - SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get() + .getPlayerSpatialResourceType()); playerSpatialResource.getSpatialStructure().collect(location.getPosition(), (float) radius, results); List players = new ArrayList<>(); @@ -146,7 +147,8 @@ public static List getPlayerRefs(@Nullable World world) { Vector3d max = Vector3d.max(loc1.getPosition(), loc2.getPosition()); ObjectList> results = SpatialResource.getThreadLocalReferenceList(); - SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get().getPlayerSpatialResourceType()); + SpatialResource, EntityStore> playerSpatialResource = store.getResource(EntityModule.get() + .getPlayerSpatialResourceType()); playerSpatialResource.getSpatialStructure().collectBox(min, max, results); List players = new ArrayList<>(); From c8f5870452f8ef4d3c1aa6bbff936e119be2dd1a Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 19:14:01 -0800 Subject: [PATCH 05/31] ExprEntitiesInRadius - add players as well --- .../entity/ExprEntitiesInRadius.java | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntitiesInRadius.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntitiesInRadius.java index 2abfd3a..2010bbc 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntitiesInRadius.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntitiesInRadius.java @@ -1,5 +1,6 @@ package com.github.skriptdev.skript.plugin.elements.expressions.entity; +import com.github.skriptdev.skript.api.hytale.utils.PlayerUtils; import com.github.skriptdev.skript.api.skript.registration.NPCRegistry; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; import com.hypixel.hytale.component.Ref; @@ -8,6 +9,7 @@ import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.server.core.entity.Entity; import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.entity.entities.Player; import com.hypixel.hytale.server.core.universe.Universe; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; @@ -27,24 +29,26 @@ public class ExprEntitiesInRadius implements Expression { public static void register(SkriptRegistration reg) { reg.newExpression(ExprEntitiesInRadius.class, Entity.class, false, - "[all] entities in radius %number% (around|of) %location%", + "[all] (entities|:players) in radius %number% (around|of) %location%", "[all] entities of type[s] %npcroles% in radius %number% (around|of) %location%", - "[all] entities within %location% and %location%", + "[all] (entities|:players) within %location% and %location%", "[all] entities of type[s] %npcroles% within %location% and %location%") .name("Entities in Radius/Cuboid") - .description("Get all entities within a radius around a location or within a cuboid.", + .description("Get all entities/players within a radius around a location or within a cuboid.", "You can optionally include an NPCRole to filter the entities by type.") .examples("loop entities in radius 10 around player:", "\tif npctype of loop-entity is sheep:", "\t\tkill loop-entity", "loop entities of type cow in radius 5 around {_loc}:", "loop entities within {_loc1} and {_loc2}:", - "loop entities of type sheep within {_loc1} and {_loc2}") + "loop entities of type sheep within {_loc1} and {_loc2}", + "send \"hi\" to all players in radius 10 of player") .since("1.0.0") .register(); } private int pattern; + private boolean players; private Expression radius; private Expression location; private Expression loc1, loc2; @@ -54,6 +58,7 @@ public static void register(SkriptRegistration reg) { @Override public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { this.pattern = matchedPattern; + this.players = parseContext.hasMark("players"); switch (matchedPattern) { case 0 -> { this.radius = (Expression) expressions[0]; @@ -111,6 +116,9 @@ public Entity[] getValues(@NotNull TriggerContext ctx) { if (world == null) return null; Store store = world.getEntityStore().getStore(); + if (this.players) { + return PlayerUtils.getPlayersInRadius(loc, radius).toArray(Player[]::new); + } for (Ref entityStoreRef : TargetUtil.getAllEntitiesInSphere(loc.getPosition(), radius, store)) { Entity entity = EntityUtils.getEntity(entityStoreRef, store); @@ -130,6 +138,10 @@ public Entity[] getValues(@NotNull TriggerContext ctx) { if (world == null) return null; Store store = world.getEntityStore().getStore(); + if (this.players) { + return PlayerUtils.getPlayersInCuboid(loc1, loc2).toArray(Player[]::new); + } + Vector3d min = Vector3d.min(loc1.getPosition(), loc2.getPosition()); Vector3d max = Vector3d.max(loc1.getPosition(), loc2.getPosition()); @@ -144,15 +156,21 @@ public Entity[] getValues(@NotNull TriggerContext ctx) { return entities.toArray(Entity[]::new); } + @Override + public Class getReturnType() { + return this.players ? Player.class : Entity.class; + } + @Override public String toString(@NotNull TriggerContext ctx, boolean debug) { + String type = this.players ? "players" : "entities"; return switch (this.pattern) { case 0 -> - "entities in radius " + this.radius.toString(ctx, debug) + " around " + this.location.toString(ctx, debug); + type + "in radius " + this.radius.toString(ctx, debug) + " around " + this.location.toString(ctx, debug); case 1 -> "entities of type " + this.roles.toString(ctx, debug) + " in radius " + this.radius.toString(ctx, debug) + " around " + this.location.toString(ctx, debug); - case 2 -> "entities within " + this.loc1.toString(ctx, debug) + " and " + this.loc2.toString(ctx, debug); + case 2 -> type + " within " + this.loc1.toString(ctx, debug) + " and " + this.loc2.toString(ctx, debug); case 3 -> "entities of type " + this.roles.toString(ctx, debug) + " within " + this.loc1.toString(ctx, debug) + " and " + this.loc2.toString(ctx, debug); From 2bd808a9fb3370d25537ce2afdb75a13107fd721 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 20:06:41 -0800 Subject: [PATCH 06/31] build.gradle.kts - switch to dev/patch parser --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index b4db8c6..18910d6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -31,7 +31,7 @@ repositories { dependencies { compileOnly("com.hypixel.hytale:Server:${hytaleVersion}") compileOnly("org.jetbrains:annotations:26.0.2") - implementation("com.github.SkriptDev:skript-parser:1.0.9") { + implementation("com.github.SkriptDev:skript-parser:dev~patch-SNAPSHOT") { isTransitive = false } implementation("com.github.Zoltus:TinyMessage:2.0.1") { From c7e1858f2be74d653457be26dca448f4fe37f8f4 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 20:33:34 -0800 Subject: [PATCH 07/31] TypesItem - fix item container codecs --- .../skriptdev/skript/plugin/elements/types/TypesItem.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesItem.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesItem.java index dd5ad13..15aa878 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesItem.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesItem.java @@ -9,7 +9,6 @@ import com.hypixel.hytale.protocol.packets.interface_.Page; import com.hypixel.hytale.server.core.inventory.Inventory; import com.hypixel.hytale.server.core.inventory.ItemStack; -import com.hypixel.hytale.server.core.inventory.container.CombinedItemContainer; import com.hypixel.hytale.server.core.inventory.container.EmptyItemContainer; import com.hypixel.hytale.server.core.inventory.container.ItemContainer; import com.hypixel.hytale.server.core.inventory.container.SimpleItemContainer; @@ -65,10 +64,10 @@ public Inventory deserialize(@NotNull Gson gson, @NotNull JsonElement element) { public JsonElement serialize(@NotNull Gson gson, @NotNull ItemContainer value) { BsonDocument document; if (value instanceof EmptyItemContainer eic) { - document = CombinedItemContainer.CODEC.encode(eic, new ExtraInfo()).asDocument(); + document = EmptyItemContainer.CODEC.encode(eic, new ExtraInfo()).asDocument(); document.put("type", new BsonString("empty")); } else if (value instanceof SimpleItemContainer sic) { - document = CombinedItemContainer.CODEC.encode(sic, new ExtraInfo()).asDocument(); + document = SimpleItemContainer.CODEC.encode(sic, new ExtraInfo()).asDocument(); document.put("type", new BsonString("simple")); } else { document = ItemContainer.CODEC.encode(value, new ExtraInfo()).asDocument(); @@ -86,7 +85,7 @@ public ItemContainer deserialize(@NotNull Gson gson, @NotNull JsonElement elemen return switch (type) { // "combined" stays as it was an old thing we used (backwards compatability) - case "container", "combined" -> CombinedItemContainer.CODEC.decode(parse, new ExtraInfo()); + case "container", "combined" -> ItemContainer.CODEC.decode(parse, new ExtraInfo()); case "empty" -> EmptyItemContainer.CODEC.decode(parse, new ExtraInfo()); case "simple" -> SimpleItemContainer.CODEC.decode(parse, new ExtraInfo()); default -> null; From b55fbce25967565e8f32191e34afb6f2b55b8571 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 21:38:36 -0800 Subject: [PATCH 08/31] Block - add rotation --- .../skriptdev/skript/api/hytale/Block.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java b/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java index 36e62e4..d44c12c 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java @@ -8,6 +8,8 @@ import com.hypixel.hytale.math.vector.Location; import com.hypixel.hytale.math.vector.Vector3i; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.RotationTuple; import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; import com.hypixel.hytale.server.core.entity.LivingEntity; import com.hypixel.hytale.server.core.inventory.ItemStack; @@ -18,6 +20,7 @@ import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.Universe; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn; import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; @@ -54,6 +57,10 @@ public Block(@NotNull Location location) { this(world, location.getPosition().toVector3i()); } + public WorldChunk getChunk() { + return this.world.getChunk(ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ())); + } + public @NotNull BlockType getType() { BlockType blockType = this.world.getBlockType(this.pos); return blockType != null ? blockType : BlockType.EMPTY; @@ -68,6 +75,56 @@ public void setType(@NotNull BlockType type, int settings) { } } + /** + * Set the rotation of this block. + * + * @param rotation Rotation of block represented by a Vector3i(yaw, pitch, roll). + */ + public void setRotation(Vector3i rotation) { + int blockId = BlockType.getAssetMap().getIndex(getType().getId()); + WorldChunk chunk = getChunk(); + BlockChunk blockChunk = chunk.getBlockChunk(); + if (blockChunk == null) return; + + Rotation pitch = getRotationFromInt(rotation.getX()); + Rotation yaw = getRotationFromInt(rotation.getY()); + Rotation roll = getRotationFromInt(rotation.getZ()); + int rotationIndex = RotationTuple.of(yaw, pitch, roll).index(); + blockChunk.setBlock(this.pos.getX(), this.pos.getY(), this.pos.getZ(), + blockId, rotationIndex, 0); + } + + /** + * Get the rotation of this block. + * + * @return Rotation of block represented as a Vector3i(yaw, pitch, roll). + */ + public Vector3i getRotation() { + int blockRotationIndex = getWorld().getBlockRotationIndex(this.pos.getX(), this.pos.getY(), this.pos.getZ()); + RotationTuple rotationTuple = RotationTuple.get(blockRotationIndex); + + int yaw = getIntFromRotation(rotationTuple.yaw()); + int pitch = getIntFromRotation(rotationTuple.pitch()); + int roll = getIntFromRotation(rotationTuple.roll()); + return new Vector3i(pitch, yaw, roll); + } + + private Rotation getRotationFromInt(int v) { + if (v < 90) return Rotation.None; + else if (v < 180) return Rotation.Ninety; + else if (v < 270) return Rotation.OneEighty; + else return Rotation.TwoSeventy; + } + + private int getIntFromRotation(Rotation rotation) { + return switch (rotation) { + case None -> 0; + case Ninety -> 90; + case OneEighty -> 180; + case TwoSeventy -> 270; + }; + } + public byte getFluidLevel() { long index = ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ()); WorldChunk chunk = this.world.getChunk(index); From ff5b5d7f42002e8b0ecab00d60e357718209c19e Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 21 Feb 2026 21:39:05 -0800 Subject: [PATCH 09/31] ExprBlockRotation - add --- .../expressions/ExpressionHandler.java | 2 + .../expressions/block/ExprBlockRotation.java | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockRotation.java diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java index 0c0d376..88b5aae 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java @@ -5,6 +5,7 @@ import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockFluid; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockHealth; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockIterator; +import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockRotation; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockSphere; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockTypeAtLocation; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockTypeOfBlock; @@ -95,6 +96,7 @@ public static void register(SkriptRegistration registration) { ExprBlockFluidLevel.register(registration); ExprBlockHealth.register(registration); ExprBlockIterator.register(registration); + ExprBlockRotation.register(registration); ExprBlockSphere.register(registration); ExprBlockTypeAtLocation.register(registration); ExprBlockTypeOfBlock.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockRotation.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockRotation.java new file mode 100644 index 0000000..f4ab2f0 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockRotation.java @@ -0,0 +1,66 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.block; + +import com.github.skriptdev.skript.api.hytale.Block; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.math.vector.Vector3i; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; +import io.github.syst3ms.skriptparser.util.CollectionUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public class ExprBlockRotation implements Expression { + + public static void register(SkriptRegistration reg) { + reg.newExpression(ExprBlockRotation.class, Vector3i.class, true, + "block rotation of %block%") + .name("Block Rotation") + .description("Get/set the rotation of a block.", + "Represented as a vector3i(pitch, yaw, roll).", + "Do note that only increments of 90 degrees are supported (0, 90, 180, 270).", + "Caution: This allows you to rotate blocks in ways they're not meant to... have fun, be careful!") + .examples("set block rotation of block at player to vector3i(0, 180, 0)") + .since("INSERT VERSION") + .register(); + } + + private Expression block; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.block = (Expression) expressions[0]; + return true; + } + + @Override + public Vector3i[] getValues(@NotNull TriggerContext ctx) { + Block block = this.block.getSingle(ctx).orElse(null); + if (block == null) return null; + + return new Vector3i[]{block.getRotation()}; + } + + @Override + public Optional[]> acceptsChange(@NotNull ChangeMode mode) { + if (mode == ChangeMode.SET) return CollectionUtils.optionalArrayOf(Vector3i.class); + return Optional.empty(); + } + + @SuppressWarnings("ConstantValue") + @Override + public void change(@NotNull TriggerContext ctx, @NotNull ChangeMode changeMode, Object @NotNull [] changeWith) { + if (changeWith == null || changeWith.length == 0 || !(changeWith[0] instanceof Vector3i rotation)) return; + + this.block.getSingle(ctx).ifPresent(block -> block.setRotation(rotation)); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "block rotation of " + this.block.toString(ctx, debug); + } + +} From aecc16d373395243047c54c95fbd44293afe607c Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sun, 22 Feb 2026 20:58:15 -0800 Subject: [PATCH 10/31] command-vault.sk - another sample --- src/main/resources/sample-scripts/command-vault.sk | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/resources/sample-scripts/command-vault.sk diff --git a/src/main/resources/sample-scripts/command-vault.sk b/src/main/resources/sample-scripts/command-vault.sk new file mode 100644 index 0000000..6d94a1f --- /dev/null +++ b/src/main/resources/sample-scripts/command-vault.sk @@ -0,0 +1,11 @@ +# Player Vaults +# This little snippet includes a command where a player can open their own storage vaults + +player command /vault: + trigger: + # If the vault isn't created yet, create it + if {vaults::%uuid of player%} is not set: + set {vaults::%uuid of player%} to itemContainer(36) # Use whatever size you'd like + + # Then open it to the player + open {vaults::%uuid of player%} to player From cc2245656793ce8d8d71a15f628fccc74b8e291f Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sun, 22 Feb 2026 22:00:31 -0800 Subject: [PATCH 11/31] SecExecuteInWorld - fix not loading variables correctly --- .../plugin/elements/sections/SecExecuteInWorld.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecExecuteInWorld.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecExecuteInWorld.java index e6f2b92..50a6328 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecExecuteInWorld.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecExecuteInWorld.java @@ -7,6 +7,7 @@ import io.github.syst3ms.skriptparser.lang.Statement; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.variables.VariableMap; import io.github.syst3ms.skriptparser.variables.Variables; import org.jetbrains.annotations.NotNull; @@ -38,21 +39,18 @@ public boolean init(Expression[] expressions, int matchedPattern, @NotNull Pa public Optional walk(@NotNull TriggerContext ctx) { Optional first = getFirst(); if (first.isPresent()) { - Optional single = this.world.getSingle(ctx); + Optional worldOptional = this.world.getSingle(ctx); // Copy variables from the time this code executes - TriggerContext dummy = TriggerContext.DUMMY; - Variables.copyLocalVariables(ctx, dummy); - + VariableMap variableMap = Variables.copyLocalVariables(ctx); Statement firstStatement = first.get(); - single.ifPresent(world -> world.execute(() -> { + worldOptional.ifPresent(world -> world.execute(() -> { // Place the variables back into the original context // This is done in case World#execute is delayed - Variables.copyLocalVariables(dummy, ctx); + Variables.setLocalVariables(ctx, variableMap); Statement.runAll(firstStatement, ctx); // Clear out old variables - Variables.clearLocalVariables(dummy); Variables.clearLocalVariables(ctx); })); } From 58d077802783b70b2a4ad7e8bee1567cc8ef9f3a Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 23 Feb 2026 08:07:33 -0800 Subject: [PATCH 12/31] EffTeleport - handle teleports on the correct world thread --- .../elements/effects/entity/EffTeleport.java | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java index c566099..c3051ee 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java @@ -1,13 +1,11 @@ package com.github.skriptdev.skript.plugin.elements.effects.entity; -import com.hypixel.hytale.component.Ref; -import com.hypixel.hytale.component.Store; +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; import com.hypixel.hytale.math.vector.Location; import com.hypixel.hytale.server.core.entity.Entity; import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; import com.hypixel.hytale.server.core.universe.Universe; import com.hypixel.hytale.server.core.universe.world.World; -import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import io.github.syst3ms.skriptparser.lang.Effect; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.TriggerContext; @@ -45,15 +43,21 @@ protected void execute(@NotNull TriggerContext ctx) { Location location = this.location.getSingle(ctx).orElse(null); if (location == null || entities == null) return; - World world = Universe.get().getWorld(location.getWorld()); + World worldTp = Universe.get().getWorld(location.getWorld()); for (Entity entity : entities) { - Ref reference = entity.getReference(); - assert reference != null; - Store store = reference.getStore(); + Runnable tpRunnable = () -> { + Teleport teleport = Teleport.createForPlayer(worldTp, location.getPosition(), location.getRotation()); + EntityUtils.putComponent(entity, Teleport.getComponentType(), teleport); + }; - Teleport teleport = Teleport.createForPlayer(world, location.getPosition(), location.getRotation()); - store.addComponent(reference, Teleport.getComponentType(), teleport); + World world = entity.getWorld(); + assert world != null; + if (world.isInThread()) { + tpRunnable.run(); + } else { + world.execute(tpRunnable); + } } } From 9b7a379f0227318a779b1ac55ce56bc367c88df3 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 23 Feb 2026 08:25:51 -0800 Subject: [PATCH 13/31] Add some world stuff --- .../elements/effects/EffectHandler.java | 2 + .../effects/world/EffCreateWorld.java | 95 ++++++++++++ .../expressions/ExpressionHandler.java | 2 + .../expressions/world/ExprAllWorlds.java | 56 +++++++ .../elements/sections/SecCreateWorld.java | 146 ++++++++++++++++++ .../elements/sections/SectionHandler.java | 1 + .../elements/types/TypesAssetStore.java | 8 + 7 files changed, 310 insertions(+) create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffCreateWorld.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/world/ExprAllWorlds.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecCreateWorld.java diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java index b2fadd2..a6c125e 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java @@ -27,6 +27,7 @@ import com.github.skriptdev.skript.plugin.elements.effects.server.EffServerShutdown; import com.github.skriptdev.skript.plugin.elements.effects.world.EffChunkLoadAsync; import com.github.skriptdev.skript.plugin.elements.effects.world.EffChunkRegenerate; +import com.github.skriptdev.skript.plugin.elements.effects.world.EffCreateWorld; import com.github.skriptdev.skript.plugin.elements.effects.world.EffExplosion; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffInteraction; import com.github.skriptdev.skript.plugin.elements.effects.world.EffParticle; @@ -72,6 +73,7 @@ public static void register(SkriptRegistration registration) { // WORLD EffChunkLoadAsync.register(registration); EffChunkRegenerate.register(registration); + EffCreateWorld.register(registration); EffExplosion.register(registration); EffParticle.register(registration); } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffCreateWorld.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffCreateWorld.java new file mode 100644 index 0000000..a5d3e87 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/world/EffCreateWorld.java @@ -0,0 +1,95 @@ +package com.github.skriptdev.skript.plugin.elements.effects.world; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import io.github.syst3ms.skriptparser.lang.Effect; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.Statement; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.variables.VariableMap; +import io.github.syst3ms.skriptparser.variables.Variables; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; +import java.util.Optional; + +public class EffCreateWorld extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffCreateWorld.class, + "create world named %string%", + "create world named %string% with [gameplay] config %gameplayconfig%") + .name("Create World") + .description("Creates a world with the specified name.", + "You can optionally specify a gameplay config to use for the world.", + "If no gameplay config is specified, the default gameplay config will be used.", + "The code run after this effect will be delayed until the world is loaded.") + .examples("create world named \"le_test_world\"", + "create world named \"le_test_world\" with gameplay config ForgottenTemple") + .since("INSERT VERSION") + .register(); + } + + private Expression name; + private Expression config; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.name = (Expression) expressions[0]; + if (matchedPattern == 1) { + this.config = (Expression) expressions[1]; + } + return true; + } + + @Override + public Optional walk(@NotNull TriggerContext ctx) { + Optional nextStatement = getNext(); + String name = this.name.getSingle(ctx).orElse(null); + if (name == null) return nextStatement; + + if (Universe.get().getWorld(name) != null) { + // World already exists + return nextStatement; + } + + GameplayConfig gameplayConfig = null; + if (this.config != null) { + gameplayConfig = this.config.getSingle(ctx).orElse(null); + } + if (gameplayConfig == null) { + gameplayConfig = GameplayConfig.DEFAULT; + } + + Path path = Universe.get().getWorldsPath().resolve(name); + WorldConfig worldConfig = new WorldConfig(); + worldConfig.setGameplayConfig(gameplayConfig.getId()); + + VariableMap variableMap = Variables.copyLocalVariables(ctx); + Universe.get().makeWorld(name, path, worldConfig).thenApply(_ -> { + if (nextStatement.isPresent()) { + Variables.setLocalVariables(ctx, variableMap); + Statement.runAll(nextStatement.get(), ctx); + Variables.clearLocalVariables(ctx); + } + return null; + }); + return Optional.empty(); + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + // Walk, so we can let the world load first + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String config = this.config != null ? " with gameplay config " + this.config.toString(ctx, debug) : ""; + return "create world named " + this.name.toString(ctx, debug) + config; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java index 88b5aae..4a6b9de 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java @@ -75,6 +75,7 @@ import com.github.skriptdev.skript.plugin.elements.expressions.player.ExprPlayerRespawnLocations; import com.github.skriptdev.skript.plugin.elements.expressions.server.ExprConsole; import com.github.skriptdev.skript.plugin.elements.expressions.server.ExprServerViewRadius; +import com.github.skriptdev.skript.plugin.elements.expressions.world.ExprAllWorlds; import com.github.skriptdev.skript.plugin.elements.expressions.world.ExprChunkAtLocation; import com.github.skriptdev.skript.plugin.elements.expressions.world.ExprEnvironmentAtLocation; import com.github.skriptdev.skript.plugin.elements.expressions.world.ExprRelativePositionResolve; @@ -178,6 +179,7 @@ public static void register(SkriptRegistration registration) { ExprServerViewRadius.register(registration); // WORLD + ExprAllWorlds.register(registration); ExprChunkAtLocation.register(registration); ExprEnvironmentAtLocation.register(registration); ExprRelativePositionResolve.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/world/ExprAllWorlds.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/world/ExprAllWorlds.java new file mode 100644 index 0000000..8ef697c --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/world/ExprAllWorlds.java @@ -0,0 +1,56 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.world; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public class ExprAllWorlds implements Expression { + + public static void register(SkriptRegistration reg) { + reg.newExpression(ExprAllWorlds.class, Object.class, false, + "all worlds", + "all world names", + "all world keys") + .name("All Worlds") + .description("Get all worlds or their names.") + .since("INSERT VERSION") + .register(); + } + + private int pattern; + + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.pattern = matchedPattern; + return true; + } + + @Override + public Object[] getValues(@NotNull TriggerContext ctx) { + Map worlds = Universe.get().getWorlds(); + if (this.pattern == 0) { + return worlds.values().toArray(new World[0]); + } else { + return worlds.keySet().toArray(new String[0]); + } + } + + @Override + public Class getReturnType() { + if (this.pattern == 0) return World.class; + return String.class; + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + if (this.pattern == 0) return "all worlds"; + return "all world names"; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecCreateWorld.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecCreateWorld.java new file mode 100644 index 0000000..b7df7e7 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecCreateWorld.java @@ -0,0 +1,146 @@ +package com.github.skriptdev.skript.plugin.elements.sections; + +import com.github.skriptdev.skript.api.skript.event.WorldContext; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.protocol.GameMode; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.WorldConfig; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.lang.CodeSection; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.Statement; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.entries.SectionConfiguration; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.parsing.ParserState; +import io.github.syst3ms.skriptparser.variables.VariableMap; +import io.github.syst3ms.skriptparser.variables.Variables; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +public class SecCreateWorld extends CodeSection { + + public static void register(SkriptRegistration reg) { + reg.newSection(SecCreateWorld.class, "create world named %string%") + .name("Create World") + .description("Creates a new world with the specified name and some optional settings.", + "**Entries**:", + " - `gameplay_config` = The GameplayConfig to use for this world [optonal: GameplayConfig].", + " - `pvp_enabled` = Whether pvp will be enabled in this world [optional: boolean].", + " - `default_gamemode` = The default GameMode to use for this world [optional: GameMode].", + " - `spawning_npc` = Whether to spawn NPCs in this world [optional: boolean].", + " - `can_save_chunks` = Whether chunks will be saved in this world [optional: boolean].", + " - `delete_on_remove` = Whether the world will be deleted when the world is removed [optional: boolean].", + " - `gametime_paused` = Whether the gametime will be paused in this world [optional: boolean].", + + " - `run_in_world` = The code to run in the newly created world [optional: section]. " + + "When run in the world, `event-world` will be the newly created world.") + .examples("create world named \"le_test_world\":", + "\tgameplay_config: default", + "\tpvp_enabled: true", + "\tdefault_gamemode: Adventure", + "\tdelete_on_remove: true", + "\trun_in_world:", + "\t\tset {_spawn} to world spawn of event-world", + "\t\tteleport {_player} to {_spawn}") + .since("INSERT VERSION") + .register(); + } + + private Expression name; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.name = (Expression) expressions[0]; + return true; + } + + SectionConfiguration config = new SectionConfiguration.Builder() + .addOptionalExpression("gameplay_config", GameplayConfig.class, false) + .addOptionalLiteral("pvp_enabled", Boolean.class) + .addOptionalLiteral("default_gamemode", GameMode.class) + .addOptionalLiteral("spawning_npc", Boolean.class) + .addOptionalLiteral("can_save_chunks", Boolean.class) + .addOptionalLiteral("delete_on_remove", Boolean.class) + .addOptionalLiteral("gametime_paused", Boolean.class) + .addOptionalSection("run_in_world") + .build(); + + @Override + public boolean loadSection(@NotNull FileSection section, @NotNull ParserState parserState, @NotNull SkriptLogger logger) { + List> contexts = new ArrayList<>(parserState.getCurrentContexts()); + contexts.add(CreateWorldContext.class); + parserState.setCurrentContexts(Set.copyOf(contexts)); + return this.config.loadConfiguration(null, section, parserState, logger); + } + + @Override + public Optional walk(@NotNull TriggerContext ctx) { + Optional nextStatement = getNext(); + String name = this.name.getSingle(ctx).orElse(null); + if (name == null) return nextStatement; + + Expression configExpr = this.config.getExpression("gameplay_config", GameplayConfig.class).orElse(null); + GameplayConfig gameplayConfig; + if (configExpr != null) { + gameplayConfig = configExpr.getSingle(ctx).orElse(null); + if (gameplayConfig == null) gameplayConfig = GameplayConfig.DEFAULT; + } else { + gameplayConfig = GameplayConfig.DEFAULT; + } + + Path path = Universe.get().getWorldsPath().resolve(name); + WorldConfig worldConfig = new WorldConfig(); + worldConfig.setGameplayConfig(gameplayConfig.getId()); + + this.config.getValue("pvp_enabled", Boolean.class).ifPresent(worldConfig::setPvpEnabled); + this.config.getValue("default_gamemode", GameMode.class).ifPresent(worldConfig::setGameMode); + this.config.getValue("spawning_npc", Boolean.class).ifPresent(worldConfig::setSpawningNPC); + this.config.getValue("can_save_chunks", Boolean.class).ifPresent(worldConfig::setCanSaveChunks); + this.config.getValue("delete_on_remove", Boolean.class).ifPresent(worldConfig::setDeleteOnRemove); + this.config.getValue("gametime_paused", Boolean.class).ifPresent(worldConfig::setGameTimePaused); + + Optional trigger = this.config.getSection("run_in_world"); + + VariableMap variableMap = Variables.copyLocalVariables(ctx); + Universe.get().makeWorld(name, path, worldConfig).thenApply(world -> { + if (trigger.isPresent()) { + CreateWorldContext createWorldContext = new CreateWorldContext(world); + Variables.setLocalVariables(createWorldContext, variableMap); + Statement.runAll(trigger.get(), createWorldContext); + Variables.clearLocalVariables(createWorldContext); + } + return null; + }); + + return nextStatement; + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "create world named " + this.name.toString(ctx, debug); + } + + public record CreateWorldContext(World world) implements WorldContext { + + @Override + public World getWorld() { + return this.world; + } + + @Override + public String getName() { + return "create world context"; + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java index 55bb30e..572ce00 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SectionHandler.java @@ -6,6 +6,7 @@ public class SectionHandler { public static void register(SkriptRegistration registration) { SecApplyStatModifier.register(registration); + SecCreateWorld.register(registration); SecDropItem.register(registration); SecExecuteInWorld.register(registration); SecParticle.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java index 1259220..46e6a6a 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java @@ -11,6 +11,7 @@ import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect; import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; import com.hypixel.hytale.server.core.asset.type.fluid.Fluid; +import com.hypixel.hytale.server.core.asset.type.gameplay.GameplayConfig; import com.hypixel.hytale.server.core.asset.type.item.config.BlockGroup; import com.hypixel.hytale.server.core.asset.type.item.config.CraftingRecipe; import com.hypixel.hytale.server.core.asset.type.item.config.Item; @@ -93,6 +94,13 @@ static void register(SkriptRegistration reg) { .since("1.0.0") .toStringFunction(Fluid::getId) .register(); + reg.newAssetStoreType(GameplayConfig.class, GameplayConfig.getAssetMap(), + "gameplayconfig", "gameplayConfig@s") + .name("Gameplay Config") + .description("Represents the types of gameplay configurations in the game.") + .toStringFunction(GameplayConfig::getId) + .since("INSERT VERSION") + .register(); reg.newAssetStoreType(Interaction.class, Interaction.getAssetMap(), "interaction", "interaction@s") .name("Interaction") From ad30e87abd538034e2786ec6ac353b457dee8906 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 23 Feb 2026 15:45:24 -0800 Subject: [PATCH 14/31] build.gradle.kts - da ferq happened here --- build.gradle.kts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0e979c3..02e5a6c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,11 +32,7 @@ dependencies { compileOnly("com.hypixel.hytale:Server:${hytaleVersion}") compileOnly("org.jetbrains:annotations:26.0.2") testImplementation("com.google.code.gson:gson:2.13.2") -<<<<<<< dev/feature - implementation("com.github.SkriptDev:skript-parser:dev~patch-SNAPSHOT") { -======= implementation("com.github.SkriptDev:skript-parser:1.0.10") { ->>>>>>> master isTransitive = false } implementation("com.github.Zoltus:TinyMessage:2.0.1") { From a5eb62e1d0d8fe6346fe2588d78c33950f8a5b5e Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 24 Feb 2026 13:07:26 -0800 Subject: [PATCH 15/31] Use shaded bstats --- build.gradle.kts | 6 + .../skript/plugin/BstatsMetrics.java | 2 + .../skriptdev/skript/plugin/Metrics.java | 1051 ----------------- 3 files changed, 8 insertions(+), 1051 deletions(-) delete mode 100644 src/main/java/com/github/skriptdev/skript/plugin/Metrics.java diff --git a/build.gradle.kts b/build.gradle.kts index 02e5a6c..4aca615 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,6 +32,11 @@ dependencies { compileOnly("com.hypixel.hytale:Server:${hytaleVersion}") compileOnly("org.jetbrains:annotations:26.0.2") testImplementation("com.google.code.gson:gson:2.13.2") + + // bStats + implementation("org.bstats:bstats-hytale:3.2.1") + + // Skript-Parser implementation("com.github.SkriptDev:skript-parser:1.0.10") { isTransitive = false } @@ -77,6 +82,7 @@ tasks { archiveClassifier.set("") relocate("io.github.syst3ms", "com.github.skriptdev.skript") relocate("fi.sulku.hytale", "com.github.skriptdev.skript.tinymessage") + relocate("org.bstats", "com.github.skriptdev.skript.bstats") } jar { dependsOn(shadowJar) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java b/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java index fd630ed..62e1702 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java @@ -1,5 +1,7 @@ package com.github.skriptdev.skript.plugin; +import org.bstats.hytale.Metrics; + public class BstatsMetrics { public static void registerMetrics(HySk plugin) { diff --git a/src/main/java/com/github/skriptdev/skript/plugin/Metrics.java b/src/main/java/com/github/skriptdev/skript/plugin/Metrics.java deleted file mode 100644 index a3d3312..0000000 --- a/src/main/java/com/github/skriptdev/skript/plugin/Metrics.java +++ /dev/null @@ -1,1051 +0,0 @@ -package com.github.skriptdev.skript.plugin; - -import com.hypixel.hytale.common.util.java.ManifestUtil; -import com.hypixel.hytale.logger.HytaleLogger; -import com.hypixel.hytale.server.core.Options; -import com.hypixel.hytale.server.core.auth.ServerAuthManager; -import com.hypixel.hytale.server.core.plugin.PluginBase; -import com.hypixel.hytale.server.core.universe.Universe; - -import javax.net.ssl.HttpsURLConnection; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; - -// Temp class until bStats fixes upstream -public class Metrics { - - private final PluginBase pluginBase; - - private final HytaleLogger logger; - - private final boolean logErrors; - - private MetricsBase metricsBase; - - public Metrics(PluginBase pluginBase, int serviceId) { - this.pluginBase = pluginBase; - this.logger = HytaleLogger.getLogger(); - if (ServerAuthManager.getInstance().isSingleplayer()) { - logErrors = false; - return; - } - - File configFile = new File("mods","bStats.txt"); - MetricsConfig config; - try { - config = new MetricsConfig(configFile, true); - } catch (IOException e) { - logErrors = false; - logger.atWarning().withCause(e).log("Failed to create bStats config"); - return; - } - logErrors = config.isLogErrorsEnabled(); - metricsBase = - new MetricsBase( - "hytale", - config.getServerUUID(), - serviceId, - config.isEnabled(), - this::appendPlatformData, - this::appendServiceData, - null, - () -> true, - (msg, throwable) -> logger.atWarning().withCause(throwable).log(msg), - msg -> logger.atInfo().log(msg), - logErrors, - config.isLogSentDataEnabled(), - config.isLogResponseStatusTextEnabled(), - false); - if (!config.didExistBefore()) { - // Send an info message when the bStats config file gets created for the first time - logger.atInfo().log( - "Some of your mods collect metrics and send them to bStats (https://bStats.org)."); - logger.atInfo().log( - "bStats collects some basic information for mod authors, like how many people use"); - logger.atInfo().log( - "their mod and their total player count. It's recommended to keep bStats enabled, but"); - logger.atInfo().log( - "if you're not comfortable with this, you can opt-out by editing the bStats.txt file in"); - logger.atInfo().log("the server root folder and setting enabled to false."); - } - } - - /** Shuts down the underlying scheduler service. */ - public void shutdown() { - if (metricsBase != null) { - metricsBase.shutdown(); - } - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - if (metricsBase != null) { - metricsBase.addCustomChart(chart); - } - } - - private void appendPlatformData(JsonObjectBuilder builder) { - tryAppend(() -> builder.appendField("playerAmount", Universe.get().getPlayerCount())); - tryAppend( - () -> - builder.appendField( - "authMode", - Options.getOrDefault( - Options.AUTH_MODE, Options.getOptionSet(), Options.AuthMode.AUTHENTICATED) - .name())); - tryAppend(() -> builder.appendField("hytaleVersion", ManifestUtil.getImplementationVersion())); - builder.appendField("javaVersion", System.getProperty("java.version")); - builder.appendField("osName", System.getProperty("os.name")); - builder.appendField("osArch", System.getProperty("os.arch")); - builder.appendField("osVersion", System.getProperty("os.version")); - builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); - } - - private void appendServiceData(JsonObjectBuilder builder) { - tryAppend( - () -> - builder.appendField("pluginVersion", pluginBase.getManifest().getVersion().toString())); - } - - /** - * Hytale is still in early access, so we want to be extra careful when using their APIs, as they - * might change at any time. This would be pretty bad, as it would break all plugins using bStats. - * - *

To mitigate this risk, we wrap all optional API calls in a try-catch block. This will not - * catch full renames (due to the imports), but hopefully make us more resilient at least some API - * changes. - */ - private void tryAppend(Runnable task) { - try { - task.run(); - } catch (Throwable e) { - if (logErrors) { - logger.atWarning().withCause(e).log("Failed to append bStats platform data"); - } - } - } - - public static class MetricsBase { - - /** The version of the Metrics class. */ - public static final String METRICS_VERSION = "3.2.1-SNAPSHOT"; - - private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - - private final ScheduledExecutorService scheduler; - - private final String platform; - - private final String serverUuid; - - private final int serviceId; - - private final Consumer appendPlatformDataConsumer; - - private final Consumer appendServiceDataConsumer; - - private final Consumer submitTaskConsumer; - - private final Supplier checkServiceEnabledSupplier; - - private final BiConsumer errorLogger; - - private final Consumer infoLogger; - - private final boolean logErrors; - - private final boolean logSentData; - - private final boolean logResponseStatusText; - - private final Set customCharts = new HashSet<>(); - - private final boolean enabled; - - /** - * Creates a new MetricsBase class instance. - * - * @param platform The platform of the service. - * @param serviceId The id of the service. - * @param serverUuid The server uuid. - * @param enabled Whether or not data sending is enabled. - * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all platform-specific data. - * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all service-specific data. - * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be - * used to delegate the data collection to a another thread to prevent errors caused by - * concurrency. Can be {@code null}. - * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. - * @param errorLogger A consumer that accepts log message and an error. - * @param infoLogger A consumer that accepts info log messages. - * @param logErrors Whether or not errors should be logged. - * @param logSentData Whether or not the sent data should be logged. - * @param logResponseStatusText Whether or not the response status text should be logged. - * @param skipRelocateCheck Whether or not the relocate check should be skipped. - */ - public MetricsBase( - String platform, - String serverUuid, - int serviceId, - boolean enabled, - Consumer appendPlatformDataConsumer, - Consumer appendServiceDataConsumer, - Consumer submitTaskConsumer, - Supplier checkServiceEnabledSupplier, - BiConsumer errorLogger, - Consumer infoLogger, - boolean logErrors, - boolean logSentData, - boolean logResponseStatusText, - boolean skipRelocateCheck) { - ScheduledThreadPoolExecutor scheduler = - new ScheduledThreadPoolExecutor( - 1, - task -> { - Thread thread = new Thread(task, "bStats-Metrics"); - thread.setDaemon(true); - return thread; - }); - // We want delayed tasks (non-periodic) that will execute in the future to be - // cancelled when the scheduler is shutdown. - // Otherwise, we risk preventing the server from shutting down even when - // MetricsBase#shutdown() is called - scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); - this.scheduler = scheduler; - this.platform = platform; - this.serverUuid = serverUuid; - this.serviceId = serviceId; - this.enabled = enabled; - this.appendPlatformDataConsumer = appendPlatformDataConsumer; - this.appendServiceDataConsumer = appendServiceDataConsumer; - this.submitTaskConsumer = submitTaskConsumer; - this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; - this.errorLogger = errorLogger; - this.infoLogger = infoLogger; - this.logErrors = logErrors; - this.logSentData = logSentData; - this.logResponseStatusText = logResponseStatusText; - if (!skipRelocateCheck) { - checkRelocation(); - } - if (enabled) { - // WARNING: Removing the option to opt-out will get your plugin banned from - // bStats - startSubmitting(); - } - } - - public void addCustomChart(CustomChart chart) { - this.customCharts.add(chart); - } - - public void shutdown() { - scheduler.shutdown(); - } - - private void startSubmitting() { - final Runnable submitTask = - () -> { - if (!enabled || !checkServiceEnabledSupplier.get()) { - // Submitting data or service is disabled - scheduler.shutdown(); - return; - } - if (submitTaskConsumer != null) { - submitTaskConsumer.accept(this::submitData); - } else { - this.submitData(); - } - }; - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven - // distribution of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into - // the initial and second delay. - // WARNING: You must not modify and part of this Metrics class, including the - // submit delay or frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just - // don't do it! - long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); - long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); - scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); - scheduler.scheduleAtFixedRate( - submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); - } - - private void submitData() { - final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); - appendPlatformDataConsumer.accept(baseJsonBuilder); - final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); - appendServiceDataConsumer.accept(serviceJsonBuilder); - JsonObjectBuilder.JsonObject[] chartData = - customCharts.stream() - .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) - .filter(Objects::nonNull) - .toArray(JsonObjectBuilder.JsonObject[]::new); - serviceJsonBuilder.appendField("id", serviceId); - serviceJsonBuilder.appendField("customCharts", chartData); - baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); - baseJsonBuilder.appendField("serverUUID", serverUuid); - baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); - JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); - scheduler.execute( - () -> { - try { - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logErrors) { - errorLogger.accept("Could not submit bStats metrics data", e); - } - } - }); - } - - private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { - if (logSentData) { - infoLogger.accept("Sent bStats metrics data: " + data.toString()); - } - String url = String.format(REPORT_URL, platform); - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "Metrics-Service/1"); - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - if (logResponseStatusText) { - infoLogger.accept("Sent data to bStats and received response: " + builder); - } - } - - /** Checks that the class was properly relocated. */ - private void checkRelocation() { - // You can use the property to disable the check in your test environment - if (System.getProperty("bstats.relocatecheck") == null - || !System.getProperty("bstats.relocatecheck").equals("false")) { - // Maven's Relocate is clever and changes strings, too. So we have to use this - // little "trick" ... :D - final String defaultPackage = - new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); - final String examplePackage = - new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure no one just copy & pastes the example and uses the wrong - // package names - if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) - || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - /** - * Gzips the given string. - * - * @param str The string to gzip. - * @return The gzipped string. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - } - - public static class DrilldownPie extends CustomChart { - - private final Callable>> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public DrilldownPie(String chartId, Callable>> callable) { - super(chartId); - this.callable = callable; - } - - @Override - public JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SimpleBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimpleBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SimplePie extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SimplePie(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public static class AdvancedPie extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedPie(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class MultiLineChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public MultiLineChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue() == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class AdvancedBarChart extends CustomChart { - - private final Callable> callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public AdvancedBarChart(String chartId, Callable> callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean allSkipped = true; - for (Map.Entry entry : map.entrySet()) { - if (entry.getValue().length == 0) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public static class SingleLineChart extends CustomChart { - - private final Callable callable; - - /** - * Class constructor. - * - * @param chartId The id of the chart. - * @param callable The callable which is used to request the chart data. - */ - public SingleLineChart(String chartId, Callable callable) { - super(chartId); - this.callable = callable; - } - - @Override - protected JsonObjectBuilder.JsonObject getChartData() throws Exception { - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - public abstract static class CustomChart { - - private final String chartId; - - protected CustomChart(String chartId) { - if (chartId == null) { - throw new IllegalArgumentException("chartId must not be null"); - } - this.chartId = chartId; - } - - public JsonObjectBuilder.JsonObject getRequestJsonObject( - BiConsumer errorLogger, boolean logErrors) { - JsonObjectBuilder builder = new JsonObjectBuilder(); - builder.appendField("chartId", chartId); - try { - JsonObjectBuilder.JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - builder.appendField("data", data); - } catch (Throwable t) { - if (logErrors) { - errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return builder.build(); - } - - protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; - } - - /** - * An extremely simple JSON builder. - * - *

While this class is neither feature-rich nor the most performant one, it's sufficient enough - * for its use-case. - */ - public static class JsonObjectBuilder { - - private StringBuilder builder = new StringBuilder(); - - private boolean hasAtLeastOneField = false; - - public JsonObjectBuilder() { - builder.append("{"); - } - - /** - * Appends a null field to the JSON. - * - * @param key The key of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendNull(String key) { - appendFieldUnescaped(key, "null"); - return this; - } - - /** - * Appends a string field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String value) { - if (value == null) { - throw new IllegalArgumentException("JSON value must not be null"); - } - appendFieldUnescaped(key, "\"" + escape(value) + "\""); - return this; - } - - /** - * Appends an integer field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int value) { - appendFieldUnescaped(key, String.valueOf(value)); - return this; - } - - /** - * Appends an object to the JSON. - * - * @param key The key of the field. - * @param object The object. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject object) { - if (object == null) { - throw new IllegalArgumentException("JSON object must not be null"); - } - appendFieldUnescaped(key, object.toString()); - return this; - } - - /** - * Appends a string array to the JSON. - * - * @param key The key of the field. - * @param values The string array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values) - .map(value -> "\"" + escape(value) + "\"") - .collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an integer array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an object array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends a field to the object. - * - * @param key The key of the field. - * @param escapedValue The escaped value of the field. - */ - private void appendFieldUnescaped(String key, String escapedValue) { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - if (key == null) { - throw new IllegalArgumentException("JSON key must not be null"); - } - if (hasAtLeastOneField) { - builder.append(","); - } - builder.append("\"").append(escape(key)).append("\":").append(escapedValue); - hasAtLeastOneField = true; - } - - /** - * Builds the JSON string and invalidates this builder. - * - * @return The built JSON string. - */ - public JsonObject build() { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - JsonObject object = new JsonObject(builder.append("}").toString()); - builder = null; - return object; - } - - /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. - * - *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. - * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). - * - * @param value The value to escape. - * @return The escaped value. - */ - private static String escape(String value) { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '"') { - builder.append("\\\""); - } else if (c == '\\') { - builder.append("\\\\"); - } else if (c <= '\u000F') { - builder.append("\\u000").append(Integer.toHexString(c)); - } else if (c <= '\u001F') { - builder.append("\\u00").append(Integer.toHexString(c)); - } else { - builder.append(c); - } - } - return builder.toString(); - } - - /** - * A super simple representation of a JSON object. - * - *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not - * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, - * JsonObject)}. - */ - public static class JsonObject { - - private final String value; - - private JsonObject(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - } - - /** - * A simple config for bStats. - * - *

This class is not used by every platform. - */ - public static class MetricsConfig { - - private final File file; - - private final boolean defaultEnabled; - - private String serverUUID; - - private boolean enabled; - - private boolean logErrors; - - private boolean logSentData; - - private boolean logResponseStatusText; - - private boolean didExistBefore = true; - - public MetricsConfig(File file, boolean defaultEnabled) throws IOException { - this.file = file; - this.defaultEnabled = defaultEnabled; - setupConfig(); - } - - public String getServerUUID() { - return serverUUID; - } - - public boolean isEnabled() { - return enabled; - } - - public boolean isLogErrorsEnabled() { - return logErrors; - } - - public boolean isLogSentDataEnabled() { - return logSentData; - } - - public boolean isLogResponseStatusTextEnabled() { - return logResponseStatusText; - } - - /** - * Checks whether the config file did exist before or not. - * - * @return If the config did exist before. - */ - public boolean didExistBefore() { - return didExistBefore; - } - - /** Creates the config file if it does not exist and read its content. */ - private void setupConfig() throws IOException { - if (!file.exists()) { - // Looks like it's the first time we create it (or someone deleted it). - didExistBefore = false; - writeConfig(); - } - readConfig(); - if (serverUUID == null) { - // Found a malformed config file with no UUID. Let's recreate it. - writeConfig(); - readConfig(); - } - } - - /** Creates a config file with teh default content. */ - private void writeConfig() throws IOException { - List configContent = new ArrayList<>(); - configContent.add( - "# bStats (https://bStats.org) collects some basic information for plugin authors, like"); - configContent.add( - "# how many people use their plugin and their total player count. It's recommended to keep"); - configContent.add( - "# bStats enabled, but if you're not comfortable with this, you can turn this setting off."); - configContent.add( - "# There is no performance penalty associated with having metrics enabled, and data sent to"); - configContent.add("# bStats is fully anonymous."); - configContent.add("# Learn more here: https://bstats.org/docs/server-owners"); - configContent.add("enabled=" + defaultEnabled); - configContent.add("server-uuid=" + UUID.randomUUID().toString()); - configContent.add("log-errors=false"); - configContent.add("log-sent-data=false"); - configContent.add("log-response-status-text=false"); - writeFile(file, configContent); - } - - /** Reads the content of the config file. */ - private void readConfig() throws IOException { - List lines = readFile(file); - if (lines == null) { - throw new AssertionError("Content of newly created file is null"); - } - enabled = getConfigValue("enabled", lines).map("true"::equals).orElse(true); - serverUUID = getConfigValue("server-uuid", lines).orElse(null); - logErrors = getConfigValue("log-errors", lines).map("true"::equals).orElse(false); - logSentData = getConfigValue("log-sent-data", lines).map("true"::equals).orElse(false); - logResponseStatusText = - getConfigValue("log-response-status-text", lines).map("true"::equals).orElse(false); - } - - /** - * Gets a config setting from the given list of lines of the file. - * - * @param key The key for the setting. - * @param lines The lines of the file. - * @return The value of the setting. - */ - private Optional getConfigValue(String key, List lines) { - return lines.stream() - .filter(line -> line.startsWith(key + "=")) - .map(line -> line.replaceFirst(Pattern.quote(key + "="), "")) - .findFirst(); - } - - /** - * Reads the text content of the given file. - * - * @param file The file to read. - * @return The lines of the given file. - */ - private List readFile(File file) throws IOException { - if (!file.exists()) { - return null; - } - try (FileReader fileReader = new FileReader(file); - BufferedReader bufferedReader = new BufferedReader(fileReader)) { - return bufferedReader.lines().collect(Collectors.toList()); - } - } - - /** - * Writes the given lines to the given file. - * - * @param file The file to write to. - * @param lines The lines to write. - */ - private void writeFile(File file, List lines) throws IOException { - if (!file.exists()) { - file.getParentFile().mkdirs(); - file.createNewFile(); - } - try (FileWriter fileWriter = new FileWriter(file); - BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { - for (String line : lines) { - bufferedWriter.write(line); - bufferedWriter.newLine(); - } - } - } - } -} From dbdf1a8409fc80f85f6590b3e7602db975b057b5 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 24 Feb 2026 14:52:38 -0800 Subject: [PATCH 16/31] JsonDocPrinter - fix missing context values when they extend another context --- .../skriptdev/skript/api/skript/docs/JsonDocPrinter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/docs/JsonDocPrinter.java b/src/main/java/com/github/skriptdev/skript/api/skript/docs/JsonDocPrinter.java index b78c4d0..d98a1ec 100644 --- a/src/main/java/com/github/skriptdev/skript/api/skript/docs/JsonDocPrinter.java +++ b/src/main/java/com/github/skriptdev/skript/api/skript/docs/JsonDocPrinter.java @@ -13,6 +13,7 @@ import io.github.syst3ms.skriptparser.lang.Effect; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.Structure; +import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.lang.base.ConditionalExpression; import io.github.syst3ms.skriptparser.lang.base.ExecutableExpression; import io.github.syst3ms.skriptparser.pattern.PatternElement; @@ -159,8 +160,10 @@ private void printEvents(BsonDocument mainDocs, SkriptRegistration registration) // CONTEXT VALUES List> valuesForThisEvent = new ArrayList<>(); allContextValues.forEach(contextValue -> { - if (event.getContexts().contains(contextValue.getContext())) { - valuesForThisEvent.add(contextValue); + for (Class context : event.getContexts()) { + if (contextValue.getContext().isAssignableFrom(context) && !valuesForThisEvent.contains(contextValue)) { + valuesForThisEvent.add(contextValue); + } } }); BsonArray contextValues = eventDoc.getArray("context values", new BsonArray()); From 8fae45ed31a27a2b43c74d4e42701838b172e335 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 24 Feb 2026 14:53:59 -0800 Subject: [PATCH 17/31] TypesAssetStore - missing docs for particle --- .../skript/plugin/elements/types/TypesAssetStore.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java index 46e6a6a..bff3f1b 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java @@ -135,6 +135,9 @@ public Item deserialize(@NotNull Gson gson, @NotNull JsonElement element) { .toStringFunction(ItemQuality::getId) .register(); reg.newAssetStoreType(ParticleSystem.class, ParticleSystem.getAssetMap(), "particle", "particle@s") + .name("Particle") + .description("Represents the types of particles in the game.") + .since("INSERT VERSION") .register(); reg.newAssetStoreType(Projectile.class, Projectile.getAssetMap(), "projectile", "projectile@s") .name("Projectile") From 7e043d8bcfa592056347c154b5d3f1d108115bb1 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 24 Feb 2026 15:05:23 -0800 Subject: [PATCH 18/31] EvtPlayerUseBlock - add example --- .../plugin/elements/events/player/EvtPlayerUseBlock.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/player/EvtPlayerUseBlock.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/player/EvtPlayerUseBlock.java index 5b71ef2..d5c23a2 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/player/EvtPlayerUseBlock.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/player/EvtPlayerUseBlock.java @@ -40,6 +40,10 @@ public static void register(SkriptRegistration reg) { .name("Player Use Block") .description("Called when a player uses a block.", "Pre is cancellable, post is not.") + .examples("on pre player use block:", + "\tif \"%event-blocktype%\" contains \"Bed\":", + "\t\tcancel event", + "\t\tsend \"No sleepy time for you!\" to player") .since("1.0.0") .register(); From b9af655480b5c4453bf7a2be3f406b0a4f16437f Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 24 Feb 2026 15:05:45 -0800 Subject: [PATCH 19/31] EffCancelEvent - fix wrong check --- .../plugin/elements/effects/other/EffCancelEvent.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/other/EffCancelEvent.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/other/EffCancelEvent.java index f2dae0a..d662372 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/other/EffCancelEvent.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/other/EffCancelEvent.java @@ -32,11 +32,11 @@ public boolean init(Expression @NotNull [] expressions, int matchedPattern, P } this.cancel = matchedPattern == 0; for (Class currentContext : parseContext.getParserState().getCurrentContexts()) { - if (!CancellableContext.class.isAssignableFrom(currentContext)) { - parseContext.getLogger().error("This event cannot be cancelled", ErrorType.SEMANTIC_ERROR); - return false; + if (CancellableContext.class.isAssignableFrom(currentContext)) { + return true; } } + parseContext.getLogger().error("This event cannot be cancelled", ErrorType.SEMANTIC_ERROR); return true; } From fc4502621a24c7a3f63d3fb15eaacb02d27d85f2 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Wed, 25 Feb 2026 07:51:33 -0800 Subject: [PATCH 20/31] BstatsMetrics - add plugin version/addons --- .../skript/plugin/BstatsMetrics.java | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java b/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java index 62e1702..25dd23d 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java @@ -1,11 +1,61 @@ package com.github.skriptdev.skript.plugin; +import com.github.skriptdev.skript.api.skript.addon.HySkriptAddon; +import com.hypixel.hytale.common.semver.Semver; +import io.github.syst3ms.skriptparser.registration.SkriptAddon; +import org.bstats.charts.DrilldownPie; import org.bstats.hytale.Metrics; +import java.util.HashMap; +import java.util.Map; + public class BstatsMetrics { - public static void registerMetrics(HySk plugin) { + static void registerMetrics(HySk plugin) { Metrics metrics = new Metrics(plugin, 29735); + + // Plugin Version Drilldown: x.y -> x.y.z + metrics.addCustomChart(new DrilldownPie("plugin_version_drilldown_pie", () -> { + Semver version = plugin.getManifest().getVersion(); + DrilldownMap map = new DrilldownMap(); + map.put(version.getMajor() + "." + version.getMinor(), version.toString()); + return map.getMap(); + })); + + // Addons Drilldown: name -> version + metrics.addCustomChart(new DrilldownPie("addons_drilldown_pie", () -> { + DrilldownMap map = new DrilldownMap(); + + for (SkriptAddon addon : SkriptAddon.getAddons()) { + if (!(addon instanceof HySkriptAddon hySkriptAddon)) continue; + + String addonName = hySkriptAddon.getAddonName(); + String version = hySkriptAddon.getManifest().getVersion(); + map.put(addonName, version); + } + + return map.getMap(); + })); + } + + private static class DrilldownMap { + private final Map> map = new HashMap<>(); + + public DrilldownMap() { + } + + public void put(String upperLabel, String lowerLabel) { + put(upperLabel, lowerLabel, 1); + } + + public void put(String upperLabel, String lowerLabel, int weight) { + this.map.computeIfAbsent(upperLabel, _ -> new HashMap<>()) + .put(lowerLabel, weight); + } + + public Map> getMap() { + return this.map; + } } } From 69ae65aee916ab36a7d229364c139b1a6bd8a519 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Wed, 25 Feb 2026 08:32:32 -0800 Subject: [PATCH 21/31] EntityUtils - change naming of put comp --- .../skript/api/hytale/utils/EntityUtils.java | 22 +++++++++++++++++-- .../elements/effects/EffectHandler.java | 2 ++ .../elements/effects/entity/EffRide.java | 2 +- .../elements/effects/entity/EffTeleport.java | 2 +- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java index 82db96a..5843853 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java @@ -142,7 +142,7 @@ public static void setNameplateName(Entity entity, @Nullable String name) { } /** - * Put a component on an Entity. + * Add a component on an Entity. * * @param entity Entity to add component to * @param type Type of component to add @@ -151,7 +151,7 @@ public static void setNameplateName(Entity entity, @Nullable String name) { * @param Type of component */ @SuppressWarnings("unchecked") - public static > void putComponent(Entity entity, ComponentType type, Component component) { + public static > void addComponent(Entity entity, ComponentType type, Component component) { Ref reference = (Ref) entity.getReference(); if (reference == null) { throw new IllegalStateException("Entity '" + entity + "' does not have a reference"); @@ -159,6 +159,24 @@ public static > void putComponent(Entity entity, C reference.getStore().addComponent(reference, type, (T) component); } + /** + * Put a component on an Entity. + * + * @param entity Entity to put component on + * @param type Type of component to put + * @param component Component to put + * @param EntityStore Type + * @param Type of component + */ + @SuppressWarnings("unchecked") + public static > void putComponent(Entity entity, ComponentType type, Component component) { + Ref reference = (Ref) entity.getReference(); + if (reference == null) { + throw new IllegalStateException("Entity '" + entity + "' does not have a reference"); + } + reference.getStore().putComponent(reference, type, (T) component); + } + /** * Try to remove a component from an Entity. * diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java index a6c125e..be2c59d 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java @@ -6,6 +6,7 @@ import com.github.skriptdev.skript.plugin.elements.effects.entity.EffDamage; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffDropItem; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffEntityEffect; +import com.github.skriptdev.skript.plugin.elements.effects.entity.EffEntityModel; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffFreeze; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffKill; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffRemoveStatModifier; @@ -43,6 +44,7 @@ public static void register(SkriptRegistration registration) { EffDamage.register(registration); EffDropItem.register(registration); EffEntityEffect.register(registration); + EffEntityModel.register(registration); EffFreeze.register(registration); EffInteraction.register(registration); EffKill.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffRide.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffRide.java index 663e497..b60a18f 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffRide.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffRide.java @@ -80,7 +80,7 @@ protected void execute(@NotNull TriggerContext ctx) { Ref vehicleRef = entity.getReference(); MountedComponent comp = new MountedComponent(vehicleRef, offset, MountController.Minecart); - EntityUtils.putComponent(passenger, MountedComponent.getComponentType(), comp); + EntityUtils.addComponent(passenger, MountedComponent.getComponentType(), comp); } else if (vehicle instanceof Block block) { Ref passengerRef = passenger.getReference(); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java index c3051ee..6b138ac 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java @@ -48,7 +48,7 @@ protected void execute(@NotNull TriggerContext ctx) { for (Entity entity : entities) { Runnable tpRunnable = () -> { Teleport teleport = Teleport.createForPlayer(worldTp, location.getPosition(), location.getRotation()); - EntityUtils.putComponent(entity, Teleport.getComponentType(), teleport); + EntityUtils.addComponent(entity, Teleport.getComponentType(), teleport); }; World world = entity.getWorld(); From f12d6357575985607a2623e2d1610a0a39881b27 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Wed, 25 Feb 2026 09:24:22 -0800 Subject: [PATCH 22/31] Add model stuff --- .../effects/entity/EffEntityModel.java | 93 +++++++++++++++++++ .../expressions/ExpressionHandler.java | 2 + .../expressions/entity/ExprEntityModel.java | 90 ++++++++++++++++++ .../elements/types/TypesAssetStore.java | 10 +- 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityModel.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityModel.java diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityModel.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityModel.java new file mode 100644 index 0000000..e9865b4 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityModel.java @@ -0,0 +1,93 @@ +package com.github.skriptdev.skript.plugin.elements.effects.entity; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import io.github.syst3ms.skriptparser.lang.Effect; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +public class EffEntityModel extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffEntityModel.class, "apply model [component] %modelasset% to %entities%", + "apply model [component] %modelasset% with scale %number% to %entities%") + .name("Entity Model") + .description("Apply the model of an Entity/Player.", + "Scale is a float value, think of 1 as 100%.", + "To remove/reset use the entity model expression.") + .examples("apply model sheep to player", + "apply model Skeleton_Sand_Archer with scale 2 to player") + .since("INSERT VERSION") + .register(); + } + + private Expression modelAsset; + private Expression scale; + private Expression entities; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + if (matchedPattern == 0) { + this.modelAsset = (Expression) expressions[0]; + this.entities = (Expression) expressions[1]; + } else if (matchedPattern == 1) { + this.modelAsset = (Expression) expressions[0]; + this.scale = (Expression) expressions[1]; + this.entities = (Expression) expressions[2]; + } + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + ModelAsset modelAsset = this.modelAsset.getSingle(ctx).orElse(null); + if (modelAsset == null) return; + + float scale = 1.0f; + if (this.scale != null) { + Number number = this.scale.getSingle(ctx).orElse(null); + if (number != null) { + // Must be greater than 0.0f + scale = Math.max(0.000001f, number.floatValue()); + } + } + + for (Entity entity : this.entities.getArray(ctx)) { + Model model = Model.createScaledModel(modelAsset, scale); + ModelComponent component = new ModelComponent(model); + EntityUtils.putComponent(entity, ModelComponent.getComponentType(), component); + if (entity instanceof NPCEntity npcEntity) { + Ref ref = npcEntity.getReference(); + assert ref != null; + Store store = ref.getStore(); + Role role = npcEntity.getRole(); + assert role != null; + role.updateMotionControllers(ref, model, model.getBoundingBox(), store); + } + } + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String entity = this.entities.toString(ctx, debug); + if (this.scale != null) { + String scale = this.scale.toString(ctx, debug); + return "apply model " + this.modelAsset.toString(ctx, debug) + " with scale " + scale + " to " + entity; + } else { + return "apply model " + this.modelAsset.toString(ctx, debug) + " to " + entity; + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java index 4a6b9de..c6ddf17 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java @@ -15,6 +15,7 @@ import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprActiveSlot; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntitiesInRadius; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntityComponents; +import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntityModel; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntityStamina; import com.github.skriptdev.skript.plugin.elements.expressions.item.ExprItemQuality; import com.github.skriptdev.skript.plugin.elements.expressions.other.ExprValueWithin; @@ -110,6 +111,7 @@ public static void register(SkriptRegistration registration) { ExprEntityComponents.register(registration); ExprEntityHeadRotation.register(registration); ExprEntityHealth.register(registration); + ExprEntityModel.register(registration); ExprEntityScale.register(registration); ExprEntityStamina.register(registration); ExprEntityStat.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityModel.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityModel.java new file mode 100644 index 0000000..1736695 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityModel.java @@ -0,0 +1,90 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.entity; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.asset.type.model.config.Model; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; +import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent; +import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.PropertyExpression; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; +import io.github.syst3ms.skriptparser.util.CollectionUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class ExprEntityModel extends PropertyExpression { + + public static void register(SkriptRegistration reg) { + reg.newPropertyExpression(ExprEntityModel.class, ModelAsset.class, + "model [component]", "entities") + .name("Entity Model") + .description("Get/resete the model of an entity.") + .examples("set {_model} to model component of player", + "reset model component of player") + .since("INSERT VERSION") + .register(); + } + + @Override + public @Nullable ModelAsset getProperty(@NotNull Entity entity) { + ModelComponent component = EntityUtils.getComponent(entity, ModelComponent.getComponentType()); + if (component == null) return null; + + String model = component.getModel().getModel(); + return ModelAsset.getAssetMap().getAsset(model); + } + + @Override + public Optional[]> acceptsChange(@NotNull ChangeMode mode) { + if (mode == ChangeMode.RESET || mode == ChangeMode.DELETE) { + return CollectionUtils.optionalArrayOf(ModelAsset.class); + } + return Optional.empty(); + } + + @Override + public void change(@NotNull TriggerContext ctx, @NotNull ChangeMode changeMode, Object @NotNull [] changeWith) { + for (Entity entity : getOwner().getArray(ctx)) { + if (entity instanceof Player player) { + resetPlayerModel(player); + } else { + resetEntityModel(entity); + } + } + } + + private void resetPlayerModel(Player player) { + PlayerSkinComponent playerSkinComponent = EntityUtils.getComponent(player, PlayerSkinComponent.getComponentType()); + assert playerSkinComponent != null; + + Model newModel = CosmeticsModule.get().createModel(playerSkinComponent.getPlayerSkin()); + ModelComponent component = new ModelComponent(newModel); + EntityUtils.putComponent(player, ModelComponent.getComponentType(), component); + playerSkinComponent.setNetworkOutdated(); + } + + private void resetEntityModel(Entity entity) { + EntityUtils.tryRemoveComponent(entity, ModelComponent.getComponentType()); + if (!(entity instanceof NPCEntity npcEntity)) return; + + Role role = npcEntity.getRole(); + assert role != null; + + Ref reference = npcEntity.getReference(); + assert reference != null; + Store store = reference.getStore(); + role.updateMotionControllers(reference, null, null, store); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java index bff3f1b..704a0a7 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesAssetStore.java @@ -17,6 +17,7 @@ import com.hypixel.hytale.server.core.asset.type.item.config.Item; import com.hypixel.hytale.server.core.asset.type.item.config.ItemQuality; import com.hypixel.hytale.server.core.asset.type.item.config.ResourceType; +import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset; import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem; import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile; import com.hypixel.hytale.server.core.asset.type.soundevent.config.SoundEvent; @@ -127,13 +128,18 @@ public Item deserialize(@NotNull Gson gson, @NotNull JsonElement element) { } }) .register(); - reg.newAssetStoreType(ItemQuality.class, ItemQuality.getAssetMap(), - "itemquality", "itemQuality@s") + reg.newAssetStoreType(ItemQuality.class, ItemQuality.getAssetMap(), "itemquality", "itemQuality@s") .name("Item Quality") .description("Represents the quality of items in the game.") .since("1.1.0") .toStringFunction(ItemQuality::getId) .register(); + reg.newAssetStoreType(ModelAsset.class, ModelAsset.getAssetMap(), "modelasset", "modelAsset@s") + .name("Model Asset") + .description("Represents the types of models in the game.") + .since("INSERT VERSION") + .toStringFunction(ModelAsset::getId) + .register(); reg.newAssetStoreType(ParticleSystem.class, ParticleSystem.getAssetMap(), "particle", "particle@s") .name("Particle") .description("Represents the types of particles in the game.") From 172fb99901b9ed526a0a576f84fb7e6db60d619b Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Wed, 25 Feb 2026 18:27:50 -0800 Subject: [PATCH 23/31] Ref - add some ref stuff --- .../hytale/utils/EntityReferenceUtils.java | 102 +++++++++++ .../skript/api/hytale/utils/EntityUtils.java | 10 +- .../skript/api/skript/event/RefContext.java | 10 ++ .../elements/effects/entity/EffTeleport.java | 50 ++++-- .../plugin/elements/events/EventHandler.java | 12 +- .../expressions/ExpressionHandler.java | 10 ++ .../ref/ExprComponentsInRadius.java | 159 ++++++++++++++++++ .../expressions/ref/ExprEntityRef.java | 47 ++++++ .../expressions/ref/ExprRefComponent.java | 70 ++++++++ .../expressions/ref/ExprRefInRadius.java | 108 ++++++++++++ .../plugin/elements/sections/SecDropItem.java | 31 ++-- .../plugin/elements/types/TypesWorld.java | 35 ++++ 12 files changed, 605 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityReferenceUtils.java create mode 100644 src/main/java/com/github/skriptdev/skript/api/skript/event/RefContext.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprComponentsInRadius.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprEntityRef.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefComponent.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefInRadius.java diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityReferenceUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityReferenceUtils.java new file mode 100644 index 0000000..ec790cd --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityReferenceUtils.java @@ -0,0 +1,102 @@ +package com.github.skriptdev.skript.api.hytale.utils; + +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.ComponentType; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.spatial.SpatialResource; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.modules.entity.EntityModule; +import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import it.unimi.dsi.fastutil.objects.ObjectList; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utilities for {@link EntityStore} {@link Ref References} + */ +@SuppressWarnings("unused") +public class EntityReferenceUtils { + + private static final List> TYPES = new ArrayList<>(); + private static final Map>, ReferenceType> TYPES_MAP = new HashMap<>(); + + public static final ReferenceType PLAYER = new ReferenceType<>( + "player", Player.class, Player.getComponentType()); + public static final ReferenceType NPC_ENTITY = new ReferenceType<>( + "npcentity", NPCEntity.class, NPCEntity.getComponentType()); + public static final ReferenceType ITEM = new ReferenceType<>( + "item", ItemComponent.class, ItemComponent.getComponentType()); + + public static List> getTypes() { + return TYPES; + } + + public static ReferenceType getType(Class> componentClass) { + return TYPES_MAP.get(componentClass); + } + + public static List> getRefsInSphere(@Nonnull Vector3d pos, double radius, @Nonnull Store store) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + EntityModule entityModule = EntityModule.get(); + SpatialResource, EntityStore> entities = store.getResource(entityModule.getEntitySpatialResourceType()); + entities.getSpatialStructure().collect(pos, (float) radius, results); + SpatialResource, EntityStore> players = store.getResource(entityModule.getPlayerSpatialResourceType()); + players.getSpatialStructure().collect(pos, (float) radius, results); + SpatialResource, EntityStore> items = store.getResource(entityModule.getItemSpatialResourceType()); + items.getSpatialStructure().collect(pos, (float) radius, results); + return results; + } + + @Nonnull + public static List> getRefsInBox(@Nonnull Vector3d min, @Nonnull Vector3d max, @Nonnull Store store) { + ObjectList> results = SpatialResource.getThreadLocalReferenceList(); + EntityModule entityModule = EntityModule.get(); + SpatialResource, EntityStore> entities = store.getResource(entityModule.getEntitySpatialResourceType()); + entities.getSpatialStructure().collectBox(min, max, results); + SpatialResource, EntityStore> players = store.getResource(entityModule.getPlayerSpatialResourceType()); + players.getSpatialStructure().collectBox(min, max, results); + SpatialResource, EntityStore> items = store.getResource(entityModule.getItemSpatialResourceType()); + items.getSpatialStructure().collectBox(min, max, results); + return results; + } + + public static class ReferenceType> { + + + private final String name; + private final Class componentClass; + private final ComponentType componentType; + + public ReferenceType(String name, Class componentClass, ComponentType componentType) { + this.name = name; + this.componentClass = componentClass; + this.componentType = componentType; + EntityReferenceUtils.TYPES.add(this); + EntityReferenceUtils.TYPES_MAP.put(componentClass, this); + } + + public String getName() { + return this.name; + } + + public Class getComponentClass() { + return this.componentClass; + } + + @SuppressWarnings("unchecked") + public @Nullable E getComponent(Ref ref) { + Store store = ref.getStore(); + return (E) store.getComponent(ref, this.componentType); + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java index 5843853..69c6d62 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java @@ -1,7 +1,6 @@ package com.github.skriptdev.skript.api.hytale.utils; import com.github.skriptdev.skript.api.skript.registration.NPCRegistry; -import com.github.skriptdev.skript.api.utils.Utils; import com.hypixel.hytale.component.AddReason; import com.hypixel.hytale.component.Component; import com.hypixel.hytale.component.ComponentType; @@ -225,8 +224,8 @@ public static > void tryRemoveComponent(Entity ent return store.getComponent(reference, MovementStatesComponent.getComponentType()); } - @SuppressWarnings({"DataFlowIssue", "deprecation"}) - public static @NotNull Pair dropItem(Store store, ItemStack itemStack, + @SuppressWarnings({"DataFlowIssue"}) + public static @NotNull Pair, ItemComponent> dropItem(Store store, ItemStack itemStack, Location location, Vector3f velocity, float pickupDelay) { if (itemStack.isEmpty() || !itemStack.isValid()) { return new Pair<>(null, null); @@ -246,9 +245,9 @@ public static > void tryRemoveComponent(Entity ent itemComponent.setPickupDelay(pickupDelay); } - store.addEntity(itemEntityHolder, AddReason.SPAWN); + Ref ref = store.addEntity(itemEntityHolder, AddReason.SPAWN); - return new Pair<>(com.hypixel.hytale.server.core.entity.EntityUtils.getEntity(itemEntityHolder), itemComponent); + return new Pair<>(ref, itemComponent); } public static boolean isTameable(NPCEntity npcEntity) { @@ -297,7 +296,6 @@ public static void setTamed(NPCEntity npcEntity, boolean tamed) { NPCRegistry.NPCRole parse = NPCRegistry.parse(roleName); - Utils.log("Changing role to %s", parse.name()); RoleChangeSystem.requestRoleChange(reference, currentRole, parse.index(), true, store); } diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/event/RefContext.java b/src/main/java/com/github/skriptdev/skript/api/skript/event/RefContext.java new file mode 100644 index 0000000..0a1bd75 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/event/RefContext.java @@ -0,0 +1,10 @@ +package com.github.skriptdev.skript.api.skript.event; + +import com.hypixel.hytale.component.Ref; +import io.github.syst3ms.skriptparser.lang.TriggerContext; + +public interface RefContext extends TriggerContext { + + Ref getRef(); + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java index 6b138ac..c5e5a0e 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTeleport.java @@ -1,11 +1,14 @@ package com.github.skriptdev.skript.plugin.elements.effects.entity; import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.vector.Location; import com.hypixel.hytale.server.core.entity.Entity; import com.hypixel.hytale.server.core.modules.entity.teleport.Teleport; import com.hypixel.hytale.server.core.universe.Universe; import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import io.github.syst3ms.skriptparser.lang.Effect; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.TriggerContext; @@ -17,46 +20,65 @@ public class EffTeleport extends Effect { public static void register(SkriptRegistration registration) { registration.newEffect(EffTeleport.class, - "teleport %entities% to %location%") + "teleport %refs/entities% to %location%") .name("Teleport") - .description("Teleport entities to a location.") + .description("Teleport entities/references to a location.") .examples("teleport all players to {_location}", "teleport player to first element of (respawn locations of player)") .since("1.0.0") .register(); } - private Expression entities; + private Expression entities; private Expression location; @SuppressWarnings("unchecked") @Override public boolean init(Expression[] expressions, int matchedPattern, @NotNull ParseContext parseContext) { - this.entities = (Expression) expressions[0]; + this.entities = expressions[0]; this.location = (Expression) expressions[1]; return true; } + @SuppressWarnings({"rawtypes", "unchecked", "ConstantValue"}) @Override protected void execute(@NotNull TriggerContext ctx) { - Entity[] entities = this.entities.getArray(ctx); + Object[] objects = this.entities.getArray(ctx); Location location = this.location.getSingle(ctx).orElse(null); - if (location == null || entities == null) return; + if (location == null || objects == null) return; - World worldTp = Universe.get().getWorld(location.getWorld()); + World worldTo = Universe.get().getWorld(location.getWorld()); + + for (Object object : objects) { + Teleport teleport = Teleport.createForPlayer(worldTo, location.getPosition(), location.getRotation()); + + World worldFrom; + if (object instanceof Entity entity) { + worldFrom = entity.getWorld(); + } else if (object instanceof Ref ref) { + Store store = ref.getStore(); + worldFrom = ((Store) store).getExternalData().getWorld(); + } else { + continue; + } - for (Entity entity : entities) { Runnable tpRunnable = () -> { - Teleport teleport = Teleport.createForPlayer(worldTp, location.getPosition(), location.getRotation()); - EntityUtils.addComponent(entity, Teleport.getComponentType(), teleport); + if (object instanceof Entity entity) { + EntityUtils.addComponent(entity, Teleport.getComponentType(), teleport); + } else if (object instanceof Ref ref) { + Store store = ref.getStore(); + store.addComponent(ref, Teleport.getComponentType(), teleport); + } }; - World world = entity.getWorld(); - assert world != null; - if (world.isInThread()) { + if (worldFrom == null) { + continue; + } + + if (worldFrom.isInThread()) { tpRunnable.run(); } else { - world.execute(tpRunnable); + worldFrom.execute(tpRunnable); } } } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java index b6eb377..ea645a9 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java @@ -6,6 +6,7 @@ import com.github.skriptdev.skript.api.skript.event.LocationContext; import com.github.skriptdev.skript.api.skript.event.PlayerContext; import com.github.skriptdev.skript.api.skript.event.PlayerRefContext; +import com.github.skriptdev.skript.api.skript.event.RefContext; import com.github.skriptdev.skript.api.skript.event.WorldContext; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; import com.github.skriptdev.skript.plugin.elements.events.entity.EvtEntityDamage; @@ -36,6 +37,7 @@ import com.github.skriptdev.skript.plugin.elements.events.server.EvtShutdown; import com.github.skriptdev.skript.plugin.elements.events.skript.EvtLoad; import com.github.skriptdev.skript.plugin.elements.events.world.EvtAtWorldTime; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.math.vector.Location; import com.hypixel.hytale.server.core.entity.entities.Player; import com.hypixel.hytale.server.core.universe.PlayerRef; @@ -104,14 +106,16 @@ private static void registerGlobalContexts(SkriptRegistration reg) { "player", PlayerContext::getPlayer) .setUsage(Usage.EXPRESSION_OR_ALONE) .register(); - reg.newSingleContextValue(WorldContext.class, World.class, - "world", WorldContext::getWorld) - .setUsage(Usage.EXPRESSION_OR_ALONE) - .register(); reg.addSingleContextValue(PlayerRefContext.class, PlayerRef.class, "playerref", PlayerRefContext::getPlayerRef); reg.addSingleContextValue(PlayerRefContext.class, PlayerRef.class, "player-ref", PlayerRefContext::getPlayerRef); + reg.addSingleContextValue(RefContext.class, Ref.class, + "ref", RefContext::getRef); + reg.newSingleContextValue(WorldContext.class, World.class, + "world", WorldContext::getWorld) + .setUsage(Usage.EXPRESSION_OR_ALONE) + .register(); } } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java index c6ddf17..8f747dc 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java @@ -74,6 +74,10 @@ import com.github.skriptdev.skript.plugin.elements.expressions.player.ExprPlayerPermissionGroup; import com.github.skriptdev.skript.plugin.elements.expressions.player.ExprPlayerPermissions; import com.github.skriptdev.skript.plugin.elements.expressions.player.ExprPlayerRespawnLocations; +import com.github.skriptdev.skript.plugin.elements.expressions.ref.ExprComponentsInRadius; +import com.github.skriptdev.skript.plugin.elements.expressions.ref.ExprEntityRef; +import com.github.skriptdev.skript.plugin.elements.expressions.ref.ExprRefComponent; +import com.github.skriptdev.skript.plugin.elements.expressions.ref.ExprRefInRadius; import com.github.skriptdev.skript.plugin.elements.expressions.server.ExprConsole; import com.github.skriptdev.skript.plugin.elements.expressions.server.ExprServerViewRadius; import com.github.skriptdev.skript.plugin.elements.expressions.world.ExprAllWorlds; @@ -176,6 +180,12 @@ public static void register(SkriptRegistration registration) { ExprPlayerPermissions.register(registration); ExprPlayerRespawnLocations.register(registration); + // REF + ExprComponentsInRadius.register(registration); + ExprEntityRef.register(registration); + ExprRefComponent.register(registration); + ExprRefInRadius.register(registration); + // SERVER ExprConsole.register(registration); ExprServerViewRadius.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprComponentsInRadius.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprComponentsInRadius.java new file mode 100644 index 0000000..65ddc39 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprComponentsInRadius.java @@ -0,0 +1,159 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.ref; + +import com.github.skriptdev.skript.api.hytale.utils.EntityReferenceUtils; +import com.github.skriptdev.skript.api.hytale.utils.EntityReferenceUtils.ReferenceType; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Location; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.Literal; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@SuppressWarnings("rawtypes") +public class ExprComponentsInRadius implements Expression { + + public static void register(SkriptRegistration reg) { + List typeNames = new ArrayList<>(); + for (ReferenceType type : EntityReferenceUtils.getTypes()) { + Class componentClass = type.getComponentClass(); + Optional> byClassExact = TypeManager.getByClassExact(componentClass); + byClassExact.ifPresent(value -> typeNames.add(value.getDocumentation().getName())); + } + String supportedTypes = String.join(", ", typeNames); + + reg.newExpression(ExprComponentsInRadius.class, Object.class, true, + "[all] %*type% components in radius %number% of %location%", + "[all] %*type% components within %location% and %location%") + .name("Components in Radius") + .description("Get all components of a specific type within a radius around a location.", + "Currently supported types: " + supportedTypes + ".") + .examples("loop ItemComponent components in radius 10 around player") + .since("INSERT VERSION") + .register(); + } + + private ReferenceType refType; + private Expression radius; + private Expression location; + private Expression loc1, loc2; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + Literal> literalType = (Literal>) expressions[0]; + Optional> typeOptional = literalType.getSingle(); + if (typeOptional.isEmpty()) return false; + + Type type = typeOptional.get(); + Class typeClass = type.getTypeClass(); + if (Component.class.isAssignableFrom(typeClass)) { + ReferenceType type1 = EntityReferenceUtils.getType((Class>) typeClass); + if (type1 != null) { + this.refType = type1; + } + } + + if (this.refType == null) { + parseContext.getLogger().error("There is no component type linked to '" + type.getBaseName() + "'", ErrorType.SEMANTIC_ERROR); + return false; + } + if (matchedPattern == 0) { + this.radius = (Expression) expressions[0]; + this.location = (Expression) expressions[1]; + } else if (matchedPattern == 1) { + this.loc1 = (Expression) expressions[0]; + this.loc2 = (Expression) expressions[1]; + } else { + return false; + } + return true; + } + + @SuppressWarnings("unchecked") + @Override + public Object[] getValues(@NotNull TriggerContext ctx) { + + List objects = new ArrayList<>(); + + if (this.location != null) { + Location loc = this.location.getSingle(ctx).orElse(null); + if (loc == null) return null; + + Number number = this.radius.getSingle(ctx).orElse(null); + if (number == null) return null; + + double radius = number.doubleValue(); + + String worldName = loc.getWorld(); + if (worldName == null) return null; + + World world = Universe.get().getWorld(worldName); + if (world == null) return null; + Store store = world.getEntityStore().getStore(); + + List> refsInSphere = EntityReferenceUtils.getRefsInSphere(loc.getPosition(), radius, store); + for (Ref entityStoreRef : refsInSphere) { + Component component = this.refType.getComponent(entityStoreRef); + if (component != null) { + objects.add(component); + } + } + } else { + Location loc1 = this.loc1.getSingle(ctx).orElse(null); + Location loc2 = this.loc2.getSingle(ctx).orElse(null); + if (loc1 == null || loc2 == null) return null; + + String worldName = loc1.getWorld(); + if (worldName == null) return null; + + World world = Universe.get().getWorld(worldName); + if (world == null) return null; + Store store = world.getEntityStore().getStore(); + + Vector3d min = Vector3d.min(loc1.getPosition(), loc2.getPosition()); + Vector3d max = Vector3d.max(loc1.getPosition(), loc2.getPosition()); + + List> refsInBox = EntityReferenceUtils.getRefsInBox(min, max, store); + for (Ref inBox : refsInBox) { + Component component = this.refType.getComponent(inBox); + if (component != null) { + objects.add(component); + } + } + } + + return objects.toArray(new Object[0]); + } + + @Override + public Class getReturnType() { + return this.refType.getComponentClass(); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + if (this.location != null) { + return "all " + this.refType.getName() + " components in radius " + this.radius.toString(ctx, debug) + + " around " + this.location.toString(ctx, debug); + } else { + return "all " + this.refType.getName() + " components within " + this.loc1.toString(ctx, debug) + " and " + + this.loc2.toString(ctx, debug); + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprEntityRef.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprEntityRef.java new file mode 100644 index 0000000..3294849 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprEntityRef.java @@ -0,0 +1,47 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.ref; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.Entity; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +@SuppressWarnings("rawtypes") +public class ExprEntityRef implements Expression { + + public static void register(SkriptRegistration reg) { + reg.newExpression(ExprEntityRef.class, Ref.class, true, + "(ref|reference) (of|from) %entity%") + .name("Entity Reference") + .description("Get the reference of an entity.") + .examples("set {_ref} to reference of player", + "set {_ref} to ref of event-entity") + .since("INSERT VERSION") + .register(); + } + + private Expression entity; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.entity = (Expression) expressions[0]; + return true; + } + + @Override + public Ref[] getValues(@NotNull TriggerContext ctx) { + Optional single = this.entity.getSingle(ctx); + return single.map(value -> new Ref[]{value.getReference()}).orElse(null); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "reference of " + this.entity.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefComponent.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefComponent.java new file mode 100644 index 0000000..bbe0a1a --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefComponent.java @@ -0,0 +1,70 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.ref; + +import com.github.skriptdev.skript.api.hytale.utils.EntityReferenceUtils; +import com.github.skriptdev.skript.api.hytale.utils.EntityReferenceUtils.ReferenceType; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Component; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class ExprRefComponent implements Expression { + + private static final List TO_STRINGS = new ArrayList<>(); + + public static void register(SkriptRegistration reg) { + List> types = EntityReferenceUtils.getTypes(); + + String[] patterns = new String[types.size()]; + for (int i = 0; i < types.size(); i++) { + ReferenceType referenceType = types.get(i); + patterns[i] = referenceType.getName() + " component of %ref%"; + TO_STRINGS.add(referenceType.getName() + " component of "); + } + reg.newExpression(ExprRefComponent.class, Object.class, true, patterns) + .name("Reference Component") + .description("Get the specified component of a reference.") + .examples("loop refs in radius 10 around player:", + "\tset {_item} to item component of loop-ref") + .since("INSERT VERSION") + .register(); + } + + private int pattern; + private Expression> refs; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.pattern = matchedPattern; + this.refs = (Expression>) expressions[0]; + return true; + } + + @Override + public Object[] getValues(@NotNull TriggerContext ctx) { + Ref ref = this.refs.getSingle(ctx).orElse(null); + if (ref == null) return null; + + ReferenceType referenceType = EntityReferenceUtils.getTypes().get(this.pattern); + Component component = referenceType.getComponent(ref); + return new Object[]{component}; + } + + @Override + public Class getReturnType() { + return EntityReferenceUtils.getTypes().get(this.pattern).getComponentClass(); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return TO_STRINGS.get(this.pattern) + this.refs.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefInRadius.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefInRadius.java new file mode 100644 index 0000000..35b736f --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ref/ExprRefInRadius.java @@ -0,0 +1,108 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.ref; + +import com.github.skriptdev.skript.api.hytale.utils.EntityReferenceUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Location; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("rawtypes") +public class ExprRefInRadius implements Expression { + + public static void register(SkriptRegistration reg) { + reg.newExpression(ExprRefInRadius.class, Ref.class, false, + "[all] (refs|references) in radius %number% (around|of) %location%", + "[all] (refs|references) within %location% and %location%") + .name("References in Radius/Cuboid") + .description("Get all references within a radius around a location or within a cuboid.") + .examples("loop refs in radius 10 around player:", + "\tif item component of loop-value is set:", + "\t\tteleport loop-ref to player") + .since("INSERT VERSION") + .register(); + } + + private Expression radius; + private Expression location; + private Expression loc1, loc2; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + if (matchedPattern == 0) { + this.radius = (Expression) expressions[0]; + this.location = (Expression) expressions[1]; + } else if (matchedPattern == 1) { + this.loc1 = (Expression) expressions[0]; + this.loc2 = (Expression) expressions[1]; + } else { + return false; + } + return true; + } + + @SuppressWarnings("unchecked") + @Override + public Ref[] getValues(@NotNull TriggerContext ctx) { + List> refs = new ArrayList<>(); + if (this.location != null) { + Location loc = this.location.getSingle(ctx).orElse(null); + if (loc == null) return null; + + Number number = this.radius.getSingle(ctx).orElse(null); + if (number == null) return null; + + double radius = number.doubleValue(); + + String worldName = loc.getWorld(); + if (worldName == null) return null; + + World world = Universe.get().getWorld(worldName); + if (world == null) return null; + Store store = world.getEntityStore().getStore(); + + List> refsInSphere = EntityReferenceUtils.getRefsInSphere(loc.getPosition(), radius, store); + refs.addAll(refsInSphere); + } else { + Location loc1 = this.loc1.getSingle(ctx).orElse(null); + Location loc2 = this.loc2.getSingle(ctx).orElse(null); + if (loc1 == null || loc2 == null) return null; + + String worldName = loc1.getWorld(); + if (worldName == null) return null; + + World world = Universe.get().getWorld(worldName); + if (world == null) return null; + Store store = world.getEntityStore().getStore(); + + Vector3d min = Vector3d.min(loc1.getPosition(), loc2.getPosition()); + Vector3d max = Vector3d.max(loc1.getPosition(), loc2.getPosition()); + + List> refsInBox = EntityReferenceUtils.getRefsInBox(min, max, store); + refs.addAll(refsInBox); + } + + return refs.toArray(new Ref[0]); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + if (this.location != null) { + return "all refs in radius " + this.radius.toString(ctx, debug) + " around " + this.location.toString(ctx, debug); + } else { + return "all refs within " + this.loc1.toString(ctx, debug) + " and " + this.loc2.toString(ctx, debug); + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecDropItem.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecDropItem.java index cdf323e..1f92bfb 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecDropItem.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/sections/SecDropItem.java @@ -1,12 +1,13 @@ package com.github.skriptdev.skript.plugin.elements.sections; import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.event.RefContext; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.vector.Location; import com.hypixel.hytale.math.vector.Vector3f; import com.hypixel.hytale.server.core.asset.type.item.config.Item; -import com.hypixel.hytale.server.core.entity.Entity; import com.hypixel.hytale.server.core.inventory.ItemStack; import com.hypixel.hytale.server.core.modules.entity.item.ItemComponent; import com.hypixel.hytale.server.core.universe.Universe; @@ -21,7 +22,6 @@ import io.github.syst3ms.skriptparser.util.Pair; import io.github.syst3ms.skriptparser.variables.Variables; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.time.Duration; import java.util.ArrayList; @@ -38,16 +38,15 @@ public static void register(SkriptRegistration reg) { "drop %item/itemstack% at %location% with velocity %vector3f% [and] [with] pickup delay %duration%") .name("Drop Item") .description("Drops the specified items.") - .examples("drop ingredient_poop at location of player", - "drop {_itemstack} at location of player with pickup delay 10 seconds", - "drop {_i} at location of player with velocity vector3f(0,1,0) and with pickup delay 5 seconds") + .examples("drop ingredient_poop at location of player:", + "drop {_itemstack} at location of player with pickup delay 10 seconds:", + "drop {_i} at location of player with velocity vector3f(0,1,0) and with pickup delay 5 seconds:", + "\tset {_ref} to event-ref") .since("1.0.0") .register(); reg.addSingleContextValue(ItemComponentContext.class, ItemComponent.class, "item-component", ItemComponentContext::getItemComponent); - reg.addSingleContextValue(ItemComponentContext.class, Entity.class, - "item-entity", ItemComponentContext::getEntity); } private Expression items; @@ -135,8 +134,8 @@ public Optional walk(@NotNull TriggerContext ctx) { setNext(null); Runnable dropRunnable = () -> { - Pair pair = EntityUtils.dropItem(store, itemStack, location, velocity, pickupDelay); - ItemComponentContext itemContext = new ItemComponentContext(pair.getFirst(), pair.getSecond()); + Pair, ItemComponent> pair = EntityUtils.dropItem(store, itemStack, location, velocity, pickupDelay); + ItemComponentContext itemContext = new ItemComponentContext(pair.getSecond(), pair.getFirst()); // Copy variables from the previous context into our section context Variables.copyLocalVariables(ctx, itemContext); @@ -166,28 +165,30 @@ public String toString(@NotNull TriggerContext ctx, boolean debug) { return "drop " + this.items.toString(ctx, debug) + " at " + this.location.toString(ctx, debug) + velocity + pickupDelay; } - public static class ItemComponentContext implements TriggerContext { + public static class ItemComponentContext implements RefContext { - private final @Nullable Entity entity; private final ItemComponent component; + private final Ref ref; - public ItemComponentContext(@Nullable Entity entity, ItemComponent component) { - this.entity = entity; + public ItemComponentContext(ItemComponent component, Ref ref) { this.component = component; + this.ref = ref; } public ItemComponent getItemComponent() { return this.component; } - public @Nullable Entity getEntity() { - return this.entity; + @Override + public Ref getRef() { + return this.ref; } @Override public String getName() { return "item component context"; } + } } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesWorld.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesWorld.java index e804c15..17412e6 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesWorld.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesWorld.java @@ -1,12 +1,19 @@ package com.github.skriptdev.skript.plugin.elements.types; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.component.Store; import com.hypixel.hytale.protocol.SoundCategory; import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeDoublePosition; import com.hypixel.hytale.server.core.command.system.arguments.types.RelativeIntPosition; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.worldgen.zone.Zone; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; +import io.github.syst3ms.skriptparser.types.changers.Changer; +import io.github.syst3ms.skriptparser.util.CollectionUtils; +import org.jetbrains.annotations.NotNull; public class TypesWorld { @@ -20,6 +27,34 @@ static void register(SkriptRegistration reg) { .toVariableNameFunction(worldChunk -> "chunk:" + worldChunk.getWorld().getName() + ":" + worldChunk.getX() + ":" + worldChunk.getZ()) .register(); + reg.newType(Ref.class, "ref", "ref@s") + .name("Reference") + .description("Represents a reference to different objects in a world, such as entities and chunk components.", + "References are the base of how Hytale holds objects in a world/chunk (store).") + .since("INSERT VERSION") + .toStringFunction(ref -> + String.format("Ref{store=%s, index=%s}", + ref.getStore().getClass().getSimpleName(), + ref.getIndex())) + .defaultChanger(new Changer<>() { + @Override + public Class[] acceptsChange(@NotNull ChangeMode mode) { + if (mode == ChangeMode.DELETE) { + return CollectionUtils.arrayOf(Ref.class); + } + return null; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void change(Ref @NotNull [] toChange, Object @NotNull [] changeWith, @NotNull ChangeMode mode) { + for (Ref ref : toChange) { + Store store = ref.getStore(); + store.removeEntity(ref, RemoveReason.REMOVE); + } + } + }) + .register(); reg.newType(RelativeDoublePosition.class, "relativeposition", "relativePosition@s") .name("Relative Position") .description("Represents a position relative to another position.") From fa6d2d19e04dce44b98778014e688dc3e095a5fa Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 26 Feb 2026 06:23:27 -0800 Subject: [PATCH 24/31] Block - add more methods --- .../skriptdev/skript/api/hytale/Block.java | 52 +++++++++++++++---- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java b/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java index d44c12c..e3a1de8 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/Block.java @@ -26,6 +26,7 @@ import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection; import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.github.syst3ms.skriptparser.util.color.Color; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -57,8 +58,12 @@ public Block(@NotNull Location location) { this(world, location.getPosition().toVector3i()); } + public long getChunkIndex() { + return ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ()); + } + public WorldChunk getChunk() { - return this.world.getChunk(ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ())); + return this.world.getChunk(getChunkIndex()); } public @NotNull BlockType getType() { @@ -75,6 +80,10 @@ public void setType(@NotNull BlockType type, int settings) { } } + public void updateChunk() { + getWorld().getNotificationHandler().updateChunk(getChunkIndex()); + } + /** * Set the rotation of this block. * @@ -125,9 +134,34 @@ private int getIntFromRotation(Rotation rotation) { }; } + public Color getTint() { + BlockChunk blockChunk = getChunk().getBlockChunk(); + if (blockChunk == null) return null; + + int x = this.pos.getX() % 32; + int z = this.pos.getZ() % 32; + int tintInt = blockChunk.getTint(x, z); + return Color.of(tintInt); + } + + public void setTint(Color color) { + setTint(color, true); + } + + public void setTint(Color color, boolean updateChunk) { + BlockChunk blockChunk = getChunk().getBlockChunk(); + if (blockChunk == null) return; + + int x = this.pos.getX() % 32; + int z = this.pos.getZ() % 32; + blockChunk.setTint(x, z, color.toJavaColor().getRGB()); + if (updateChunk) { + updateChunk(); + } + } + public byte getFluidLevel() { - long index = ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ()); - WorldChunk chunk = this.world.getChunk(index); + WorldChunk chunk = this.world.getChunk(getChunkIndex()); if (chunk == null) return 0; Ref columnRef = chunk.getReference(); @@ -147,8 +181,7 @@ public byte getFluidLevel() { } public void setFluidLevel(byte level) { - long index = ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ()); - this.world.getChunkAsync(index).thenApply((chunk) -> { + this.world.getChunkAsync(getChunkIndex()).thenApply((chunk) -> { Ref columnRef = chunk.getReference(); Store store = columnRef.getStore(); ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType()); @@ -181,8 +214,7 @@ public Fluid getFluid() { } public void setFluid(@NotNull Fluid fluid, @Nullable Integer level) { - long index = ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ()); - this.world.getChunkAsync(index).thenApply((chunk) -> { + this.world.getChunkAsync(getChunkIndex()).thenApply((chunk) -> { Ref columnRef = chunk.getReference(); Store store = columnRef.getStore(); ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType()); @@ -217,7 +249,7 @@ public void breakBlock(int settings) { } public void damage(@Nullable LivingEntity performer, @Nullable ItemStack itemStack, float damage) { - WorldChunk chunk = this.world.getChunk(ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ())); + WorldChunk chunk = this.world.getChunk(getChunkIndex()); if (chunk == null) return; Ref ref = chunk.getReference(); @@ -252,7 +284,7 @@ public void damage(@Nullable LivingEntity performer, @Nullable ItemStack itemSta } public float getBlockHealth() { - WorldChunk chunk = this.world.getChunk(ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ())); + WorldChunk chunk = getChunk(); if (chunk == null) return 0; Ref ref = chunk.getReference(); @@ -265,7 +297,7 @@ public float getBlockHealth() { } public void setBlockHealth(float health) { - WorldChunk chunk = this.world.getChunk(ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ())); + WorldChunk chunk = getChunk(); if (chunk == null) return; Ref ref = chunk.getReference(); From 922e9743bb3d42c3782c1e7b345c2991bf07388c Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 26 Feb 2026 06:23:56 -0800 Subject: [PATCH 25/31] ExprBlockTint - add --- .../expressions/ExpressionHandler.java | 2 + .../expressions/block/ExprBlockTint.java | 66 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockTint.java diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java index 8f747dc..2f413cc 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java @@ -7,6 +7,7 @@ import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockIterator; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockRotation; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockSphere; +import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockTint; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockTypeAtLocation; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockTypeOfBlock; import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprBlockFluidLevel; @@ -104,6 +105,7 @@ public static void register(SkriptRegistration registration) { ExprBlockIterator.register(registration); ExprBlockRotation.register(registration); ExprBlockSphere.register(registration); + ExprBlockTint.register(registration); ExprBlockTypeAtLocation.register(registration); ExprBlockTypeOfBlock.register(registration); ExprHighestBlock.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockTint.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockTint.java new file mode 100644 index 0000000..082d256 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/block/ExprBlockTint.java @@ -0,0 +1,66 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.block; + +import com.github.skriptdev.skript.api.hytale.Block; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.universe.world.World; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.PropertyExpression; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; +import io.github.syst3ms.skriptparser.util.CollectionUtils; +import io.github.syst3ms.skriptparser.util.color.Color; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ExprBlockTint extends PropertyExpression { + + public static void register(SkriptRegistration reg) { + reg.newPropertyExpression(ExprBlockTint.class, Color.class, "block tint", "blocks") + .name("Block Tint") + .description("Get/set the tint of a block.", + "Do note this is for the entire block column, not actually per block.") + .examples("set {_color} to block tint of target block of player", + "set block tint of block at player's location to red", + "set block tint of blocks in radius 3 around player to color from hex \"#3caba9\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public @Nullable Color getProperty(@NotNull Block block) { + return block.getTint(); + } + + @Override + public Optional[]> acceptsChange(@NotNull ChangeMode mode) { + if (mode == ChangeMode.SET) return CollectionUtils.optionalArrayOf(Color.class); + return Optional.empty(); + } + + @SuppressWarnings("ConstantValue") + @Override + public void change(@NotNull TriggerContext ctx, @NotNull ChangeMode changeMode, Object @NotNull [] changeWith) { + if (changeWith == null || changeWith.length == 0 || !(changeWith[0] instanceof Color color)) return; + + Map> chunkIndexes = new HashMap<>(); + for (Block block : getOwner().getArray(ctx)) { + block.setTint(color, false); + List longs = chunkIndexes.computeIfAbsent(block.getWorld(), _ -> new ArrayList<>()); + if (!longs.contains(block.getChunkIndex())) { + longs.add(block.getChunkIndex()); + } + } + // Update all chunks at once rather than for each block + chunkIndexes.forEach((world, indexes) -> { + for (Long index : indexes) { + world.getNotificationHandler().updateChunk(index); + } + }); + } + +} From 72e5e3232c0d471ece9c79800b471afbb6c2684a Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 27 Feb 2026 16:02:43 -0800 Subject: [PATCH 26/31] Attitude and locked target --- .../skript/api/hytale/utils/EntityUtils.java | 25 ++++ .../elements/effects/EffectHandler.java | 14 ++- .../effects/entity/EffEntityAttitude.java | 104 +++++++++++++++++ .../expressions/ExpressionHandler.java | 4 + .../entity/ExprEntityAttitude.java | 64 ++++++++++ .../expressions/entity/ExprLockedTarget.java | 109 ++++++++++++++++++ .../plugin/elements/types/TypesEntity.java | 8 +- 7 files changed, 321 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityAttitude.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityAttitude.java create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprLockedTarget.java diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java index 69c6d62..d0518e4 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/EntityUtils.java @@ -23,6 +23,7 @@ import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.npc.entities.NPCEntity; import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.MarkedEntitySupport; import com.hypixel.hytale.server.npc.systems.RoleChangeSystem; import io.github.syst3ms.skriptparser.util.Pair; import org.jetbrains.annotations.NotNull; @@ -299,4 +300,28 @@ public static void setTamed(NPCEntity npcEntity, boolean tamed) { RoleChangeSystem.requestRoleChange(reference, currentRole, parse.index(), true, store); } + public static void clearMarkedEntity(NPCEntity npcEntity) { + clearMarkedEntity(npcEntity, null); + } + + public static void clearMarkedEntity(NPCEntity npcEntity, @Nullable Entity target) { + Role role = npcEntity.getRole(); + assert role != null; + MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport(); + if (target != null) { + Ref reference = target.getReference(); + assert reference != null; + + for (int i = 0; i < markedEntitySupport.getEntityTargets().length; i++) { + if (markedEntitySupport.hasMarkedEntity(reference, i)) { + markedEntitySupport.clearMarkedEntity(i); + } + } + } else { + for (int i = 0; i < markedEntitySupport.getMarkedEntitySlotCount(); i++) { + markedEntitySupport.clearMarkedEntity(i); + } + } + } + } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java index be2c59d..faa95cd 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/EffectHandler.java @@ -5,32 +5,33 @@ import com.github.skriptdev.skript.plugin.elements.effects.block.EffDamageBlock; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffDamage; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffDropItem; +import com.github.skriptdev.skript.plugin.elements.effects.entity.EffEntityAttitude; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffEntityEffect; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffEntityModel; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffFreeze; +import com.github.skriptdev.skript.plugin.elements.effects.entity.EffInteraction; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffKill; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffRemoveStatModifier; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffRide; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffShoot; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffSpawnEntity; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffTame; -import com.github.skriptdev.skript.plugin.elements.effects.player.EffConnect; -import com.github.skriptdev.skript.plugin.elements.effects.other.EffExecuteCommand; -import com.github.skriptdev.skript.plugin.elements.effects.other.EffSendMessage; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffTeleport; -import com.github.skriptdev.skript.plugin.elements.effects.player.EffBan; import com.github.skriptdev.skript.plugin.elements.effects.other.EffBroadcast; import com.github.skriptdev.skript.plugin.elements.effects.other.EffCancelEvent; import com.github.skriptdev.skript.plugin.elements.effects.other.EffDelay; -import com.github.skriptdev.skript.plugin.elements.effects.player.EffKick; +import com.github.skriptdev.skript.plugin.elements.effects.other.EffExecuteCommand; +import com.github.skriptdev.skript.plugin.elements.effects.other.EffSendMessage; import com.github.skriptdev.skript.plugin.elements.effects.other.EffSendTitle; +import com.github.skriptdev.skript.plugin.elements.effects.player.EffBan; +import com.github.skriptdev.skript.plugin.elements.effects.player.EffConnect; +import com.github.skriptdev.skript.plugin.elements.effects.player.EffKick; import com.github.skriptdev.skript.plugin.elements.effects.player.EffOpenItemContainer; import com.github.skriptdev.skript.plugin.elements.effects.server.EffServerShutdown; import com.github.skriptdev.skript.plugin.elements.effects.world.EffChunkLoadAsync; import com.github.skriptdev.skript.plugin.elements.effects.world.EffChunkRegenerate; import com.github.skriptdev.skript.plugin.elements.effects.world.EffCreateWorld; import com.github.skriptdev.skript.plugin.elements.effects.world.EffExplosion; -import com.github.skriptdev.skript.plugin.elements.effects.entity.EffInteraction; import com.github.skriptdev.skript.plugin.elements.effects.world.EffParticle; public class EffectHandler { @@ -43,6 +44,7 @@ public static void register(SkriptRegistration registration) { // ENTITY EffDamage.register(registration); EffDropItem.register(registration); + EffEntityAttitude.register(registration); EffEntityEffect.register(registration); EffEntityModel.register(registration); EffFreeze.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityAttitude.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityAttitude.java new file mode 100644 index 0000000..6e27560 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffEntityAttitude.java @@ -0,0 +1,104 @@ +package com.github.skriptdev.skript.plugin.elements.effects.entity; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import io.github.syst3ms.skriptparser.lang.Effect; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +import java.time.Duration; + +public class EffEntityAttitude extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffEntityAttitude.class, + "make %npcentities% ignore %entities% [for %duration%]", + "make %npcentities% hostile towards %entities% [for %duration%]", + "make %npcentities% neutral to %entities% [for %duration%]", + "make %npcentities% friendly to %entities% [for %duration%]", + "make %npcentities% revered towards %entities% [for %duration%]") + .name("Entity Attitude") + .description("Temporarily change the attitude of NPC entities to other entities.", + "If using the optional duration, this will last then revert back to its original attitude.", + "**NOTE**: This is not persistent.") + .examples("make target entity of player friendly to player for 10 minutes") + .since("INSERT VERSION") + .register(); + } + + private int pattern; + private Expression npcEntities; + private Expression targets; + private Expression duration; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.pattern = matchedPattern; + this.npcEntities = (Expression) expressions[0]; + this.targets = (Expression) expressions[1]; + if (expressions.length == 3) { + this.duration = (Expression) expressions[2]; + } + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + double durationSeconds = Double.MAX_VALUE; + if (this.duration != null) { + Duration duration = this.duration.getSingle(ctx).orElse(null); + if (duration == null) return; + durationSeconds = (double) duration.toMillis() / 1000; + } + + Attitude attitude = Attitude.values()[this.pattern]; + + + + for (NPCEntity npcEntity : this.npcEntities.getArray(ctx)) { + Role npcRole = npcEntity.getRole(); + if (npcRole == null) continue; + + WorldSupport worldSupport = npcRole.getWorldSupport(); + Ref npcRef = npcEntity.getReference(); + if (npcRef == null) continue; + + for (Entity target : this.targets.getArray(ctx)) { + Ref targetRef = target.getReference(); + if (targetRef == null) continue; + + // Clear current targetted entities + EntityUtils.clearMarkedEntity(npcEntity, target); + + // Override attitude + worldSupport.overrideAttitude(targetRef, attitude, durationSeconds); + } + } + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String duration = this.duration != null ? " for " + this.duration.toString(ctx, debug) : ""; + String type = switch (this.pattern) { + case 0 -> "ignore"; + case 1 -> "hostile towards"; + case 2 -> "neutral to"; + case 3 -> "friendly to"; + case 4 -> "revered towards"; + default -> "unknown"; + }; + return "make " + this.npcEntities.toString(ctx, debug) + " " + type + " " + + this.targets.toString(ctx, debug) + duration; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java index 2f413cc..dab7fbf 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/ExpressionHandler.java @@ -15,9 +15,11 @@ import com.github.skriptdev.skript.plugin.elements.expressions.block.ExprTargetBlockOfPlayer; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprActiveSlot; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntitiesInRadius; +import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntityAttitude; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntityComponents; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntityModel; import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprEntityStamina; +import com.github.skriptdev.skript.plugin.elements.expressions.entity.ExprLockedTarget; import com.github.skriptdev.skript.plugin.elements.expressions.item.ExprItemQuality; import com.github.skriptdev.skript.plugin.elements.expressions.other.ExprValueWithin; import com.github.skriptdev.skript.plugin.elements.expressions.player.ExprPlayerDefenseLevel; @@ -114,6 +116,7 @@ public static void register(SkriptRegistration registration) { // ENTITY ExprActiveSlot.register(registration); ExprEntitiesInRadius.register(registration); + ExprEntityAttitude.register(registration); ExprEntityComponents.register(registration); ExprEntityHeadRotation.register(registration); ExprEntityHealth.register(registration); @@ -123,6 +126,7 @@ public static void register(SkriptRegistration registration) { ExprEntityStat.register(registration); ExprEntityVelocity.register(registration); ExprHeldItem.register(registration); + ExprLockedTarget.register(registration); ExprName.register(registration); ExprNPCType.register(registration); ExprTargetEntityOfEntity.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityAttitude.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityAttitude.java new file mode 100644 index 0000000..8b0206c --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprEntityAttitude.java @@ -0,0 +1,64 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.entity; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.WorldSupport; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +public class ExprEntityAttitude implements Expression { + + public static void register(SkriptRegistration reg) { + reg.newExpression(ExprEntityAttitude.class, Attitude.class, true, + "attitude of %npcentity% towards %entity%") + .name("Entity Attitude") + .description("Get the attitude of an NPC towards an entity.", + "Cannot be set, instead use the Entity Attitude effect to apply a timed attitude change.") + .examples("set {_a} to attitude of target entity of player towards player", + "if attitude of target entity of player towards player = neutral:") + .since("INSERT VERSION") + .register(); + } + + private Expression npcEntity; + private Expression target; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.npcEntity = (Expression) expressions[0]; + this.target = (Expression) expressions[1]; + return true; + } + + @Override + public Attitude[] getValues(@NotNull TriggerContext ctx) { + NPCEntity npc = this.npcEntity.getSingle(ctx).orElse(null); + Entity target = this.target.getSingle(ctx).orElse(null); + if (npc == null || target == null) return null; + + Role role = npc.getRole(); + if (role == null) return null; + + Ref npcRef = npc.getReference(); + Ref targetRef = target.getReference(); + if (npcRef == null || targetRef == null) return null; + + WorldSupport worldSupport = role.getWorldSupport(); + Attitude attitude = worldSupport.getAttitude(npcRef, targetRef, npcRef.getStore()); + return new Attitude[]{attitude}; + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "attitude of " + this.npcEntity.toString(ctx, debug) + " towards " + this.target.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprLockedTarget.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprLockedTarget.java new file mode 100644 index 0000000..ab86934 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/expressions/entity/ExprLockedTarget.java @@ -0,0 +1,109 @@ +package com.github.skriptdev.skript.plugin.elements.expressions.entity; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.server.core.entity.Entity; +import com.hypixel.hytale.server.core.entity.EntityUtils; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import com.hypixel.hytale.server.npc.role.Role; +import com.hypixel.hytale.server.npc.role.support.MarkedEntitySupport; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; +import io.github.syst3ms.skriptparser.util.CollectionUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class ExprLockedTarget implements Expression { + + public static void register(SkriptRegistration reg) { + reg.newExpression(ExprLockedTarget.class, Object.class, true, + "locked target entity of %npcentities%", + "locked target entity (refs|references) of %npcentities%") + .name("NPCEntity Locked Target") + .description("Get/set/clear the locked target entity of an NPCEntity.", + "This represents the entity an NPC is locked on to, such as a skeleton attacking a player.") + .since("INSERT VERSION") + .register(); + } + + private int pattern; + private Expression npcentities; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.pattern = matchedPattern; + this.npcentities = (Expression) expressions[0]; + return true; + } + + @SuppressWarnings("deprecation") + @Override + public Object[] getValues(@NotNull TriggerContext ctx) { + if (this.pattern == 0) { + List targets = new ArrayList<>(); + for (Ref ref : getRefs(ctx)) { + Entity entity1 = EntityUtils.getEntity(ref, ref.getStore()); + if (entity1 != null) { + targets.add(entity1); + } + } + return targets.toArray(); + } else { + return getRefs(ctx).toArray(); + } + } + + @Override + public Optional[]> acceptsChange(@NotNull ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.DELETE) { + return CollectionUtils.optionalArrayOf(Entity.class, Ref.class); + } + return Optional.empty(); + } + + @SuppressWarnings({"ConstantValue", "rawtypes", "unchecked"}) + @Override + public void change(@NotNull TriggerContext ctx, @NotNull ChangeMode changeMode, Object @NotNull [] changeWith) { + Ref ref = null; + if (changeMode == ChangeMode.SET && changeWith != null && changeWith.length > 0) { + if (changeWith[0] instanceof Entity entity) { + ref = entity.getReference(); + } else if (changeWith[0] instanceof Ref ref1) { + ref = (Ref) ref1; + } + } + + for (NPCEntity npc : this.npcentities.getArray(ctx)) { + Role role = npc.getRole(); + assert role != null; + MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport(); + markedEntitySupport.setMarkedEntity("LockedTarget", ref); + } + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return ""; + } + + private List> getRefs(TriggerContext ctx) { + List> refs = new ArrayList<>(); + for (NPCEntity entity : this.npcentities.getArray(ctx)) { + Role role = entity.getRole(); + assert role != null; + + MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport(); + Ref lockedTarget = markedEntitySupport.getMarkedEntityRef("LockedTarget"); + refs.add(lockedTarget); + } + return refs; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesEntity.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesEntity.java index 8f1a5d8..27999ed 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesEntity.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/types/TypesEntity.java @@ -5,6 +5,7 @@ import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.RemoveReason; +import com.hypixel.hytale.server.core.asset.type.attitude.Attitude; import com.hypixel.hytale.server.core.entity.Entity; import com.hypixel.hytale.server.core.entity.LivingEntity; import com.hypixel.hytale.server.core.entity.effect.ActiveEntityEffect; @@ -21,7 +22,7 @@ public class TypesEntity { private static final Changer ENTITY_CHANGER = new Changer<>() { @Override public Class[] acceptsChange(@NotNull ChangeMode mode) { - if (mode == ChangeMode.DELETE) return new Class[] {Entity.class}; + if (mode == ChangeMode.DELETE) return new Class[]{Entity.class}; return null; } @@ -56,6 +57,11 @@ static void register(SkriptRegistration reg) { .since("1.0.0") .toStringFunction(ActiveEntityEffect::toString) .register(); + reg.newEnumType(Attitude.class, "attitude", "attitude@s") + .name("Attitude") + .description("Represents the attitude of an NPC Entity towards another entity.") + .since("INSERT VERSION") + .register(); reg.newType(Entity.class, "entity", "entit@y@ies") .name("Entity") .description("Represents any Entity in the game, including Players and NPCs.") From bfb3d775add10187e093f6b66b4646bbc769923e Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 27 Feb 2026 19:02:15 -0800 Subject: [PATCH 27/31] EvtBoot - fix not being triggered --- .../github/skriptdev/skript/plugin/Skript.java | 5 +++++ .../plugin/elements/events/server/EvtBoot.java | 16 +++------------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/Skript.java b/src/main/java/com/github/skriptdev/skript/plugin/Skript.java index cb28568..c604b07 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/Skript.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/Skript.java @@ -13,8 +13,10 @@ import com.github.skriptdev.skript.api.utils.Utils; import com.github.skriptdev.skript.plugin.elements.ElementRegistration; import com.github.skriptdev.skript.plugin.elements.events.EventHandler; +import com.github.skriptdev.skript.plugin.elements.events.server.EvtBoot; import com.hypixel.hytale.server.core.event.events.BootEvent; import io.github.syst3ms.skriptparser.config.Config.ConfigSection; +import io.github.syst3ms.skriptparser.lang.TriggerMap; import io.github.syst3ms.skriptparser.log.LogEntry; import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.registration.SkriptAddon; @@ -101,6 +103,9 @@ private void setupSkript() { // FINALIZE SCRIPT LOADING this.hySk.getEventRegistry().register(BootEvent.class, event -> { Utils.debug("Hytale finished booting, starting post-load triggers..."); + // Fire boot event + TriggerMap.callTriggersByContext(new EvtBoot.BootContext(event)); + // Start any post-load triggers after Hytale finishes booting. getAddons().forEach(SkriptAddon::finishedLoading); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/server/EvtBoot.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/server/EvtBoot.java index 31be021..8266716 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/server/EvtBoot.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/server/EvtBoot.java @@ -1,12 +1,9 @@ package com.github.skriptdev.skript.plugin.elements.events.server; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; -import com.github.skriptdev.skript.plugin.HySk; -import com.hypixel.hytale.event.EventRegistration; import com.hypixel.hytale.server.core.event.events.BootEvent; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.TriggerContext; -import io.github.syst3ms.skriptparser.lang.TriggerMap; import io.github.syst3ms.skriptparser.lang.event.SkriptEvent; import io.github.syst3ms.skriptparser.parsing.ParseContext; import org.jetbrains.annotations.NotNull; @@ -15,22 +12,15 @@ public class EvtBoot extends SkriptEvent { public static void register(SkriptRegistration reg) { reg.newEvent(EvtBoot.class, "boot", "server boot") + .setHandledContexts(BootContext.class) .name("Server Boot") - .description("Called when the server is starting up.") + .description("Called when the server is done starting up.") .since("1.0.0") .register(); } - private static EventRegistration LISTENER; - @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - if (LISTENER == null) { - LISTENER = HySk.getInstance().getEventRegistry().registerGlobal(BootEvent.class, event -> { - BootContext bootContext = new BootContext(event); - TriggerMap.callTriggersByContext(bootContext); - }); - } return true; } @@ -44,7 +34,7 @@ public String toString(@NotNull TriggerContext ctx, boolean debug) { return "server boot event"; } - private record BootContext(BootEvent event) implements TriggerContext { + public record BootContext(BootEvent event) implements TriggerContext { @Override public String getName() { return "boot context"; From 89a12cbb2d39d940e8cfd16ee4388c8fd9d38735 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 27 Feb 2026 19:37:24 -0800 Subject: [PATCH 28/31] EvtPeriodical - move over from skript-parser: - add optional world - fix unloading not stoping the scheduler - use Hytale's scheduler instance --- .../plugin/elements/events/EventHandler.java | 2 + .../elements/events/skript/EvtPeriodical.java | 107 ++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/main/java/com/github/skriptdev/skript/plugin/elements/events/skript/EvtPeriodical.java diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java index ea645a9..c7a82bb 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/EventHandler.java @@ -36,6 +36,7 @@ import com.github.skriptdev.skript.plugin.elements.events.server.EvtBoot; import com.github.skriptdev.skript.plugin.elements.events.server.EvtShutdown; import com.github.skriptdev.skript.plugin.elements.events.skript.EvtLoad; +import com.github.skriptdev.skript.plugin.elements.events.skript.EvtPeriodical; import com.github.skriptdev.skript.plugin.elements.events.world.EvtAtWorldTime; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.math.vector.Location; @@ -81,6 +82,7 @@ public static void register(SkriptRegistration registration) { // SKRIPT EvtLoad.register(registration); + EvtPeriodical.register(registration); // WORLD EvtAtWorldTime.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/skript/EvtPeriodical.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/skript/EvtPeriodical.java new file mode 100644 index 0000000..30096ad --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/skript/EvtPeriodical.java @@ -0,0 +1,107 @@ +package com.github.skriptdev.skript.plugin.elements.events.skript; + +import com.github.skriptdev.skript.api.skript.event.WorldContext; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.Literal; +import io.github.syst3ms.skriptparser.lang.Trigger; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.TriggerMap; +import io.github.syst3ms.skriptparser.lang.VariableString; +import io.github.syst3ms.skriptparser.lang.event.SkriptEvent; +import io.github.syst3ms.skriptparser.lang.event.StartOnLoadEvent; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.parsing.ParseContext; + +import java.time.Duration; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class EvtPeriodical extends SkriptEvent implements StartOnLoadEvent { + + public static void register(SkriptRegistration reg) { + reg.newEvent(EvtPeriodical.class, + "*every %*duration%", + "*every %*duration% in world %*string%") + .setHandledContexts(PeriodicalContext.class) + .name("Periodical") + .description("Triggered every interval of a certain duration.", + "You can optionally include a world to ensure the code in the event is executed in that world.") + .examples("every 10 seconds in world \"default\":", + "\tsend \"HELLO FELLOW WORLDERS\" to all players in event-world") + .since("1.0.0") + .register(); + } + + private Literal duration; + private World world; + private ScheduledFuture scheduledFuture; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + this.duration = (Literal) expressions[0]; + if (expressions.length > 1) { + VariableString name = (VariableString) expressions[1]; + String worldName = name.getSingle(TriggerContext.DUMMY).orElse(null); + this.world = Universe.get().getWorld(worldName); + if (this.world == null) { + parseContext.getLogger().error("Invalid world: '" + worldName + "'", ErrorType.SEMANTIC_ERROR); + return false; + } + } + return true; + } + + @Override + public boolean check(TriggerContext ctx) { + return ctx instanceof PeriodicalContext && this.duration.getSingle(ctx).isPresent(); + } + + @SuppressWarnings("NullableProblems") + @Override + public String toString(TriggerContext ctx, boolean debug) { + String world = this.world != null ? " in world \"" + this.world.getName() : "\""; + return "every " + this.duration.toString(ctx, debug) + world; + } + + @Override + public void onInitialLoad(Trigger trigger) { + Duration dur = this.duration.getSingle().orElseThrow(AssertionError::new); + long durationMillis = dur.toMillis(); + + Runnable runTrigger = () -> TriggerMap.callTriggersByContext(new PeriodicalContext(this.world)); + + this.scheduledFuture = HytaleServer.SCHEDULED_EXECUTOR.scheduleAtFixedRate(() -> { + if (this.world != null) { + this.world.execute(runTrigger); + } else { + runTrigger.run(); + } + }, durationMillis, durationMillis, TimeUnit.MILLISECONDS); + } + + @Override + public void unload() { + if (this.scheduledFuture == null) return; + this.scheduledFuture.cancel(true); + } + + public record PeriodicalContext(World world) implements WorldContext { + + @Override + public World getWorld() { + return this.world; + } + + @Override + public String getName() { + return "periodical context"; + } + + } + +} From 49a8c8bfd8a547f5bef42dabdcf65daf875f2efe Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 27 Feb 2026 20:05:12 -0800 Subject: [PATCH 29/31] BstatsMetrics - update version string to end with ".x" --- .../java/com/github/skriptdev/skript/plugin/BstatsMetrics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java b/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java index 25dd23d..0e56ac7 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/BstatsMetrics.java @@ -18,7 +18,7 @@ static void registerMetrics(HySk plugin) { metrics.addCustomChart(new DrilldownPie("plugin_version_drilldown_pie", () -> { Semver version = plugin.getManifest().getVersion(); DrilldownMap map = new DrilldownMap(); - map.put(version.getMajor() + "." + version.getMinor(), version.toString()); + map.put(version.getMajor() + "." + version.getMinor() + ".x", version.toString()); return map.getMap(); })); From 2d452e2d7a0100dc79e2e240f52ffff0fd693d19 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Fri, 27 Feb 2026 20:15:32 -0800 Subject: [PATCH 30/31] config.yml - update doc link --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index ca9ae8d..3b60a91 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: false contact_links: - name: 📖 HySkript Docs - url: https://github.com/SkriptDev/HySkript/wiki + url: https://skripthub.net/hyskript/docs/ about: Find HySkript's docs and 100's of examples - name: 💻 Support url: https://discord.gg/phpWHGU From 6dbd5bdf725cd704a94fd9d4bb353a06168c88c2 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 28 Feb 2026 05:32:02 -0800 Subject: [PATCH 31/31] build.gradle.kts - update parser --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 4aca615..e21aa50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { implementation("org.bstats:bstats-hytale:3.2.1") // Skript-Parser - implementation("com.github.SkriptDev:skript-parser:1.0.10") { + implementation("com.github.SkriptDev:skript-parser:1.0.11") { isTransitive = false } implementation("com.github.Zoltus:TinyMessage:2.0.1") {