diff --git a/.github/contributing.md b/.github/contributing.md index d18757a7..d30758ac 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -23,6 +23,8 @@ This way the team can discuss with you whether or not we want this in HySkript. - Use descriptive commit messages - Use descriptive PR titles - Ensure you follow the code style of this project +- Do make sure you add tests and run the test to make sure it works. + - See the [Testing Guide](https://github.com/SkriptDev/HySkript/tree/master/src/test/README.md) for more information. ### Don't: - Don't commit directly to `master` @@ -60,3 +62,6 @@ This way the team can discuss with you whether or not we want this in HySkript. - For expressions, please provide an example of using the getter as well as each changer you have applied. - For all others, please provide at least one example per pattern. - Please see other examples in HySkript for further inspiration. + +### Tests: +See the [Testing Guide](https://github.com/SkriptDev/HySkript/tree/master/src/test/README.md) for more information. diff --git a/.gitignore b/.gitignore index 8098e8e9..95247ca2 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,6 @@ bin/ ### Mac OS ### .DS_Store **/.DS_Store + +### Run Folder ### +run/ diff --git a/build.gradle.kts b/build.gradle.kts index b5bfd4ea..219b6576 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,12 +9,17 @@ java.sourceCompatibility = JavaVersion.VERSION_25 group = "com.github.SkriptDev" val projectVersion = "1.0.0" -val hytaleVersion = "2026.02.06-aa1b071c2" +val hytaleVersion = "2026.02.18-f3b8fff95" // You can find Hytale versions on their maven repo: // https://maven.hytale.com/release/com/hypixel/hytale/Server/maven-metadata.xml // https://maven.hytale.com/pre-release/com/hypixel/hytale/Server/maven-metadata.xml // (Pre-releases shouldn't be used for production) +// Location of the Hytale Server Assets +// This is used in testing +// Change this to wherever you have it on your computer +val assetLocation = "/Users/ShaneBee/Desktop/Server/Hytale/Assets/Assets.zip" + repositories { mavenCentral() mavenLocal() @@ -26,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.8") { + implementation("com.github.SkriptDev:skript-parser:dev~patch-SNAPSHOT") { isTransitive = false } implementation("com.github.Zoltus:TinyMessage:2.0.1") { @@ -34,6 +39,12 @@ dependencies { } } +// This is used to enable Gson in the test environment via HytaleServer +val testRunnerClasspath by configurations.creating { + extendsFrom(configurations.compileOnly.get()) + isCanBeResolved = true +} + tasks { register("server", Copy::class) { dependsOn("jar") @@ -42,6 +53,14 @@ tasks { destinationDir = file("/Users/ShaneBee/Desktop/Server/Hytale/Creative/mods/") } } + register("testRunner") { + dependsOn("jar") + group = "application" + mainClass.set("com.github.skriptdev.skript.api.skript.testing.TestRunnerMain") + args(hytaleVersion, projectVersion, assetLocation) + + classpath = sourceSets["main"].runtimeClasspath + testRunnerClasspath + } processResources { filesNotMatching("assets/**") { expand("pluginVersion" to projectVersion, "hytaleVersion" to hytaleVersion) @@ -71,7 +90,8 @@ tasks { options.encoding = Charsets.UTF_8.name() exclude( "com/github/skriptdev/skript/plugin/elements", - "com/github/skriptdev/skript/plugin/command" + "com/github/skriptdev/skript/plugin/command", + "com/github/skriptdev/skript/api/skript/testing/elements" ) (options as StandardJavadocDocletOptions).links( "https://javadoc.io/doc/org.jetbrains/annotations/latest/", 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 949cc67f..36e62e48 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 @@ -1,6 +1,7 @@ package com.github.skriptdev.skript.api.hytale; -import com.github.skriptdev.skript.api.utils.Utils; +import com.github.skriptdev.skript.api.hytale.utils.StoreUtils; +import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.math.util.ChunkUtil; @@ -8,15 +9,26 @@ 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.fluid.Fluid; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.ItemStack; +import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealth; +import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealthChunk; +import com.hypixel.hytale.server.core.modules.blockhealth.BlockHealthModule; +import com.hypixel.hytale.server.core.modules.interaction.BlockHarvestUtils; +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.ChunkColumn; import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; 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 org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Map; +import java.util.function.Predicate; + /** * Represents a block in a world. * Hytale doesn't appear to have a representation of a block in the world. @@ -137,7 +149,6 @@ public void setFluid(@NotNull Fluid fluid, @Nullable Integer level) { if (fluidLevel <= 0) fluidLevel = (byte) fluid.getMaxFluidLevel(); } fluidLevel = (byte) Math.clamp((int) fluidLevel, 0, fluid.getMaxFluidLevel()); - Utils.log("Set fluid level to %s", fluidLevel); fluidSection.setFluid(this.pos.getX(), this.pos.getY(), this.pos.getZ(), fluid, fluidLevel); } return chunk; @@ -148,6 +159,75 @@ public void breakBlock(int settings) { this.world.breakBlock(this.pos.getX(), this.pos.getY(), this.pos.getZ(), 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())); + if (chunk == null) return; + + Ref ref = chunk.getReference(); + Store chunkStore = this.world.getChunkStore().getStore(); + CommandBuffer commandBuffer = StoreUtils.getCommandBuffer(this.world.getEntityStore().getStore()); + + if (performer == null) { + BlockHarvestUtils.performBlockDamage( + this.pos, + null, + null, + damage, + 0, + ref, + commandBuffer, + chunkStore); + } else { + BlockHarvestUtils.performBlockDamage( + performer, + performer.getReference(), + this.pos, + itemStack, + null, + null, // TODO figure out how to get this + false, + damage, + 0, + ref, + commandBuffer, + chunkStore); + } + } + + public float getBlockHealth() { + WorldChunk chunk = this.world.getChunk(ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ())); + if (chunk == null) return 0; + + Ref ref = chunk.getReference(); + Store chunkStore = this.world.getChunkStore().getStore(); + + BlockHealthChunk component = chunkStore.getComponent(ref, BlockHealthModule.get().getBlockHealthChunkComponentType()); + if (component == null) return 0; + + return component.getBlockHealth(this.pos); + } + + public void setBlockHealth(float health) { + WorldChunk chunk = this.world.getChunk(ChunkUtil.indexChunkFromBlock(this.pos.getX(), this.pos.getZ())); + if (chunk == null) return; + + Ref ref = chunk.getReference(); + Store chunkStore = this.world.getChunkStore().getStore(); + BlockHealthChunk component = chunkStore.getComponent(ref, BlockHealthModule.get().getBlockHealthChunkComponentType()); + if (component == null) return; + + Map blockHealthMap = component.getBlockHealthMap(); + BlockHealth blockHealth = blockHealthMap.getOrDefault(this.pos, new BlockHealth()); + blockHealth.setHealth(health); + blockHealthMap.put(this.pos, blockHealth); + + if (!blockHealth.isDestroyed()) { + Predicate filter = (player) -> true; + world.getNotificationHandler().updateBlockDamage(this.pos.getX(), this.pos.getY(), + this.pos.getZ(), blockHealth.getHealth(), health, filter); + } + } + public @NotNull World getWorld() { return this.world; } diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/AssetStoreUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/AssetStoreUtils.java index b7db9bac..e54d4632 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/AssetStoreUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/AssetStoreUtils.java @@ -1,6 +1,7 @@ package com.github.skriptdev.skript.api.hytale.utils; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import com.hypixel.hytale.server.core.asset.type.environment.config.Environment; import com.hypixel.hytale.server.core.asset.type.item.config.Item; import com.hypixel.hytale.server.core.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -42,4 +43,24 @@ public class AssetStoreUtils { return null; } + /** + * Get the AssetStore index of an Environment + * + * @param environment Environment to get index from + * @return Index from Environment + */ + public static int getEnvironmentIndex(Environment environment) { + return Environment.getAssetMap().getIndex(environment.getId()); + } + + /** + * Get an Environment from its AssetStore index. + * + * @param index Index to grab Environment from + * @return Environment from index, or null if not found + */ + public static Environment getEnvironment(int index) { + return Environment.getAssetMap().getAsset(index); + } + } 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 3bf41373..7ec55b51 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,5 +1,7 @@ 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; @@ -20,6 +22,9 @@ import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsModule; import com.hypixel.hytale.server.core.universe.world.World; 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.systems.RoleChangeSystem; import io.github.syst3ms.skriptparser.util.Pair; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -99,7 +104,7 @@ public static void setNameplateName(Entity entity, @Nullable String name) { } /** - * Get a component from an Entity + * Get a component from an Entity. * * @param entity Entity to get component from * @param type Component type to get @@ -116,6 +121,26 @@ public static void setNameplateName(Entity entity, @Nullable String name) { return store.getComponent(reference, type); } + /** + * Get a component from an Entity or create it if not present. + * + * @param entity Entity to get component from + * @param type Component type to get + * @param EntityStore Type + * @param Type of returned component + * @return Component from entity if available otherwise will create/add a new one + */ + @SuppressWarnings("unchecked") + public static > @NotNull T ensureAndGetComponent(Entity entity, ComponentType type) { + Ref reference = (Ref) entity.getReference(); + if (reference == null) { + throw new IllegalStateException("Entity '" + entity + "' does not have a reference"); + } + + Store store = reference.getStore(); + return store.ensureAndGetComponent(reference, type); + } + /** * Get the EntityStatMap component of an entity. * @@ -173,4 +198,54 @@ public static void setNameplateName(Entity entity, @Nullable String name) { return new Pair<>(com.hypixel.hytale.server.core.entity.EntityUtils.getEntity(itemEntityHolder), itemComponent); } + public static boolean isTameable(NPCEntity npcEntity) { + Role role = npcEntity.getRole(); + if (role == null) return false; + + String roleName = role.getRoleName(); + if (roleName.contains("Tamed_")) { + return true; + } + // I know this is hacky, but Hytale doesn't have any API for taming + // Maybe we'll get lucky and Hytale will create API for this + NPCRegistry.NPCRole parse = NPCRegistry.parse("tamed_" + roleName); + return parse != null; + } + + public static boolean isTamed(NPCEntity npcEntity) { + Role role = npcEntity.getRole(); + if (role == null) return false; + + // I know this is hacky, but Hytale doesn't have any API for taming + // Maybe we'll get lucky and Hytale will create API for this + String roleName = role.getRoleName(); + return roleName.startsWith("Tamed_"); + } + + public static void setTamed(NPCEntity npcEntity, boolean tamed) { + if (!isTameable(npcEntity)) { + return; + } + if ((tamed && isTamed(npcEntity)) || (!tamed && !isTamed(npcEntity))) { + return; + } + Ref reference = npcEntity.getReference(); + if (reference == null) return; + + Store store = reference.getStore(); + + // I know this is hacky, but Hytale doesn't have any API for taming + // Maybe we'll get lucky and Hytale will create API for this + Role currentRole = npcEntity.getRole(); + if (currentRole == null || currentRole.isRoleChangeRequested()) return; + + String roleName = currentRole.getRoleName(); + roleName = tamed ? "Tamed_" + roleName : roleName.replace("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/hytale/utils/StoreUtils.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/StoreUtils.java index d3a3a66d..cdeaba18 100644 --- a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/StoreUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/StoreUtils.java @@ -6,6 +6,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +/** + * Utilities for a {@link Store Hytale Store} + */ public class StoreUtils { @SuppressWarnings("unchecked") diff --git a/src/main/java/com/github/skriptdev/skript/api/hytale/utils/package-info.java b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/package-info.java new file mode 100644 index 00000000..7695cdd8 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/hytale/utils/package-info.java @@ -0,0 +1,4 @@ +/** + * Utility classes for Hytale. + */ +package com.github.skriptdev.skript.api.hytale.utils; diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/addon/HySkriptAddon.java b/src/main/java/com/github/skriptdev/skript/api/skript/addon/HySkriptAddon.java index fad80fae..bfc1d94a 100644 --- a/src/main/java/com/github/skriptdev/skript/api/skript/addon/HySkriptAddon.java +++ b/src/main/java/com/github/skriptdev/skript/api/skript/addon/HySkriptAddon.java @@ -1,10 +1,17 @@ package com.github.skriptdev.skript.api.skript.addon; import com.github.skriptdev.skript.api.utils.Utils; +import com.github.skriptdev.skript.plugin.HySk; +import com.hypixel.hytale.component.ComponentRegistryProxy; +import com.hypixel.hytale.event.EventRegistry; import com.hypixel.hytale.logger.HytaleLogger; import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.command.system.CommandRegistry; +import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import fi.sulku.hytale.TinyMsg; import io.github.syst3ms.skriptparser.registration.SkriptAddon; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -13,14 +20,17 @@ /** * Base class for addons for HySkript. */ +@SuppressWarnings("unused") public abstract class HySkriptAddon extends SkriptAddon { + private final HySk hySkInstance; private Manifest manifest; private final HytaleLogger hytaleLogger; public HySkriptAddon(String name) { super(name); this.hytaleLogger = Utils.getAddonLogger(name); + this.hySkInstance = HySk.getInstance(); } /** @@ -35,18 +45,79 @@ public HySkriptAddon(String name) { */ abstract public void shutdown(); + /** + * Get the {@link CommandRegistry Hytale CommandRegistry}. + * + * @return Hytale CommandRegistry + */ + public final CommandRegistry getHytaleCommandRegistry() { + return this.hySkInstance.getCommandRegistry(); + } + + /** + * Get the {@link ComponentRegistryProxy Hytale EntityStoreRegistry}. + * + * @return Hytale EntityStoreRegistry + */ + public final ComponentRegistryProxy getHytaleEntityStoreRegistry() { + return this.hySkInstance.getEntityStoreRegistry(); + } + + /** + * Get the {@link ComponentRegistryProxy Hytale ChunkStoreRegistry}. + * + * @return Hytale ChunkStoreRegistry + */ + public final ComponentRegistryProxy getHytaleChunkStoreRegistry() { + return this.hySkInstance.getChunkStoreRegistry(); + } + + /** + * Get the {@link EventRegistry Hytale EventRegistry}. + * + * @return Hytale EventRegistry + */ + public final EventRegistry getHytaleEventRegistry() { + return this.hySkInstance.getEventRegistry(); + } + + /** + * Get your addon instance of the {@link HytaleLogger}. + *
You can override this if you wish to create your own logger instance + * + * @return Addon instance of HytaleLogger + */ public HytaleLogger getHytaleLogger() { return this.hytaleLogger; } + /** + * Get the instance of HySkript. + * + * @return Instance of HySkript + */ + public final HySk getHySkriptInstance() { + return this.hySkInstance; + } + + @ApiStatus.Internal final void setManifest(Manifest manifest) { this.manifest = manifest; } + /** + * Get the addon Manifest + * + * @return Addon Manifest + */ public final Manifest getManifest() { return manifest; } + /** + * @hidden + */ + @ApiStatus.Internal public final Message[] getInfo() { List info = new ArrayList<>(); info.add(TinyMsg.parse("Version: " + this.manifest.getVersion())); diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/addon/Manifest.java b/src/main/java/com/github/skriptdev/skript/api/skript/addon/Manifest.java index ec09a061..7ccd54a6 100644 --- a/src/main/java/com/github/skriptdev/skript/api/skript/addon/Manifest.java +++ b/src/main/java/com/github/skriptdev/skript/api/skript/addon/Manifest.java @@ -3,9 +3,13 @@ import com.hypixel.hytale.codec.Codec; import com.hypixel.hytale.codec.KeyedCodec; import com.hypixel.hytale.codec.builder.BuilderCodec; +import org.jetbrains.annotations.ApiStatus; import java.util.Arrays; +/** + * Represents a manifest of information about a {@link HySkriptAddon} + */ public class Manifest { private static final BuilderCodec.Builder BUILDER = BuilderCodec.builder(Manifest.class, Manifest::new); @@ -36,26 +40,52 @@ public class Manifest { private String[] authors; private String website; + @ApiStatus.Internal public String getMainClass() { return this.mainClass; } + /** + * Get the name of this addon. + * + * @return Name of addon + */ public String getName() { return this.name; } + /** + * Get the version of this addon. + * + * @return Version of addon + */ public String getVersion() { return this.version; } + /** + * Get the description of this addon. + * + * @return Description of addon + */ public String getDescription() { return this.description; } + /** + * Get the authors of this addon. + * + * @return Authors of addon + */ public String[] getAuthors() { return this.authors; } + /** + * Get the website of this addon. + * + * @return Website of addon + */ public String getWebsite() { return this.website; } diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/addon/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/addon/package-info.java new file mode 100644 index 00000000..eaac920a --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/addon/package-info.java @@ -0,0 +1,4 @@ +/** + * Main package related to HySkript Addons + */ +package com.github.skriptdev.skript.api.skript.addon; diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/command/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/command/package-info.java new file mode 100644 index 00000000..ac559776 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/command/package-info.java @@ -0,0 +1,4 @@ +/** + * Main package regarding commands in HySkript. + */ +package com.github.skriptdev.skript.api.skript.command; diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/config/SkriptConfig.java b/src/main/java/com/github/skriptdev/skript/api/skript/config/SkriptConfig.java index 5c321361..ff27bd14 100644 --- a/src/main/java/com/github/skriptdev/skript/api/skript/config/SkriptConfig.java +++ b/src/main/java/com/github/skriptdev/skript/api/skript/config/SkriptConfig.java @@ -8,6 +8,8 @@ import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.log.LogEntry; import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.util.SkriptDate; +import io.github.syst3ms.skriptparser.util.Time; import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; @@ -18,13 +20,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** - * Config for Skript + * Config for HySkript */ public class SkriptConfig { @@ -88,6 +91,34 @@ public SkriptConfig(Skript skript) { // Set up commands generate permissions this.commandsGeneratePermissions = this.config.getBoolean("commands-generate-permissions", true); + // Setup Date/Time formats + String defaultTime = "HH:mm:ss"; + String time = this.config.getString("default-time-format", defaultTime); + if (time != null) { + try { + // Validate the pattern before using it + DateTimeFormatter.ofPattern(time); + Time.setDefaultTimeFormat(time); + } catch (IllegalArgumentException e) { + logger.error("Invalid default time format: '" + time + "' Reason: '" + e.getMessage() + "'", + ErrorType.STRUCTURE_ERROR); + Time.setDefaultTimeFormat(defaultTime); + } + } + String defaultDate = "EEEE dd MMMM yyyy HH:mm:ss"; + String date = this.config.getString("default-date-format", defaultDate); + if (date != null) { + try { + // Validate the pattern before using it + DateTimeFormatter.ofPattern(date); + SkriptDate.setDefaultDateFormat(date); + } catch (IllegalArgumentException e) { + logger.error("Invalid default date format: '" + date + "' Reason: '" + e.getMessage() + "'", + ErrorType.STRUCTURE_ERROR); + SkriptDate.setDefaultDateFormat(defaultDate); + } + } + logger.finalizeLogs(); for (LogEntry logEntry : logger.close()) { Utils.log(null, logEntry); diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/config/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/config/package-info.java new file mode 100644 index 00000000..7cdace87 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Main package for HySkript config. + */ +package com.github.skriptdev.skript.api.skript.config; 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 30a61618..b78c4d0c 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 @@ -11,6 +11,7 @@ import io.github.syst3ms.skriptparser.docs.Documentation; import io.github.syst3ms.skriptparser.lang.CodeSection; 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.base.ConditionalExpression; import io.github.syst3ms.skriptparser.lang.base.ExecutableExpression; @@ -20,13 +21,11 @@ import io.github.syst3ms.skriptparser.registration.SkriptRegistration; import io.github.syst3ms.skriptparser.registration.SyntaxInfo; import io.github.syst3ms.skriptparser.registration.context.ContextValue; -import io.github.syst3ms.skriptparser.structures.functions.Function; import io.github.syst3ms.skriptparser.structures.functions.FunctionParameter; -import io.github.syst3ms.skriptparser.structures.functions.Functions; -import io.github.syst3ms.skriptparser.structures.functions.JavaFunction; import io.github.syst3ms.skriptparser.types.PatternType; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; import io.github.syst3ms.skriptparser.util.StringUtils; import org.bson.BsonArray; import org.bson.BsonBoolean; @@ -37,7 +36,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -254,7 +252,29 @@ private void printExpressions(BsonDocument mainDocs, SkriptRegistration registra if (returnTypeName == null) returnTypeName = returnType.getBaseName(); expressionDoc.put("return type", new BsonString(returnTypeName)); + // Changers Class syntaxClass = expressionInfo.getSyntaxClass(); + List changers = new ArrayList<>(); + try { + Object o = syntaxClass.getConstructor().newInstance(); + if (o instanceof Expression expression) { + for (ChangeMode value : ChangeMode.values()) { + if (expression.acceptsChange(value).isPresent()) { + changers.add(value.name().toLowerCase(Locale.ROOT)); + } + } + + } + if (!changers.isEmpty()) { + BsonArray changerArray = new BsonArray(); + changers.stream().sorted().forEach(s -> + changerArray.add(new BsonString(s))); + expressionDoc.put("changers", changerArray); + } + } catch (Exception ignore) { + + } + if (ExecutableExpression.class.isAssignableFrom(syntaxClass)) { // TODO new section on the docs?!?!?! exprsArray.add(expressionDoc); @@ -276,84 +296,83 @@ private void printExpressions(BsonDocument mainDocs, SkriptRegistration registra @SuppressWarnings("unchecked") private void printFunctions(BsonDocument mainDocs, SkriptRegistration registration) { BsonArray functionsArray = mainDocs.getArray("functions", new BsonArray()); - Functions.getJavaFunctions(registration).stream().sorted(Comparator.comparing(Function::getName)).forEach(function -> { - if (function instanceof JavaFunction jf) { - Documentation documentation = jf.getDocumentation(); - if (documentation.isNoDoc()) return; + for (SkriptRegistration.FunctionRegistrar function : registration.getFunctions()) { + Documentation documentation = function.getDocumentation(); + if (documentation.isNoDoc()) return; - BsonDocument functionDoc = new BsonDocument(); - String name = documentation.getName(); - if (name == null) name = function.getName(); - - functionDoc.put("name", new BsonString(name)); - functionDoc.put("id", getId("function", name)); - - // Create params - FunctionParameter[] parameters = jf.getParameters(); - - List parameterNames = new ArrayList<>(); - for (FunctionParameter parameter : parameters) { - Optional> byClass = TypeManager.getByClass(parameter.getType()); - if (byClass.isPresent()) { - Type type = byClass.get(); - String typeName; - if (parameter.isSingle()) { - typeName = type.getBaseName(); - } else { - typeName = type.getPluralForm(); - } - String format = String.format("%s:%s", parameter.getName(), typeName); - parameterNames.add(format); + BsonDocument functionDoc = new BsonDocument(); + String name = documentation.getName(); + if (name == null) name = function.getFunctionName(); + + functionDoc.put("name", new BsonString(name)); + functionDoc.put("id", getId("function", name)); + + // Create params + List> parameters = function.getParams(); + + List parameterNames = new ArrayList<>(); + for (FunctionParameter parameter : parameters) { + Optional> byClass = TypeManager.getByClass(parameter.getType()); + if (byClass.isPresent()) { + Type type = byClass.get(); + String typeName; + if (parameter.isSingle()) { + typeName = type.getBaseName(); + } else { + typeName = type.getPluralForm(); } + String format = String.format("%s:%s", parameter.getName(), typeName); + parameterNames.add(format); } + } - // Create a pattern for a function - String pattern = String.format("%s(%s)", jf.getName(), String.join(", ", parameterNames)); - functionDoc.put("patterns", new BsonArray(List.of(new BsonString(pattern)))); - - // DESCRIPTION - BsonArray descriptionArray = new BsonArray(); - for (String s : documentation.getDescription()) { - descriptionArray.add(new BsonString(s)); - } - functionDoc.put("description", descriptionArray); + // Create a pattern for a function + String pattern = String.format("%s(%s)", function.getFunctionName(), String.join(", ", parameterNames)); + functionDoc.put("patterns", new BsonArray(List.of(new BsonString(pattern)))); - // USAGE - String usage = documentation.getUsage(); - if (usage != null) { - functionDoc.put("usage", new BsonString(usage)); - } + // DESCRIPTION + BsonArray descriptionArray = new BsonArray(); + for (String s : documentation.getDescription()) { + descriptionArray.add(new BsonString(s)); + } + functionDoc.put("description", descriptionArray); - // EXAMPLES - String[] examples = documentation.getExamples(); - if (examples != null) { - BsonArray exampleArray = new BsonArray(); - for (String s : examples) { - exampleArray.add(new BsonString(s)); - } - functionDoc.put("examples", exampleArray); - } + // USAGE + String usage = documentation.getUsage(); + if (usage != null) { + functionDoc.put("usage", new BsonString(usage)); + } - // SINCE - String since = documentation.getSince(); - if (since != null) { - functionDoc.put("since", new BsonArray(List.of(new BsonString(since)))); - } else { - Utils.warn(this.sender, "Function '%s' has no since tag!", name); + // EXAMPLES + String[] examples = documentation.getExamples(); + if (examples != null) { + BsonArray exampleArray = new BsonArray(); + for (String s : examples) { + exampleArray.add(new BsonString(s)); } + functionDoc.put("examples", exampleArray); + } - // RETURN TYPE - Optional> returnType = (Optional>) jf.getReturnType(); - if (returnType.isPresent()) { - Optional> byClass = TypeManager.getByClass(returnType.get()); - byClass.ifPresent(type -> - functionDoc.put("return type", new BsonString(type.getBaseName()))); - } - functionsArray.add(functionDoc); + // SINCE + String since = documentation.getSince(); + if (since != null) { + functionDoc.put("since", new BsonArray(List.of(new BsonString(since)))); + } else { + Utils.warn(this.sender, "Function '%s' has no since tag!", name); + } + // RETURN TYPE + Class returnType = function.getReturnType(); + if (returnType != null) { + Optional> byClass = TypeManager.getByClass(returnType); + byClass.ifPresent(type -> + functionDoc.put("return type", new BsonString(type.getBaseName()))); } - }); + functionsArray.add(functionDoc); + + + } mainDocs.put("functions", functionsArray); } diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/docs/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/docs/package-info.java new file mode 100644 index 00000000..9d898ae4 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/docs/package-info.java @@ -0,0 +1,4 @@ +/** + * Main package regarding {@link io.github.syst3ms.skriptparser.docs.Documentation} + */ +package com.github.skriptdev.skript.api.skript.docs; diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/event/WorldContext.java b/src/main/java/com/github/skriptdev/skript/api/skript/event/WorldContext.java index 13437904..9470d2b2 100644 --- a/src/main/java/com/github/skriptdev/skript/api/skript/event/WorldContext.java +++ b/src/main/java/com/github/skriptdev/skript/api/skript/event/WorldContext.java @@ -3,6 +3,9 @@ import com.hypixel.hytale.server.core.universe.world.World; import io.github.syst3ms.skriptparser.lang.TriggerContext; +/** + * Represents a {@link TriggerContext} which includes a {@link World} + */ public interface WorldContext extends TriggerContext { World getWorld(); diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/event/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/event/package-info.java new file mode 100644 index 00000000..2b4cf200 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/event/package-info.java @@ -0,0 +1,5 @@ +/** + * Main package regarding {@link io.github.syst3ms.skriptparser.lang.TriggerContext Contexts} in + * {@link io.github.syst3ms.skriptparser.lang.event.SkriptEvent Events} + */ +package com.github.skriptdev.skript.api.skript.event; diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/registration/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/registration/package-info.java new file mode 100644 index 00000000..d446b1dd --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/registration/package-info.java @@ -0,0 +1,4 @@ +/** + * Main package for registrations. + */ +package com.github.skriptdev.skript.api.skript.registration; diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestProperties.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestProperties.java new file mode 100644 index 00000000..4b9eacf2 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestProperties.java @@ -0,0 +1,11 @@ +package com.github.skriptdev.skript.api.skript.testing; + +/** + * Properties related to tests. + */ +public class TestProperties { + + public static final boolean ENABLED = Boolean.parseBoolean(System.getProperty("skript.test.enabled", "false")); + public static final String TEST_SCRIPTS_FOLDER = System.getProperty("skript.test.scripts", "mods/skript_HySkript/tests"); + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestResults.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestResults.java new file mode 100644 index 00000000..6389632d --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestResults.java @@ -0,0 +1,88 @@ +package com.github.skriptdev.skript.api.skript.testing; + +import com.github.skriptdev.skript.api.utils.Utils; +import com.github.skriptdev.skript.plugin.HySk; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Handles the results of tests. + */ +public class TestResults { + + private boolean success = true; + private int failCount = 0; + private final Map> successMap = new TreeMap<>(); + private final Map> failureMap = new TreeMap<>(); + + public boolean isSuccess() { + return this.success; + } + + public int getFailCount() { + return this.failCount; + } + + public Map> getSuccessMap() { + return this.successMap; + } + + public Map> getFailureMap() { + return this.failureMap; + } + + public void addSuccess(String test, String value) { + this.successMap.computeIfAbsent(test, _ -> new ArrayList<>()).add(value); + } + + public void addFailure(String test, String value) { + this.success = false; + this.failCount++; + this.failureMap.computeIfAbsent(test, _ -> new ArrayList<>()).add(value); + } + + public void process() { + this.failureMap.forEach((test, _) -> { + // We don't care about success if other tests failed in that test + this.successMap.remove(test); + }); + + } + + public void clear() { + this.success = true; + this.successMap.clear(); + this.failureMap.clear(); + } + + @SuppressWarnings({"CallToPrintStackTrace"}) + public void printToJsonFile() { + Path resolve = HySk.getInstance().getDataDirectory().resolve("test-results.json"); + + try { + Files.createDirectories(resolve.getParent()); + } catch (IOException e) { + throw new RuntimeException("Failed to create directories for " + resolve.toAbsolutePath(), e); + } + + Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create(); + + try (BufferedWriter writer = Files.newBufferedWriter(resolve, StandardCharsets.UTF_8)) { + gson.toJson(this, writer); + Utils.log("Test-Results successfully written to " + resolve.toAbsolutePath()); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestRunner.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestRunner.java new file mode 100644 index 00000000..b4665b9c --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestRunner.java @@ -0,0 +1,151 @@ +package com.github.skriptdev.skript.api.skript.testing; + +import com.github.skriptdev.skript.api.skript.testing.elements.EvtTest.TestContext; +import com.github.skriptdev.skript.api.utils.Utils; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.Message; +import com.hypixel.hytale.server.core.universe.Universe; +import com.hypixel.hytale.server.core.universe.world.World; +import com.hypixel.hytale.server.core.util.MessageUtil; +import fi.sulku.hytale.TinyMsg; +import io.github.syst3ms.skriptparser.lang.Statement; +import io.github.syst3ms.skriptparser.lang.Trigger; +import io.github.syst3ms.skriptparser.lang.TriggerMap; +import io.github.syst3ms.skriptparser.log.LogEntry; +import io.github.syst3ms.skriptparser.log.LogType; +import io.github.syst3ms.skriptparser.parsing.ScriptLoader; +import io.github.syst3ms.skriptparser.registration.SkriptAddon; +import io.github.syst3ms.skriptparser.variables.Variables; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Runner for HySkript tests. + */ +public class TestRunner { + + private static final ScheduledExecutorService SCHEDULED = HytaleServer.SCHEDULED_EXECUTOR; + private final TestResults testResults = new TestResults(); + private final World world = Universe.get().getWorld("default"); + + @SuppressWarnings("DataFlowIssue") + public void start() { + Runnable runTestsRunnable = () -> { + Utils.log("Running tests in world 'default'..."); + runTests(); + }; + Runnable loadTestsRunnable = () -> { + Utils.log("Testing has started!"); + Utils.log("Loading test scripts..."); + loadTests(); + Utils.log("Finished loading test scripts!"); + + // Make sure the world isn't paused + if (this.world.isPaused()) this.world.setPaused(false); + + // Run our tests in the world to make sure we have access to blocks/entities + this.world.execute(runTestsRunnable); + }; + + // Delay start to make sure the server has finished loading + SCHEDULED.schedule(loadTestsRunnable, 1, TimeUnit.SECONDS); + } + + private void loadTests() { + Path path = Path.of(TestProperties.TEST_SCRIPTS_FOLDER); + loadScripts(path); + } + + private void runTests() { + // Catch exceptions and treat them as failures + Statement.setExceptionHandler(e -> + this.testResults.addFailure("Exception", + e.getClass().getSimpleName() + ": " + e.getMessage())); + + // Run all the test triggers + for (Trigger allTrigger : TriggerMap.getAllTriggers()) { + TestContext context = new TestContext(this.testResults, this.world); + Statement.runAll(allTrigger, context); + Variables.clearLocalVariables(context); + } + + Runnable finishingRunnable = () -> { + // Process results + this.testResults.process(); + + // Print results + if (this.testResults.isSuccess()) { + Message message = TinyMsg.parse("All tests passed!"); + Utils.log(MessageUtil.toAnsiString(message).toAnsi()); + } else { + Utils.error(this.testResults.getFailCount() + " tests failed!"); + this.testResults.getFailureMap().forEach((test, failure) -> + Utils.error(" - [" + test + "]: " + failure)); + } + + Utils.log("Finished running tests!"); + + // Print results to file + this.testResults.printToJsonFile(); + this.testResults.clear(); + + // Shutdown server + // Give it a bit of delay to really make sure we're finished + SCHEDULED.schedule(() -> HytaleServer.get().shutdownServer(), 1, TimeUnit.SECONDS); + }; + + // Delay finish to allow waits in tests + SCHEDULED.schedule(finishingRunnable, 1, TimeUnit.SECONDS); + } + + private void loadScripts(Path directory) { + File scriptsDirectory = directory.toFile(); + Utils.log("Loading test directory '" + scriptsDirectory.getAbsolutePath() + "'..."); + List scriptNames = loadScriptsInDirectory(scriptsDirectory); + SkriptAddon.getAddons().forEach(SkriptAddon::finishedLoading); + + Utils.log("Loaded " + scriptNames.size() + " scripts!"); + } + + private List loadScriptsInDirectory(File directory) { + if (directory == null || !directory.isDirectory()) return List.of(); + + List loadedScripts = new ArrayList<>(); + + File[] files = directory.listFiles(); + if (files == null) return loadedScripts; + + Arrays.sort(files, + Comparator.comparing(File::isDirectory).reversed() // Directories first + .thenComparing(File::getName, String.CASE_INSENSITIVE_ORDER)); // Then sort by name alphabetically + + for (File file : files) { + // Skip disabled files and hidden files + String fileName = file.getName(); + if (fileName.startsWith("-") || fileName.startsWith(".")) continue; + if (file.isDirectory()) { + loadedScripts.addAll(loadScriptsInDirectory(file)); + } else { + if (!fileName.endsWith(".sk")) continue; + Utils.log("Loading script '" + fileName + "'..."); + List logEntries = ScriptLoader.loadScript(file.toPath(), false); + for (LogEntry logEntry : logEntries) { + Utils.log(null, logEntry); + if (logEntry.getType() == LogType.ERROR) { + this.testResults.addFailure("Parsing Error:" + fileName, logEntry.getMessage()); + } + } + loadedScripts.add(fileName.substring(0, fileName.length() - 3)); + } + } + return loadedScripts; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestRunnerMain.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestRunnerMain.java new file mode 100644 index 00000000..128381fb --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/TestRunnerMain.java @@ -0,0 +1,165 @@ +package com.github.skriptdev.skript.api.skript.testing; + +import com.google.gson.Gson; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; + +/** + * Main runner for {@link TestRunner}. + */ +public class TestRunnerMain { + + private static final String GREEN = "\u001B[92m"; + private static final String LIGHT_GREY = "\u001B[37m"; + private static final String RED = "\u001B[91m"; + private static final String RESET = "\u001B[0m"; + + private static String serverVersion; + private static String pluginVersion; + private static String assetPath; + + /** + * Run the {@link TestRunner} + * + * @param args serverVersion, pluginVersion, assetPath + */ + static void main(String[] args) { + serverVersion = args[0]; + pluginVersion = args[1]; + assetPath = args[2]; + + System.out.println("Downloading Hytale Server..."); + downloadHytaleServer(); + System.out.println("Download complete!"); + + System.out.println("Moving plugin to mods folder..."); + movePlugin(); + System.out.println("Plugin moved!"); + + System.out.println("Starting Hytale server..."); + runServer(); + } + + @SuppressWarnings({"resource", "CallToPrintStackTrace"}) + private static void downloadHytaleServer() { + String url = "https://maven.hytale.com/release/com/hypixel/hytale/Server/" + + serverVersion + "/Server-" + serverVersion + ".jar"; + String targetDir = "run/testServer/"; + String newName = "HytaleServer.jar"; + + try { + // 1. Create the directory if it doesn't exist + Path directoryPath = Paths.get(targetDir); + if (!Files.exists(directoryPath)) { + Files.createDirectories(directoryPath); + } + + // 2. Download the file to a temporary location + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .build(); + + // We download directly to a path to save memory + Path tempFile = Files.createTempFile("tempDownload", ".tmp"); + client.send(request, HttpResponse.BodyHandlers.ofFile(tempFile)); + + // 3. Move and Rename the file + Path finalPath = directoryPath.resolve(newName); + Files.move(tempFile, finalPath, StandardCopyOption.REPLACE_EXISTING); + + System.out.println("File saved to: " + finalPath.toAbsolutePath()); + + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static void movePlugin() { + File file = new File("run/testServer/mods"); + file.mkdirs(); + try { + Files.copy(Path.of("build/libs/HySkript-" + pluginVersion + ".jar"), + Path.of("run/testServer/mods/HySkript-" + pluginVersion + ".jar"), + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("CallToPrintStackTrace") + private static void runServer() { + try { + File serverFolder = new File("run/testServer/"); + ProcessBuilder processBuilder = new ProcessBuilder( + "java", + "-Xms2G", + "-Xmx2G", + "-Dskript.test.enabled=true", + "-Dskript.test.scripts=../../src/test/skript/tests", + "-jar", "HytaleServer.jar", + "--assets", assetPath + ); + processBuilder.inheritIO(); + processBuilder.directory(serverFolder); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + + // Read results written by the plugin (no Gson required). + Path resultsPath = Path.of("run/testServer/mods/skript_HySkript/test-results.json"); + if (!Files.exists(resultsPath)) { + throw new IllegalStateException( + "Test results file not found at " + resultsPath.toAbsolutePath() + + " (server exit code was " + exitCode + ")" + ); + } + + Gson gson = new Gson(); + TestResults results; + + try (BufferedReader reader = Files.newBufferedReader(resultsPath, StandardCharsets.UTF_8)) { + results = gson.fromJson(reader, TestResults.class); + + System.out.println("Successfully loaded results from: " + resultsPath.getFileName()); + } catch (Exception e) { + System.err.println("Could not read test results: " + e.getMessage()); + e.printStackTrace(); + return; + } + + System.out.println("Succeeded:"); + if (!results.getSuccessMap().isEmpty()) { + results.getSuccessMap().forEach((test, _) -> + System.out.println(" - " + GREEN + test + RESET)); + } else { + System.out.println(" - none"); + } + + System.out.println("Failed:"); + if (results.getFailCount() > 0) { + results.getFailureMap().forEach((test, errorList) -> + errorList.forEach(error -> + System.out.println(" - " + RED + test + LIGHT_GREY + ": " + error + RESET))); + } else { + System.out.println(" - none"); + } + + System.exit(results.getFailCount()); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/EffAssert.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/EffAssert.java new file mode 100644 index 00000000..e3d9ffca --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/EffAssert.java @@ -0,0 +1,85 @@ +package com.github.skriptdev.skript.api.skript.testing.elements; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.github.skriptdev.skript.api.skript.testing.TestResults; +import com.github.skriptdev.skript.api.skript.testing.elements.EvtTest.TestContext; +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.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.parsing.SyntaxParser; +import io.github.syst3ms.skriptparser.types.PatternType; +import io.github.syst3ms.skriptparser.types.TypeManager; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public class EffAssert extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffAssert.class, "assert <.+> with %string%") + .noDoc() + .register(); + } + + private String fileName; + private int lineNumber; + private Expression condition; + private String conditionString; + private Expression message; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.message = (Expression) expressions[0]; + + SkriptLogger logger = parseContext.getLogger(); + this.fileName = logger.getFileName(); + this.lineNumber = logger.getLine() + 1; // I think it gets the last line?!?! + this.conditionString = parseContext.getMatches().getFirst().group(); + Optional> patternType = TypeManager.getPatternType("boolean"); + if (patternType.isEmpty()) { + return false; + } + + Optional> expression = SyntaxParser.parseExpression(this.conditionString, + patternType.get(), + parseContext.getParserState(), + parseContext.getLogger()); + if (expression.isEmpty() || expression.get().getReturnType() != Boolean.class) { + return false; + } + this.condition = (Expression) expression.get(); + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + if (!(ctx instanceof TestContext context)) return; + + Optional b = this.condition.getSingle(ctx).filter(Boolean::booleanValue); + TestResults testResults = context.getTestResults(); + + if (b.isEmpty()) { + // Test failed + String message = this.message.getSingle(ctx).orElseThrow(); + String failure = String.format("assert '%s' failed with message \"%s\" {%s:%d}", + this.conditionString, + message, + this.fileName, + this.lineNumber); + testResults.addFailure(context.getTestSubject(), failure); + } else { + String success = String.format("assert '%s' passed", + this.conditionString); + testResults.addSuccess(context.getTestSubject(), success); + } + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "assert " + this.condition.toString(ctx, debug) + " with " + this.message.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/ElementHandler.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/ElementHandler.java new file mode 100644 index 00000000..9ab1a652 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/ElementHandler.java @@ -0,0 +1,12 @@ +package com.github.skriptdev.skript.api.skript.testing.elements; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; + +public class ElementHandler { + + public static void register(SkriptRegistration reg) { + EffAssert.register(reg); + EvtTest.register(reg); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/EvtTest.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/EvtTest.java new file mode 100644 index 00000000..1cb48831 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/elements/EvtTest.java @@ -0,0 +1,79 @@ +package com.github.skriptdev.skript.api.skript.testing.elements; + +import com.github.skriptdev.skript.api.skript.event.WorldContext; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.github.skriptdev.skript.api.skript.testing.TestResults; +import com.github.skriptdev.skript.api.utils.Utils; +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.lang.VariableString; +import io.github.syst3ms.skriptparser.lang.event.SkriptEvent; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +public class EvtTest extends SkriptEvent { + + public static void register(SkriptRegistration reg) { + reg.newEvent(EvtTest.class, "test %*string%") + .setHandledContexts(TestContext.class) + .noDoc() + .register(); + } + + private String testSubject; + + @SuppressWarnings({"OptionalGetWithoutIsPresent"}) + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + this.testSubject = ((VariableString) expressions[0]).getSingle(TriggerContext.DUMMY).get(); + return true; + } + + @Override + public boolean check(TriggerContext triggerContext) { + if (!(triggerContext instanceof TestContext context)) return false; + context.setTestSubject(this.testSubject); + Utils.logColored("Running test: '" + this.testSubject + "'"); + return true; + } + + @Override + public String toString(@NotNull TriggerContext triggerContext, boolean b) { + return "test " + this.testSubject; + } + + public static final class TestContext implements WorldContext { + private final TestResults testResults; + private String testSubject; + private final World world; + + public TestContext(TestResults testResults, World world) { + this.testResults = testResults; + this.world = world; + } + + public void setTestSubject(String testSubject) { + this.testSubject = testSubject; + } + + public String getTestSubject() { + return testSubject; + } + + public TestResults getTestResults() { + return this.testResults; + } + + @Override + public World getWorld() { + return this.world; + } + + @Override + public String getName() { + return "test context"; + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/testing/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/testing/package-info.java new file mode 100644 index 00000000..2d0b9797 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/testing/package-info.java @@ -0,0 +1,4 @@ +/** + * Main package for testing + */ +package com.github.skriptdev.skript.api.skript.testing; diff --git a/src/main/java/com/github/skriptdev/skript/api/skript/variables/package-info.java b/src/main/java/com/github/skriptdev/skript/api/skript/variables/package-info.java new file mode 100644 index 00000000..1aba3c3b --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/api/skript/variables/package-info.java @@ -0,0 +1,4 @@ +/** + * Main package for handling variables. + */ +package com.github.skriptdev.skript.api.skript.variables; diff --git a/src/main/java/com/github/skriptdev/skript/api/utils/ReflectionUtils.java b/src/main/java/com/github/skriptdev/skript/api/utils/ReflectionUtils.java index f3ccde70..20cae7be 100644 --- a/src/main/java/com/github/skriptdev/skript/api/utils/ReflectionUtils.java +++ b/src/main/java/com/github/skriptdev/skript/api/utils/ReflectionUtils.java @@ -29,7 +29,7 @@ public static void init() { /** * Get access to the HytaleBanProvider. - * This is currently private with no getter. + *
This is currently private with no getter. * * @return HytaleBanProvider */ diff --git a/src/main/java/com/github/skriptdev/skript/api/utils/Utils.java b/src/main/java/com/github/skriptdev/skript/api/utils/Utils.java index bbbd17f5..0963c6bc 100644 --- a/src/main/java/com/github/skriptdev/skript/api/utils/Utils.java +++ b/src/main/java/com/github/skriptdev/skript/api/utils/Utils.java @@ -79,6 +79,11 @@ public static void log(String message, Object... args) { log(null, Level.INFO, message, args); } + public static void logColored(String message, Object... args) { + String ansi = MessageUtil.toAnsiString(TinyMsg.parse(message)).toAnsi(); + log(null, Level.INFO, ansi, args); + } + public static void log(IMessageReceiver receiver, String message, Object... args) { log(receiver, Level.INFO, message, args); } 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 e15bad3e..0cce07af 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/Skript.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/Skript.java @@ -6,6 +6,8 @@ import com.github.skriptdev.skript.api.skript.command.ArgUtils; import com.github.skriptdev.skript.api.skript.config.SkriptConfig; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.github.skriptdev.skript.api.skript.testing.TestProperties; +import com.github.skriptdev.skript.api.skript.testing.TestRunner; import com.github.skriptdev.skript.api.skript.variables.JsonVariableStorage; import com.github.skriptdev.skript.api.utils.ReflectionUtils; import com.github.skriptdev.skript.api.utils.Utils; @@ -106,6 +108,12 @@ private void setupSkript() { Utils.debug("Hytale finished booting, starting post-load triggers..."); // Start any post-load triggers after Hytale finishes booting. getAddons().forEach(SkriptAddon::finishedLoading); + + // RUN TESTS + if (TestProperties.ENABLED) { + TestRunner testRunner = new TestRunner(); + testRunner.start(); + } }); } @@ -189,7 +197,7 @@ private void loadVariables() { * * @return The Skript registration. */ - public @NotNull io.github.syst3ms.skriptparser.registration.SkriptRegistration getSkriptRegistration() { + public @NotNull SkriptRegistration getSkriptRegistration() { return this.registration; } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/ElementRegistration.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/ElementRegistration.java index 7954536a..622cb817 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/ElementRegistration.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/ElementRegistration.java @@ -1,21 +1,21 @@ package com.github.skriptdev.skript.plugin.elements; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.github.skriptdev.skript.api.skript.testing.TestProperties; +import com.github.skriptdev.skript.api.skript.testing.elements.ElementHandler; import com.github.skriptdev.skript.api.utils.Utils; import com.github.skriptdev.skript.plugin.elements.command.ScriptCommand; import com.github.skriptdev.skript.plugin.elements.command.ScriptSubCommand; -import com.github.skriptdev.skript.plugin.elements.types.DefaultComparators; -import com.github.skriptdev.skript.plugin.elements.types.DefaultConverters; import com.github.skriptdev.skript.plugin.elements.conditions.ConditionHandler; import com.github.skriptdev.skript.plugin.elements.effects.EffectHandler; import com.github.skriptdev.skript.plugin.elements.events.EventHandler; import com.github.skriptdev.skript.plugin.elements.expressions.ExpressionHandler; import com.github.skriptdev.skript.plugin.elements.functions.DefaultFunctions; import com.github.skriptdev.skript.plugin.elements.sections.SectionHandler; +import com.github.skriptdev.skript.plugin.elements.types.DefaultComparators; +import com.github.skriptdev.skript.plugin.elements.types.DefaultConverters; import com.github.skriptdev.skript.plugin.elements.types.Types; import io.github.syst3ms.skriptparser.Parser; -import io.github.syst3ms.skriptparser.lang.Structure; -import io.github.syst3ms.skriptparser.registration.SkriptEventInfo; import io.github.syst3ms.skriptparser.structures.functions.Functions; public class ElementRegistration { @@ -61,6 +61,11 @@ public void registerElements() { ScriptCommand.register(this.registration); ScriptSubCommand.register(this.registration); + // TEST ELEMENTS + if (TestProperties.ENABLED) { + ElementHandler.register(this.registration); + } + // FINALIZE SETUP this.registration.register(); @@ -70,22 +75,8 @@ public void registerElements() { private void printSyntaxCount() { var mainRegistration = Parser.getMainRegistration(); - int structureSize = 0; - int eventSize = 0; - for (SkriptEventInfo event : this.registration.getEvents()) { - if (Structure.class.isAssignableFrom(event.getSyntaxClass())) { - structureSize++; - } else { - eventSize++; - } - } - for (SkriptEventInfo event : mainRegistration.getEvents()) { - if (Structure.class.isAssignableFrom(event.getSyntaxClass())) { - structureSize++; - } else { - eventSize++; - } - } + int structureSize = this.registration.getStructures().size() + mainRegistration.getStructures().size(); + int eventSize = this.registration.getEvents().size() + mainRegistration.getEvents().size(); int effectSize = this.registration.getEffects().size() + mainRegistration.getEffects().size(); int expsSize = this.registration.getExpressions().size() + mainRegistration.getExpressions().size(); int secSize = this.registration.getSections().size() + mainRegistration.getSections().size(); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondPlayerIsCrouching.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondPlayerIsCrouching.java deleted file mode 100644 index a512fa3c..00000000 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondPlayerIsCrouching.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.github.skriptdev.skript.plugin.elements.conditions; - -import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; -import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; -import com.hypixel.hytale.server.core.entity.entities.Player; -import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; -import io.github.syst3ms.skriptparser.lang.Expression; -import io.github.syst3ms.skriptparser.lang.TriggerContext; -import io.github.syst3ms.skriptparser.lang.base.ConditionalExpression; -import io.github.syst3ms.skriptparser.parsing.ParseContext; -import org.jetbrains.annotations.NotNull; - -public class CondPlayerIsCrouching extends ConditionalExpression { - - public static void register(SkriptRegistration reg) { - reg.newExpression(CondPlayerIsCrouching.class, Boolean.class, true, - "%players% (is|are) crouching", - "%players% (isn't|is not|aren't|are not) crouching") - .name("Player is Crouching") - .description("Checks if the player is crouching.") - .examples("if player is crouching:", - "\tmessage \"You are crouching!\"") - .since("1.0.0") - .register(); - } - - private Expression players; - - @SuppressWarnings("unchecked") - @Override - public boolean init(Expression[] expressions, int matchedPattern, @NotNull ParseContext parseContext) { - setNegated(matchedPattern == 1); - this.players = (Expression) expressions[0]; - return true; - } - - @Override - public boolean check(@NotNull TriggerContext ctx) { - return this.players.check(ctx, player -> { - MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); - if (component == null) return false; - - return component.getMovementStates().crouching; - }, isNegated()); - } - - @Override - public String toString(@NotNull TriggerContext ctx, boolean debug) { - boolean single = this.players.isSingle(); - String s = isNegated() ? (single ? "isn't" : "aren't") : (single ? "is" : "are"); - return this.players.toString(ctx, debug) + " " + s + " crouching"; - } - -} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/ConditionHandler.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/ConditionHandler.java index 2404495f..4a96c9d4 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/ConditionHandler.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/ConditionHandler.java @@ -2,13 +2,59 @@ import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.github.skriptdev.skript.plugin.elements.conditions.block.CondBlockIsSolid; +import com.github.skriptdev.skript.plugin.elements.conditions.entity.CondEntityIsAlive; +import com.github.skriptdev.skript.plugin.elements.conditions.entity.CondEntityIsFrozen; +import com.github.skriptdev.skript.plugin.elements.conditions.entity.CondEntityIsTameable; +import com.github.skriptdev.skript.plugin.elements.conditions.entity.CondEntityIsTamed; +import com.github.skriptdev.skript.plugin.elements.conditions.item.CondInventoryCanHold; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerHasPermission; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementCrouching; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementFalling; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementFlying; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementIdle; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementJumping; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementOnGround; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementCanFly; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementRunning; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementSitting; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementSleeping; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementSprinting; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementSwimming; +import com.github.skriptdev.skript.plugin.elements.conditions.player.CondPlayerMovementWalking; public class ConditionHandler { public static void register(SkriptRegistration registration) { - CondHasPermission.register(registration); + // Please keep in alphabetical order + + // BLOCK + CondBlockIsSolid.register(registration); + + // ENTITY + CondEntityIsAlive.register(registration); + CondEntityIsFrozen.register(registration); + CondEntityIsTameable.register(registration); + CondEntityIsTamed.register(registration); + + // ITEM CondInventoryCanHold.register(registration); - CondPlayerIsCrouching.register(registration); + + // PLAYER + CondPlayerHasPermission.register(registration); + CondPlayerMovementCanFly.register(registration); + CondPlayerMovementCrouching.register(registration); + CondPlayerMovementFalling.register(registration); + CondPlayerMovementFlying.register(registration); + CondPlayerMovementIdle.register(registration); + CondPlayerMovementJumping.register(registration); + CondPlayerMovementOnGround.register(registration); + CondPlayerMovementRunning.register(registration); + CondPlayerMovementSitting.register(registration); + CondPlayerMovementSleeping.register(registration); + CondPlayerMovementSprinting.register(registration); + CondPlayerMovementSwimming.register(registration); + CondPlayerMovementWalking.register(registration); } } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/block/CondBlockIsSolid.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/block/CondBlockIsSolid.java new file mode 100644 index 00000000..4a6bcedd --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/block/CondBlockIsSolid.java @@ -0,0 +1,39 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.block; + +import com.github.skriptdev.skript.api.hytale.Block; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.protocol.BlockMaterial; +import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; + +public class CondBlockIsSolid extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondBlockIsSolid.class, "blocks/blocktypes", + ConditionalType.BE, "solid") + .name("Block is Solid") + .description("Check if a Block/BlockType is solid.") + .examples("if target block of player is solid:") + .since("INSERT VERSION") + .register(); + } + + @SuppressWarnings("NullableProblems") + @Override + public boolean check(TriggerContext ctx) { + return getPerformer().check(ctx, o -> { + BlockType blockType; + if (o instanceof BlockType bt) { + blockType = bt; + } else if (o instanceof Block block) { + blockType = block.getType(); + } else { + return false; + } + return blockType.getMaterial() == BlockMaterial.Solid; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsAlive.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsAlive.java new file mode 100644 index 00000000..c2e69587 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsAlive.java @@ -0,0 +1,49 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.entity; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import org.jetbrains.annotations.NotNull; + +public class CondEntityIsAlive extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondEntityIsAlive.class, + "livingentities", + ConditionalType.BE, + "(alive|:dead)") + .name("Entity is Alive/Dead") + .description("Check if a LivingEntity is alive/dead.") + .examples("if player is alive:", + "\tkill player") + .since("INSERT VERSION") + .register(); + } + + private boolean isDead; + + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, ParseContext parseContext) { + this.isDead = parseContext.hasMark("dead"); + return super.init(expressions, matchedPattern, parseContext); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return getPerformer().check(ctx, entity -> { + DeathComponent component = EntityUtils.getComponent(entity, DeathComponent.getComponentType()); + if (this.isDead) { + return component != null; + } else { + return component == null; + } + }); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsFrozen.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsFrozen.java new file mode 100644 index 00000000..133b28bc --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsFrozen.java @@ -0,0 +1,39 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.entity; + +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.entity.Frozen; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondEntityIsFrozen extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondEntityIsFrozen.class, + "livingentities", + ConditionalType.BE, + "frozen") + .name("Entity is Frozen") + .description("Checks if the living entities are frozen.") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, livingEntity -> { + Ref reference = livingEntity.getReference(); + if (reference == null) return false; + + Store store = reference.getStore(); + Frozen component = store.getComponent(reference, Frozen.getComponentType()); + return component != null; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsTameable.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsTameable.java new file mode 100644 index 00000000..1fd33881 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsTameable.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.entity; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondEntityIsTameable extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondEntityIsTameable.class, "livingentities", + ConditionalType.BE, "tameable") + .name("Entity is Tameable") + .description("Check if an entity is tameable.") + .experimental("Hytale is just adding taming, so this may change in the future.") + .examples("if target entity of player is tameable:") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, entity -> { + if (entity instanceof NPCEntity npcEntity) { + return EntityUtils.isTameable(npcEntity); + } + return false; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsTamed.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsTamed.java new file mode 100644 index 00000000..2f8c006a --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/entity/CondEntityIsTamed.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.entity; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondEntityIsTamed extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondEntityIsTamed.class, "livingentities", + ConditionalType.BE, "tamed") + .name("Entity is Tamed") + .description("Check if an entity is tamed.") + .experimental("Hytale is just adding taming, so this may change in the future.") + .examples("if target entity of player is tameable:") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, entity -> { + if (entity instanceof NPCEntity npcEntity) { + return EntityUtils.isTamed(npcEntity); + } + return false; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondInventoryCanHold.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/item/CondInventoryCanHold.java similarity index 97% rename from src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondInventoryCanHold.java rename to src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/item/CondInventoryCanHold.java index 866b82a7..cc2be19f 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondInventoryCanHold.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/item/CondInventoryCanHold.java @@ -1,4 +1,4 @@ -package com.github.skriptdev.skript.plugin.elements.conditions; +package com.github.skriptdev.skript.plugin.elements.conditions.item; import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; import com.hypixel.hytale.server.core.inventory.Inventory; diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondHasPermission.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerHasPermission.java similarity index 91% rename from src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondHasPermission.java rename to src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerHasPermission.java index 65473708..f1b1c9e9 100644 --- a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/CondHasPermission.java +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerHasPermission.java @@ -1,4 +1,4 @@ -package com.github.skriptdev.skript.plugin.elements.conditions; +package com.github.skriptdev.skript.plugin.elements.conditions.player; import com.hypixel.hytale.server.core.entity.entities.Player; import com.hypixel.hytale.server.core.permissions.PermissionsModule; @@ -12,10 +12,10 @@ import java.util.UUID; -public class CondHasPermission extends ConditionalExpression { +public class CondPlayerHasPermission extends ConditionalExpression { public static void register(SkriptRegistration registration) { - registration.newExpression(CondHasPermission.class, Boolean.class, true, + registration.newExpression(CondPlayerHasPermission.class, Boolean.class, true, "%players/playerrefs/uuid% (has|have) permission %strings%", "%players/playerrefs/uuid% (don't|do not|doesn't|does not) (has|have) permission %strings%") .name("Permission") diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementCanFly.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementCanFly.java new file mode 100644 index 00000000..dc1b8332 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementCanFly.java @@ -0,0 +1,84 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.protocol.packets.player.SetMovementStates; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.movement.MovementManager; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; + +public class CondPlayerMovementCanFly extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementCanFly.class, + "players", ConditionalType.CAN, "fly") + .name("Player Movement - Can Fly") + .description("Check/set whether the player can fly.") + .examples("if player can fly:", + "set player can fly to false", + "reset player can fly") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return getPerformer().check(ctx, player -> { + MovementManager component = EntityUtils.getComponent(player, MovementManager.getComponentType()); + assert component != null; + return component.getSettings().canFly; + }, isNegated()); + } + + @Override + public Optional[]> acceptsChange(@NotNull ChangeMode mode) { + if (mode == ChangeMode.SET || mode == ChangeMode.RESET) { + return Optional.of(new Class[]{Boolean.class}); + } + return Optional.empty(); + } + + @SuppressWarnings("ConstantValue") + @Override + public void change(@NotNull TriggerContext ctx, @NotNull ChangeMode changeMode, Object @NotNull [] changeWith) { + Boolean changeValue = null; + if (changeWith != null && changeWith.length > 0 && changeWith[0] instanceof Boolean bool) { + changeValue = bool; + } + for (Player player : getPerformer().getArray(ctx)) { + MovementManager component = EntityUtils.getComponent(player, MovementManager.getComponentType()); + if (component == null) continue; + + PlayerRef ref = EntityUtils.getComponent(player, PlayerRef.getComponentType()); + assert ref != null; + + if (changeMode == ChangeMode.RESET) { + component.getSettings().canFly = component.getDefaultSettings().canFly; + } else if (changeValue != null) { + component.getSettings().canFly = changeValue; + + } + + if (!component.getSettings().canFly) { + // Stop the player from actually flying + MovementStatesComponent movementStates = EntityUtils.getMovementStatesComponent(player); + assert movementStates != null; + movementStates.getMovementStates().flying = false; + ref.getPacketHandler().writeNoCache(new SetMovementStates(new SavedMovementStates(false))); + + } + + component.update(ref.getPacketHandler()); + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementCrouching.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementCrouching.java new file mode 100644 index 00000000..b70ba591 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementCrouching.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementCrouching extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementCrouching.class, + "players", ConditionalType.BE, "crouching") + .name("Player Movement - Crouching") + .description("Checks if the player is crouching.") + .examples("if player is crouching:", + "\tmessage \"You are crouching!\"") + .since("1.0.0") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().crouching; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementFalling.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementFalling.java new file mode 100644 index 00000000..294892bb --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementFalling.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementFalling extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementFalling.class, + "players", ConditionalType.BE, "falling") + .name("Player Movement - Falling") + .description("Checks if the player is falling.") + .examples("if player is falling:", + "\tmessage \"You are falling!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().falling; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementFlying.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementFlying.java new file mode 100644 index 00000000..cf64dda8 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementFlying.java @@ -0,0 +1,67 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.protocol.SavedMovementStates; +import com.hypixel.hytale.protocol.packets.player.SetMovementStates; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import com.hypixel.hytale.server.core.universe.PlayerRef; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +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 CondPlayerMovementFlying extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementFlying.class, + "players", ConditionalType.BE, "flying") + .name("Player Movement - Flying") + .description("Checks if the player is flying. Can be set.") + .examples("if player is flying:", + "\tmessage \"You shouldn't be flying!\"", + "\tset player is flying to false") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().flying; + }, isNegated()); + } + + @Override + public Optional[]> acceptsChange(@NotNull ChangeMode mode) { + if (mode == ChangeMode.SET) { + return CollectionUtils.optionalArrayOf(Boolean.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 Boolean bool)) { + return; + } + + for (Player player : getPerformer().getArray(ctx)) { + PlayerRef ref = EntityUtils.getComponent(player, PlayerRef.getComponentType()); + assert ref != null; + MovementStatesComponent movementStates = EntityUtils.getMovementStatesComponent(player); + assert movementStates != null; + movementStates.getMovementStates().flying = bool; + ref.getPacketHandler().writeNoCache(new SetMovementStates(new SavedMovementStates(bool))); + } + } +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementIdle.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementIdle.java new file mode 100644 index 00000000..e287bb0e --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementIdle.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementIdle extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementIdle.class, + "players", ConditionalType.BE, "(idle|idling)") + .name("Player Movement - Idle") + .description("Checks if the player is idle/doing nothing.") + .examples("if player is idling:", + "\tmessage \"You are idling!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().idle; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementJumping.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementJumping.java new file mode 100644 index 00000000..de4817cd --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementJumping.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementJumping extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementJumping.class, + "players", ConditionalType.BE, "jumping") + .name("Player Movement - Jumping") + .description("Checks if the player is jumping.") + .examples("if player is jumping:", + "\tmessage \"You are jumping!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().jumping; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementOnGround.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementOnGround.java new file mode 100644 index 00000000..51b9e38e --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementOnGround.java @@ -0,0 +1,33 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementOnGround extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementOnGround.class, + "players", ConditionalType.BE, "on ground") + .name("Player Movement - On Ground") + .description("Check if a player is on the ground.") + .description("if player is on ground:") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + assert component != null; + return component.getMovementStates().onGround; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementRunning.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementRunning.java new file mode 100644 index 00000000..118d6781 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementRunning.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementRunning extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementRunning.class, + "players", ConditionalType.BE, "running") + .name("Player Movement - Running") + .description("Checks if the player is running.") + .examples("if player is running:", + "\tmessage \"You are running!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().running; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSitting.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSitting.java new file mode 100644 index 00000000..70c16b98 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSitting.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementSitting extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementSitting.class, + "players", ConditionalType.BE, "sitting") + .name("Player Movement - Sitting") + .description("Checks if the player is sitting.") + .examples("if player is sitting:", + "\tmessage \"You are sitting!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().sitting; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSleeping.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSleeping.java new file mode 100644 index 00000000..031e24db --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSleeping.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementSleeping extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementSleeping.class, + "players", ConditionalType.BE, "sleeping") + .name("Player Movement - Sleeping") + .description("Checks if the player is sleeping.") + .examples("if player is sleeping:", + "\tmessage \"You are sleeping!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().sleeping; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSprinting.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSprinting.java new file mode 100644 index 00000000..cf1310ce --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSprinting.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementSprinting extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementSprinting.class, + "players", ConditionalType.BE, "sprinting") + .name("Player Movement - Sprinting") + .description("Checks if the player is sprinting.") + .examples("if player is sprinting:", + "\tmessage \"You are sprinting!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().sprinting; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSwimming.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSwimming.java new file mode 100644 index 00000000..055dbba5 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementSwimming.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementSwimming extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementSwimming.class, + "players", ConditionalType.BE, "swimming") + .name("Player Movement - Swimming") + .description("Checks if the player is swimming.") + .examples("if player is swimming:", + "\tmessage \"You are swimming!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().swimming; + }, isNegated()); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementWalking.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementWalking.java new file mode 100644 index 00000000..6994e5b5 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/conditions/player/CondPlayerMovementWalking.java @@ -0,0 +1,35 @@ +package com.github.skriptdev.skript.plugin.elements.conditions.player; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; +import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; +import org.jetbrains.annotations.NotNull; + +public class CondPlayerMovementWalking extends PropertyConditional { + + public static void register(SkriptRegistration reg) { + reg.newPropertyConditional(CondPlayerMovementWalking.class, + "players", ConditionalType.BE, "walking") + .name("Player Movement - Walking") + .description("Checks if the player is walking.") + .examples("if player is walking:", + "\tmessage \"You are walking!\"") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean check(@NotNull TriggerContext ctx) { + return this.getPerformer().check(ctx, player -> { + MovementStatesComponent component = EntityUtils.getMovementStatesComponent(player); + + assert component != null; + return component.getMovementStates().walking; + }, isNegated()); + } + +} 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 d5851a83..edaf91cb 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 @@ -2,11 +2,15 @@ import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; import com.github.skriptdev.skript.plugin.elements.effects.block.EffBreakBlock; +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.EffEntityEffect; +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.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; @@ -17,6 +21,8 @@ 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.EffSendTitle; +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.EffChunkRegenerate; import com.github.skriptdev.skript.plugin.elements.effects.world.EffExplosion; import com.github.skriptdev.skript.plugin.elements.effects.entity.EffInteraction; @@ -26,14 +32,18 @@ public class EffectHandler { public static void register(SkriptRegistration registration) { // BLOCK EffBreakBlock.register(registration); + EffDamageBlock.register(registration); // ENTITY EffDamage.register(registration); EffDropItem.register(registration); EffEntityEffect.register(registration); + EffFreeze.register(registration); EffInteraction.register(registration); EffKill.register(registration); + EffShoot.register(registration); EffSpawnEntity.register(registration); + EffTame.register(registration); EffTeleport.register(registration); // OTHER @@ -48,6 +58,10 @@ public static void register(SkriptRegistration registration) { EffBan.register(registration); EffConnect.register(registration); EffKick.register(registration); + EffOpenItemContainer.register(registration); + + // SERVER + EffServerShutdown.register(registration); // WORLD EffChunkRegenerate.register(registration); diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/block/EffDamageBlock.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/block/EffDamageBlock.java new file mode 100644 index 00000000..a9273d9d --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/block/EffDamageBlock.java @@ -0,0 +1,82 @@ +package com.github.skriptdev.skript.plugin.elements.effects.block; + +import com.github.skriptdev.skript.api.hytale.Block; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.inventory.ItemStack; +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 EffDamageBlock extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffDamageBlock.class, "block damage %blocks% by %number%", + "make %livingEntity% block damage %blocks% by %number%", + "make %livingEntity% block damage %blocks% by %number% using %itemstack%") + .name("Damage Block") + .description("Damages the specified blocks by the specified amount.", + "You can optionally include a LivingEntity to destroy the block,", + "and an ItemStack to simulate the damage as if the entity was holding that item.") + .examples("block damage {_block} by 10", + "make player block damage {_block} by 10", + "make player block damage target block of player by 10 using hotbar item of player") + .experimental("Currently the ItemStack thing doesn't work.") + .since("INSERT VERSION") + .register(); + } + + private Expression livingEntity; + private Expression blocks; + private Expression damage; + private Expression itemstack; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + if (matchedPattern == 0) { + this.blocks = (Expression) expressions[0]; + this.damage = (Expression) expressions[1]; + } else if (matchedPattern == 1) { + this.livingEntity = (Expression) expressions[0]; + this.blocks = (Expression) expressions[1]; + this.damage = (Expression) expressions[2]; + } else if (matchedPattern == 2) { + this.livingEntity = (Expression) expressions[0]; + this.blocks = (Expression) expressions[1]; + this.damage = (Expression) expressions[2]; + this.itemstack = (Expression) expressions[3]; + } + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + Number number = this.damage.getSingle(ctx).orElse(null); + if (number == null) return; + + LivingEntity performer = null; + if (this.livingEntity != null) { + performer = this.livingEntity.getSingle(ctx).orElse(null); + } + ItemStack itemStack = null; + if (this.itemstack != null) { + itemStack = this.itemstack.getSingle(ctx).orElse(null); + } + + float damage = number.floatValue(); + for (Block block : this.blocks.getArray(ctx)) { + block.damage(performer, itemStack, damage); + } + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String performer = this.livingEntity == null ? "" : "make " + this.livingEntity.toString(ctx, debug) + " "; + String item = this.itemstack == null ? "" : "using " + this.itemstack.toString(ctx, debug); + return performer + "block damage " + this.blocks.toString(ctx, debug) + " by " + this.damage.toString(ctx, debug) + item; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffFreeze.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffFreeze.java new file mode 100644 index 00000000..849b55da --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffFreeze.java @@ -0,0 +1,61 @@ +package com.github.skriptdev.skript.plugin.elements.effects.entity; + +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.entity.Frozen; +import com.hypixel.hytale.server.core.entity.LivingEntity; +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 org.jetbrains.annotations.NotNull; + +public class EffFreeze extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffFreeze.class, "freeze %livingentities%", + "unfreeze %livingentities%") + .name("Freeze Entity") + .description("Freeze or unfreeze the specified entities.", + "This doesn't appear to work on players.") + .examples("freeze entities in radius 10 around player", + "unfreeze {_e}") + .since("INSERT VERSION") + .register(); + } + + private boolean freeze; + private Expression livingEntities; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.freeze = matchedPattern == 0; + this.livingEntities = (Expression) expressions[0]; + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + for (LivingEntity livingEntity : this.livingEntities.getArray(ctx)) { + Ref reference = livingEntity.getReference(); + if (reference == null) continue; + + Store store = reference.getStore(); + if (this.freeze) { + store.ensureComponent(reference, Frozen.getComponentType()); + } else { + store.removeComponentIfExists(reference, Frozen.getComponentType()); + } + } + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String freeze = this.freeze ? "freeze" : "unfreeze"; + return freeze + " " + this.livingEntities.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffShoot.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffShoot.java new file mode 100644 index 00000000..24f6c480 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffShoot.java @@ -0,0 +1,80 @@ +package com.github.skriptdev.skript.plugin.elements.effects.entity; + +import com.github.skriptdev.skript.api.hytale.utils.EntityUtils; +import com.github.skriptdev.skript.api.hytale.utils.StoreUtils; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.math.vector.Vector3d; +import com.hypixel.hytale.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; +import com.hypixel.hytale.server.core.modules.projectile.ProjectileModule; +import com.hypixel.hytale.server.core.modules.projectile.config.ProjectileConfig; +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 org.jetbrains.annotations.NotNull; + +public class EffShoot extends Effect { + + // TODO it doesn't actually work, hopefully it's just a Hytale Bug + public static void register(SkriptRegistration reg) { + reg.newEffect(EffShoot.class, + "make %livingentity% shoot %projectileconfig%", + "make %livingentity% shoot %projectileconfig% in direction %vector3d%") + .name("Shoot") + .description("Make a LivingEntity shoot a projectile.") + .since("INSERT VERSION"); + //.register(); + } + + private Expression livingEntity; + private Expression projectile; + private Expression direction; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.livingEntity = (Expression) expressions[0]; + this.projectile = (Expression) expressions[1]; + if (matchedPattern == 1) { + this.direction = (Expression) expressions[2]; + } + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + LivingEntity entity = this.livingEntity.getSingle(ctx).orElse(null); + ProjectileConfig config = this.projectile.getSingle(ctx).orElse(null); + if (entity == null || config == null) return; + Ref ref = entity.getReference(); + if (ref == null) return; + Store store = ref.getStore(); + CommandBuffer buffer = StoreUtils.getCommandBuffer(store); + + TransformComponent component = EntityUtils.getComponent(entity, TransformComponent.getComponentType()); + if (component == null) return; + + Vector3d pos = component.getTransform().getPosition().clone(); + Vector3d dir = component.getTransform().getDirection().clone(); + + if (this.direction != null) { + Vector3d vector3d = this.direction.getSingle(ctx).orElse(null); + if (vector3d != null) { + dir = vector3d; + } + } + + ProjectileModule.get().spawnProjectile(null, buffer, config, pos, dir); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "make " + this.livingEntity.toString(ctx, debug) + " shoot " + this.projectile.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTame.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTame.java new file mode 100644 index 00000000..3ba95abc --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/entity/EffTame.java @@ -0,0 +1,52 @@ +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.server.core.entity.LivingEntity; +import com.hypixel.hytale.server.npc.entities.NPCEntity; +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 EffTame extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffTame.class, "tame %livingentities%", "untame %livingentities%") + .name("Entity Taming") + .description("Tame/untame a LivingEntity.", + "This will only work on tameable entities.") + .examples("tame target entity of player", + "untame all entities in radius 5 around player") + .since("INSERT VERSION") + .register(); + } + + private boolean tame; + private Expression entities; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.tame = matchedPattern == 0; + this.entities = (Expression) expressions[0]; + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + for (LivingEntity livingEntity : this.entities.getArray(ctx)) { + if (livingEntity instanceof NPCEntity npcEntity) { + EntityUtils.setTamed(npcEntity, this.tame); + } + } + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String type = this.tame ? "tame" : "untame"; + return type + " " + this.entities.toString(ctx, debug); + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/player/EffOpenItemContainer.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/player/EffOpenItemContainer.java new file mode 100644 index 00000000..113e4b8e --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/player/EffOpenItemContainer.java @@ -0,0 +1,80 @@ +package com.github.skriptdev.skript.plugin.elements.effects.player; + +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.protocol.packets.interface_.Page; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.entity.entities.player.windows.ContainerWindow; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +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 org.jetbrains.annotations.NotNull; + +public class EffOpenItemContainer extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffOpenItemContainer.class, + "open %itemContainer% to %players%", + "open %itemContainer% to %players% (with|using) page %page%") + .name("Open ItemContainer") + .description("Opens an ItemContainer to the specified players.", + "You can optionally choose a page type to use (Default is Bench).", + "Don't use the `custom` page, it'll kick the player.") + .examples("open storage item container of inventory of player to player") + .since("INSERT VERSION") + .register(); + } + + private Expression itemContainer; + private Expression players; + private Expression page; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + this.itemContainer = (Expression) expressions[0]; + this.players = (Expression) expressions[1]; + if (matchedPattern == 1) { + this.page = (Expression) expressions[2]; + } + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + ItemContainer container = this.itemContainer.getSingle(ctx).orElse(null); + if (container == null) return; + + Page page = Page.Bench; + if (this.page != null) { + Page page1 = this.page.getSingle(ctx).orElse(null); + if (page1 != null) page = page1; + } + + for (Player player : this.players.getArray(ctx)) { + openContainer(container, player, page); + } + } + + private void openContainer(ItemContainer container, Player player, Page page) { + Ref ref = player.getReference(); + if (ref == null) return; + + Store store = ref.getStore(); + + player.getPageManager().setPageWithWindows(ref, store, page, + true, new ContainerWindow(container)); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + String page = this.page != null ? " with page " + this.page.toString(ctx, debug) : ""; + return "open item container " + this.itemContainer.toString(ctx, debug) + + " to " + players.toString(ctx, debug) + page; + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/server/EffServerShutdown.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/server/EffServerShutdown.java new file mode 100644 index 00000000..0c34e1ab --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/effects/server/EffServerShutdown.java @@ -0,0 +1,39 @@ +package com.github.skriptdev.skript.plugin.elements.effects.server; + +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.server.core.HytaleServer; +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 EffServerShutdown extends Effect { + + public static void register(SkriptRegistration reg) { + reg.newEffect(EffServerShutdown.class, "shutdown server") + .name("Shutdown Server") + .description("Shuts down the server.") + .since("INSERT VERSION") + .register(); + } + + @Override + public boolean init(Expression @NotNull [] expressions, int matchedPattern, @NotNull ParseContext parseContext) { + return true; + } + + @Override + protected void execute(@NotNull TriggerContext ctx) { + if (HytaleServer.get().isShuttingDown()) { + return; + } + HytaleServer.get().shutdownServer(); + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "shutdown server"; + } + +} 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 c5dce8a2..8501ab24 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 @@ -28,10 +28,12 @@ import com.github.skriptdev.skript.plugin.elements.events.player.EvtPlayerPlaceBlock; import com.github.skriptdev.skript.plugin.elements.events.player.EvtPlayerSetupConnect; import com.github.skriptdev.skript.plugin.elements.events.player.EvtPlayerSetupDisconnect; +import com.github.skriptdev.skript.plugin.elements.events.player.EvtPlayerSwitchActiveSlot; import com.github.skriptdev.skript.plugin.elements.events.player.EvtPlayerUseBlock; 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.world.EvtAtWorldTime; import com.hypixel.hytale.server.core.entity.entities.Player; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; @@ -64,6 +66,7 @@ public static void register(SkriptRegistration registration) { EvtPlayerPlaceBlock.register(registration); EvtPlayerSetupConnect.register(registration); EvtPlayerSetupDisconnect.register(registration); + EvtPlayerSwitchActiveSlot.register(registration); EvtPlayerUseBlock.register(registration); // SERVER @@ -73,6 +76,9 @@ public static void register(SkriptRegistration registration) { // SKRIPT EvtLoad.register(registration); + // WORLD + EvtAtWorldTime.register(registration); + // CONTEXT registerGlobalContexts(registration); } diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/player/EvtPlayerSwitchActiveSlot.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/player/EvtPlayerSwitchActiveSlot.java new file mode 100644 index 00000000..787815aa --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/player/EvtPlayerSwitchActiveSlot.java @@ -0,0 +1,158 @@ +package com.github.skriptdev.skript.plugin.elements.events.player; + +import com.github.skriptdev.skript.api.skript.event.CancellableContext; +import com.github.skriptdev.skript.api.skript.event.PlayerContext; +import com.github.skriptdev.skript.api.skript.event.SystemEvent; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.ArchetypeChunk; +import com.hypixel.hytale.component.CommandBuffer; +import com.hypixel.hytale.component.Ref; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.component.query.Query; +import com.hypixel.hytale.component.system.EntityEventSystem; +import com.hypixel.hytale.server.core.entity.entities.Player; +import com.hypixel.hytale.server.core.event.events.ecs.SwitchActiveSlotEvent; +import com.hypixel.hytale.server.core.inventory.container.ItemContainer; +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.lang.TriggerMap; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.registration.context.ContextValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class EvtPlayerSwitchActiveSlot extends SystemEvent> { + + public static void register(SkriptRegistration reg) { + reg.newEvent(EvtPlayerSwitchActiveSlot.class, + "player switch active slot", + "player switch active hot[ ]bar slot", + "player switch active utility slot") + .setHandledContexts(SwitchSlotContext.class) + .name("Player Switch Active Slot") + .description("Called when a player switches their active slot.") + .experimental("This event doesn't appear to actually get called by the server for hotbar.") + .examples("") + .since("INSERT VERSION") + .register(); + + reg.newSingleContextValue(SwitchSlotContext.class, Number.class, + "slot-index", SwitchSlotContext::getPreviousSlot) + .description("The slot number before the change happened.") + .setState(ContextValue.State.PAST) + .register(); + + reg.newSingleContextValue(SwitchSlotContext.class, Number.class, + "slot-index", SwitchSlotContext::getNewSlot) + .description("The slot number after the change happened. Can be set.") + .setState(ContextValue.State.PRESENT) + .addSetter(SwitchSlotContext::setNewSlot) + .register(); + + reg.newSingleContextValue(SwitchSlotContext.class, ItemContainer.class, + "item-container", SwitchSlotContext::getContainer) + .description("The ItemContainer used in this event.") + .register(); + } + + private static SwitchSlotSystem SYSTEM; + private int pattern; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + if (SYSTEM == null) { + SYSTEM = new SwitchSlotSystem(); + applySystem(SYSTEM); + } + this.pattern = matchedPattern; + return true; + } + + @Override + public boolean check(TriggerContext ctx) { + if (ctx instanceof SwitchSlotContext ssc) { + if (this.pattern == 0) return true; + else if (this.pattern == 1) return ssc.isHotbar(); + else if (this.pattern == 2) return ssc.isUtility(); + } + return false; + } + + @Override + public String toString(@NotNull TriggerContext ctx, boolean debug) { + return "player switch active slot"; + } + + public record SwitchSlotContext(SwitchActiveSlotEvent event, Player player) + implements PlayerContext, CancellableContext { + + @Override + public Player getPlayer() { + return this.player; + } + + public int getPreviousSlot() { + return this.event.getPreviousSlot(); + } + + public int getNewSlot() { + return this.event.getNewSlot(); + } + + public void setNewSlot(Number number) { + this.event.setNewSlot(number.byteValue()); + } + + public ItemContainer getContainer() { + return this.player.getInventory().getSectionById(this.event.getInventorySectionId()); + } + + public boolean isHotbar() { + return this.event.getInventorySectionId() == -1; + } + + public boolean isUtility() { + return this.event.getInventorySectionId() == -5; + } + + @Override + public boolean isCancelled() { + return this.event.isCancelled(); + } + + @Override + public void setCancelled(boolean cancelled) { + this.event.setCancelled(cancelled); + } + + @Override + public String getName() { + return "switch active slot context"; + } + } + + public static class SwitchSlotSystem extends EntityEventSystem { + + protected SwitchSlotSystem() { + super(SwitchActiveSlotEvent.class); + } + + @Override + public void handle(int i, @NotNull ArchetypeChunk archetypeChunk, + @NotNull Store store, @NotNull CommandBuffer commandBuffer, + @NotNull SwitchActiveSlotEvent event) { + + Ref ref = archetypeChunk.getReferenceTo(i); + Player player = commandBuffer.getComponent(ref, Player.getComponentType()); + SwitchSlotContext context = new SwitchSlotContext(event, player); + TriggerMap.callTriggersByContext(context); + } + + @Override + public @Nullable Query getQuery() { + return Player.getComponentType(); + } + } + +} diff --git a/src/main/java/com/github/skriptdev/skript/plugin/elements/events/world/EvtAtWorldTime.java b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/world/EvtAtWorldTime.java new file mode 100644 index 00000000..0c0275a0 --- /dev/null +++ b/src/main/java/com/github/skriptdev/skript/plugin/elements/events/world/EvtAtWorldTime.java @@ -0,0 +1,122 @@ +package com.github.skriptdev.skript.plugin.elements.events.world; + +import com.github.skriptdev.skript.api.skript.event.WorldContext; +import com.github.skriptdev.skript.api.skript.registration.SkriptRegistration; +import com.hypixel.hytale.component.Store; +import com.hypixel.hytale.server.core.HytaleServer; +import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; +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.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 io.github.syst3ms.skriptparser.util.Time; +import org.jetbrains.annotations.NotNull; + +import java.util.Optional; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class EvtAtWorldTime extends SkriptEvent implements StartOnLoadEvent { + + public static void register(SkriptRegistration reg) { + reg.newEvent(EvtAtWorldTime.class, + "*at %*time% [world time] in world %*string%") + .setHandledContexts(WorldTimeContext.class) + .name("At World Time") + .description("Triggered every ingame day at a given time.", + "Internally this is handled by the minute.") + .examples("at 10:00 pm in world \"default\":", + "\tkill all players in event-world") + .since("INSERT VERSION") + .register(); + } + + private Literal