Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99707958e8 | ||
|
|
755b9e8d03 | ||
|
|
8cfee3ce39 |
No files matched your search
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
java-version: 25
|
||||
cache: gradle
|
||||
|
||||
- name: Validate conventional commits
|
||||
@@ -35,6 +35,6 @@ jobs:
|
||||
- name: Upload development artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: spigot-tyrant-${{ github.sha }}
|
||||
name: purpur-tyrant-${{ github.sha }}
|
||||
path: build/libs/*.jar
|
||||
if-no-files-found: error
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 17
|
||||
java-version: 25
|
||||
cache: gradle
|
||||
|
||||
- name: Set up Node.js
|
||||
@@ -86,8 +86,8 @@ jobs:
|
||||
if: steps.release.outputs.created == 'true'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: spigot-tyrant-${{ steps.release.outputs.version }}
|
||||
path: build/libs/spigot-tyrant-${{ steps.release.outputs.version }}.jar
|
||||
name: purpur-tyrant-${{ steps.release.outputs.version }}
|
||||
path: build/libs/purpur-tyrant-${{ steps.release.outputs.version }}.jar
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create Gitea release and upload plugin
|
||||
@@ -101,7 +101,7 @@ jobs:
|
||||
PREVIOUS_TAG: ${{ steps.previous_tag.outputs.value }}
|
||||
run: |
|
||||
api_url="${GITEA_SERVER_URL}/api/v1"
|
||||
jar="build/libs/spigot-tyrant-${VERSION}.jar"
|
||||
jar="build/libs/purpur-tyrant-${VERSION}.jar"
|
||||
export RELEASE_BODY
|
||||
if [ -n "$PREVIOUS_TAG" ]; then
|
||||
RELEASE_BODY=$(git log --pretty='format:- %s (%h)' "${PREVIOUS_TAG}..HEAD")
|
||||
@@ -138,4 +138,4 @@ jobs:
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary "@${jar}" \
|
||||
"${api_url}/repos/${REPOSITORY}/releases/${release_id}/assets?name=spigot-tyrant-${VERSION}.jar"
|
||||
"${api_url}/repos/${REPOSITORY}/releases/${release_id}/assets?name=purpur-tyrant-${VERSION}.jar"
|
||||
@@ -1,9 +1,9 @@
|
||||
# spigot-tyrant agent entrypoint
|
||||
# purpur-tyrant agent entrypoint
|
||||
|
||||
The canonical stories, engineering guidance, and **all process documents** are in the private [SoMC OKF wiki](https://git.garvis.dev/dmg/somc-okf/src/branch/main/index.md).
|
||||
|
||||
Before work, read the sibling `../somc-okf/index.md`, `../somc-okf/processes/index.md`, `../somc-okf/projects/spigot-tyrant/index.md`, `engineering.md` in that project section, and relevant `../somc-okf/user-stories/spigot-tyrant/` stories. Also follow the parent workspace `AGENTS.md` when present.
|
||||
Before work, read the sibling `../somc-okf/index.md`, `../somc-okf/processes/index.md`, `../somc-okf/projects/purpur-tyrant/index.md`, `engineering.md` in that project section, and relevant `../somc-okf/user-stories/purpur-tyrant/` stories. Also follow the parent workspace `AGENTS.md` when present.
|
||||
|
||||
For standalone checkouts, start at the [project page](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/spigot-tyrant/index.md) and [shared process](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md). Obtain wiki access before feature work; do not recreate a local knowledge bundle. Source builds do not require private wiki access.
|
||||
For standalone checkouts, start at the [project page](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/purpur-tyrant/index.md) and [shared process](https://git.garvis.dev/dmg/somc-okf/src/branch/main/processes/development.md). Obtain wiki access before feature work; do not recreate a local knowledge bundle. Source builds do not require private wiki access.
|
||||
|
||||
Development follows [Development cycle](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/development-cycle.md): approved stories, failing tests, passing implementation, verification, then source/wiki commit and push. GitOps updates are committed locally **without pushing**; only [Do release](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/do-release.md) authorizes a reviewed GitOps push.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Purpur Tyrant
|
||||
|
||||
The asymmetric Tyrant/Vigilante game plugin, targeting Java 25 and Purpur 26.3 build 2651.
|
||||
|
||||
## Build and releases
|
||||
|
||||
```sh
|
||||
./gradlew clean check jar
|
||||
```
|
||||
|
||||
New artifacts use `build/libs/purpur-tyrant-<version>.jar`. Gitea CI checks pushes/pull requests; approved conventional commits on main drive versioned releases. Distribution tests inspect the actual JAR, version, Java 25 bytecode, retained runtime identity and workflow names.
|
||||
|
||||
The full verification command compiles against Purpur API `26.3.build.2651-stable`, downloads build 2651 from the Purpur API, verifies SHA-256 `0677c1febecfbc2e5b0a7f76abd647b70a1f38d3cbdf3dd6a82e401b4262a598`, and starts a clean loopback-only Purpur server. The native lifecycle check loads, enables, commands, and disables the packaged plugin while proving persisted role/game state and a captured-entity snapshot survive the cycle. It uses generated build-local server state, not the untracked Compose environment, and is automated compatibility evidence rather than a live-player test.
|
||||
|
||||
## Migration compatibility
|
||||
|
||||
The repository and checkout are now `purpur-tyrant`, previously `spigot-tyrant`. Runtime identity remains **SpigotTyrant**, including `plugins/SpigotTyrant/`, Java entrypoints/packages, command/permission names and persistent keys for roles, captured mobs and legacy items. The migration changes the build/distribution target, not gameplay or persisted schemas. New releases no longer support Java 17.
|
||||
|
||||
Replace the old plugin JAR rather than installing both distributions. Old tags/assets remain available; both the origin GitOps v1.24.0 and separately prepared local v1.25.0 URLs were verified to redirect and download identical bytes after the repository rename. No deployment or GitOps push is implied by publication.
|
||||
|
||||
Unrelated local testing files are preserved; their commands may still reference the historical artifact names.
|
||||
|
||||
See the [canonical project](https://git.garvis.dev/dmg/somc-okf/src/branch/main/projects/purpur-tyrant/index.md), [stories](https://git.garvis.dev/dmg/somc-okf/src/branch/main/user-stories/purpur-tyrant/index.md), and [development cycle](https://git.garvis.dev/dmg/somc-okf/src/branch/main/runbooks/development-cycle.md).
|
||||
+69
-5
@@ -1,3 +1,6 @@
|
||||
import java.net.URI
|
||||
import java.security.MessageDigest
|
||||
|
||||
plugins {
|
||||
java
|
||||
}
|
||||
@@ -9,23 +12,24 @@ version = providers.gradleProperty("releaseVersion")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
|
||||
maven("https://repo.purpurmc.org/snapshots/")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile>().configureEach {
|
||||
options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
|
||||
// Retain supported legacy Bukkit APIs while preserving runtime compatibility.
|
||||
options.compilerArgs.addAll(listOf("-Xlint:all,-deprecation,-removal", "-Werror"))
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.spigotmc:spigot-api:26.2-R0.1-SNAPSHOT")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:26.3.build.2651-stable")
|
||||
|
||||
testImplementation("org.spigotmc:spigot-api:26.2-R0.1-SNAPSHOT")
|
||||
testImplementation("org.purpurmc.purpur:purpur-api:26.3.build.2651-stable")
|
||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
testImplementation("org.mockito:mockito-core:5.18.0")
|
||||
@@ -35,8 +39,68 @@ dependencies {
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
dependsOn(tasks.jar)
|
||||
systemProperty("distribution.jar", tasks.jar.get().archiveFile.get().asFile.absolutePath)
|
||||
systemProperty("distribution.version", project.version.toString())
|
||||
}
|
||||
|
||||
val nativeRuntime = layout.buildDirectory.dir("native-runtime")
|
||||
val downloadNativeRuntime = tasks.register("downloadNativeRuntime") {
|
||||
val launcher = nativeRuntime.map { it.file("purpur-26.3-2651.jar") }
|
||||
outputs.file(launcher)
|
||||
doLast {
|
||||
val file = launcher.get().asFile
|
||||
file.parentFile.mkdirs()
|
||||
val connection = URI("https://api.purpurmc.org/v2/purpur/26.3/2651/download")
|
||||
.toURL().openConnection()
|
||||
connection.connectTimeout = 30_000
|
||||
connection.readTimeout = 120_000
|
||||
val bytes = connection.getInputStream().use { it.readBytes() }
|
||||
val digest = MessageDigest.getInstance("SHA-256")
|
||||
.digest(bytes).joinToString("") { "%02x".format(it) }
|
||||
check(digest == "0677c1febecfbc2e5b0a7f76abd647b70a1f38d3cbdf3dd6a82e401b4262a598") {
|
||||
"Purpur 2651 checksum mismatch"
|
||||
}
|
||||
file.writeBytes(bytes)
|
||||
}
|
||||
}
|
||||
val prepareNativeRuntime = tasks.register<JavaExec>("prepareNativeRuntime") {
|
||||
dependsOn(downloadNativeRuntime)
|
||||
javaLauncher = javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(25) }
|
||||
classpath = files(nativeRuntime.map { it.file("purpur-26.3-2651.jar") })
|
||||
mainClass = "io.papermc.paperclip.Main"
|
||||
jvmArgs("-Dpaperclip.patchonly=true")
|
||||
workingDir(nativeRuntime)
|
||||
outputs.dir(nativeRuntime.map { it.dir("versions") })
|
||||
outputs.dir(nativeRuntime.map { it.dir("libraries") })
|
||||
}
|
||||
val nativeTest = sourceSets.create("nativeTest")
|
||||
dependencies {
|
||||
add(nativeTest.implementationConfigurationName, platform("org.junit:junit-bom:5.13.4"))
|
||||
add(nativeTest.implementationConfigurationName, "org.junit.jupiter:junit-jupiter")
|
||||
add(nativeTest.runtimeOnlyConfigurationName, "org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
val nativeRuntimeJars = files(fileTree(nativeRuntime) {
|
||||
include("versions/**/*.jar", "libraries/**/*.jar")
|
||||
}).builtBy(prepareNativeRuntime)
|
||||
nativeTest.compileClasspath += sourceSets.main.get().output + nativeRuntimeJars
|
||||
nativeTest.runtimeClasspath += sourceSets.main.get().runtimeClasspath + nativeRuntimeJars
|
||||
val nativeLifecycleTest = tasks.register<Test>("nativeLifecycleTest") {
|
||||
description = "Loads, enables, and disables the packaged plugin on the pinned Purpur runtime"
|
||||
dependsOn(tasks.jar)
|
||||
testClassesDirs = nativeTest.output.classesDirs
|
||||
classpath = nativeTest.runtimeClasspath
|
||||
useJUnitPlatform()
|
||||
maxHeapSize = "1G"
|
||||
workingDir(nativeRuntime)
|
||||
systemProperty("distribution.jar", tasks.jar.get().archiveFile.get().asFile.absolutePath)
|
||||
systemProperty(
|
||||
"purpur.launcher",
|
||||
nativeRuntime.map { it.file("purpur-26.3-2651.jar") }.get().asFile.absolutePath
|
||||
)
|
||||
}
|
||||
tasks.check { dependsOn(nativeLifecycleTest) }
|
||||
|
||||
val pluginVersion = version
|
||||
|
||||
tasks.processResources {
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
rootProject.name = "spigot-tyrant"
|
||||
rootProject.name = "purpur-tyrant"
|
||||
@@ -0,0 +1,155 @@
|
||||
package games.dmg.spigottyrant;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/** Exercises the packaged plugin through a real, isolated Purpur server lifecycle. */
|
||||
final class SpigotTyrantLifecycleNativeTest {
|
||||
private static final String TYRANT_ID = "11111111-1111-1111-1111-111111111111";
|
||||
|
||||
@Test
|
||||
void packagedPluginLoadsStateAndCapturedEntitySnapshotAcrossEnableAndDisable()
|
||||
throws Exception {
|
||||
Path runtime = Path.of("").toAbsolutePath();
|
||||
Path plugins = Files.createDirectories(runtime.resolve("plugins"));
|
||||
Path dataDirectory = Files.createDirectories(plugins.resolve("SpigotTyrant"));
|
||||
Files.copy(
|
||||
Path.of(System.getProperty("distribution.jar")),
|
||||
plugins.resolve("purpur-tyrant-native-test.jar"),
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
);
|
||||
try (var config = getClass().getClassLoader().getResourceAsStream("config.yml")) {
|
||||
if (config == null) {
|
||||
throw new AssertionError("Missing packaged default config");
|
||||
}
|
||||
Files.copy(config, dataDirectory.resolve("config.yml"));
|
||||
}
|
||||
Files.writeString(dataDirectory.resolve("state.yml"), """
|
||||
game:
|
||||
lifecycle: RUNNING
|
||||
tyrant-id: 11111111-1111-1111-1111-111111111111
|
||||
tyrant-level: 3
|
||||
unspent-choices: 2
|
||||
purchases: [ASSASSIN]
|
||||
claimed-armor: [HELMET]
|
||||
players:
|
||||
11111111-1111-1111-1111-111111111111:
|
||||
name: NativeTyrant
|
||||
tyrant-class: TAMER
|
||||
captured-mobs:
|
||||
- entity-type: ZOMBIE
|
||||
data:
|
||||
snapshot: '{id:"minecraft:zombie",CustomName:"Native"}'
|
||||
""");
|
||||
Files.writeString(runtime.resolve("eula.txt"), "eula=true\n");
|
||||
Files.writeString(runtime.resolve("server.properties"), """
|
||||
online-mode=false
|
||||
server-port=0
|
||||
query.port=0
|
||||
enable-query=false
|
||||
enable-rcon=false
|
||||
spawn-protection=0
|
||||
view-distance=2
|
||||
simulation-distance=2
|
||||
level-name=native-lifecycle-world
|
||||
motd=Spigot Tyrant native lifecycle
|
||||
""");
|
||||
|
||||
Process process = new ProcessBuilder(
|
||||
Path.of(System.getProperty("java.home"), "bin", "java").toString(),
|
||||
"-Xms512M",
|
||||
"-Xmx1G",
|
||||
"-jar",
|
||||
System.getProperty("purpur.launcher"),
|
||||
"--nogui"
|
||||
).directory(runtime.toFile()).redirectErrorStream(true).start();
|
||||
CountDownLatch started = new CountDownLatch(1);
|
||||
CompletableFuture<String> output = CompletableFuture.supplyAsync(() ->
|
||||
captureOutput(process, started)
|
||||
);
|
||||
try {
|
||||
assertTrue(started.await(120, TimeUnit.SECONDS), () ->
|
||||
"Purpur did not finish startup:\n" + completedOutput(output)
|
||||
);
|
||||
process.outputWriter(StandardCharsets.UTF_8).append("tyrantadmin status\n")
|
||||
.append("tyrantadmin grantpoint\n").flush();
|
||||
awaitPersistedProgress(dataDirectory.resolve("state.yml"));
|
||||
process.outputWriter(StandardCharsets.UTF_8).append("stop\n").flush();
|
||||
assertTrue(process.waitFor(60, TimeUnit.SECONDS), "Purpur did not stop cleanly");
|
||||
} finally {
|
||||
if (process.isAlive()) {
|
||||
process.destroyForcibly();
|
||||
process.waitFor(10, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
String log = output.get(10, TimeUnit.SECONDS);
|
||||
assertEquals(0, process.exitValue(), log);
|
||||
assertTrue(log.contains("Enabling SpigotTyrant"), log);
|
||||
assertTrue(log.contains("Spigot Tyrant enabled."), log);
|
||||
assertTrue(log.contains("Lifecycle: RUNNING"), log);
|
||||
assertTrue(log.contains("Tyrant: " + TYRANT_ID), log);
|
||||
assertTrue(log.contains("NativeTyrant (" + TYRANT_ID + "): class=TAMER"), log);
|
||||
assertTrue(log.contains("Disabling SpigotTyrant"), log);
|
||||
|
||||
PersistentState saved = new YamlTyrantStateRepository(
|
||||
dataDirectory.resolve("state.yml")
|
||||
).load();
|
||||
assertEquals(4, saved.game().tyrantLevel());
|
||||
assertEquals(3, saved.game().unspentChoices());
|
||||
CapturedMob captured = saved.players().values().iterator().next().capturedMobs().getFirst();
|
||||
assertEquals("ZOMBIE", captured.entityType());
|
||||
assertEquals(
|
||||
"{id:\"minecraft:zombie\",CustomName:\"Native\"}",
|
||||
captured.data().get("snapshot")
|
||||
);
|
||||
}
|
||||
|
||||
private static String captureOutput(Process process, CountDownLatch started) {
|
||||
StringBuilder output = new StringBuilder();
|
||||
try (var reader = new BufferedReader(new InputStreamReader(
|
||||
process.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
output.append(line).append('\n');
|
||||
if (line.contains("Done (") && line.contains("For help, type")) {
|
||||
started.countDown();
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
} catch (IOException exception) {
|
||||
throw new CompletionException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void awaitPersistedProgress(Path stateFile) throws Exception {
|
||||
long deadline = System.nanoTime() + Duration.ofSeconds(20).toNanos();
|
||||
while (System.nanoTime() < deadline) {
|
||||
PersistentState state = new YamlTyrantStateRepository(stateFile).load();
|
||||
if (state.game().tyrantLevel() == 4 && state.game().unspentChoices() == 3) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
throw new TimeoutException("Tyrant state did not reflect the native grantpoint command");
|
||||
}
|
||||
|
||||
private static String completedOutput(CompletableFuture<String> output) {
|
||||
return output.isDone() ? output.join() : "<server still running>";
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ class BukkitRoleControlItemServiceTest {
|
||||
void setup() {
|
||||
Plugin plugin = mock(Plugin.class);
|
||||
when(plugin.getName()).thenReturn("SpigotTyrant");
|
||||
when(plugin.namespace()).thenReturn("spigottyrant");
|
||||
service = new BukkitRoleControlItemService(plugin, PluginSettings.from(Map.of()));
|
||||
when(player.getUniqueId()).thenReturn(owner);
|
||||
when(player.getInventory()).thenReturn(inventory);
|
||||
|
||||
@@ -79,6 +79,7 @@ class CapturedMobInventorySynchronizationTest {
|
||||
when(view.getCursor()).thenAnswer(call -> cursor.get());
|
||||
Plugin plugin = mock(Plugin.class);
|
||||
when(plugin.getName()).thenReturn("SpigotTyrant");
|
||||
when(plugin.namespace()).thenReturn("spigottyrant");
|
||||
items = new BukkitCapturedMobItemService(plugin, PluginSettings.from(Map.of()));
|
||||
egg = egg(owner, captureId);
|
||||
contents[3] = egg;
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package games.dmg.spigottyrant;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.jar.JarFile;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
class PurpurDistributionTest {
|
||||
@Test
|
||||
void packagesNewNameWithoutChangingRuntimeIdentityOrBundlingTheApi() throws Exception {
|
||||
Path artifact = Path.of(System.getProperty("distribution.jar"));
|
||||
String version = System.getProperty("distribution.version");
|
||||
assertEquals("purpur-tyrant-" + version + ".jar", artifact.getFileName().toString());
|
||||
try (var jar = new JarFile(artifact.toFile())) {
|
||||
Map<?, ?> metadata = new Yaml().load(jar.getInputStream(jar.getJarEntry("plugin.yml")));
|
||||
assertEquals(version, metadata.get("version"));
|
||||
assertEquals("SpigotTyrant", metadata.get("name"), "Retain the data directory and persistent namespace");
|
||||
assertEquals("games.dmg.spigottyrant.SpigotTyrantPlugin", metadata.get("main"));
|
||||
assertTrue(((Map<?, ?>) metadata.get("permissions")).containsKey("spigottyrant.admin"));
|
||||
assertFalse(jar.stream().anyMatch(entry -> entry.getName().startsWith("org/bukkit/")
|
||||
|| entry.getName().startsWith("org/purpurmc/")
|
||||
|| entry.getName().startsWith("net/minecraft/")
|
||||
|| entry.getName().startsWith("com/comphenix/protocol/")
|
||||
|| entry.getName().contains("/test/")
|
||||
|| entry.getName().contains("/nativeTest/")
|
||||
|| entry.getName().endsWith("Test.class")));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pluginBytecodeTargetsJava25() throws Exception {
|
||||
try (var jar = new JarFile(System.getProperty("distribution.jar"));
|
||||
var bytecode = new DataInputStream(jar.getInputStream(
|
||||
jar.getJarEntry("games/dmg/spigottyrant/SpigotTyrantPlugin.class")))) {
|
||||
assertEquals(0xCAFEBABE, bytecode.readInt());
|
||||
bytecode.readUnsignedShort();
|
||||
assertEquals(69, bytecode.readUnsignedShort());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseAndCiAgreeWithThePurpurDistributionNameAndToolchain() throws Exception {
|
||||
String build = Files.readString(Path.of("build.gradle.kts"));
|
||||
assertTrue(build.contains("purpur-api:26.3.build.2651-stable"));
|
||||
assertTrue(build.contains("purpur-26.3-2651.jar"));
|
||||
assertTrue(build.contains("https://api.purpurmc.org/v2/purpur/26.3/2651/download"));
|
||||
assertTrue(build.contains("0677c1febecfbc2e5b0a7f76abd647b70a1f38d3cbdf3dd6a82e401b4262a598"));
|
||||
assertFalse(build.contains("26.2.build.2618-stable"));
|
||||
assertFalse(build.contains("org.spigotmc:spigot-api"));
|
||||
String readme = Files.readString(Path.of("README.md"));
|
||||
assertTrue(readme.contains("Purpur 26.3 build 2651"));
|
||||
for (String workflow : new String[] {"ci.yml", "release.yml"}) {
|
||||
String content = Files.readString(Path.of(".gitea/workflows", workflow));
|
||||
assertTrue(content.contains("java-version: 25"));
|
||||
assertTrue(content.contains("purpur-tyrant-"), workflow);
|
||||
assertFalse(content.contains("spigot-tyrant-"), workflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ final class RoleArenaLifecycleTest {
|
||||
void vacantArenaWithoutNearbyPlayersDoesNotSpawnABoss() {
|
||||
Fixture f = new Fixture();
|
||||
f.controller.run();
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), any());
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any());
|
||||
}
|
||||
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@@ -39,7 +39,7 @@ final class RoleArenaLifecycleTest {
|
||||
}
|
||||
Player player = f.player(18.01, 64, 0);
|
||||
f.controller.run();
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), any());
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any());
|
||||
when(player.getLocation()).thenReturn(new Location(f.world, 18, 64, 0));
|
||||
f.controller.run();
|
||||
f.controller.run();
|
||||
@@ -61,11 +61,11 @@ final class RoleArenaLifecycleTest {
|
||||
when(f.world.isChunkLoaded(0, 0)).thenReturn(false);
|
||||
f.controller.run();
|
||||
verify(f.world, never()).getChunkAt(0, 0);
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), any());
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any());
|
||||
when(f.world.isChunkLoaded(0, 0)).thenReturn(true);
|
||||
when(f.chunk.isEntitiesLoaded()).thenReturn(false);
|
||||
f.controller.run();
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), any());
|
||||
verify(f.world, never()).spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any());
|
||||
when(f.chunk.isEntitiesLoaded()).thenReturn(true);
|
||||
f.controller.run();
|
||||
org.junit.jupiter.api.Assertions.assertEquals(1, f.spawned.size());
|
||||
@@ -132,7 +132,7 @@ final class RoleArenaLifecycleTest {
|
||||
f.player(15, 64, 0);
|
||||
Zombie rejected = f.taggedBoss();
|
||||
f.entities.remove(rejected.getUniqueId());
|
||||
when(f.world.spawn(any(Location.class), eq(Zombie.class), any())).thenReturn(rejected);
|
||||
when(f.world.spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any())).thenReturn(rejected);
|
||||
f.controller.run();
|
||||
verify(rejected).remove();
|
||||
verify(f.world, never()).spawnParticle(eq(org.bukkit.Particle.PORTAL), any(Location.class),
|
||||
@@ -236,13 +236,13 @@ final class RoleArenaLifecycleTest {
|
||||
void failedSpawnLeavesNoOwnershipAndBacksOffBeforeRetry() {
|
||||
Fixture f = new Fixture();
|
||||
f.player(15, 64, 0);
|
||||
when(f.world.spawn(any(Location.class), eq(Zombie.class), any()))
|
||||
when(f.world.spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any()))
|
||||
.thenThrow(new IllegalStateException("spawn rejected"));
|
||||
org.junit.jupiter.api.Assertions.assertDoesNotThrow(f.controller::run);
|
||||
for (int i = 0; i < 9; i++) {
|
||||
f.controller.run();
|
||||
}
|
||||
verify(f.world).spawn(any(Location.class), eq(Zombie.class), any());
|
||||
verify(f.world).spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any());
|
||||
org.junit.jupiter.api.Assertions.assertEquals(0, f.entities.size());
|
||||
f.controller.close();
|
||||
}
|
||||
@@ -385,7 +385,7 @@ final class RoleArenaLifecycleTest {
|
||||
PlayerState.newPlayer(player.getUniqueId(), "Visitor")));
|
||||
return states;
|
||||
});
|
||||
when(world.spawn(any(Location.class), eq(Zombie.class), any())).thenAnswer(call -> {
|
||||
when(world.spawn(any(Location.class), eq(Zombie.class), org.mockito.ArgumentMatchers.<java.util.function.Consumer<Zombie>>any())).thenAnswer(call -> {
|
||||
Zombie boss = newZombie();
|
||||
spawned.add(boss);
|
||||
java.util.function.Consumer<Zombie> initializer = call.getArgument(2);
|
||||
|
||||
@@ -225,6 +225,7 @@ class RoleControlInventoryMovementTest {
|
||||
when(view.getCursor()).thenAnswer(call -> cursor.get());
|
||||
Plugin plugin = mock(Plugin.class);
|
||||
when(plugin.getName()).thenReturn("SpigotTyrant");
|
||||
when(plugin.namespace()).thenReturn("spigottyrant");
|
||||
var items = new BukkitRoleControlItemService(plugin, PluginSettings.from(Map.of()));
|
||||
var manager = mock(TyrantStateManager.class);
|
||||
when(manager.game()).thenReturn(new GameState(GameLifecycle.RUNNING,
|
||||
|
||||
Reference in new issue
Block a user