fix(sanctuary): resynchroniser le panneau de quête après rotation hors chunk
Verification 26.2 / check (pull_request) Canceled after 0s
Verification 26.2 / check (pull_request) Canceled after 0s
Le contrat quotidien était déjà régénéré en data pendant que le panneau gardait l'ancien texte. Un clic réservait donc une autre quête que celle affichée. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -244,7 +244,9 @@ tasks.register("verifyBlackMarket") {
|
||||
|| !quests.contains("SanctuaryRegistries.questBoardTier")
|
||||
|| quests.contains("activationTier(")
|
||||
|| quests.contains("QuestBoardTier.ACTIVATION_COST")
|
||||
|| !quests.contains("addTreasury(claim.depositCurrency(), claim.depositAmount())")) {
|
||||
|| !quests.contains("addTreasury(claim.depositCurrency(), claim.depositAmount())")
|
||||
|| !quests.contains("shouldRewriteSign")
|
||||
|| !quests.contains("syncLoadedSigns")) {
|
||||
throw new GradleException("The physical Black Market escrow/delivery contract is incomplete")
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -75,9 +75,12 @@ final class QuestBoardGenerator {
|
||||
private QuestBoardGenerator() {}
|
||||
|
||||
static QuestBoardData.Contract generate(MinecraftServer server, String boardId, QuestBoardTier tier, long now) {
|
||||
long day = Math.floorDiv(now, 86_400_000L);
|
||||
long seed = boardId.hashCode() * 0x9E3779B97F4A7C15L ^ day * 0xD1B54A32D192ED03L ^ tier.ordinal();
|
||||
RandomSource random = RandomSource.create(seed);
|
||||
return generate(server, boardId, tier, now, Math.floorDiv(now, 86_400_000L));
|
||||
}
|
||||
|
||||
static QuestBoardData.Contract generate(MinecraftServer server, String boardId, QuestBoardTier tier,
|
||||
long now, long salt) {
|
||||
RandomSource random = RandomSource.create(contractSeed(boardId, tier, salt));
|
||||
List<Template> pool = switch (tier) {
|
||||
case EMERALD -> EASY;
|
||||
case RUBY -> MEDIUM;
|
||||
@@ -93,6 +96,10 @@ final class QuestBoardGenerator {
|
||||
java.util.Optional.empty(), now);
|
||||
}
|
||||
|
||||
static long contractSeed(String boardId, QuestBoardTier tier, long salt) {
|
||||
return boardId.hashCode() * 0x9E3779B97F4A7C15L ^ salt * 0xD1B54A32D192ED03L ^ tier.ordinal();
|
||||
}
|
||||
|
||||
static long nextRefreshAt(String boardId, long now) {
|
||||
LocalDate tomorrow = Instant.ofEpochMilli(now).atZone(ZoneOffset.UTC).toLocalDate().plusDays(1);
|
||||
long midnight = tomorrow.atStartOfDay(ZoneOffset.UTC).toInstant().toEpochMilli();
|
||||
|
||||
+38
-9
@@ -262,17 +262,17 @@ public final class QuestBoardService {
|
||||
}
|
||||
|
||||
private static void tick(MinecraftServer server) {
|
||||
QuestBoardData data = QuestBoardData.get(server);
|
||||
syncLoadedSigns(server, data);
|
||||
if (++tickCounter < 20) return;
|
||||
tickCounter = 0;
|
||||
long now = System.currentTimeMillis();
|
||||
QuestBoardData data = QuestBoardData.get(server);
|
||||
for (QuestBoardData.Board board : data.boards()) {
|
||||
ServerLevel level = level(server, board.dimension());
|
||||
BlockPos pos = BlockPos.of(board.pos());
|
||||
if (level != null && level.isLoaded(pos) && !(level.getBlockEntity(pos) instanceof SignBlockEntity)) {
|
||||
removeBoard(server, data, board, "server", "sign_missing", true); continue;
|
||||
}
|
||||
if (level != null && level.isLoaded(pos) && refreshedSignLayouts.add(board.id())) refreshSign(server, board);
|
||||
QuestBoardData.Contract contract = board.contract().orElse(null);
|
||||
if (contract != null && contract.claim().isPresent() && now >= contract.claim().get().deadlineAt()) {
|
||||
QuestBoardData.Claim claim = contract.claim().get();
|
||||
@@ -288,18 +288,41 @@ public final class QuestBoardService {
|
||||
}
|
||||
if (!board.frozen() && now >= board.nextRefreshAt()
|
||||
&& (contract == null || contract.claim().isEmpty())) {
|
||||
QuestBoardData.Contract generated = QuestBoardGenerator.generate(server, board.id(), board.questTier(), now);
|
||||
QuestBoardData.Board refreshed = board.withContract(Optional.of(generated),
|
||||
QuestBoardGenerator.nextRefreshAt(board.id(), now));
|
||||
QuestBoardData.Board latest = data.board(board.id()).orElse(null);
|
||||
if (latest == null || latest.frozen()
|
||||
|| latest.contract().flatMap(QuestBoardData.Contract::claim).isPresent()) continue;
|
||||
QuestBoardData.Contract generated = QuestBoardGenerator.generate(server, board.id(), latest.questTier(), now);
|
||||
QuestBoardData.Board refreshed = latest.withContract(Optional.of(generated),
|
||||
QuestBoardGenerator.nextRefreshAt(latest.id(), now));
|
||||
data.replace(refreshed);
|
||||
data.audit(new QuestBoardData.AuditEntry(now, "refresh", "server", board.id(), generated.id(), "daily"));
|
||||
data.audit(new QuestBoardData.AuditEntry(now, "refresh", "server", latest.id(), generated.id(), "daily"));
|
||||
refreshSign(server, refreshed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void syncLoadedSigns(MinecraftServer server, QuestBoardData data) {
|
||||
for (QuestBoardData.Board board : data.boards()) {
|
||||
ServerLevel level = level(server, board.dimension());
|
||||
BlockPos pos = BlockPos.of(board.pos());
|
||||
boolean loaded = level != null && level.isLoaded(pos);
|
||||
if (!shouldRewriteSign(refreshedSignLayouts, board.id(), loaded)) continue;
|
||||
refreshSign(server, board);
|
||||
}
|
||||
}
|
||||
|
||||
/** A loaded board whose layout was forgotten (chunk unload or failed write) must be rewritten. */
|
||||
static boolean shouldRewriteSign(Set<String> syncedLayouts, String boardId, boolean chunkLoaded) {
|
||||
if (!chunkLoaded) {
|
||||
syncedLayouts.remove(boardId);
|
||||
return false;
|
||||
}
|
||||
return syncedLayouts.add(boardId);
|
||||
}
|
||||
|
||||
private static void removeBoard(MinecraftServer server, QuestBoardData data, QuestBoardData.Board board,
|
||||
String actor, String reason, boolean refundDeposit) {
|
||||
refreshedSignLayouts.remove(board.id());
|
||||
if (!data.remove(board)) return;
|
||||
long now = System.currentTimeMillis();
|
||||
board.contract().flatMap(QuestBoardData.Contract::claim).ifPresent(claim -> {
|
||||
@@ -330,7 +353,8 @@ public final class QuestBoardService {
|
||||
QuestBoardData.Board board = resolve(data, id);
|
||||
if (board == null || board.contract().flatMap(QuestBoardData.Contract::claim).isPresent()) return false;
|
||||
long now = System.currentTimeMillis();
|
||||
QuestBoardData.Contract contract = QuestBoardGenerator.generate(server, board.id(), board.questTier(), now);
|
||||
QuestBoardData.Contract contract = QuestBoardGenerator.generate(server, board.id(), board.questTier(), now,
|
||||
now ^ UUID.randomUUID().getMostSignificantBits());
|
||||
QuestBoardData.Board updated = board.withContract(Optional.of(contract), QuestBoardGenerator.nextRefreshAt(board.id(), now));
|
||||
data.replace(updated);
|
||||
data.audit(new QuestBoardData.AuditEntry(now, "operator_refresh", actor, board.id(), contract.id(), ""));
|
||||
@@ -338,11 +362,15 @@ public final class QuestBoardService {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void refreshSign(MinecraftServer server, QuestBoardData.Board board) {
|
||||
static boolean refreshSign(MinecraftServer server, QuestBoardData.Board board) {
|
||||
ServerLevel level = level(server, board.dimension());
|
||||
BlockPos pos = BlockPos.of(board.pos());
|
||||
if (level != null && level.isLoaded(pos) && level.getBlockEntity(pos) instanceof SignBlockEntity sign)
|
||||
if (level != null && level.isLoaded(pos) && level.getBlockEntity(pos) instanceof SignBlockEntity sign) {
|
||||
writeSign(level, sign, board);
|
||||
return true;
|
||||
}
|
||||
refreshedSignLayouts.remove(board.id());
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void writeSign(ServerLevel level, SignBlockEntity sign, QuestBoardData.Board board) {
|
||||
@@ -370,6 +398,7 @@ public final class QuestBoardService {
|
||||
sign.setWaxed(true);
|
||||
sign.setChanged();
|
||||
level.sendBlockUpdated(sign.getBlockPos(), sign.getBlockState(), sign.getBlockState(), 3);
|
||||
refreshedSignLayouts.add(board.id());
|
||||
}
|
||||
|
||||
private static void inspect(ServerPlayer player, QuestBoardData.Board board) {
|
||||
|
||||
+21
@@ -1,6 +1,8 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.shop;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
@@ -39,6 +41,25 @@ public final class QuestBoardModelSmoke {
|
||||
long refresh = QuestBoardGenerator.nextRefreshAt("board", now);
|
||||
require(refresh > now && refresh - now < 28L * 60L * 60L * 1_000L,
|
||||
"Daily refresh escaped its UTC window");
|
||||
long day = Math.floorDiv(now, 86_400_000L);
|
||||
require(QuestBoardGenerator.contractSeed("board", QuestBoardTier.RUBY, day)
|
||||
== QuestBoardGenerator.contractSeed("board", QuestBoardTier.RUBY, day),
|
||||
"Daily contracts on the same UTC day must stay deterministic");
|
||||
require(QuestBoardGenerator.contractSeed("board", QuestBoardTier.RUBY, day)
|
||||
!= QuestBoardGenerator.contractSeed("board", QuestBoardTier.RUBY, now),
|
||||
"An operator refresh must not reuse the daily seed");
|
||||
Set<String> layouts = new HashSet<>();
|
||||
require(!QuestBoardService.shouldRewriteSign(layouts, "board", false),
|
||||
"An unloaded board must not rewrite its sign");
|
||||
require(layouts.isEmpty(), "Unloading must forget the cached sign layout");
|
||||
require(QuestBoardService.shouldRewriteSign(layouts, "board", true),
|
||||
"The first load after a rotation must rewrite the sign from saved data");
|
||||
require(!QuestBoardService.shouldRewriteSign(layouts, "board", true),
|
||||
"A still-loaded board must not rewrite its sign every tick");
|
||||
require(!QuestBoardService.shouldRewriteSign(layouts, "board", false),
|
||||
"Leaving the chunk must forget the layout without rewriting");
|
||||
require(QuestBoardService.shouldRewriteSign(layouts, "board", true),
|
||||
"Returning after an unloaded daily refresh must rewrite the new contract");
|
||||
}
|
||||
|
||||
private static void require(boolean value, String message) {
|
||||
|
||||
Reference in New Issue
Block a user