Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Build

on:
push:
branches:
- master
- feature/item-stash-integration
pull_request:
branches:
- master

permissions:
contents: read

jobs:
test-and-package:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4

- name: Set up Java 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: gradle

- name: Test and package
run: ./gradlew clean test shadowJar --no-daemon

- name: Upload plugin JAR
uses: actions/upload-artifact@v4
with:
name: AuctionHouse-1.5.4
path: build/libs/AuctionHouse-1.5.4.jar
if-no-files-found: error
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,5 @@ runs/

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
!libs/
!libs/Skyblock-Core-API-0.1.0-SNAPSHOT.jar
41 changes: 36 additions & 5 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ version = "1.5.4"

repositories {
mavenCentral()
maven { url = "https://repo.papermc.io/repository/maven-public/" }
maven {
name = "spigotmc-repo"
url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/"
Expand All @@ -22,9 +23,23 @@ repositories {
maven { url = "https://mvn-repo.arim.space/lesser-gpl3/" }
}

def configuredSkyblockCoreApi = providers.gradleProperty("skyblockCoreApiJar")
.orElse(providers.environmentVariable("SKYBLOCK_CORE_API_JAR"))
.orNull
def workspaceSkyblockCoreApi = file("../Skyblock-Core/build/libs/Skyblock-Core-0.1.0-SNAPSHOT.jar")
def portableSkyblockCoreApi = file("libs/Skyblock-Core-API-0.1.0-SNAPSHOT.jar")
def skyblockCoreApi = configuredSkyblockCoreApi != null
? file(configuredSkyblockCoreApi)
: (portableSkyblockCoreApi.isFile() ? portableSkyblockCoreApi : workspaceSkyblockCoreApi)

if (!skyblockCoreApi.isFile()) {
throw new GradleException("Skyblock-Core API JAR not found. Set -PskyblockCoreApiJar=<path> or SKYBLOCK_CORE_API_JAR.")
}

//alt + ins
dependencies {
compileOnly("org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT")
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
compileOnly(files(skyblockCoreApi))
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
exclude group: "org.bukkit", module: "bukkit"
}
Expand All @@ -33,6 +48,12 @@ dependencies {
implementation("net.kyori:adventure-text-minimessage:4.25.0")
implementation("net.kyori:adventure-text-serializer-legacy:4.25.0")
implementation("space.arim.morepaperlib:morepaperlib:0.5.2")

testImplementation(files(skyblockCoreApi))
testImplementation("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter:6.0.3")
testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.110.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.3")
}

tasks.named("jar") {
Expand All @@ -45,10 +66,20 @@ tasks.named("shadowJar") {
archiveVersion.set(version)
relocate("net.kyori.adventure", "me.elaineqheart.auctionHouse.libs.adventure")
relocate("space.arim.morepaperlib", "me.elaineqheart.auctionHouse.libs.morepaperlib")
destinationDirectory.set(file("/media/elaine/Boot/Elaine/programmieren/java/minecraft/MC_test_servers/1.21.5_paper/plugins"))
//destinationDirectory.set(file("/media/elaine/Boot/Elaine/programmieren/java/minecraft/MC_test_servers/26.2_spigot/plugins"))
//destinationDirectory.set(file("/media/elaine/Boot/Elaine/programmieren/java/minecraft/MC_test_servers/1.21.11_purpur/plugins"))
destinationDirectory.set(layout.buildDirectory.dir("libs"))
mergeServiceFiles()
doLast {
def leakedApiClasses = zipTree(archiveFile.get().asFile).matching {
include("dev/chengzhi/skyblockcore/**")
}.files
if (!leakedApiClasses.isEmpty()) {
throw new GradleException("Skyblock-Core classes must not be bundled in AuctionHouse: ${leakedApiClasses}")
}
}
}

tasks.named("test") {
useJUnitPlatform()
}

tasks {
Expand Down Expand Up @@ -85,4 +116,4 @@ processResources {
filesMatching('plugin.yml') {
expand props
}
}
}
Empty file modified gradlew
100644 → 100755
Empty file.
12 changes: 12 additions & 0 deletions libs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Skyblock-Core compile-only API

`Skyblock-Core-API-0.1.0-SNAPSHOT.jar` contains only the seven public
`dev.chengzhi.skyblockcore.api.delivery` classes required to compile the optional
hook. They are compiled from the supplied Skyblock-Core API sources with
`--release 21`, so the AuctionHouse build remains compatible with Paper's Java 21
baseline even when the local full Skyblock-Core JAR was built with a newer JDK.

The dependency is `compileOnly`; `shadowJar` also fails the build if any
`dev/chengzhi/skyblockcore/**` class is found in the AuctionHouse artifact.

SHA-256: `5D2DEA1F7006D806C98C07DF5DB6074E6C7272D5F73435D2F213296ED829A005`
Binary file added libs/Skyblock-Core-API-0.1.0-SNAPSHOT.jar
Binary file not shown.
58 changes: 52 additions & 6 deletions src/main/java/me/elaineqheart/auctionHouse/AuctionHouse.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@
import me.elaineqheart.auctionHouse.commands.DynamicCommandRegisterer;
import me.elaineqheart.auctionHouse.data.persistentStorage.ItemNoteStorage;
import me.elaineqheart.auctionHouse.data.persistentStorage.local.data.ConfigManager;
import me.elaineqheart.auctionHouse.delivery.AuctionItemDeliveryService;
import me.elaineqheart.auctionHouse.delivery.AuctionProcessingLocks;
import me.elaineqheart.auctionHouse.delivery.BukkitMainThreadExecutor;
import me.elaineqheart.auctionHouse.delivery.ItemDeliveryBootstrap;
import me.elaineqheart.auctionHouse.delivery.MainThreadExecutor;
import me.elaineqheart.auctionHouse.delivery.RetainedItemDeliveryCoordinator;
import me.elaineqheart.auctionHouse.listeners.AhConfigurationListener;
import me.elaineqheart.auctionHouse.listeners.PlayerJoinCollectListener;
import me.elaineqheart.auctionHouse.listeners.PurchaseSettlementRecoveryListener;
import me.elaineqheart.auctionHouse.pluginDependencies.AuctionHousePAPIExpansion;
import me.elaineqheart.auctionHouse.pluginDependencies.LocaleAPIExtension;
import me.elaineqheart.auctionHouse.purchase.BukkitPurchaseAuctionRepository;
import me.elaineqheart.auctionHouse.purchase.JsonPurchaseSettlementStore;
import me.elaineqheart.auctionHouse.purchase.PurchaseSettlementService;
import me.elaineqheart.auctionHouse.purchase.VaultEconomyGateway;
import me.elaineqheart.auctionHouse.world.displays.DisplayKillListener;
import me.elaineqheart.auctionHouse.world.displays.DisplayListener;
import me.elaineqheart.auctionHouse.world.displays.UpdateDisplay;
Expand All @@ -22,6 +33,8 @@
import space.arim.morepaperlib.scheduling.GracefulScheduling;

import java.io.IOException;
import java.time.Clock;
import java.util.UUID;

public final class AuctionHouse extends JavaPlugin {

Expand All @@ -31,7 +44,15 @@ public final class AuctionHouse extends JavaPlugin {
public static GUIManager getGuiManager() {return guiManager;}
public static InputGUIManager getInputManager() {return inputGUIManager;}
private MorePaperLib morePaperLib;
private AuctionItemDeliveryService itemDeliveryService;
private AuctionProcessingLocks processingLocks;
private RetainedItemDeliveryCoordinator retainedItemDelivery;
private PurchaseSettlementService purchaseSettlements;
public static AuctionHouse getInstance() {return instance;}
public AuctionItemDeliveryService getItemDeliveryService() {return itemDeliveryService;}
public AuctionProcessingLocks getProcessingLocks() {return processingLocks;}
public RetainedItemDeliveryCoordinator getRetainedItemDelivery() {return retainedItemDelivery;}
public PurchaseSettlementService getPurchaseSettlements() {return purchaseSettlements;}
public GracefulScheduling getScheduler() {
//morePaperLib.scheduling()... It uses Paper's threaded-regions schedulers if Folia is used, otherwise it falls back to the default Bukkit scheduler.
return morePaperLib.scheduling();
Expand All @@ -54,12 +75,6 @@ public void onEnable() {
return;
}

Bukkit.getPluginManager().registerEvents(new NPCListener(), this);
Bukkit.getPluginManager().registerEvents(new DisplayListener(), this);
Bukkit.getPluginManager().registerEvents(new PlayerJoinCollectListener(), this);
Bukkit.getPluginManager().registerEvents(new AhConfigurationListener(), this);
DisplayKillListener.register();

ConfigManager.setupConfigs();

//if(SettingManager.useRedis) RedisManager.connect();
Expand All @@ -71,6 +86,37 @@ public void onEnable() {
throw new RuntimeException(e);
}

MainThreadExecutor mainThread = new BukkitMainThreadExecutor(this);
processingLocks = new AuctionProcessingLocks();
itemDeliveryService = ItemDeliveryBootstrap.create(this, mainThread);
retainedItemDelivery = new RetainedItemDeliveryCoordinator(
itemDeliveryService, processingLocks, mainThread);
try {
JsonPurchaseSettlementStore settlementStore = new JsonPurchaseSettlementStore(
getDataFolder().toPath().resolve("data").resolve("purchase-settlements"));
purchaseSettlements = new PurchaseSettlementService(
settlementStore,
new BukkitPurchaseAuctionRepository(),
new VaultEconomyGateway(),
itemDeliveryService,
processingLocks,
mainThread,
UUID::randomUUID,
Clock.systemUTC(),
getLogger());
purchaseSettlements.recover(Bukkit.getOnlinePlayers());
} catch (IOException e) {
getLogger().severe("Failed to load AuctionHouse purchase settlements");
throw new RuntimeException(e);
}

Bukkit.getPluginManager().registerEvents(new NPCListener(), this);
Bukkit.getPluginManager().registerEvents(new DisplayListener(), this);
Bukkit.getPluginManager().registerEvents(new PlayerJoinCollectListener(), this);
Bukkit.getPluginManager().registerEvents(new PurchaseSettlementRecoveryListener(this), this);
Bukkit.getPluginManager().registerEvents(new AhConfigurationListener(), this);
DisplayKillListener.register();

DynamicCommandRegisterer.init();
UpdateDisplay.init();
//NoteStorage.purge();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import me.elaineqheart.auctionHouse.data.ram.AuctionHouseStorage;
import me.elaineqheart.auctionHouse.data.ram.ItemManager;
import me.elaineqheart.auctionHouse.data.ram.ItemNote;
import me.elaineqheart.auctionHouse.delivery.DeliveryFeedback;
import me.elaineqheart.auctionHouse.delivery.RetainedDeliveryKind;
import me.elaineqheart.auctionHouse.delivery.RetainedDeliveryStatus;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
Expand All @@ -21,6 +24,7 @@ public class AdminConfirmGUI extends InventoryGUI{
private final String reason;
private final boolean delete;
private final AhConfiguration c;
private boolean processing;

public AdminConfirmGUI(String reason, ItemNote note, boolean delete, AhConfiguration configuration) {
super();
Expand Down Expand Up @@ -85,6 +89,11 @@ private InventoryButton confirmExpireItem() {
.creator(player -> ItemManager.confirm)
.consumer(event -> {
Player p = (Player) event.getWhoClicked();
if (processing) {
DeliveryFeedback.sendProcessing(p);
Sounds.villagerDeny(event);
return;
}
ItemNote test = AuctionHouseStorage.getNote(note.getNoteID());
if (test == null) {
p.sendMessage(M.getFormatted("chat.non-existent2"));
Expand All @@ -96,8 +105,21 @@ private InventoryButton confirmExpireItem() {
Sounds.villagerDeny(event);
return;
}
boolean success = ItemNoteStorage.adminConfirmExpireItem(note, reason);
if (!AuctionHouse.getInstance().getProcessingLocks()
.tryLock(note.getNoteID())) {
DeliveryFeedback.sendProcessing(p);
return;
}
processing = true;
boolean success;
try {
success = ItemNoteStorage.adminConfirmExpireItem(note, reason);
} finally {
AuctionHouse.getInstance().getProcessingLocks()
.unlock(note.getNoteID());
}
if (!success) {
processing = false;
p.sendMessage(M.getFormatted("chat.non-existent"));
Sounds.villagerDeny(event);
return;
Expand All @@ -114,9 +136,8 @@ private InventoryButton confirmDeleteItem() {
.creator(player -> ItemManager.confirm)
.consumer(event -> {
Player p = (Player) event.getWhoClicked();
//check if inventory is full
if(p.getInventory().firstEmpty() == -1) {
p.sendMessage(M.getFormatted("chat.inventory-full"));
if (processing) {
DeliveryFeedback.sendProcessing(p);
Sounds.villagerDeny(event);
return;
}
Expand All @@ -133,18 +154,46 @@ private InventoryButton confirmDeleteItem() {
return;
}
ItemStack item = note.getItem();
boolean success = ItemNoteStorage.adminConfirmDeleteItem(note, reason);
if (!success) {
p.sendMessage(M.getFormatted("chat.non-existent"));
Sounds.villagerDeny(event);
return;
}

p.getInventory().addItem(item);
p.closeInventory();
Sounds.experience(event);
Sounds.breakWood(event);
p.sendMessage(M.getFormatted("chat.admin-delete-auction","%reason%", reason));
processing = true;
AuctionHouse.getInstance().getRetainedItemDelivery().deliver(
p,
note.getNoteID(),
RetainedDeliveryKind.ADMIN_RETURN,
item,
java.util.Map.of("seller_uuid", note.getPlayerUUID().toString()),
() -> {
if (!ItemNoteStorage.adminConfirmDeleteItem(note, reason)) {
throw new IllegalStateException("Admin return source changed");
}
}
).whenComplete((result, failure) -> {
if (failure != null || result == null
|| result.status() == RetainedDeliveryStatus.COMMIT_FAILED) {
processing = false;
DeliveryFeedback.sendReconciliationRequired(p);
AuctionHouse.getInstance().getLogger().warning(
"Failed to finalize admin return " + note.getNoteID());
return;
}
if (result.status() == RetainedDeliveryStatus.PROCESSING) {
processing = false;
DeliveryFeedback.sendProcessing(p);
Sounds.villagerDeny(event);
return;
}
if (result.status() == RetainedDeliveryStatus.DELIVERY_FAILED) {
processing = false;
DeliveryFeedback.sendFailure(p, result.delivery());
Sounds.villagerDeny(event);
return;
}
DeliveryFeedback.sendDestination(p, result.delivery());
p.closeInventory();
Sounds.experience(event);
Sounds.breakWood(event);
p.sendMessage(M.getFormatted("chat.admin-delete-auction",
"%reason%", reason));
});
});
}
private InventoryButton cancel(){
Expand Down
Loading
Loading