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
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.proc>full</maven.compiler.proc>
<denareconomy.version>0.2.0</denareconomy.version>
<denareconomy.version>0.3.0</denareconomy.version>
<rpcharacters.version>2.16.0</rpcharacters.version>
<simplefactions.version>3.0.1</simplefactions.version>
<tlibs.version>2.3.0</tlibs.version>
<simplefactions.version>4.0.0</simplefactions.version>
<tlibs.version>2.4.0</tlibs.version>
</properties>

<dependencyManagement>
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/tfminecraft/games/Games.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class Games extends JavaPlugin {
@Override
public void onEnable() {
plugin = this;
net.tfminecraft.games.wager.WagerJournal.initialize();
createFolders();
createConfigs();
if (!loadConfigs()) {
Expand All @@ -49,6 +50,7 @@ public void onEnable() {
getServer().getPluginManager().registerEvents(GameSelectGui.INSTANCE, this);
getServer().getPluginManager().registerEvents(TableOptionsGui.INSTANCE, this);
TableManager.get().loadAll();
getServer().getScheduler().runTaskTimer(this, () -> TableManager.get().loadDeferred(), 100L, 100L);
TableManager.get().startClock();

var gameCmd = getCommand("games");
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/net/tfminecraft/games/guild/GuildTableMigration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.tfminecraft.games.guild;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.tfminecraft.games.persistence.DurableFiles;

/** Stamp an old table's guild incarnation once; a deleted guild never adopts a later namesake. */
public final class GuildTableMigration {
private GuildTableMigration() {}

public static boolean prepare(Path tableFile, Path migrationFolder) throws IOException {
JsonObject data = JsonParser.parseString(Files.readString(tableFile)).getAsJsonObject();
if (data.has("ownerGuildMigrated") && data.get("ownerGuildMigrated").getAsBoolean()) return true;
if (!data.has("ownerGuildId") || data.get("ownerGuildId").isJsonNull()
|| data.get("ownerGuildId").getAsString().isBlank()) return true;
if (!GuildTables.ready()) return false;
Path backup = migrationFolder.resolve("backup").resolve(tableFile.getFileName());
Path manifest = migrationFolder.resolve(tableFile.getFileName());
JsonObject migrated;
if (Files.exists(manifest)) {
// A complete plan is written before the table. Replay the exact incarnation, never resolve again.
migrated = JsonParser.parseString(Files.readString(manifest)).getAsJsonObject();
} else {
GuildTables.Lookup lookup = GuildTables.resolveIncarnation(data.get("ownerGuildId").getAsString());
// A failed lookup is not a deleted guild: stamp nothing and let a later pass retry.
if (lookup.failed()) return false;
if (!Files.exists(backup)) DurableFiles.write(backup, Files.readAllBytes(tableFile));
migrated = data.deepCopy();
UUID incarnation = lookup.incarnation();
if (incarnation != null) migrated.addProperty("ownerGuildIncarnation", incarnation.toString());
migrated.addProperty("ownerGuildMigrated", true);
DurableFiles.write(manifest, migrated.toString());
}
DurableFiles.write(tableFile, migrated.toString());
return true;
}
}
65 changes: 54 additions & 11 deletions src/main/java/net/tfminecraft/games/guild/GuildTables.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package net.tfminecraft.games.guild;

import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;

import net.tfminecraft.simplefactions.guild.Guild;
import net.tfminecraft.simplefactions.managers.FactionManager;
import net.tfminecraft.simplefactions.objects.Bank;
import net.tfminecraft.simplefactions.api.SimpleFactionsApi;
import net.tfminecraft.simplefactions.enums.GuildModifier;
import net.tfminecraft.games.Games;
import net.tfminecraft.games.Messages;
Expand Down Expand Up @@ -53,7 +54,7 @@ public static String guildId(Player player) {
if (!sfReady()) {
return null;
}
Guild guild = FactionManager.getGuildByMember(player.getName());
Guild guild = FactionManager.getGuildByMember(player);
if (guild == null) {
return null;
}
Expand Down Expand Up @@ -100,8 +101,10 @@ public static int cap(String guildId) {

public static int count(String guildId) {
int n = 0;
UUID current = incarnation(guildId);
for (Table table : TableManager.get().tables()) {
if (counts(table) && guildId.equals(table.ownerGuildId())) {
if (counts(table) && guildId.equals(table.ownerGuildId())
&& current != null && current.equals(table.ownerGuildIncarnation())) {
n++;
}
}
Expand All @@ -117,10 +120,12 @@ public static boolean overCap(String guildId) {
}

public static boolean frozen(Table table) {
return counts(table) && overCap(table.ownerGuildId());
return counts(table) && (!owns(table.ownerGuildId(), table.ownerGuildIncarnation())
|| overCap(table.ownerGuildId()));
}

public static boolean canStartGuildAutoRound(Table table) {
if (counts(table) && !owns(table.ownerGuildId(), table.ownerGuildIncarnation())) return false;
if (table.live()) {
return true;
}
Expand All @@ -145,7 +150,9 @@ public static String refuseKey(Player player, TableHouse house, Table existing)
if (id == null) {
return "place.no_guild";
}
if (!isLeader(id, player)) {
if (existing == null && house.ownerGuildIncarnation() == null)
house.setOwnerGuildIncarnation(incarnation(id));
if (!owns(id, house.ownerGuildIncarnation()) || !isLeader(id, player)) {
return "place.not_leader";
}
if (existing != null && counts(existing)) {
Expand Down Expand Up @@ -174,6 +181,7 @@ public static void stampGuild(Player player, TableHouse house) {
}
if (house.ownerGuildId() == null || house.ownerGuildId().isBlank()) {
house.setOwnerGuildId(guildId(player));
house.setOwnerGuildIncarnation(incarnation(house.ownerGuildId()));
}
}

Expand All @@ -186,15 +194,51 @@ public static boolean mayDeal(Table table, Player player) {
}
String guildId = table.ownerGuildId();
if (guildId != null && !guildId.isBlank()) {
return isMember(guildId, player);
return owns(guildId, table.ownerGuildIncarnation()) && isMember(guildId, player);
}
return table.ownerPlayer() != null && table.ownerPlayer().equals(player.getUniqueId());
}

/** True for the leader of the guild that owns this table, who is the one who may place and remove it. */
public static boolean leadsOwner(Table table, Player player) {
String guildId = table.ownerGuildId();
return guildId != null && !guildId.isBlank() && isLeader(guildId, player);
return owns(guildId, table.ownerGuildIncarnation()) && isLeader(guildId, player);
}

/** An incarnation lookup that keeps "no such guild" apart from "could not ask". */
public record Lookup(UUID incarnation, boolean failed) {}

public static Lookup resolveIncarnation(String guildId) {
if (guildId == null || guildId.isBlank()) return new Lookup(null, false);
try {
if (!sfReady()) return new Lookup(null, true);
Guild guild = FactionManager.getGuildByString(guildId);
if (guild == null) return new Lookup(null, false);
UUID incarnation = guild.getIncarnationId();
// A live guild without an incarnation is not proof of deletion; ask again later.
return incarnation == null ? new Lookup(null, true) : new Lookup(incarnation, false);
} catch (RuntimeException | LinkageError ex) {
warn(ex);
return new Lookup(null, true);
}
}

public static UUID incarnation(String guildId) {
Lookup lookup = resolveIncarnation(guildId);
return lookup.failed() ? null : lookup.incarnation();
}

public static boolean owns(String guildId, UUID incarnation) {
return incarnation != null && incarnation.equals(incarnation(guildId));
}

public static boolean ready() {
try {
return sfReady();
} catch (RuntimeException | LinkageError ex) {
warn(ex);
return false;
}
}

private static boolean isMember(String guildId, Player player) {
Expand All @@ -213,8 +257,7 @@ private static boolean isLeader(String guildId, Player player) {
if (guild == null) {
return false;
}
String leader = guild.getLeader();
return leader != null && leader.equalsIgnoreCase(player.getName());
return guild.isLeader(player);
} catch (RuntimeException | LinkageError ex) {
warn(ex);
return false;
Expand All @@ -233,13 +276,13 @@ private static Guild guild(String guildId) {

private static boolean sfReady() {
Plugin plugin = Bukkit.getPluginManager().getPlugin("SimpleFactions");
return plugin != null && plugin.isEnabled();
return plugin != null && plugin.isEnabled() && SimpleFactionsApi.ready();
}

private static void warn(Throwable ex) {
if (!loggedFail) {
loggedFail = true;
Games.plugin.getLogger().warning("[Games] SimpleFactions guild tables skipped: " + ex.getMessage());
if (Games.plugin != null) Games.plugin.getLogger().warning("[Games] SimpleFactions guild tables skipped: " + ex.getMessage());
}
}
}
48 changes: 48 additions & 0 deletions src/main/java/net/tfminecraft/games/persistence/DurableFiles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.tfminecraft.games.persistence;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;

/** Publish whole, flushed files before money or inventory changes. */
public final class DurableFiles {
private DurableFiles() {}

public static void write(Path target, String text) throws IOException {
write(target, text.getBytes(StandardCharsets.UTF_8));
}

public static void write(Path target, byte[] bytes) throws IOException {
target = target.toAbsolutePath();
Files.createDirectories(target.getParent());
Path temp = Files.createTempFile(target.getParent(), ".pending-", ".tmp");
try {
try (FileChannel channel = FileChannel.open(temp, StandardOpenOption.WRITE)) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
while (buffer.hasRemaining()) channel.write(buffer);
channel.force(true);
}
Files.move(temp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
// Parent entries also need flushing when an intent creates its first directory.
for (Path directory = target.getParent(); directory != null; directory = directory.getParent()) sync(directory);
Comment thread
ryanbarlow97 marked this conversation as resolved.
} finally {
Files.deleteIfExists(temp);
}
}

public static void delete(Path target) throws IOException {
Files.deleteIfExists(target);
sync(target.getParent());
}

private static void sync(Path directory) throws IOException {
try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
channel.force(true);
}
}
}
20 changes: 20 additions & 0 deletions src/main/java/net/tfminecraft/games/table/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ public final class Table {
private int maxBet;
private int autoCountdown;
private UUID ownerPlayer;
private boolean moneyBlocked;
private final java.util.Map<UUID, net.tfminecraft.tlibs.character.CharacterRef> wagerCharacters = new java.util.HashMap<>();

public boolean moneyBlocked() { return moneyBlocked; }
public void setMoneyBlocked(boolean blocked) { moneyBlocked = blocked; }
public java.util.Map<UUID, net.tfminecraft.tlibs.character.CharacterRef> wagerCharacters() { return wagerCharacters; }

private String ownerGuildId;
private UUID ownerGuildIncarnation;
private boolean autoDealer;
private boolean staffMint;
private int houseFloat;
Expand Down Expand Up @@ -373,9 +381,18 @@ public String ownerGuildId() {
}

public void setOwnerGuildId(String ownerGuildId) {
if (!java.util.Objects.equals(this.ownerGuildId, ownerGuildId)) ownerGuildIncarnation = null;
this.ownerGuildId = ownerGuildId;
}

public UUID ownerGuildIncarnation() {
return ownerGuildIncarnation;
}

public void setOwnerGuildIncarnation(UUID incarnation) {
this.ownerGuildIncarnation = incarnation;
}

public boolean autoDealer() {
return autoDealer;
}
Expand Down Expand Up @@ -481,6 +498,7 @@ public void startSession() {
}

public void clearSession() {
if (moneyBlocked || net.tfminecraft.games.wager.WagerJournal.blocked(this)) return;
live = false;
actor = null;
phase = null;
Expand All @@ -489,5 +507,7 @@ public void clearSession() {
handIndex = 0;
autoCountdown = 0;
if (!poker.enabled() || !poker.occupied()) roundMoney.clear();
wagerCharacters.entrySet().removeIf(entry -> ledger.total(entry.getKey()) == 0
&& !(ledger.total(id) > 0 && (entry.getKey().equals(dealerId) || entry.getKey().equals(floatOwner))));
}
}
12 changes: 12 additions & 0 deletions src/main/java/net/tfminecraft/games/table/TableHouse.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public final class TableHouse {

private UUID ownerPlayer;
private String ownerGuildId;
private UUID ownerGuildIncarnation;
private boolean autoDealer;
private boolean staffMint;
private int minBet;
Expand Down Expand Up @@ -53,6 +54,7 @@ public static TableHouse from(Table table) {
TableHouse house = new TableHouse();
house.ownerPlayer = table.ownerPlayer();
house.ownerGuildId = table.ownerGuildId();
house.ownerGuildIncarnation = table.ownerGuildIncarnation();
house.autoDealer = table.autoDealer();
house.staffMint = table.staffMint();
house.minBet = Math.max(1, table.minBet());
Expand All @@ -67,6 +69,7 @@ public static TableHouse from(Table table) {
public void apply(Table table) {
table.setOwnerPlayer(ownerPlayer);
table.setOwnerGuildId(ownerGuildId);
table.setOwnerGuildIncarnation(ownerGuildIncarnation);
table.setAutoDealer(autoDealer);
table.setStaffMint(staffMint);
table.setMinBet(minBet);
Expand All @@ -90,9 +93,18 @@ public String ownerGuildId() {
}

public void setOwnerGuildId(String ownerGuildId) {
if (!java.util.Objects.equals(this.ownerGuildId, ownerGuildId)) ownerGuildIncarnation = null;
this.ownerGuildId = ownerGuildId;
}

public UUID ownerGuildIncarnation() {
return ownerGuildIncarnation;
}

public void setOwnerGuildIncarnation(UUID incarnation) {
this.ownerGuildIncarnation = incarnation;
}

public boolean autoDealer() {
return autoDealer;
}
Expand Down
Loading
Loading