This commit is contained in:
1 parent
d18a41af6c
commit
8ae8cdb603
6 files changed
+27
-22
No files matched your search
@@ -20,8 +20,9 @@ import org.bukkit.entity.Player;
|
||||
public final class CombatRewardEventBridge {
|
||||
public record Capture(UUID targetId,String targetType,String experienceClass,String coinClass,
|
||||
CombatRewardRules.KillPosition killPosition,ManagedEncounterOrigin managedOrigin,
|
||||
String stage,Instant defeatedAt){
|
||||
public Capture{Objects.requireNonNull(targetId);Objects.requireNonNull(targetType);Objects.requireNonNull(experienceClass);Objects.requireNonNull(coinClass);Objects.requireNonNull(stage);Objects.requireNonNull(defeatedAt);}
|
||||
String stage,Instant defeatedAt,java.util.List<UUID> directContributors,
|
||||
String rewardUnitId,int rewardUnitCount,BigInteger experiencePool,BigInteger coinPool){
|
||||
public Capture{Objects.requireNonNull(targetId);Objects.requireNonNull(targetType);Objects.requireNonNull(experienceClass);Objects.requireNonNull(coinClass);Objects.requireNonNull(stage);Objects.requireNonNull(defeatedAt);directContributors=java.util.List.copyOf(directContributors);Objects.requireNonNull(rewardUnitId);Objects.requireNonNull(experiencePool);Objects.requireNonNull(coinPool);if(rewardUnitCount<1||experiencePool.signum()<0||coinPool.signum()<0)throw new IllegalArgumentException("Invalid combat reward capture");}
|
||||
}
|
||||
private final Consumer<EventInput> sink;private final Clock clock;
|
||||
public CombatRewardEventBridge(Consumer<EventInput> sink,Clock clock){this.sink=Objects.requireNonNull(sink);this.clock=Objects.requireNonNull(clock);}
|
||||
@@ -29,7 +30,7 @@ public final class CombatRewardEventBridge {
|
||||
CombatRewardRules.KillPosition restricted,CombatRewardRules rules,Capture capture,
|
||||
CharacterRecord before,CharacterStorage.CombatAward award,EquipmentSessions.Active active,
|
||||
Player player,String nativeSync,String feedback){
|
||||
if(award==null||award.record().isEmpty()||before==null)return;var after=award.record().orElseThrow();var data=new LinkedHashMap<String,Object>();data.put("operation_id",operation.toString());data.put("reward_receipt",operation.toString());data.put("stage",capture.stage());data.put("recipient_character_id",character.toString());data.put("target_id",capture.targetId().toString());data.put("target_type",capture.targetType());data.put("experience_class",capture.experienceClass());data.put("coin_class",capture.coinClass());data.put("defeated_at",capture.defeatedAt().toString());if(capture.killPosition()!=null)data.put("kill_location",position(capture.killPosition()));data.put("allocated_experience",allocatedExperience.toString());data.put("allocated_coins",allocatedCoins.toString());var appliedExperience=after.experience().subtract(before.experience()).max(BigInteger.ZERO);var appliedCoins=after.coins().subtract(before.coins()).max(BigInteger.ZERO);data.put("applied_experience",appliedExperience.toString());data.put("applied_coins",appliedCoins.toString());data.put("suppressed_experience",allocatedExperience.subtract(appliedExperience.min(allocatedExperience)).toString());data.put("suppressed_coins",allocatedCoins.subtract(appliedCoins.min(allocatedCoins)).toString());data.put("before_revision",before.revision().toString());data.put("after_revision",after.revision().toString());data.put("storage_status",award.status().name().toLowerCase(java.util.Locale.ROOT));data.put("levels_gained",award.levels());data.put("native_sync",nativeSync);data.put("feedback",feedback);if(capture.managedOrigin()!=null){data.put("resolution","managed_origin_exempt");data.put("managed_origin_kind",capture.managedOrigin().kind().name().toLowerCase(java.util.Locale.ROOT));data.put("managed_origin_source",capture.managedOrigin().source().toString());data.put("spacing_applicability","authenticated_managed_origin_exempt");}else if(restricted!=null){boolean blocked=award.status()==CharacterStorage.CombatAwardStatus.SPACING_BLOCKED;data.put("resolution",blocked?"ordinary_spacing_blocked":"ordinary_spacing_eligible");data.put("spacing_applicability","ordinary_restricted_kill");data.put("spacing_minimum",rules.restrictedSpacing().toPlainString());var prior=before.restrictedKills().get(restricted.world());if(prior!=null)data.put("ordinary_history_before",position(prior));var current=after.restrictedKills().get(restricted.world());if(current!=null)data.put("ordinary_history_after",position(current));}else{data.put("resolution","spacing_not_applicable");data.put("spacing_applicability","passive_or_fantasy_character");}data.put("loss_reason",loss(award.status()));var context=context(active,player,after.name());try{sink.accept(new EventInput(id(operation),operation,clock.instant(),"combat.reward_resolved",context,Map.copyOf(data)));}catch(RuntimeException ignored){}
|
||||
if(award==null||award.record().isEmpty()||before==null)return;var after=award.record().orElseThrow();var data=new LinkedHashMap<String,Object>();data.put("operation_id",operation.toString());data.put("reward_receipt",operation.toString());data.put("stage",capture.stage());data.put("recipient_character_id",character.toString());data.put("target_id",capture.targetId().toString());data.put("target_type",capture.targetType());data.put("experience_class",capture.experienceClass());data.put("coin_class",capture.coinClass());data.put("defeated_at",capture.defeatedAt().toString());data.put("direct_contributor_character_ids",capture.directContributors().stream().map(UUID::toString).sorted().toList());data.put("reward_unit_id",capture.rewardUnitId());data.put("reward_unit_count",capture.rewardUnitCount());data.put("experience_pool",capture.experiencePool().toString());data.put("coin_pool",capture.coinPool().toString());if(capture.killPosition()!=null)data.put("kill_location",position(capture.killPosition()));data.put("allocated_experience",allocatedExperience.toString());data.put("allocated_coins",allocatedCoins.toString());var appliedExperience=after.experience().subtract(before.experience()).max(BigInteger.ZERO);var appliedCoins=after.coins().subtract(before.coins()).max(BigInteger.ZERO);data.put("applied_experience",appliedExperience.toString());data.put("applied_coins",appliedCoins.toString());data.put("suppressed_experience",allocatedExperience.subtract(appliedExperience.min(allocatedExperience)).toString());data.put("suppressed_coins",allocatedCoins.subtract(appliedCoins.min(allocatedCoins)).toString());data.put("before_revision",before.revision().toString());data.put("after_revision",after.revision().toString());data.put("before_level",before.level());data.put("after_level",after.level());data.put("before_experience",before.experience().toString());data.put("after_experience",after.experience().toString());data.put("before_points",before.unspentPoints().toString());data.put("after_points",after.unspentPoints().toString());data.put("storage_status",award.status().name().toLowerCase(java.util.Locale.ROOT));data.put("levels_gained",award.levels());data.put("native_sync",nativeSync);data.put("feedback",feedback);if(capture.managedOrigin()!=null){data.put("resolution","managed_origin_exempt");data.put("managed_origin_kind",capture.managedOrigin().kind().name().toLowerCase(java.util.Locale.ROOT));data.put("managed_origin_source",capture.managedOrigin().source().toString());data.put("spacing_applicability","authenticated_managed_origin_exempt");}else if(restricted!=null){boolean blocked=award.status()==CharacterStorage.CombatAwardStatus.SPACING_BLOCKED;data.put("resolution",blocked?"ordinary_spacing_blocked":"ordinary_spacing_eligible");data.put("spacing_applicability","ordinary_restricted_kill");data.put("spacing_minimum",rules.restrictedSpacing().toPlainString());var prior=before.restrictedKills().get(restricted.world());if(prior!=null)data.put("ordinary_history_before",position(prior));var current=after.restrictedKills().get(restricted.world());if(current!=null)data.put("ordinary_history_after",position(current));}else{data.put("resolution","spacing_not_applicable");data.put("spacing_applicability","passive_or_fantasy_character");}data.put("loss_reason",loss(award.status()));var context=context(active,player,after.name());try{sink.accept(new EventInput(id(operation),operation,clock.instant(),"combat.reward_resolved",context,Map.copyOf(data)));}catch(RuntimeException ignored){}
|
||||
}
|
||||
private EventInput.Context context(EquipmentSessions.Active active,Player player,String characterName){if(active==null||player==null)return null;var location=player.getLocation();if(location==null||location.getWorld()==null)return new EventInput.Context(active.player(),player.getName(),active.character(),characterName,active.session(),null);return new EventInput.Context(active.player(),player.getName(),active.character(),characterName,active.session(),new EventInput.Location(location.getWorld().getUID(),location.getWorld().getName(),location.getX(),location.getY(),location.getZ(),clock.instant(),"exact"));}
|
||||
private static Map<String,Object> position(CombatRewardRules.KillPosition value){return Map.of("world_id",value.world().toString(),"x",value.x().toPlainString(),"y",value.y().toPlainString(),"z",value.z().toPlainString());}
|
||||
|
||||
@@ -211,12 +211,12 @@ public final class CombatExperience implements Listener, AutoCloseable {
|
||||
|
||||
/** Freeze final-time eligibility and exact shares before the durable death claim. */
|
||||
public List<games.dmg.fantasystick.session.DownedEpisode.Reward> allocateDownedDefeat(games.dmg.fantasystick.session.DownedEpisode episode){
|
||||
var attribution=episode.attribution();if(!attribution.playerDefeat()||attribution.contributors().isEmpty())return List.of();var units=rewardUnits(new ArrayList<>(attribution.contributors()));if(units.isEmpty())return List.of();var capturedRewards=rewards;var replaySafeRandom=new java.util.Random(episode.id().getMostSignificantBits()^episode.id().getLeastSignificantBits());var xp=games.dmg.fantasystick.progression.RewardAllocator.allocate(capturedRewards.pool(CombatRewardRules.Target.FANTASY_CHARACTER,attribution.victimLevel()),units,replaySafeRandom);var coins=games.dmg.fantasystick.progression.RewardAllocator.allocate(attribution.victimCoins(),units,replaySafeRandom);return xp.keySet().stream().sorted().map(character->new games.dmg.fantasystick.session.DownedEpisode.Reward(character,xp.get(character),coins.get(character))).toList();
|
||||
var attribution=episode.attribution();if(!attribution.playerDefeat()||attribution.contributors().isEmpty())return List.of();var units=rewardUnits(new ArrayList<>(attribution.contributors()));if(units.isEmpty())return List.of();var capturedRewards=rewards;var replaySafeRandom=new java.util.Random(episode.id().getMostSignificantBits()^episode.id().getLeastSignificantBits());var experiencePool=capturedRewards.pool(CombatRewardRules.Target.FANTASY_CHARACTER,attribution.victimLevel());var xp=games.dmg.fantasystick.progression.RewardAllocator.allocate(experiencePool,units,replaySafeRandom);var coins=games.dmg.fantasystick.progression.RewardAllocator.allocate(attribution.victimCoins(),units,replaySafeRandom);return xp.keySet().stream().sorted().map(character->{var unit=units.stream().filter(value->value.recipients().contains(character)).findFirst().orElseThrow();return new games.dmg.fantasystick.session.DownedEpisode.Reward(character,xp.get(character),coins.get(character),String.valueOf(unit.key()),units.size());}).toList();
|
||||
}
|
||||
|
||||
/** Apply only the shares frozen in the winning durable final-death claim. */
|
||||
public CompletionStage<Void> settleDownedDefeat(games.dmg.fantasystick.session.DownedEpisode episode){
|
||||
if(episode.finalRewards().isEmpty())return CompletableFuture.completedStage(null);var capturedRewards=rewards;var settlements=new ArrayList<CompletableFuture<Void>>();for(var reward:episode.finalRewards()){UUID receipt=UUID.nameUUIDFromBytes(("downed:"+episode.id()+":"+reward.character()).getBytes(java.nio.charset.StandardCharsets.UTF_8));var capture=new games.dmg.fantasystick.events.CombatRewardEventBridge.Capture(episode.player(),"player","fantasy_character","pvp_balance",null,null,"final_death",episode.resolvedAt());var proposed=new PendingAward(receipt,reward.character(),reward.experience(),reward.coins(),null,capturedRewards,capture);var existing=pending.putIfAbsent(receipt,proposed);var operation=existing==null?proposed:existing;settlements.add(operation.settled);attempt(operation);}return CompletableFuture.allOf(settlements.toArray(CompletableFuture[]::new));
|
||||
if(episode.finalRewards().isEmpty())return CompletableFuture.completedStage(null);var capturedRewards=rewards;var settlements=new ArrayList<CompletableFuture<Void>>();for(var reward:episode.finalRewards()){UUID receipt=UUID.nameUUIDFromBytes(("downed:"+episode.id()+":"+reward.character()).getBytes(java.nio.charset.StandardCharsets.UTF_8));var capture=new games.dmg.fantasystick.events.CombatRewardEventBridge.Capture(episode.player(),"player","fantasy_character","pvp_balance",null,null,"final_death",episode.resolvedAt(),episode.attribution().contributors(),reward.unitId(),reward.unitCount(),capturedRewards.pool(CombatRewardRules.Target.FANTASY_CHARACTER,episode.attribution().victimLevel()),episode.attribution().victimCoins());var proposed=new PendingAward(receipt,reward.character(),reward.experience(),reward.coins(),null,capturedRewards,capture);var existing=pending.putIfAbsent(receipt,proposed);var operation=existing==null?proposed:existing;settlements.add(operation.settled);attempt(operation);}return CompletableFuture.allOf(settlements.toArray(CompletableFuture[]::new));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
@@ -244,7 +244,8 @@ public final class CombatExperience implements Listener, AutoCloseable {
|
||||
? coinRules.get().mob(coinTarget(target), random) : defeatedCoins;
|
||||
if (units.isEmpty()) {managed.ifPresent(this::settled);return;} // The defeat transition has already persisted any PvP balance loss.
|
||||
var capturedRewards = rewards;
|
||||
var xp = games.dmg.fantasystick.progression.RewardAllocator.allocate(capturedRewards.pool(kind, level), units, random);
|
||||
var experiencePool=capturedRewards.pool(kind,level);
|
||||
var xp = games.dmg.fantasystick.progression.RewardAllocator.allocate(experiencePool, units, random);
|
||||
var coins = games.dmg.fantasystick.progression.RewardAllocator.allocate(coinPool, units, random);
|
||||
CombatRewardRules.KillPosition restricted = managed.isPresent()||kind == CombatRewardRules.Target.PASSIVE
|
||||
|| kind == CombatRewardRules.Target.FANTASY_CHARACTER ? null : position(target.getLocation());
|
||||
@@ -254,7 +255,7 @@ public final class CombatExperience implements Listener, AutoCloseable {
|
||||
for (UUID character : xp.keySet()) {
|
||||
String receiptAuthority=managed.isPresent()?authority:targetReward+":"+now+":"+authority;
|
||||
UUID receipt = UUID.nameUUIDFromBytes((receiptAuthority+":"+character).getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||
var capture=new games.dmg.fantasystick.events.CombatRewardEventBridge.Capture(target.getUniqueId(),target.getType().name().toLowerCase(java.util.Locale.ROOT),kind.name().toLowerCase(java.util.Locale.ROOT),coinTarget(target).name().toLowerCase(java.util.Locale.ROOT),defeatedCharacter==null?position(target.getLocation()):null,managed.orElse(null),defeatedCharacter==null?"mob_defeat":"fantasy_character_defeat",now);
|
||||
var unit=units.stream().filter(value->value.recipients().contains(character)).findFirst().orElseThrow();var capture=new games.dmg.fantasystick.events.CombatRewardEventBridge.Capture(target.getUniqueId(),target.getType().name().toLowerCase(java.util.Locale.ROOT),kind.name().toLowerCase(java.util.Locale.ROOT),coinTarget(target).name().toLowerCase(java.util.Locale.ROOT),defeatedCharacter==null?position(target.getLocation()):null,managed.orElse(null),defeatedCharacter==null?"mob_defeat":"fantasy_character_defeat",now,direct,String.valueOf(unit.key()),units.size(),experiencePool,coinPool);
|
||||
var proposed = new PendingAward(receipt, character, xp.get(character), coins.get(character), restricted,capturedRewards,capture);
|
||||
var existing=pending.putIfAbsent(receipt,proposed);var operation=existing==null?proposed:existing;settlements.add(operation.settled);attempt(operation);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public final class DownedDirectory implements AutoCloseable{
|
||||
|
||||
private Map<UUID,DownedEpisode> load()throws IOException{if(Files.notExists(file))return Map.of();try{if(!Files.isRegularFile(file,LinkOption.NOFOLLOW_LINKS)||Files.isSymbolicLink(file)||Files.size(file)>1_048_576)throw new IllegalArgumentException();Object decoded=yaml().load(Files.readString(file,StandardCharsets.UTF_8));if(!(decoded instanceof Map<?,?> root)||!"1".equals(String.valueOf(root.get("schema-version"))))throw new IllegalArgumentException();var result=new LinkedHashMap<UUID,DownedEpisode>();for(Object raw:list(root.get("episodes"))){var row=map(raw);var value=decode(row);if(result.put(value.character(),value)!=null)throw new IllegalArgumentException();}return Map.copyOf(result);}catch(RuntimeException invalid){throw new IOException("Invalid downed state",invalid);}}
|
||||
private void save(Map<UUID,DownedEpisode> state)throws IOException{Files.createDirectories(file.getParent());var root=new LinkedHashMap<String,Object>();root.put("schema-version","1");root.put("episodes",state.values().stream().sorted(Comparator.comparing(DownedEpisode::character)).map(this::encode).toList());Path temporary=Files.createTempFile(file.getParent(),".downed-",".tmp");try{byte[] bytes=yaml().dump(root).getBytes(StandardCharsets.UTF_8);try(var out=FileChannel.open(temporary,StandardOpenOption.WRITE)){var buffer=ByteBuffer.wrap(bytes);while(buffer.hasRemaining())out.write(buffer);out.force(true);}Files.move(temporary,file,StandardCopyOption.ATOMIC_MOVE,StandardCopyOption.REPLACE_EXISTING);try(var directory=FileChannel.open(file.getParent(),StandardOpenOption.READ)){directory.force(true);}snapshot=Map.copyOf(state);}finally{Files.deleteIfExists(temporary);}}
|
||||
private Map<String,Object> encode(DownedEpisode value){var row=new LinkedHashMap<String,Object>();row.put("id",value.id().toString());row.put("character",value.character().toString());row.put("player",value.player().toString());row.put("session",value.session().toString());row.put("world",value.world().toString());row.put("x",BigDecimal.valueOf(value.x()).toPlainString());row.put("y",BigDecimal.valueOf(value.y()).toPlainString());row.put("z",BigDecimal.valueOf(value.z()).toPlainString());row.put("yaw",Float.toString(value.yaw()));row.put("pitch",Float.toString(value.pitch()));row.put("entry-world",value.entryWorld().toString());row.put("entry-x",BigDecimal.valueOf(value.entryX()).toPlainString());row.put("entry-y",BigDecimal.valueOf(value.entryY()).toPlainString());row.put("entry-z",BigDecimal.valueOf(value.entryZ()).toPlainString());row.put("entry-yaw",Float.toString(value.entryYaw()));row.put("entry-pitch",Float.toString(value.entryPitch()));row.put("downed-at",value.downedAt().toString());row.put("expires-at",value.expiresAt().toString());row.put("attacker",value.attribution().attacker()==null?null:value.attribution().attacker().toString());row.put("cause",value.attribution().cause());row.put("player-defeat",value.attribution().playerDefeat());row.put("contributors",value.attribution().contributors().stream().map(UUID::toString).collect(java.util.stream.Collectors.toCollection(ArrayList::new)));row.put("victim-level",value.attribution().victimLevel());row.put("victim-coins",value.attribution().victimCoins().toString());row.put("character-name",value.characterName());row.put("player-name",value.playerName());row.put("character-revision",value.characterRevision()==null?null:value.characterRevision().toString());row.put("phase",value.phase().name());row.put("outcome",value.outcome()==null?null:value.outcome().name());row.put("outcome-operation",value.outcomeOperation()==null?null:value.outcomeOperation().toString());row.put("resolved-at",value.resolvedAt()==null?null:value.resolvedAt().toString());row.put("protection-until",value.protectionUntil()==null?null:value.protectionUntil().toString());var decision=value.decision();row.put("decision-source",decision==null?null:decision.source());row.put("decision-actor-player",decision==null||decision.actorPlayer()==null?null:decision.actorPlayer().toString());row.put("decision-actor-player-name",decision==null?null:decision.actorPlayerName());row.put("decision-actor-character",decision==null||decision.actorCharacter()==null?null:decision.actorCharacter().toString());row.put("decision-actor-character-name",decision==null?null:decision.actorCharacterName());row.put("decision-actor-session",decision==null||decision.actorSession()==null?null:decision.actorSession().toString());row.put("decision-action",decision==null?null:decision.actionId().toString());row.put("decision-cast-at",decision==null||decision.castAt()==null?null:decision.castAt().toString());row.put("decision-cooldown-until",decision==null||decision.cooldownUntil()==null?null:decision.cooldownUntil().toString());row.put("decision-final-range",decision==null||decision.finalRange()==null?null:Double.toString(decision.finalRange()));row.put("decision-line-of-sight",decision==null?null:decision.lineOfSight());var phoenix=decision==null?null:decision.phoenix();row.put("decision-phoenix-quantity-before",phoenix==null?null:phoenix.quantityBefore().toString());row.put("decision-phoenix-quantity-after",phoenix==null?null:phoenix.quantityAfter().toString());row.put("decision-phoenix-health-before",phoenix==null?null:phoenix.healthBefore().toPlainString());row.put("decision-phoenix-health-after",phoenix==null?null:phoenix.healthAfter().toPlainString());row.put("decision-phoenix-maximum",phoenix==null?null:phoenix.maximum().toPlainString());row.put("decision-phoenix-entry-point",phoenix==null?null:phoenix.entryPoint());row.put("final-rewards",value.finalRewards().stream().map(reward->{var saved=new LinkedHashMap<String,Object>();saved.put("character",reward.character().toString());saved.put("experience",reward.experience().toString());saved.put("coins",reward.coins().toString());return saved;}).collect(java.util.stream.Collectors.toCollection(ArrayList::new)));row.put("published",value.published());return row;}
|
||||
private Map<String,Object> encode(DownedEpisode value){var row=new LinkedHashMap<String,Object>();row.put("id",value.id().toString());row.put("character",value.character().toString());row.put("player",value.player().toString());row.put("session",value.session().toString());row.put("world",value.world().toString());row.put("x",BigDecimal.valueOf(value.x()).toPlainString());row.put("y",BigDecimal.valueOf(value.y()).toPlainString());row.put("z",BigDecimal.valueOf(value.z()).toPlainString());row.put("yaw",Float.toString(value.yaw()));row.put("pitch",Float.toString(value.pitch()));row.put("entry-world",value.entryWorld().toString());row.put("entry-x",BigDecimal.valueOf(value.entryX()).toPlainString());row.put("entry-y",BigDecimal.valueOf(value.entryY()).toPlainString());row.put("entry-z",BigDecimal.valueOf(value.entryZ()).toPlainString());row.put("entry-yaw",Float.toString(value.entryYaw()));row.put("entry-pitch",Float.toString(value.entryPitch()));row.put("downed-at",value.downedAt().toString());row.put("expires-at",value.expiresAt().toString());row.put("attacker",value.attribution().attacker()==null?null:value.attribution().attacker().toString());row.put("cause",value.attribution().cause());row.put("player-defeat",value.attribution().playerDefeat());row.put("contributors",value.attribution().contributors().stream().map(UUID::toString).collect(java.util.stream.Collectors.toCollection(ArrayList::new)));row.put("victim-level",value.attribution().victimLevel());row.put("victim-coins",value.attribution().victimCoins().toString());row.put("character-name",value.characterName());row.put("player-name",value.playerName());row.put("character-revision",value.characterRevision()==null?null:value.characterRevision().toString());row.put("phase",value.phase().name());row.put("outcome",value.outcome()==null?null:value.outcome().name());row.put("outcome-operation",value.outcomeOperation()==null?null:value.outcomeOperation().toString());row.put("resolved-at",value.resolvedAt()==null?null:value.resolvedAt().toString());row.put("protection-until",value.protectionUntil()==null?null:value.protectionUntil().toString());var decision=value.decision();row.put("decision-source",decision==null?null:decision.source());row.put("decision-actor-player",decision==null||decision.actorPlayer()==null?null:decision.actorPlayer().toString());row.put("decision-actor-player-name",decision==null?null:decision.actorPlayerName());row.put("decision-actor-character",decision==null||decision.actorCharacter()==null?null:decision.actorCharacter().toString());row.put("decision-actor-character-name",decision==null?null:decision.actorCharacterName());row.put("decision-actor-session",decision==null||decision.actorSession()==null?null:decision.actorSession().toString());row.put("decision-action",decision==null?null:decision.actionId().toString());row.put("decision-cast-at",decision==null||decision.castAt()==null?null:decision.castAt().toString());row.put("decision-cooldown-until",decision==null||decision.cooldownUntil()==null?null:decision.cooldownUntil().toString());row.put("decision-final-range",decision==null||decision.finalRange()==null?null:Double.toString(decision.finalRange()));row.put("decision-line-of-sight",decision==null?null:decision.lineOfSight());var phoenix=decision==null?null:decision.phoenix();row.put("decision-phoenix-quantity-before",phoenix==null?null:phoenix.quantityBefore().toString());row.put("decision-phoenix-quantity-after",phoenix==null?null:phoenix.quantityAfter().toString());row.put("decision-phoenix-health-before",phoenix==null?null:phoenix.healthBefore().toPlainString());row.put("decision-phoenix-health-after",phoenix==null?null:phoenix.healthAfter().toPlainString());row.put("decision-phoenix-maximum",phoenix==null?null:phoenix.maximum().toPlainString());row.put("decision-phoenix-entry-point",phoenix==null?null:phoenix.entryPoint());row.put("final-rewards",value.finalRewards().stream().map(reward->{var saved=new LinkedHashMap<String,Object>();saved.put("character",reward.character().toString());saved.put("experience",reward.experience().toString());saved.put("coins",reward.coins().toString());saved.put("unit-id",reward.unitId());saved.put("unit-count",reward.unitCount());return saved;}).collect(java.util.stream.Collectors.toCollection(ArrayList::new)));row.put("published",value.published());return row;}
|
||||
private DownedEpisode decode(Map<?,?> row){
|
||||
String attacker=optional(row,"attacker"),outcome=optional(row,"outcome"),operation=optional(row,"outcome-operation"),resolved=optional(row,"resolved-at"),protection=optional(row,"protection-until"),revision=optional(row,"character-revision"),characterName=optional(row,"character-name"),playerName=optional(row,"player-name"),decisionSource=optional(row,"decision-source"),decisionPlayer=optional(row,"decision-actor-player"),decisionPlayerName=optional(row,"decision-actor-player-name"),decisionCharacter=optional(row,"decision-actor-character"),decisionCharacterName=optional(row,"decision-actor-character-name"),decisionSession=optional(row,"decision-actor-session"),decisionAction=optional(row,"decision-action"),decisionCastAt=optional(row,"decision-cast-at"),decisionCooldown=optional(row,"decision-cooldown-until"),decisionRange=optional(row,"decision-final-range");
|
||||
UUID world=uuid(row,"world"),entryWorld=row.containsKey("entry-world")?uuid(row,"entry-world"):world;
|
||||
@@ -41,7 +41,7 @@ public final class DownedDirectory implements AutoCloseable{
|
||||
float yaw=Float.parseFloat(text(row,"yaw")),pitch=Float.parseFloat(text(row,"pitch"));
|
||||
var phoenixQuantity=optional(row,"decision-phoenix-quantity-before");var phoenix=phoenixQuantity==null?null:new DownedEpisode.Phoenix(new java.math.BigInteger(phoenixQuantity),new java.math.BigInteger(text(row,"decision-phoenix-quantity-after")),new java.math.BigDecimal(text(row,"decision-phoenix-health-before")),new java.math.BigDecimal(text(row,"decision-phoenix-health-after")),new java.math.BigDecimal(text(row,"decision-phoenix-maximum")),text(row,"decision-phoenix-entry-point"));
|
||||
var decision=decisionSource==null?null:new DownedEpisode.Decision(decisionSource,decisionPlayer==null?null:UUID.fromString(decisionPlayer),decisionPlayerName,decisionCharacter==null?null:UUID.fromString(decisionCharacter),decisionCharacterName,decisionSession==null?null:UUID.fromString(decisionSession),UUID.fromString(decisionAction),decisionCastAt==null?null:Instant.parse(decisionCastAt),decisionCooldown==null?null:Instant.parse(decisionCooldown),decisionRange==null?null:Double.valueOf(decisionRange),row.get("decision-line-of-sight")==null?null:Boolean.valueOf(String.valueOf(row.get("decision-line-of-sight"))),phoenix);
|
||||
return new DownedEpisode(uuid(row,"id"),uuid(row,"character"),uuid(row,"player"),uuid(row,"session"),world,x,y,z,yaw,pitch,entryWorld,row.containsKey("entry-x")?Double.parseDouble(text(row,"entry-x")):x,row.containsKey("entry-y")?Double.parseDouble(text(row,"entry-y")):y,row.containsKey("entry-z")?Double.parseDouble(text(row,"entry-z")):z,row.containsKey("entry-yaw")?Float.parseFloat(text(row,"entry-yaw")):yaw,row.containsKey("entry-pitch")?Float.parseFloat(text(row,"entry-pitch")):pitch,Instant.parse(text(row,"downed-at")),Instant.parse(text(row,"expires-at")),new DownedEpisode.Attribution(attacker==null?null:UUID.fromString(attacker),text(row,"cause"),Boolean.parseBoolean(String.valueOf(row.containsKey("player-defeat")?row.get("player-defeat"):false)),row.containsKey("contributors")?list(row.get("contributors")).stream().map(String::valueOf).map(UUID::fromString).toList():List.of(),row.containsKey("victim-level")?Integer.parseInt(String.valueOf(row.get("victim-level"))):0,row.containsKey("victim-coins")?new java.math.BigInteger(String.valueOf(row.get("victim-coins"))):java.math.BigInteger.ZERO),characterName,playerName,revision==null?null:new java.math.BigInteger(revision),DownedEpisode.Phase.valueOf(text(row,"phase")),outcome==null?null:DownedEpisode.Outcome.valueOf(outcome),operation==null?null:UUID.fromString(operation),resolved==null?null:Instant.parse(resolved),protection==null?null:Instant.parse(protection),decision,list(row.get("final-rewards")).stream().map(DownedDirectory::map).map(reward->new DownedEpisode.Reward(uuid(reward,"character"),new java.math.BigInteger(text(reward,"experience")),new java.math.BigInteger(text(reward,"coins")))).toList(),Boolean.parseBoolean(String.valueOf(row.containsKey("published")?row.get("published"):false)));
|
||||
return new DownedEpisode(uuid(row,"id"),uuid(row,"character"),uuid(row,"player"),uuid(row,"session"),world,x,y,z,yaw,pitch,entryWorld,row.containsKey("entry-x")?Double.parseDouble(text(row,"entry-x")):x,row.containsKey("entry-y")?Double.parseDouble(text(row,"entry-y")):y,row.containsKey("entry-z")?Double.parseDouble(text(row,"entry-z")):z,row.containsKey("entry-yaw")?Float.parseFloat(text(row,"entry-yaw")):yaw,row.containsKey("entry-pitch")?Float.parseFloat(text(row,"entry-pitch")):pitch,Instant.parse(text(row,"downed-at")),Instant.parse(text(row,"expires-at")),new DownedEpisode.Attribution(attacker==null?null:UUID.fromString(attacker),text(row,"cause"),Boolean.parseBoolean(String.valueOf(row.containsKey("player-defeat")?row.get("player-defeat"):false)),row.containsKey("contributors")?list(row.get("contributors")).stream().map(String::valueOf).map(UUID::fromString).toList():List.of(),row.containsKey("victim-level")?Integer.parseInt(String.valueOf(row.get("victim-level"))):0,row.containsKey("victim-coins")?new java.math.BigInteger(String.valueOf(row.get("victim-coins"))):java.math.BigInteger.ZERO),characterName,playerName,revision==null?null:new java.math.BigInteger(revision),DownedEpisode.Phase.valueOf(text(row,"phase")),outcome==null?null:DownedEpisode.Outcome.valueOf(outcome),operation==null?null:UUID.fromString(operation),resolved==null?null:Instant.parse(resolved),protection==null?null:Instant.parse(protection),decision,list(row.get("final-rewards")).stream().map(DownedDirectory::map).map(reward->new DownedEpisode.Reward(uuid(reward,"character"),new java.math.BigInteger(text(reward,"experience")),new java.math.BigInteger(text(reward,"coins")),reward.containsKey("unit-id")?text(reward,"unit-id"):"legacy:"+text(reward,"character"),reward.containsKey("unit-count")?Integer.parseInt(String.valueOf(reward.get("unit-count"))):1)).toList(),Boolean.parseBoolean(String.valueOf(row.containsKey("published")?row.get("published"):false)));
|
||||
}
|
||||
private static Yaml yaml(){var options=new LoaderOptions();options.setAllowDuplicateKeys(false);options.setMaxAliasesForCollections(0);options.setCodePointLimit(1_048_576);return new Yaml(new SafeConstructor(options));}private static List<?> list(Object value){return value==null?List.of():(List<?>)value;}private static Map<?,?> map(Object value){return(Map<?,?>)value;}private static String text(Map<?,?> map,String key){Object value=map.get(key);if(!(value instanceof String text)||text.isBlank())throw new IllegalArgumentException(key);return text;}private static String optional(Map<?,?> map,String key){Object value=map.get(key);return value==null?null:text(map,key);}private static UUID uuid(Map<?,?> map,String key){return UUID.fromString(text(map,key));}
|
||||
private <T>CompletableFuture<T> submit(Callable<T> work){var result=new CompletableFuture<T>();synchronized(this){if(closed){result.completeExceptionally(new IOException("Downed directory closed"));return result;}pending++;}worker.execute(()->{try{result.complete(work.call());}catch(Throwable failure){result.completeExceptionally(failure);}finally{finished();}});return result;}private synchronized void finished(){pending--;if(closed&&pending==0)drained.complete(null);}public CompletionStage<Void> drained(){return drained.minimalCompletionStage();}@Override public synchronized void close(){closed=true;worker.shutdown();if(pending==0)drained.complete(null);}
|
||||
|
||||
@@ -19,12 +19,14 @@ public record DownedEpisode(UUID id, UUID character, UUID player, UUID session,
|
||||
public enum Phase { PENDING, RESOLVED }
|
||||
public enum Outcome { REVIVED, FINAL_DEATH }
|
||||
|
||||
public record Reward(UUID character, BigInteger experience, BigInteger coins) {
|
||||
public record Reward(UUID character, BigInteger experience, BigInteger coins,String unitId,int unitCount) {
|
||||
public Reward(UUID character,BigInteger experience,BigInteger coins){this(character,experience,coins,"legacy:"+character,1);}
|
||||
public Reward {
|
||||
Objects.requireNonNull(character);
|
||||
Objects.requireNonNull(experience);
|
||||
Objects.requireNonNull(coins);
|
||||
if (experience.signum() < 0 || coins.signum() < 0) {
|
||||
Objects.requireNonNull(unitId);
|
||||
if (experience.signum() < 0 || coins.signum() < 0 || unitId.isBlank() || unitCount < 1) {
|
||||
throw new IllegalArgumentException("Invalid downed reward");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class CombatExperienceNativeTest {
|
||||
assertEquals(new java.math.BigDecimal("1.0"), blocked.restrictedKills().values().iterator().next().x(),
|
||||
"Blocked attempts still advance the spacing reference");
|
||||
}
|
||||
var records=events(data).stream().filter(event->event.get("type").getAsString().endsWith("combat.reward_resolved")).toList();assertEquals(2,records.size());var allowed=records.get(0).getAsJsonObject("data");var blocked=records.get(1).getAsJsonObject("data");assertEquals(recipient.toString(),allowed.get("character_id").getAsString());assertEquals("ordinary_spacing_eligible",allowed.get("resolution").getAsString());assertEquals("10",allowed.get("allocated_coins").getAsString());assertEquals("10",allowed.get("applied_coins").getAsString());assertEquals("ordinary_spacing_blocked",blocked.get("resolution").getAsString());assertEquals("10",blocked.get("allocated_coins").getAsString());assertEquals("0",blocked.get("applied_coins").getAsString());assertEquals("spacing_refusal",blocked.get("feedback").getAsString());assertEquals(worldId.toString(),blocked.getAsJsonObject("ordinary_history_after").get("world_id").getAsString());assertEquals("1.0",blocked.getAsJsonObject("ordinary_history_after").get("x").getAsString());
|
||||
var records=events(data).stream().filter(event->event.get("type").getAsString().endsWith("combat.reward_resolved")).toList();assertEquals(2,records.size());var allowed=records.get(0).getAsJsonObject("data");var blocked=records.get(1).getAsJsonObject("data");assertEquals(recipient.toString(),allowed.get("character_id").getAsString());assertEquals("ordinary_spacing_eligible",allowed.get("resolution").getAsString());assertEquals("10",allowed.get("allocated_coins").getAsString());assertEquals("10",allowed.get("applied_coins").getAsString());assertEquals("10",allowed.get("experience_pool").getAsString());assertEquals("1",allowed.get("reward_unit_count").getAsString());assertEquals(java.util.List.of(recipient.toString()),allowed.getAsJsonArray("direct_contributor_character_ids").asList().stream().map(com.google.gson.JsonElement::getAsString).toList());assertTrue(allowed.has("before_points"));assertTrue(allowed.has("after_points"));assertEquals("ordinary_spacing_blocked",blocked.get("resolution").getAsString());assertEquals("10",blocked.get("allocated_coins").getAsString());assertEquals("0",blocked.get("applied_coins").getAsString());assertEquals("spacing_refusal",blocked.get("feedback").getAsString());assertEquals(worldId.toString(),blocked.getAsJsonObject("ordinary_history_after").get("world_id").getAsString());assertEquals("1.0",blocked.getAsJsonObject("ordinary_history_after").get("x").getAsString());
|
||||
}
|
||||
|
||||
@Test void everyManagedEncounterOriginWaivesDistanceWithoutChangingRestrictedHistory() throws Exception {
|
||||
@@ -372,28 +372,28 @@ class CombatExperienceNativeTest {
|
||||
var writes = new java.util.concurrent.atomic.AtomicInteger();
|
||||
when(storage.awardCombatReward(eq(original.id()), any(), any(), eq(java.math.BigInteger.valueOf(20)), any(),
|
||||
same(progression), any(), any())).thenAnswer(ignored -> writes.getAndIncrement() == 0 ? failed : success);
|
||||
var failures = new ArrayList<games.dmg.fantasystick.party.CombatExperience.Failure>();
|
||||
var failures = new ArrayList<games.dmg.fantasystick.party.CombatExperience.Failure>();var emitted=new ArrayList<games.dmg.fantasystick.events.EventInput>();var clock=java.time.Clock.fixed(java.time.Instant.parse("2026-09-19T20:00:00Z"), java.time.ZoneOffset.UTC);
|
||||
var service = new games.dmg.fantasystick.party.CombatExperience(storage, progression,
|
||||
games.dmg.fantasystick.progression.CombatRewardRules.defaults(), sessions,
|
||||
java.time.Clock.fixed(java.time.Instant.parse("2026-09-19T20:00:00Z"), java.time.ZoneOffset.UTC), Runnable::run,
|
||||
clock, Runnable::run,
|
||||
ignored -> attacker, failures::add);
|
||||
var authority=new games.dmg.fantasystick.progression.ManagedEncounterOrigin(games.dmg.fantasystick.progression.ManagedEncounterOrigin.Kind.DUNGEON,UUID.randomUUID());var settled=new ArrayList<games.dmg.fantasystick.progression.ManagedEncounterOrigin>();service.attachManagedOrigins(ignored->java.util.Optional.of(authority));service.attachManagedSettlement(settled::add);
|
||||
var world = mock(org.bukkit.World.class); when(world.getUID()).thenReturn(UUID.randomUUID());
|
||||
var authority=new games.dmg.fantasystick.progression.ManagedEncounterOrigin(games.dmg.fantasystick.progression.ManagedEncounterOrigin.Kind.DUNGEON,UUID.randomUUID());var settled=new ArrayList<games.dmg.fantasystick.progression.ManagedEncounterOrigin>();service.attachManagedOrigins(ignored->java.util.Optional.of(authority));service.attachManagedSettlement(settled::add);service.attachEvents(new games.dmg.fantasystick.events.CombatRewardEventBridge(emitted::add,clock));
|
||||
var world = mock(org.bukkit.World.class); when(world.getUID()).thenReturn(UUID.randomUUID());when(world.getName()).thenReturn("world");when(attacker.getName()).thenReturn("Attacker");when(attacker.getLocation()).thenReturn(new Location(world,0,64,0));
|
||||
var target = mob(EntityType.WITCH, new Location(world, 0, 64, 0));
|
||||
var damageSource = mock(org.bukkit.damage.DamageSource.class);
|
||||
when(damageSource.getDirectEntity()).thenReturn(attacker); when(damageSource.getCausingEntity()).thenReturn(attacker);
|
||||
service.damage(new EntityDamageByEntityEvent(attacker, target, DamageCause.ENTITY_ATTACK, damageSource, 1));
|
||||
service.mobDeath(new org.bukkit.event.entity.EntityDeathEvent(target, damageSource, new ArrayList<>(), 0));
|
||||
verify(storage, times(1)).awardCombatReward(eq(original.id()), any(), any(), any(), any(), any(), any(), any());
|
||||
assertEquals(1, failures.size());assertTrue(settled.isEmpty(),"slot authority remains unsettled while reward persistence is uncertain");verify(attacker, never()).sendMessage(anyString());
|
||||
assertEquals(1, failures.size());assertTrue(settled.isEmpty(),"slot authority remains unsettled while reward persistence is uncertain");assertTrue(emitted.isEmpty(),"Failed persistence cannot claim a reward resolution");verify(attacker, never()).sendMessage(anyString());
|
||||
service.configure(service.rules().withHardPool(java.math.BigInteger.valueOf(25)));
|
||||
service.retryPending();
|
||||
verify(storage, times(2)).awardCombatReward(eq(original.id()), any(), any(), any(), any(), any(), any(), any());
|
||||
verify(attacker, times(1)).sendMessage(contains("character coins"));assertEquals(java.util.List.of(authority),settled,"slot defeat may publish only after the captured payout is durable");
|
||||
verify(attacker, times(1)).sendMessage(contains("character coins"));assertEquals(java.util.List.of(authority),settled,"slot defeat may publish only after the captured payout is durable");assertEquals(1,emitted.size());var evidence=emitted.getFirst();assertEquals("20",evidence.data().get("experience_pool"));assertEquals("managed_origin_exempt",evidence.data().get("resolution"));assertEquals(authority.source().toString(),evidence.data().get("managed_origin_source"));assertEquals(java.util.List.of(original.id().toString()),evidence.data().get("direct_contributor_character_ids"));
|
||||
service.mobDeath(new org.bukkit.event.entity.EntityDeathEvent(target, damageSource, new ArrayList<>(), 0));
|
||||
service.retryPending();
|
||||
verify(storage, times(2)).awardCombatReward(eq(original.id()), any(), any(), any(), any(), any(), any(), any());
|
||||
verify(attacker, times(1)).sendMessage(contains("character coins"));
|
||||
verify(attacker, times(1)).sendMessage(contains("character coins"));assertEquals(1,emitted.size(),"Duplicate target callbacks cannot mint another reward record");
|
||||
service.close();
|
||||
}
|
||||
|
||||
@@ -482,13 +482,13 @@ class CombatExperienceNativeTest {
|
||||
assertEquals(java.math.BigInteger.ZERO, record(f, a).experience(), "Cancelled damage never contributes");
|
||||
}
|
||||
|
||||
var second = data.resolve("accepted"); java.nio.file.Files.createDirectories(second);
|
||||
var second = data.resolve("accepted"); java.nio.file.Files.createDirectories(second);UUID finalAttacker,finalVictimPlayer;
|
||||
try (var f = new FantasyStickLifecycleNativeTest.Fixture(second)) {
|
||||
f.plugin.setEnabled(true);
|
||||
var a = new FantasyStickLifecycleNativeTest.Actor(second);
|
||||
var b = new FantasyStickLifecycleNativeTest.Actor(second);
|
||||
f.players.put(a.core.id, a.player); f.players.put(b.core.id, b.player);
|
||||
f.activate(a); f.activate(b);
|
||||
f.activate(a); f.activate(b);finalAttacker=a.core.record.id();finalVictimPlayer=b.core.id;
|
||||
var attackerCoins = record(f, a).coins();
|
||||
var victimCoins = record(f, b).coins();
|
||||
hit(f, a.player, b.player, false);
|
||||
@@ -504,6 +504,7 @@ class CombatExperienceNativeTest {
|
||||
f.dispatcher.accept(defeat); settle(f);
|
||||
assertEquals(java.math.BigInteger.TEN, record(f, a).experience(), "Repeated intercepted callback cannot award twice");
|
||||
}
|
||||
var rewards=events(second).stream().filter(event->event.get("type").getAsString().endsWith("combat.reward_resolved")).toList();assertEquals(1,rewards.size(),"Initial downing and repeated callbacks emit no extra reward resolution");var reward=rewards.getFirst().getAsJsonObject("data");assertEquals("final_death",reward.get("stage").getAsString());assertEquals(finalVictimPlayer.toString(),reward.get("target_id").getAsString());assertEquals("10",reward.get("experience_pool").getAsString());assertEquals("10",reward.get("allocated_experience").getAsString());assertEquals("10",reward.get("applied_experience").getAsString());assertEquals("spacing_not_applicable",reward.get("resolution").getAsString());assertEquals(java.util.List.of(finalAttacker.toString()),reward.getAsJsonArray("direct_contributor_character_ids").asList().stream().map(com.google.gson.JsonElement::getAsString).toList());assertTrue(reward.has("reward_unit_id"));
|
||||
}
|
||||
|
||||
private static EntityDamageByEntityEvent hit(FantasyStickLifecycleNativeTest.Fixture f, org.bukkit.entity.Entity source,
|
||||
|
||||
@@ -17,5 +17,5 @@ import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CombatRewardEventBridgeTest {
|
||||
@Test void sinkFailureCannotChangeAnAcknowledgedRewardAndIdentityIsStable(){var before=new games.dmg.fantasystick.crafting.CharacterFactory().create(ProgressionRules.defaults());var receipt=UUID.randomUUID();var position=new CombatRewardRules.KillPosition(UUID.randomUUID(),java.math.BigDecimal.ZERO,java.math.BigDecimal.valueOf(64),java.math.BigDecimal.ZERO);var history=new HashMap<>(before.restrictedKills());history.put(position.world(),position);var receipts=new HashSet<>(before.rewardReceipts());receipts.add(receipt);var after=new CharacterRecord(before.id(),before.name(),before.revision().add(BigInteger.ONE),before.stats(),before.level(),before.experience().add(BigInteger.TEN),before.unspentPoints(),before.health(),before.learned(),before.loadout(),before.passives(),before.cooldowns(),history,false,before.deathId(),before.diedAt(),before.coins().add(BigInteger.TEN),true,receipts,before.pvpCoinEscrow(),before.gear(),before.sheetGeneration());var award=new CharacterStorage.CombatAward(CharacterStorage.CombatAwardStatus.APPLIED,Optional.of(after),0);var capture=new CombatRewardEventBridge.Capture(UUID.randomUUID(),"zombie","ordinary","hostile",position,null,"mob_defeat",Instant.parse("2026-09-25T00:00:00Z"));var bridge=new CombatRewardEventBridge(event->{throw new IllegalStateException("disk");},Clock.fixed(Instant.parse("2026-09-25T00:00:01Z"),ZoneOffset.UTC));assertDoesNotThrow(()->bridge.resolved(receipt,before.id(),BigInteger.TEN,BigInteger.TEN,position,CombatRewardRules.defaults(),capture,before,award,null,null,"not_applicable_offline","not_delivered_offline"));}
|
||||
@Test void sinkFailureCannotChangeAnAcknowledgedRewardAndIdentityIsStable(){var before=new games.dmg.fantasystick.crafting.CharacterFactory().create(ProgressionRules.defaults());var receipt=UUID.randomUUID();var position=new CombatRewardRules.KillPosition(UUID.randomUUID(),java.math.BigDecimal.ZERO,java.math.BigDecimal.valueOf(64),java.math.BigDecimal.ZERO);var history=new HashMap<>(before.restrictedKills());history.put(position.world(),position);var receipts=new HashSet<>(before.rewardReceipts());receipts.add(receipt);var after=new CharacterRecord(before.id(),before.name(),before.revision().add(BigInteger.ONE),before.stats(),before.level(),before.experience().add(BigInteger.TEN),before.unspentPoints(),before.health(),before.learned(),before.loadout(),before.passives(),before.cooldowns(),history,false,before.deathId(),before.diedAt(),before.coins().add(BigInteger.TEN),true,receipts,before.pvpCoinEscrow(),before.gear(),before.sheetGeneration());var award=new CharacterStorage.CombatAward(CharacterStorage.CombatAwardStatus.APPLIED,Optional.of(after),0);var capture=new CombatRewardEventBridge.Capture(UUID.randomUUID(),"zombie","ordinary","hostile",position,null,"mob_defeat",Instant.parse("2026-09-25T00:00:00Z"),java.util.List.of(before.id()),before.id().toString(),1,BigInteger.TEN,BigInteger.TEN);var bridge=new CombatRewardEventBridge(event->{throw new IllegalStateException("disk");},Clock.fixed(Instant.parse("2026-09-25T00:00:01Z"),ZoneOffset.UTC));assertDoesNotThrow(()->bridge.resolved(receipt,before.id(),BigInteger.TEN,BigInteger.TEN,position,CombatRewardRules.defaults(),capture,before,award,null,null,"not_applicable_offline","not_delivered_offline"));}
|
||||
}
|
||||
Reference in new issue
Block a user