fix(sanctuary): réserver les requêtes aux panneaux dédiés (#25) #67
@@ -60,15 +60,20 @@ Les quêtes possèdent leur propre sauvegarde (`black_market_quests`, version 1)
|
||||
panneau actif contient un contrat réel : ajouter des panneaux augmente donc le nombre
|
||||
de quêtes quotidiennes disponibles.
|
||||
|
||||
Activation d'un panneau vanilla libre :
|
||||
Création d'un panneau de quête serveur :
|
||||
|
||||
1. Tenir huit gemmes du niveau voulu : émeraude pour facile, rubis pour moyen, saphir
|
||||
pour difficile.
|
||||
2. S'accroupir et faire clic droit sur le panneau. Les huit gemmes sont consommées et
|
||||
le panneau est protégé au nom du joueur qui l'a créé.
|
||||
1. Fabriquer le panneau dédié du niveau voulu autour d'un panneau vanilla : huit
|
||||
émeraudes pour facile, huit rubis pour moyen ou huit saphirs pour difficile.
|
||||
2. Poser ce panneau dédié. Il devient immédiatement un panneau de quête protégé au nom
|
||||
du joueur qui l'a créé.
|
||||
3. Le renouvellement a lieu une fois par jour réel, autour de minuit UTC avec un
|
||||
décalage déterministe compris entre zéro et deux heures.
|
||||
|
||||
Un panneau vanilla libre ne peut plus être converti en panneau de quête avec des gemmes.
|
||||
Le panneau rose `sanctuary:quest_request_sign` reste le seul support des demandes
|
||||
communautaires. Les anciens panneaux vanilla qui possèdent déjà un contrat enregistré
|
||||
restent utilisables jusqu'à leur destruction afin de préserver les réservations existantes.
|
||||
|
||||
Interactions :
|
||||
|
||||
- clic droit : réserver une quête libre, puis livrer ou vérifier sa propre quête ;
|
||||
|
||||
@@ -121,7 +121,9 @@ tasks.register("verifyBlackMarket") {
|
||||
|| !service.contains("currencyStacks")
|
||||
|| !questData.contains('SAVE_KEY = "black_market_quests"')
|
||||
|| !quests.contains("ServerLivingEntityEvents.AFTER_DEATH")
|
||||
|| !quests.contains("QuestBoardTier.ACTIVATION_COST")
|
||||
|| !quests.contains("SanctuaryRegistries.questBoardTier")
|
||||
|| quests.contains("activationTier(")
|
||||
|| quests.contains("QuestBoardTier.ACTIVATION_COST")
|
||||
|| !quests.contains("addTreasury(claim.depositCurrency(), claim.depositAmount())")) {
|
||||
throw new GradleException("The physical Black Market escrow/delivery contract is incomplete")
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ public final class SanctuaryMod implements ModInitializer {
|
||||
SanctuaryProgressionGates.initialize();
|
||||
SanctuaryPlayerColors.initialize();
|
||||
ShopService.initialize();
|
||||
// Community request signs must claim their interactions before the generic
|
||||
// gem activation callback used by ordinary signs.
|
||||
// Community request signs must claim their interactions before the other
|
||||
// physical sign services.
|
||||
QuestRequestService.initialize();
|
||||
QuestBoardService.initialize();
|
||||
BlackMarketService.initialize();
|
||||
|
||||
+7
@@ -99,6 +99,13 @@ public final class SanctuaryRegistries {
|
||||
return java.util.List.of(EMERALD_QUEST_SIGN, RUBY_QUEST_SIGN, SAPPHIRE_QUEST_SIGN);
|
||||
}
|
||||
|
||||
public static QuestBoardTier questBoardTier(Block block) {
|
||||
for (QuestSignSet signs : questSigns()) {
|
||||
if (block == signs.standing() || block == signs.wall()) return signs.tier();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isQuestRequestSign(Block block) {
|
||||
return block == QUEST_REQUEST_SIGN.standing() || block == QUEST_REQUEST_SIGN.wall();
|
||||
}
|
||||
|
||||
+12
-34
@@ -1,6 +1,7 @@
|
||||
package fr.koka99cab.sanctuary26.sanctuary.shop;
|
||||
|
||||
import fr.koka99cab.sanctuary26.sanctuary.api.inventory.SanctuaryPlayerInventoryView;
|
||||
import fr.koka99cab.sanctuary26.sanctuary.registry.SanctuaryRegistries;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -50,14 +51,9 @@ public final class QuestBoardService {
|
||||
UseBlockCallback.EVENT.register((player, level, hand, hit) -> {
|
||||
if (hand != InteractionHand.MAIN_HAND || !(level.getBlockEntity(hit.getBlockPos()) instanceof SignBlockEntity sign))
|
||||
return InteractionResult.PASS;
|
||||
QuestBoardTier heldTier = activationTier(player.getMainHandItem());
|
||||
if (level.isClientSide()) {
|
||||
return player.isSecondaryUseActive() && heldTier != null
|
||||
&& player.getMainHandItem().getCount() >= QuestBoardTier.ACTIVATION_COST
|
||||
? InteractionResult.SUCCESS : InteractionResult.PASS;
|
||||
}
|
||||
if (level.isClientSide()) return InteractionResult.PASS;
|
||||
return player instanceof ServerPlayer serverPlayer
|
||||
? interact(serverPlayer, (ServerLevel) level, hit.getBlockPos(), sign, heldTier)
|
||||
? interact(serverPlayer, (ServerLevel) level, hit.getBlockPos(), sign)
|
||||
: InteractionResult.PASS;
|
||||
});
|
||||
PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> {
|
||||
@@ -81,34 +77,19 @@ public final class QuestBoardService {
|
||||
}
|
||||
|
||||
private static InteractionResult interact(ServerPlayer player, ServerLevel level, BlockPos pos,
|
||||
SignBlockEntity sign, QuestBoardTier heldTier) {
|
||||
SignBlockEntity sign) {
|
||||
QuestBoardData data = QuestBoardData.get(level.getServer());
|
||||
QuestBoardData.Board board = data.boardAt(dimension(level), pos.asLong()).orElse(null);
|
||||
if (board != null) {
|
||||
handleBoard(player, level, sign, data, board);
|
||||
return InteractionResult.SUCCESS_SERVER;
|
||||
}
|
||||
if (!player.isSecondaryUseActive() || heldTier == null
|
||||
|| player.getMainHandItem().getCount() < QuestBoardTier.ACTIVATION_COST) return InteractionResult.PASS;
|
||||
if (BlackMarketData.get(level.getServer()).listingAt(dimension(level), pos.asLong()).isPresent()) {
|
||||
message(player, "occupied"); return InteractionResult.CONSUME;
|
||||
}
|
||||
if (BlackMarketData.get(level.getServer()).isBanned(player.getUUID())) {
|
||||
player.sendSystemMessage(Component.translatable("message.sanctuary.black_market.banned"));
|
||||
return InteractionResult.CONSUME;
|
||||
}
|
||||
ItemStack held = player.getMainHandItem();
|
||||
held.shrink(QuestBoardTier.ACTIVATION_COST);
|
||||
player.getInventory().setChanged();
|
||||
if (!createBoard(player, level, pos, sign, heldTier)) {
|
||||
held.grow(QuestBoardTier.ACTIVATION_COST); message(player, "occupied"); return InteractionResult.CONSUME;
|
||||
}
|
||||
if (board == null) return InteractionResult.PASS;
|
||||
handleBoard(player, level, sign, data, board);
|
||||
return InteractionResult.SUCCESS_SERVER;
|
||||
}
|
||||
|
||||
/** Activates one of the three crafted quest signs without charging its material cost twice. */
|
||||
public static void activatePlacedSign(ServerPlayer player, ServerLevel level, BlockPos pos,
|
||||
SignBlockEntity sign, QuestBoardTier tier) {
|
||||
QuestBoardTier placedTier = SanctuaryRegistries.questBoardTier(level.getBlockState(pos).getBlock());
|
||||
if (!canActivateDedicatedSign(placedTier, tier)) return;
|
||||
if (BlackMarketData.get(level.getServer()).isBanned(player.getUUID())) {
|
||||
player.sendSystemMessage(Component.translatable("message.sanctuary.black_market.banned"));
|
||||
return;
|
||||
@@ -116,6 +97,10 @@ public final class QuestBoardService {
|
||||
if (!createBoard(player, level, pos, sign, tier)) message(player, "occupied");
|
||||
}
|
||||
|
||||
static boolean canActivateDedicatedSign(QuestBoardTier placedTier, QuestBoardTier requestedTier) {
|
||||
return placedTier != null && placedTier == requestedTier;
|
||||
}
|
||||
|
||||
private static boolean createBoard(ServerPlayer player, ServerLevel level, BlockPos pos,
|
||||
SignBlockEntity sign, QuestBoardTier tier) {
|
||||
QuestBoardData data = QuestBoardData.get(level.getServer());
|
||||
@@ -466,13 +451,6 @@ public final class QuestBoardService {
|
||||
if (item != null) player.getInventory().placeItemBackInInventory(new ItemStack(item, deposit.amount));
|
||||
}
|
||||
|
||||
private static QuestBoardTier activationTier(ItemStack stack) {
|
||||
if (stack.isEmpty()) return null;
|
||||
Identifier id = BuiltInRegistries.ITEM.getKey(stack.getItem());
|
||||
for (QuestBoardTier tier : QuestBoardTier.values()) if (tier.currencyItem().equals(id)) return tier;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ServerPlayer killer(LivingEntity entity, DamageSource source) {
|
||||
Entity attacker = source.getEntity();
|
||||
if (attacker instanceof ServerPlayer player) return player;
|
||||
|
||||
@@ -5,13 +5,12 @@ import java.util.Locale;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/** Physical quest-board tiers. The activation material is also the default refundable bond. */
|
||||
/** Physical quest-board tiers. Each tier currency is also its default refundable bond. */
|
||||
public enum QuestBoardTier {
|
||||
EMERALD("emerald", Identifier.withDefaultNamespace("emerald"), 1, Duration.ofMinutes(15), ChatFormatting.GREEN),
|
||||
RUBY("ruby", Identifier.fromNamespaceAndPath("anotherworld", "ruby"), 1, Duration.ofMinutes(45), ChatFormatting.RED),
|
||||
SAPPHIRE("sapphire", Identifier.fromNamespaceAndPath("anotherworld", "sapphire"), 1, Duration.ofMinutes(90), ChatFormatting.AQUA);
|
||||
|
||||
public static final int ACTIVATION_COST = 8;
|
||||
private final String id;
|
||||
private final Identifier currencyItem;
|
||||
private final int deposit;
|
||||
|
||||
+6
@@ -29,6 +29,12 @@ public final class QuestBoardModelSmoke {
|
||||
require(reserved.withObjective(objective.advance(20)).objective().complete(), "Kill progress did not complete");
|
||||
require(QuestBoardTier.SAPPHIRE.reservationMillis() > QuestBoardTier.RUBY.reservationMillis(),
|
||||
"Difficulty deadline order is invalid");
|
||||
require(!QuestBoardService.canActivateDedicatedSign(null, QuestBoardTier.EMERALD),
|
||||
"A vanilla sign can still become a quest board");
|
||||
require(!QuestBoardService.canActivateDedicatedSign(QuestBoardTier.EMERALD, QuestBoardTier.RUBY),
|
||||
"A mismatched dedicated sign accepted the wrong quest tier");
|
||||
require(QuestBoardService.canActivateDedicatedSign(QuestBoardTier.SAPPHIRE, QuestBoardTier.SAPPHIRE),
|
||||
"A dedicated quest sign rejected its own tier");
|
||||
long now = 1_800_000_000_000L;
|
||||
long refresh = QuestBoardGenerator.nextRefreshAt("board", now);
|
||||
require(refresh > now && refresh - now < 28L * 60L * 60L * 1_000L,
|
||||
|
||||
Reference in New Issue
Block a user