feat(arena): export original geometry and activation context
Release / release (push) Failing after 12m31s
Release / release (push) Failing after 12m31s
This commit is contained in:
1 parent
12cdce1845
commit
e83c6fbabc
6 files changed
+270
-8
No files matched your search
@@ -44,7 +44,7 @@ public final class AdventureQuestController implements Listener,AutoCloseable {
|
||||
private java.util.function.BiPredicate<UUID,Location> dungeonRoomPresence=(ignored,location)->false;
|
||||
private org.bukkit.scheduler.BukkitTask pulseTask;private boolean closed;
|
||||
private static final class MenuHolder implements InventoryHolder {final UUID catalog;final int page;final AdventureQuestState.Giver giver;MenuHolder(UUID catalog,int page,AdventureQuestState.Giver giver){this.catalog=catalog;this.page=page;this.giver=giver;}@Override public Inventory getInventory(){return null;}}
|
||||
private static final class Attempt {AdventureQuestState.Site auditSite;Map<String,Object> auditConfiguration=Map.of();List<org.bukkit.entity.EntityType> auditRoster=List.of();final Map<UUID,String> auditEntities=new LinkedHashMap<>();final UUID id=UUID.randomUUID(),site,catalog;final AdventureQuestState.SiteType type;final Set<UUID> entities=new HashSet<>(),represented=new HashSet<>();final AdventureQuestState.Arena arena;final int resetSeconds;BossBar bossBar;LivingEntity champion;Instant absentSince;
|
||||
private static final class Attempt {AdventureQuestState.Site auditSite;Map<String,Object> auditActivation=Map.of("capture_quality","unavailable");Map<String,Object> auditConfiguration=Map.of();List<org.bukkit.entity.EntityType> auditRoster=List.of();final Map<UUID,String> auditEntities=new LinkedHashMap<>();final UUID id=UUID.randomUUID(),site,catalog;final AdventureQuestState.SiteType type;final Set<UUID> entities=new HashSet<>(),represented=new HashSet<>();final AdventureQuestState.Arena arena;final int resetSeconds;BossBar bossBar;LivingEntity champion;Instant absentSince;
|
||||
UUID defeatedEntity;Map<String,Object> defeatLocation=Map.of("status","unavailable");Instant defeatedAt;
|
||||
Attempt(UUID site,UUID catalog,AdventureQuestState.SiteType type,AdventureQuestState.Arena arena,int resetSeconds){this.site=site;this.catalog=catalog;this.type=type;this.arena=arena;this.resetSeconds=resetSeconds;}}
|
||||
public AdventureQuestController(Plugin plugin,AdventureQuestDirectory directory,AdventureQuestSettings settings,EquipmentSessions sessions,PartyRoster parties,CharacterStorage characters,ProgressionRules progression,Consumer<Runnable> nextTick,Clock clock,java.util.random.RandomGenerator random){this(plugin,directory,settings,sessions,parties,characters,progression,nextTick,clock,random,games.dmg.fantasystick.presentation.ParticleDelivery.legacyBroadcast());}
|
||||
@@ -130,7 +130,7 @@ public final class AdventureQuestController implements Listener,AutoCloseable {
|
||||
public boolean trackingAvailable(Player player){var context=context(player);return context!=null&&directory.activeFor(context.character,context.partyId).filter(quest->quest.snapshot().contains(context.character)).isPresent();}
|
||||
public void tracking(Player player,Boolean enabled){var context=context(player);if(context==null){player.sendMessage("Quest tracking refused: actively play a living fantasy character first.");return;}var quest=directory.activeFor(context.character,context.partyId).filter(value->value.snapshot().contains(context.character)).orElse(null);if(quest==null){player.sendMessage("Quest tracking refused: this character has no eligible active quest.");refreshTracking(player);return;}if(enabled==null){player.sendMessage("Quest tracking is "+(directory.trackingEnabled(quest.id(),context.character)?"on":"off")+" for the current adventure quest.");return;}directory.setTracking(quest.id(),context.character,enabled).whenComplete((status,failure)->schedule(()->{if(failure!=null||status==AdventureQuestDirectory.TrackingStatus.INELIGIBLE){player.sendMessage("Quest tracking change failed; retained state is unchanged.");return;}refreshTracking(player);player.sendMessage("Quest tracking is now "+(enabled?"on.":"off."));}));}
|
||||
public void inspect(Player player){var context=context(player);if(context==null){player.sendMessage("No active fantasy character.");return;}var quest=directory.activeFor(context.character,context.partyId).orElse(null);if(quest==null){player.sendMessage("No active adventure quest.");return;}var site=directory.findSite(quest.site()).orElse(null);if(site==null){player.sendMessage("Active quest site is unavailable.");return;}player.sendMessage(site.displayName()+"; state="+(attempts.containsKey(site.id())?"active":"waiting")+"; expires "+quest.expiresAt()+"; Quest Compass guidance="+(directory.trackingEnabled(quest.id(),context.character)?"on":"off")+".");}
|
||||
private void pulse(){if(closed)return;directory.ensureToday().whenComplete((catalog,failure)->{if(failure==null)schedule(()->{if(directory.snapshot().reset()!=null)publishPendingReset();});});if(directory.snapshot().reset()!=null){publishPendingReset();return;}directory.expireParties(parties::exists);refreshTrackingPlayers(true);showArenaBoundaries();for(var attempt:new ArrayList<>(attempts.values())){var catalog=directory.snapshot().catalog();if(catalog==null||!catalog.revision().equals(attempt.catalog)||!clock.instant().isBefore(catalog.expiresAt())){cleanup(attempt,"catalog_or_utc_expired");attempts.remove(attempt.site);readyAt.remove(attempt.site);continue;}contain(attempt);syncBossBar(attempt);boolean present=eligibleInside(attempt.site,attempt.arena).findAny().isPresent();if(present)attempt.absentSince=null;else if(attempt.absentSince==null)attempt.absentSince=clock.instant();else if(!clock.instant().isBefore(attempt.absentSince.plusSeconds(attempt.resetSeconds))){cleanup(attempt,"no_eligible_accepted_character_remained");attempts.remove(attempt.site);readyAt.put(attempt.site,clock.instant());}}for(var quest:directory.active()){var type=directory.findSite(quest.site()).map(AdventureQuestState.Site::type).orElse(null);if(type==AdventureQuestState.SiteType.DUNGEON||type==AdventureQuestState.SiteType.FISHING)continue;if(attempts.containsKey(quest.site())||starting.contains(quest.site()))continue;var ready=readyAt.get(quest.site());if(ready!=null&&clock.instant().isBefore(ready))continue;if(eligibleInside(quest.site()).findAny().isPresent())start(quest.site(),quest.catalog());}}
|
||||
private void pulse(){if(closed)return;directory.ensureToday().whenComplete((catalog,failure)->{if(failure==null)schedule(()->{if(directory.snapshot().reset()!=null)publishPendingReset();});});if(directory.snapshot().reset()!=null){publishPendingReset();return;}directory.expireParties(parties::exists);refreshTrackingPlayers(true);showArenaBoundaries();for(var attempt:new ArrayList<>(attempts.values())){var catalog=directory.snapshot().catalog();if(catalog==null||!catalog.revision().equals(attempt.catalog)||!clock.instant().isBefore(catalog.expiresAt())){cleanup(attempt,"catalog_or_utc_expired");attempts.remove(attempt.site);readyAt.remove(attempt.site);continue;}contain(attempt);syncBossBar(attempt);boolean present=eligibleInside(attempt.site,attempt.arena).findAny().isPresent();if(present)attempt.absentSince=null;else if(attempt.absentSince==null)attempt.absentSince=clock.instant();else if(!clock.instant().isBefore(attempt.absentSince.plusSeconds(attempt.resetSeconds))){cleanup(attempt,"no_eligible_accepted_character_remained");attempts.remove(attempt.site);readyAt.put(attempt.site,clock.instant());}}for(var quest:directory.active()){var type=directory.findSite(quest.site()).map(AdventureQuestState.Site::type).orElse(null);if(type==AdventureQuestState.SiteType.DUNGEON||type==AdventureQuestState.SiteType.FISHING)continue;if(attempts.containsKey(quest.site())||starting.contains(quest.site()))continue;var ready=readyAt.get(quest.site());if(ready!=null&&clock.instant().isBefore(ready))continue;var activation=eligibleInside(quest.site()).findAny();if(activation.isPresent())start(quest.site(),quest.catalog(),activation.orElseThrow());}}
|
||||
private void showArenaBoundaries(){for(UUID siteId:directory.active().stream().map(AdventureQuestState.Quest::site).distinct().toList()){var site=directory.findSite(siteId).orElse(null);if(site==null||site.type()==AdventureQuestState.SiteType.DUNGEON)continue;var world=plugin.getServer().getWorld(site.world());if(world==null)continue;var center=new Location(world,site.x(),site.y(),site.z());var points=perimeter(arena(site));for(var player:plugin.getServer().getOnlinePlayers()){if(eligible(player,siteId)==null)continue;var viewer=player.getLocation();if(viewer.getWorld()==null||!viewer.getWorld().getUID().equals(site.world())||viewer.distanceSquared(center)>64d*64d)continue;for(var point:points)particles.viewer(player,Particle.END_ROD,new Location(world,site.x()+point[0],site.y()+0.2,site.z()+point[1]),1,0,0,0,0);}}}
|
||||
private static List<double[]> perimeter(AdventureQuestState.Arena arena){var result=new ArrayList<double[]>();if(arena.shape()==AdventureQuestState.ArenaShape.CIRCLE){int count=Math.min(256,Math.max(16,(int)Math.ceil(Math.PI*arena.xBlocks())));for(int index=0;index<count;index++){double angle=2*Math.PI*index/count;result.add(new double[]{Math.cos(angle)*arena.xBlocks(),Math.sin(angle)*arena.xBlocks()});}}else{double halfX=arena.xBlocks()/2d,halfZ=arena.zBlocks()/2d;edge(result,-halfX,halfX,-halfZ,true);edge(result,-halfX,halfX,halfZ,true);edge(result,-halfZ,halfZ,-halfX,false);edge(result,-halfZ,halfZ,halfX,false);}return result.size()<=256?List.copyOf(result):List.copyOf(result.subList(0,256));}
|
||||
private static void edge(List<double[]> result,double from,double to,double fixed,boolean xAxis){for(double value=from;value<to;value+=2)result.add(xAxis?new double[]{value,fixed}:new double[]{fixed,value});result.add(xAxis?new double[]{to,fixed}:new double[]{fixed,to});}
|
||||
@@ -138,8 +138,31 @@ public final class AdventureQuestController implements Listener,AutoCloseable {
|
||||
private java.util.stream.Stream<Player> eligibleInside(UUID site){var value=directory.findSite(site).orElse(null);return value==null?java.util.stream.Stream.empty():eligibleInside(site,arena(value));}
|
||||
private java.util.stream.Stream<Player> eligibleInside(UUID site,AdventureQuestState.Arena arena){var value=directory.findSite(site).orElse(null);if(value==null)return java.util.stream.Stream.empty();return plugin.getServer().getOnlinePlayers().stream().map(Player.class::cast).filter(player->eligible(player,site)!=null).filter(player->inside(player.getLocation(),value,arena));}
|
||||
private AdventureQuestState.Quest eligible(Player player,UUID site){var context=context(player);if(context==null)return null;var quest=directory.activeFor(context.character,context.partyId).orElse(null);return quest!=null&&quest.site().equals(site)&&quest.snapshot().contains(context.character)?quest:null;}
|
||||
private void start(UUID site,UUID catalog){var value=directory.findSite(site).orElse(null);if(value==null||value.type()==AdventureQuestState.SiteType.DUNGEON||value.type()==AdventureQuestState.SiteType.FISHING)return;if(value.type()==AdventureQuestState.SiteType.CHAMPION){startChampion(value,catalog);return;}starting.add(site);directory.roster(site).whenComplete((roster,failure)->schedule(()->{starting.remove(site);if(failure!=null||closed||attempts.containsKey(site))return;var current=directory.findSite(site).orElse(null);if(current==null)return;var world=plugin.getServer().getWorld(current.world());if(world==null||!world.isChunkLoaded(section(current.x()),section(current.z())))return;var rules=settings.rules();var attempt=new Attempt(site,catalog,current.type(),arena(current),rules.resetSeconds());attempt.auditSite=current;attempt.auditRoster=List.copyOf(roster);attempts.put(site,attempt);boolean spawned=false;try{for(int index=0;index<roster.size();index++)spawnEnemy(world,current,roster.get(index),attempt,index);spawned=true;}finally{auditAttempt("adventure.camp.started",attempt,"eligible_accepted_unit_entered",spawned);}}));}
|
||||
private void startChampion(AdventureQuestState.Site site,UUID catalog){if(closed||attempts.containsKey(site.id())||starting.contains(site.id()))return;var world=plugin.getServer().getWorld(site.world());if(world==null||!world.isChunkLoaded(section(site.x()),section(site.z())))return;starting.add(site.id());Attempt observed=null;boolean spawned=false;try{var rules=settings.rules();var attempt=new Attempt(site.id(),catalog,site.type(),arena(site),rules.resetSeconds());observed=attempt;attempt.auditSite=site;attempt.auditRoster=List.of(org.bukkit.entity.EntityType.VINDICATOR);attempt.auditConfiguration=Map.of("requested_health",rules.championHealth(),"requested_damage",rules.championDamage());attempt.bossBar=plugin.getServer().createBossBar("Daybreak Champion",BarColor.RED,BarStyle.SOLID);attempts.put(site.id(),attempt);world.spawn(new Location(world,site.x(),site.y(),site.z()),Vindicator.class,champion->{configureEnemy(champion,attempt);var maximum=Objects.requireNonNull(champion.getAttribute(Attribute.MAX_HEALTH));maximum.setBaseValue(rules.championHealth());champion.setHealth(rules.championHealth());Objects.requireNonNull(champion.getAttribute(Attribute.ATTACK_DAMAGE)).setBaseValue(rules.championDamage());champion.customName(net.kyori.adventure.text.Component.text("Daybreak Champion"));champion.setCustomNameVisible(true);attempt.champion=champion;});syncBossBar(attempt);spawned=true;}catch(RuntimeException failure){var attempt=attempts.remove(site.id());if(attempt!=null)cleanup(attempt);plugin.getLogger().log(java.util.logging.Level.WARNING,"Champion attempt could not start at site "+site.id(),failure);}finally{try{if(observed!=null){captureChampionConfiguration(observed);auditAttempt("adventure.camp.started",observed,spawned?"eligible_accepted_unit_entered":"native_spawn_or_configuration_failed",spawned);}}catch(RuntimeException ignored){/* Audit cannot interrupt native lifecycle. */}starting.remove(site.id());}}
|
||||
private Map<String,Object> captureArenaActivation(Player player,AdventureQuestState.Site site,AdventureQuestState.Arena arena){
|
||||
var facts=new LinkedHashMap<String,Object>();facts.put("capture_quality","unavailable");facts.put("observed_at",clock.instant().toString());
|
||||
try{
|
||||
for(var field:games.dmg.fantasystick.events.CampEventBridge.arenaData(site,arena).entrySet()){
|
||||
if(field.getValue() instanceof Map<?,?> geometry)for(var part:geometry.entrySet())facts.put(field.getKey()+"_"+part.getKey(),part.getValue());
|
||||
else facts.put(field.getKey(),field.getValue());
|
||||
}
|
||||
facts.put("player_id",player.getUniqueId().toString());
|
||||
var context=context(player);
|
||||
if(context!=null){facts.put("character_id",context.character.toString());facts.put("session_id",context.active.session().toString());
|
||||
var quest=directory.activeFor(context.character,context.partyId).filter(value->value.site().equals(site.id())).orElse(null);
|
||||
if(quest!=null){facts.put("quest_id",quest.id().toString());facts.put("unit_id",quest.unit().toString());facts.put("unit_kind",quest.unitType().name().toLowerCase(Locale.ROOT));}
|
||||
}
|
||||
var at=player.getLocation();
|
||||
if(at!=null&&at.getWorld()!=null&&Double.isFinite(at.getX())&&Double.isFinite(at.getY())&&Double.isFinite(at.getZ())){
|
||||
facts.put("location_world_id",at.getWorld().getUID().toString());facts.put("location_x",at.getX());facts.put("location_y",at.getY());facts.put("location_z",at.getZ());
|
||||
double dx=at.getX()-site.x(),dz=at.getZ()-site.z();boolean within=at.getWorld().getUID().equals(site.world())&&arena.contains(dx,dz);
|
||||
boolean edge=arena.shape()==AdventureQuestState.ArenaShape.CIRCLE?dx*dx+dz*dz==arena.xBlocks()*(double)arena.xBlocks():Math.abs(dx)==arena.xBlocks()/2d||Math.abs(dz)==arena.zBlocks()/2d;
|
||||
facts.put("boundary_result",within?edge?"edge":"inside":"outside");facts.put("capture_quality","native_activation_observed");
|
||||
}else facts.put("location_capture_quality","unavailable");
|
||||
}catch(RuntimeException unavailable){/* Independent original metadata cannot interrupt an eligible native start. */}
|
||||
return Map.copyOf(facts);
|
||||
}
|
||||
private void start(UUID site,UUID catalog,Player activation){var value=directory.findSite(site).orElse(null);if(value==null||value.type()==AdventureQuestState.SiteType.DUNGEON||value.type()==AdventureQuestState.SiteType.FISHING)return;var activationFacts=captureArenaActivation(activation,value,arena(value));if(value.type()==AdventureQuestState.SiteType.CHAMPION){startChampion(value,catalog,activationFacts);return;}starting.add(site);directory.roster(site).whenComplete((roster,failure)->schedule(()->{starting.remove(site);if(failure!=null||closed||attempts.containsKey(site))return;var current=directory.findSite(site).orElse(null);if(current==null)return;var world=plugin.getServer().getWorld(current.world());if(world==null||!world.isChunkLoaded(section(current.x()),section(current.z())))return;var rules=settings.rules();var attempt=new Attempt(site,catalog,current.type(),arena(current),rules.resetSeconds());attempt.auditSite=current;attempt.auditActivation=activationFacts;attempt.auditRoster=List.copyOf(roster);attempts.put(site,attempt);boolean spawned=false;try{for(int index=0;index<roster.size();index++)spawnEnemy(world,current,roster.get(index),attempt,index);spawned=true;}finally{auditAttempt("adventure.camp.started",attempt,"eligible_accepted_unit_entered",spawned);}}));}
|
||||
private void startChampion(AdventureQuestState.Site site,UUID catalog,Map<String,Object> activation){if(closed||attempts.containsKey(site.id())||starting.contains(site.id()))return;var world=plugin.getServer().getWorld(site.world());if(world==null||!world.isChunkLoaded(section(site.x()),section(site.z())))return;starting.add(site.id());Attempt observed=null;boolean spawned=false;try{var rules=settings.rules();var attempt=new Attempt(site.id(),catalog,site.type(),arena(site),rules.resetSeconds());observed=attempt;attempt.auditSite=site;attempt.auditActivation=activation;attempt.auditRoster=List.of(org.bukkit.entity.EntityType.VINDICATOR);attempt.auditConfiguration=Map.of("requested_health",rules.championHealth(),"requested_damage",rules.championDamage());attempt.bossBar=plugin.getServer().createBossBar("Daybreak Champion",BarColor.RED,BarStyle.SOLID);attempts.put(site.id(),attempt);world.spawn(new Location(world,site.x(),site.y(),site.z()),Vindicator.class,champion->{configureEnemy(champion,attempt);var maximum=Objects.requireNonNull(champion.getAttribute(Attribute.MAX_HEALTH));maximum.setBaseValue(rules.championHealth());champion.setHealth(rules.championHealth());Objects.requireNonNull(champion.getAttribute(Attribute.ATTACK_DAMAGE)).setBaseValue(rules.championDamage());champion.customName(net.kyori.adventure.text.Component.text("Daybreak Champion"));champion.setCustomNameVisible(true);attempt.champion=champion;});syncBossBar(attempt);spawned=true;}catch(RuntimeException failure){var attempt=attempts.remove(site.id());if(attempt!=null)cleanup(attempt);plugin.getLogger().log(java.util.logging.Level.WARNING,"Champion attempt could not start at site "+site.id(),failure);}finally{try{if(observed!=null){captureChampionConfiguration(observed);auditAttempt("adventure.camp.started",observed,spawned?"eligible_accepted_unit_entered":"native_spawn_or_configuration_failed",spawned);}}catch(RuntimeException ignored){/* Audit cannot interrupt native lifecycle. */}starting.remove(site.id());}}
|
||||
private void spawnEnemy(World world,AdventureQuestState.Site site,org.bukkit.entity.EntityType type,Attempt attempt,int index){var location=new Location(world,site.x()+(index%3)-1,site.y(),site.z()+(index/3));switch(type){case HUSK->world.spawn(location,Husk.class,mob->configureEnemy(mob,attempt));case PILLAGER->world.spawn(location,Pillager.class,mob->configureEnemy(mob,attempt));case VINDICATOR->world.spawn(location,Vindicator.class,mob->configureEnemy(mob,attempt));case WITCH->world.spawn(location,Witch.class,mob->configureEnemy(mob,attempt));default->throw new IllegalArgumentException("Unsupported camp entity");}}
|
||||
private void configureEnemy(Mob mob,Attempt attempt){mob.getPersistentDataContainer().set(typeKey,PersistentDataType.STRING,ENEMY);mob.getPersistentDataContainer().set(siteKey,PersistentDataType.STRING,attempt.site.toString());mob.getPersistentDataContainer().set(catalogKey,PersistentDataType.STRING,attempt.catalog.toString());mob.getPersistentDataContainer().set(attemptKey,PersistentDataType.STRING,attempt.id.toString());var kind=attempt.type==AdventureQuestState.SiteType.CHAMPION?games.dmg.fantasystick.progression.ManagedEncounterOrigin.Kind.CHAMPION:games.dmg.fantasystick.progression.ManagedEncounterOrigin.Kind.CAMP;mob.getPersistentDataContainer().set(encounterKindKey,PersistentDataType.STRING,kind.name());mob.setCanPickupItems(false);mob.setRemoveWhenFarAway(false);mob.setPersistent(true);mob.setFireTicks(0);if(mob instanceof Husk husk){husk.setShouldBurnInDay(false);husk.setCanBreakDoors(false);}if(mob instanceof Raider raider){raider.setPatrolLeader(false);raider.setCanJoinRaid(false);}UUID entityId=mob.getUniqueId();attempt.entities.add(entityId);auditNativeEnemy(attempt,entityId,mob);}
|
||||
@EventHandler(priority=EventPriority.HIGHEST,ignoreCancelled=true)public void damage(EntityDamageEvent event){if(enemy(event.getEntity())&&encounterOrigin(event.getEntity()).isPresent()){if(!(event instanceof EntityDamageByEntityEvent by)||source(by)==null||eligible(source(by),site(event.getEntity()))==null)event.setCancelled(true);return;}if(event instanceof EntityDamageByEntityEvent by&&managed(by.getDamager())){UUID site=site(by.getDamager());if(!(event.getEntity() instanceof Player player)||site==null||eligible(player,site)==null)event.setCancelled(true);}}
|
||||
@@ -209,7 +232,7 @@ public final class AdventureQuestController implements Listener,AutoCloseable {
|
||||
var facts=new games.dmg.fantasystick.events.CampEventBridge.CampAttempt(attempt.id,attempt.catalog,
|
||||
attempt.auditSite,attempt.arena,attempt.resetSeconds,attempt.auditRoster.stream().map(Enum::name).toList(),entities,units,
|
||||
attempt.entities.size(),attempt.defeatedEntity,attempt.defeatLocation,attempt.defeatedAt,current,
|
||||
type.equals("adventure.camp.reset")||type.equals("adventure.camp.started")?auditNativePresence(attempt):Map.of("status","not_applicable"));
|
||||
type.equals("adventure.camp.reset")||type.equals("adventure.camp.started")?auditNativePresence(attempt):Map.of("status","not_applicable"),attempt.auditActivation);
|
||||
if(champion)championEvents.attemptTransition(switch(type){case "adventure.camp.started"->"adventure.champion.spawned";case "adventure.camp.cleared"->"adventure.champion.defeated";case "adventure.camp.reset"->"adventure.champion.reset";default->throw new IllegalArgumentException("Unknown champion transition");},facts,attempt.auditConfiguration,reason,succeeded);
|
||||
else campEvents.attemptTransition(type,facts,reason,succeeded);
|
||||
}catch(RuntimeException ignored){/* Metadata cannot spawn, reset, represent, clear or award gameplay. */}
|
||||
|
||||
@@ -79,7 +79,19 @@ public final class CampEventBridge implements AutoCloseable {
|
||||
data.put("revision_capture_quality",site.registrationRevision()==null?"unavailable":"persisted");
|
||||
if(site.registrationRevision()!=null)data.put("site_revision",site.registrationRevision().toString());
|
||||
data.put("world_id",site.world().toString());data.put("x",site.x());data.put("y",site.y());data.put("z",site.z());
|
||||
data.put("arena",Map.of("shape",arena.shape().name().toLowerCase(java.util.Locale.ROOT),"x_blocks",arena.xBlocks(),"z_blocks",arena.zBlocks()));
|
||||
data.putAll(arenaData(site,arena));
|
||||
return data;
|
||||
}
|
||||
|
||||
public static Map<String,Object> arenaData(AdventureQuestState.Site site,AdventureQuestState.Arena arena) {
|
||||
var data=new LinkedHashMap<String,Object>();var geometry=new LinkedHashMap<String,Object>();
|
||||
geometry.put("shape",arena.shape().name().toLowerCase(java.util.Locale.ROOT));
|
||||
geometry.put("source",site.arena()==null?"configured_default":"explicit");
|
||||
geometry.put("x_blocks",arena.xBlocks());geometry.put("z_blocks",arena.zBlocks());
|
||||
if(arena.shape()==AdventureQuestState.ArenaShape.CIRCLE)geometry.put("radius",arena.xBlocks());
|
||||
data.put("arena",Map.copyOf(geometry));
|
||||
data.put("arena_revision_capture_quality",site.registrationRevision()==null?"unavailable":"persisted_site_definition");
|
||||
if(site.registrationRevision()!=null)data.put("arena_revision",site.registrationRevision().toString());
|
||||
data.put("arena_definition",site.arena()==null?"effective_legacy_fallback":"persisted");
|
||||
return data;
|
||||
}
|
||||
@@ -161,8 +173,13 @@ public final class CampEventBridge implements AutoCloseable {
|
||||
|
||||
public record CampAttempt(UUID id,UUID catalog,AdventureQuestState.Site site,AdventureQuestState.Arena arena,int resetSeconds,
|
||||
List<String> roster,List<Map<String,Object>> entities,List<Map<String,Object>> representedUnits,
|
||||
int liveEntities,UUID finalEnemy,Map<String,Object> finalLocation,java.time.Instant defeatedAt,boolean currentGeneration,Map<String,Object> nativePresence){
|
||||
public CampAttempt{roster=List.copyOf(roster);entities=entities.stream().map(Map::copyOf).toList();representedUnits=representedUnits.stream().map(Map::copyOf).toList();finalLocation=Map.copyOf(finalLocation);nativePresence=Map.copyOf(nativePresence);}
|
||||
int liveEntities,UUID finalEnemy,Map<String,Object> finalLocation,java.time.Instant defeatedAt,boolean currentGeneration,Map<String,Object> nativePresence,Map<String,Object> activation){
|
||||
public CampAttempt(UUID id,UUID catalog,AdventureQuestState.Site site,AdventureQuestState.Arena arena,int resetSeconds,
|
||||
List<String> roster,List<Map<String,Object>> entities,List<Map<String,Object>> representedUnits,
|
||||
int liveEntities,UUID finalEnemy,Map<String,Object> finalLocation,java.time.Instant defeatedAt,boolean currentGeneration,Map<String,Object> nativePresence){
|
||||
this(id,catalog,site,arena,resetSeconds,roster,entities,representedUnits,liveEntities,finalEnemy,finalLocation,defeatedAt,currentGeneration,nativePresence,Map.of("capture_quality","unavailable"));
|
||||
}
|
||||
public CampAttempt{roster=List.copyOf(roster);entities=entities.stream().map(Map::copyOf).toList();representedUnits=representedUnits.stream().map(Map::copyOf).toList();finalLocation=Map.copyOf(finalLocation);nativePresence=Map.copyOf(nativePresence);activation=Map.copyOf(activation);}
|
||||
}
|
||||
public void attemptTransition(String type,CampAttempt attempt,String reason,boolean succeeded){
|
||||
try{
|
||||
@@ -175,6 +192,7 @@ public final class CampEventBridge implements AutoCloseable {
|
||||
if(site.registrationRevision()!=null)data.put("site_revision",site.registrationRevision().toString());
|
||||
data.put("encounter_location",Map.of("world_id",site.world().toString(),"x",site.x(),"y",site.y(),"z",site.z()));
|
||||
data.put("arena_shape",attempt.arena().shape().name().toLowerCase(java.util.Locale.ROOT));data.put("arena_x_blocks",attempt.arena().xBlocks());data.put("arena_z_blocks",attempt.arena().zBlocks());
|
||||
data.putAll(arenaData(site,attempt.arena()));data.put("activation",attempt.activation());
|
||||
data.put("reset_seconds",attempt.resetSeconds());data.put("reason",reason);
|
||||
String nativeOutcome=succeeded?"succeeded":"failed";
|
||||
String outcome=succeeded?"native_observed":"native_failed";
|
||||
|
||||
@@ -84,6 +84,7 @@ public final class ChampionEventBridge implements AutoCloseable {
|
||||
if(site.registrationRevision()!=null)data.put("site_revision",site.registrationRevision().toString());
|
||||
data.put("encounter_location",Map.of("world_id",site.world().toString(),"x",site.x(),"y",site.y(),"z",site.z()));
|
||||
data.put("arena_shape",attempt.arena().shape().name().toLowerCase(java.util.Locale.ROOT));data.put("arena_x_blocks",attempt.arena().xBlocks());data.put("arena_z_blocks",attempt.arena().zBlocks());
|
||||
data.putAll(CampEventBridge.arenaData(site,attempt.arena()));data.put("activation",attempt.activation());
|
||||
data.put("reset_seconds",attempt.resetSeconds());data.put("reason",reason);data.putAll(configuration);
|
||||
String nativeOutcome=succeeded?"succeeded":"failed";String outcome=succeeded?"native_observed":"native_failed";
|
||||
if(type.equals("adventure.champion.spawned")){
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package games.dmg.fantasystick;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static games.dmg.fantasystick.WitchAuditTestSupport.*;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import org.bukkit.damage.DamageSource;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityDeathEvent;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
class ArenaAttemptEventsNativeTest {
|
||||
@TempDir Path data;
|
||||
@BeforeAll static void bootstrap()throws Exception{NativeRuntime.bootstrap();}
|
||||
|
||||
@Test void actualAcceptedRectangleOutsideThenExactCornerActivationFreezesOriginalActorGeometryThroughLaterDefinitionAndNativeDefeat()throws Exception{
|
||||
try(var championFixture=new ChampionAuditNativeFixture(data)){
|
||||
var fixture=championFixture.base;var f=fixture.f;var actor=fixture.actor;
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","place","champion","--mode","individual-only","8","16","Edge Champion"});
|
||||
ReskillEventsNativeTest.await(f,()->fixture.directory.snapshot().sites().size()==1);var site=fixture.directory.snapshot().sites().getFirst();
|
||||
fixture.giver();fixture.resetCatalog();f.activate(actor);var before=CombatExperienceNativeTest.record(f,actor);
|
||||
var session=((games.dmg.fantasystick.session.EquipmentSessions)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"sessions")).active(actor.core.id).orElseThrow().session();
|
||||
var menu=fixture.open(actor);actor.core.location.setX(site.x()+4.01);actor.core.location.setZ(site.z()+8);
|
||||
fixture.click(actor,menu,false);ReskillEventsNativeTest.await(f,()->fixture.directory.active().size()==1);var quest=fixture.directory.active().getFirst();
|
||||
fixture.pulse();f.tick();
|
||||
assertTrue(fixture.spawned.stream().noneMatch(org.bukkit.entity.Vindicator.class::isInstance),"An accepted but horizontally outside holder cannot activate the rectangle");
|
||||
actor.core.location.setX(site.x()+4);actor.core.location.setY(site.y()+40);fixture.pulse();
|
||||
ReskillEventsNativeTest.await(f,()->fixture.spawned.stream().filter(org.bukkit.entity.Vindicator.class::isInstance).count()==1);var champion=fixture.spawned.stream().filter(org.bukkit.entity.Vindicator.class::isInstance).findFirst().orElseThrow();
|
||||
String attempt=champion.getPersistentDataContainer().get(new org.bukkit.NamespacedKey(f.plugin,"adventure_attempt_id"),org.bukkit.persistence.PersistentDataType.STRING);
|
||||
assertTrue(fixture.controller.hasActiveEncounter());assertEquals(before,CombatExperienceNativeTest.record(f,actor));
|
||||
var source=mock(DamageSource.class);when(source.getCausingEntity()).thenReturn(actor.player);when(source.getDirectEntity()).thenReturn(actor.player);
|
||||
var cancelled=new EntityDamageByEntityEvent(actor.player,champion,EntityDamageEvent.DamageCause.ENTITY_ATTACK,source,1);cancelled.setCancelled(true);f.dispatcher.accept(cancelled);assertTrue(cancelled.isCancelled());
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","mode",site.id().toString(),"party-required"});
|
||||
ReskillEventsNativeTest.await(f,()->fixture.directory.findSite(site.id()).orElseThrow().participationMode()==games.dmg.fantasystick.adventure.AdventureQuestState.ParticipationMode.PARTY_REQUIRED);
|
||||
assertNotEquals(site.registrationRevision(),fixture.directory.findSite(site.id()).orElseThrow().registrationRevision());assertEquals(quest,fixture.directory.findQuest(quest.id()).orElseThrow());
|
||||
var hit=new EntityDamageByEntityEvent(actor.player,champion,EntityDamageEvent.DamageCause.ENTITY_ATTACK,source,1);when(champion.getLastDamageCause()).thenReturn(hit);f.dispatcher.accept(hit);assertFalse(hit.isCancelled());
|
||||
var death=new EntityDeathEvent(champion,source,new ArrayList<>(),7);f.dispatcher.accept(death);f.dispatcher.accept(death);
|
||||
ReskillEventsNativeTest.await(f,()->fixture.directory.active().isEmpty());assertTrue(death.getDrops().isEmpty());assertEquals(0,death.getDroppedExp());assertEquals(before,CombatExperienceNativeTest.record(f,actor));
|
||||
ReskillEventsNativeTest.await(f,()->{try{return rows("adventure.champion.spawned").size()==1&&rows("adventure.champion.defeated").size()==1;}catch(Exception failure){throw new AssertionError(failure);}});
|
||||
f.plugin.setEnabled(false);f.awaitWorkers();drain(f,"championAuditDrain");var start=rows("adventure.champion.spawned").getFirst().getAsJsonObject("data");var defeat=rows("adventure.champion.defeated").getFirst().getAsJsonObject("data");
|
||||
var arena=start.getAsJsonObject("arena");assertNotNull(arena,"Native activation must export the exact captured arena, not only generic attempt identity");
|
||||
assertEquals("rectangle",arena.get("shape").getAsString());assertEquals("explicit",arena.get("source").getAsString());assertEquals(8,arena.get("x_blocks").getAsInt());assertEquals(16,arena.get("z_blocks").getAsInt());
|
||||
assertEquals(site.registrationRevision().toString(),start.get("arena_revision").getAsString());assertEquals(arena,defeat.getAsJsonObject("arena"));assertEquals(start.get("arena_revision"),defeat.get("arena_revision"));
|
||||
var activation=start.getAsJsonObject("activation");assertNotNull(activation);assertEquals("edge",activation.get("boundary_result").getAsString());
|
||||
assertEquals(actor.core.id.toString(),activation.get("player_id").getAsString());assertEquals(before.id().toString(),activation.get("character_id").getAsString());assertEquals(session.toString(),activation.get("session_id").getAsString());
|
||||
assertEquals(quest.id().toString(),activation.get("quest_id").getAsString());assertEquals(quest.unit().toString(),activation.get("unit_id").getAsString());
|
||||
assertEquals(site.world().toString(),activation.get("location_world_id").getAsString());assertEquals(site.x()+4,activation.get("location_x").getAsDouble());assertEquals(site.y()+40,activation.get("location_y").getAsDouble());assertEquals(site.z()+8,activation.get("location_z").getAsDouble());
|
||||
assertEquals("rectangle",activation.get("arena_shape").getAsString());assertEquals(8,activation.get("arena_x_blocks").getAsInt());assertEquals(16,activation.get("arena_z_blocks").getAsInt());
|
||||
assertEquals(activation,defeat.getAsJsonObject("activation"));assertEquals(attempt,start.get("attempt_id").getAsString());assertEquals(attempt,defeat.get("attempt_id").getAsString());
|
||||
assertEquals(1,rows("adventure.champion.spawned").size());assertEquals(1,rows("adventure.champion.defeated").size());assertTrue(rows("adventure.champion.reset").isEmpty());
|
||||
assertEquals(champion.getLocation().getX(),defeat.getAsJsonObject("final_enemy_location").get("x").getAsDouble());
|
||||
}
|
||||
}
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.CsvSource({"camp,circle,edge","camp,rectangle,inside","camp,default,inside","champion,circle,inside","champion,rectangle,edge","champion,default,edge"})
|
||||
void actualAcceptedArenaVariantsExcludeOutsideAndUnacceptedPlayersThenPreserveGeometryActivationAndQuietPulsesThroughReset(String kind,String form,String result)throws Exception{
|
||||
try(var championFixture=new ChampionAuditNativeFixture(data)){
|
||||
var fixture=championFixture.base;var f=fixture.f;var actor=fixture.actor;
|
||||
var args=new java.util.ArrayList<>(java.util.List.of("adventure-site","place",kind,"--mode","individual-only"));
|
||||
if(form.equals("circle"))args.add("10");else if(form.equals("rectangle")){args.add("20");args.add("30");}args.add("Arena Variant");
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",args.toArray(String[]::new));ReskillEventsNativeTest.await(f,()->fixture.directory.snapshot().sites().size()==1);var site=fixture.directory.snapshot().sites().getFirst();
|
||||
fixture.giver();fixture.resetCatalog();f.activate(actor);var before=CombatExperienceNativeTest.record(f,actor);var arena=fixture.controller.arena(site);double halfX=form.equals("rectangle")?10:arena.xBlocks();
|
||||
var menu=fixture.open(actor);actor.core.location.setX(site.x()+halfX+0.01);fixture.click(actor,menu,false);ReskillEventsNativeTest.await(f,()->fixture.directory.active().size()==1);var quest=fixture.directory.active().getFirst();
|
||||
var outsider=new FantasyStickLifecycleNativeTest.Actor(data);outsider.core.location.setWorld(actor.world);outsider.core.location.setX(site.x());outsider.core.location.setY(site.y());outsider.core.location.setZ(site.z());f.players.put(outsider.core.id,outsider.player);f.activate(outsider);
|
||||
fixture.pulse();f.tick();assertFalse(fixture.controller.hasActiveEncounter(),"Neither an outside accepted holder nor an inside unaccepted living character can activate");
|
||||
assertTrue(fixture.spawned.stream().noneMatch(entity->fixture.controller.encounterOrigin(entity).isPresent()));
|
||||
double offset=result.equals("edge")?halfX:halfX/2;actor.core.location.setX(site.x()+offset);actor.core.location.setY(site.y()+40);fixture.pulse();
|
||||
ReskillEventsNativeTest.await(f,fixture.controller::hasActiveEncounter);ReskillEventsNativeTest.await(f,()->{
|
||||
var roster=fixture.directory.snapshot().rosters().get(site.id());
|
||||
return (kind.equals("champion")||roster!=null)&&fixture.spawned.stream().filter(entity->fixture.controller.encounterOrigin(entity).isPresent()).count()==(kind.equals("champion")?1:roster.size());
|
||||
});
|
||||
String startType=kind.equals("champion")?"adventure.champion.spawned":"adventure.camp.started",resetType="adventure."+kind+".reset";
|
||||
ReskillEventsNativeTest.await(f,()->{try{return rows(startType).size()==1;}catch(Exception failure){throw new AssertionError(failure);}});var start=rows(startType).getFirst();var values=start.getAsJsonObject("data");var originalGeometry=values.getAsJsonObject("arena").deepCopy();var activation=values.getAsJsonObject("activation").deepCopy();
|
||||
assertEquals(result,activation.get("boundary_result").getAsString());assertEquals(actor.core.id.toString(),activation.get("player_id").getAsString());assertEquals(before.id().toString(),activation.get("character_id").getAsString());assertEquals(quest.id().toString(),activation.get("quest_id").getAsString());
|
||||
assertEquals(site.x()+offset,activation.get("location_x").getAsDouble());assertEquals(site.y()+40,activation.get("location_y").getAsDouble());assertEquals(site.z(),activation.get("location_z").getAsDouble());
|
||||
assertEquals(form.equals("rectangle")?"rectangle":"circle",originalGeometry.get("shape").getAsString());assertEquals(form.equals("default")?"configured_default":"explicit",originalGeometry.get("source").getAsString());assertEquals(arena.xBlocks(),originalGeometry.get("x_blocks").getAsInt());assertEquals(arena.zBlocks(),originalGeometry.get("z_blocks").getAsInt());
|
||||
actor.particleCalls.clear();for(int index=0;index<3;index++)fixture.pulse();assertFalse(actor.particleCalls.isEmpty(),"Actual accepted perimeter presentation remains wired");assertEquals(java.util.List.of(start),rows(startType));
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"config","adventure-site-radius","32"});ReskillEventsNativeTest.await(f,()->actor.messages.stream().anyMatch(value->value.startsWith("Saved adventure-site-radius=32")));
|
||||
actor.core.location.setX(site.x()+halfX+1);f.dispatcher.accept(new org.bukkit.event.world.ChunkUnloadEvent(actor.world.getChunkAt(0,-1),false));assertFalse(fixture.controller.hasActiveEncounter());assertEquals(quest,fixture.directory.findQuest(quest.id()).orElseThrow());assertEquals(before,CombatExperienceNativeTest.record(f,actor));
|
||||
assertTrue(((games.dmg.fantasystick.adventure.FishingDirectory)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"fishing")).trophies(quest.unit()).isEmpty());
|
||||
ReskillEventsNativeTest.await(f,()->{try{return rows(resetType).size()==1;}catch(Exception failure){throw new AssertionError(failure);}});f.plugin.setEnabled(false);f.awaitWorkers();drain(f,kind.equals("camp")?"campAuditDrain":"championAuditDrain");
|
||||
var reset=rows(resetType).getFirst().getAsJsonObject("data");assertEquals(originalGeometry,reset.getAsJsonObject("arena"));assertEquals(activation,reset.getAsJsonObject("activation"));assertEquals(values.get("attempt_id"),reset.get("attempt_id"));assertEquals(values.get("arena_revision"),reset.get("arena_revision"));assertEquals("chunk_unload",reset.get("reason").getAsString());
|
||||
assertEquals(1,rows(startType).size());assertEquals(1,rows(resetType).size());assertTrue(rows(kind.equals("champion")?"adventure.champion.defeated":"adventure.camp.cleared").isEmpty());
|
||||
}
|
||||
}
|
||||
private java.util.List<com.google.gson.JsonObject> rows(String type)throws Exception{
|
||||
return ReskillEventsNativeTest.records(data).stream().filter(row->row.get("type").getAsString().endsWith(type)).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package games.dmg.fantasystick;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static games.dmg.fantasystick.WitchAuditTestSupport.*;
|
||||
|
||||
import games.dmg.fantasystick.adventure.AdventureQuestState;
|
||||
import games.dmg.fantasystick.persistence.YamlAdventureQuestRepository;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
class ArenaRegistrationEventsNativeTest {
|
||||
@TempDir Path data;
|
||||
@BeforeAll static void bootstrap()throws Exception{NativeRuntime.bootstrap();}
|
||||
|
||||
@Test void actualRegisteredExplicitCirclePreservesSavedGeometryAndOriginalAdministratorArenaContextInJsonl()throws Exception{
|
||||
try(var fixture=new CampAuditNativeFixture(data)){
|
||||
var f=fixture.f;var actor=fixture.actor;f.activate(actor);var before=CombatExperienceNativeTest.record(f,actor);
|
||||
var session=((games.dmg.fantasystick.session.EquipmentSessions)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"sessions"))
|
||||
.active(actor.core.id).orElseThrow().session();
|
||||
assertTrue(f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","place","champion","--mode","individual-only","8","Round Arena"}));
|
||||
ReskillEventsNativeTest.await(f,()->actor.messages.stream().anyMatch(value->value.startsWith("Placed champion")));
|
||||
var saved=fixture.directory.snapshot().sites().getFirst();assertEquals(AdventureQuestState.Arena.circle(8),saved.arena());
|
||||
assertEquals(saved,new YamlAdventureQuestRepository(data).load().sites().getFirst());assertEquals(before,CombatExperienceNativeTest.record(f,actor));
|
||||
ReskillEventsNativeTest.await(f,()->{try{return rows().size()==1;}catch(Exception failure){throw new AssertionError(failure);}});
|
||||
f.plugin.setEnabled(false);f.awaitWorkers();drain(f,"championAuditDrain");var rows=rows();assertEquals(1,rows.size());
|
||||
var row=rows.getFirst();var values=row.getAsJsonObject("data");var arena=values.getAsJsonObject("arena");
|
||||
assertEquals(actor.core.id.toString(),row.get("playerid").getAsString());assertEquals(before.id().toString(),row.get("characterid").getAsString());
|
||||
assertEquals(session.toString(),values.get("session_id").getAsString());assertEquals(saved.id().toString(),values.get("site_id").getAsString());
|
||||
assertEquals(saved.world().toString(),values.get("world_id").getAsString());assertEquals(12,values.get("x").getAsDouble());
|
||||
assertEquals(70,values.get("y").getAsDouble());assertEquals(-8,values.get("z").getAsDouble());assertEquals("circle",arena.get("shape").getAsString());
|
||||
assertEquals("explicit",arena.has("source")?arena.get("source").getAsString():null,
|
||||
"Acknowledged registration must distinguish persisted explicit geometry from configured-default context in its actual JSONL record");
|
||||
assertEquals(8,arena.get("radius").getAsInt());assertEquals(saved.registrationRevision().toString(),values.get("arena_revision").getAsString());
|
||||
assertEquals("persisted_site_definition",values.get("arena_revision_capture_quality").getAsString());
|
||||
assertEquals("individual-only",values.get("mode_after").getAsString());assertEquals("acknowledged",values.get("outcome").getAsString());
|
||||
}
|
||||
}
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.CsvSource({"camp,circle","camp,rectangle","camp,default","champion,circle","champion,rectangle","champion,default"})
|
||||
void allRegisteredArenaFormsRetainOriginalEffectiveGeometryAcrossLaterConfigurationAndReload(String kind,String form)throws Exception{
|
||||
try(var fixture=new CampAuditNativeFixture(data)){
|
||||
var f=fixture.f;var actor=fixture.actor;f.activate(actor);var before=CombatExperienceNativeTest.record(f,actor);
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"config","adventure-site-radius","24"});
|
||||
ReskillEventsNativeTest.await(f,()->actor.messages.stream().anyMatch(value->value.startsWith("Saved adventure-site-radius=24")));
|
||||
var args=new java.util.ArrayList<>(java.util.List.of("adventure-site","place",kind,"--mode","individual-only"));
|
||||
if(form.equals("circle"))args.add("10");else if(form.equals("rectangle")){args.add("20");args.add("30");}args.add("Geometry Original");
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",args.toArray(String[]::new));
|
||||
ReskillEventsNativeTest.await(f,()->actor.messages.stream().anyMatch(value->value.startsWith("Placed "+kind)));
|
||||
var site=fixture.directory.snapshot().sites().getFirst();assertEquals(site,new YamlAdventureQuestRepository(data).load().sites().getFirst());
|
||||
if(form.equals("default"))assertNull(site.arena());else assertEquals(form.equals("circle")?AdventureQuestState.Arena.circle(10):AdventureQuestState.Arena.rectangle(20,30),site.arena());
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"config","adventure-site-radius","32"});
|
||||
ReskillEventsNativeTest.await(f,()->actor.messages.stream().anyMatch(value->value.startsWith("Saved adventure-site-radius=32")));
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","list"});fixture.pulse();fixture.pulse();
|
||||
ReskillEventsNativeTest.await(f,()->{try{return rows(kind).size()==1;}catch(Exception failure){throw new AssertionError(failure);}});
|
||||
f.plugin.setEnabled(false);f.awaitWorkers();drain(f,kind.equals("camp")?"campAuditDrain":"championAuditDrain");
|
||||
var row=rows(kind).getFirst();var values=row.getAsJsonObject("data");var geometry=values.getAsJsonObject("arena");
|
||||
assertEquals(form.equals("rectangle")?"rectangle":"circle",geometry.get("shape").getAsString());assertEquals(form.equals("default")?"configured_default":"explicit",geometry.get("source").getAsString());
|
||||
assertEquals(form.equals("default")?24:form.equals("rectangle")?20:10,geometry.get("x_blocks").getAsInt());assertEquals(form.equals("default")?24:form.equals("rectangle")?30:10,geometry.get("z_blocks").getAsInt());
|
||||
if(form.equals("rectangle"))assertFalse(geometry.has("radius"));else assertEquals(form.equals("default")?24:10,geometry.get("radius").getAsInt());
|
||||
assertEquals(site.registrationRevision().toString(),values.get("arena_revision").getAsString());assertEquals(actor.core.id.toString(),row.get("playerid").getAsString());assertEquals(before.id().toString(),row.get("characterid").getAsString());
|
||||
assertEquals(12,values.getAsJsonObject("actor_location").get("x").getAsDouble());assertEquals(70,values.getAsJsonObject("actor_location").get("y").getAsDouble());assertEquals(-8,values.getAsJsonObject("actor_location").get("z").getAsDouble());
|
||||
assertEquals(site.world().toString(),values.get("world_id").getAsString());assertEquals(1,rows(kind).size());assertTrue(fixture.spawned.isEmpty());
|
||||
var bytes=java.nio.file.Files.readAllBytes(data.resolve("adventure-quests.yml"));f.players.clear();f.plugin.setEnabled(true);
|
||||
ReskillEventsNativeTest.await(f,()->((games.dmg.fantasystick.adventure.AdventureQuestDirectory)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"adventureQuests")).findSite(site.id()).isPresent());
|
||||
assertArrayEquals(bytes,java.nio.file.Files.readAllBytes(data.resolve("adventure-quests.yml")));assertEquals(site,((games.dmg.fantasystick.adventure.AdventureQuestDirectory)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"adventureQuests")).findSite(site.id()).orElseThrow());
|
||||
assertEquals(before,((games.dmg.fantasystick.persistence.CharacterStorage)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"characters")).find(before.id()).get().orElseThrow());
|
||||
f.plugin.setEnabled(false);f.awaitWorkers();drain(f,kind.equals("camp")?"campAuditDrain":"championAuditDrain");assertEquals(java.util.List.of(row),rows(kind));
|
||||
}
|
||||
}
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.ValueSource(strings={"camp","champion"})
|
||||
void actualMalformedDeniedAndFailedRegistrationProduceNoArenaSuccessWhileRetryAndListRemainExact(String kind)throws Exception{
|
||||
try(var fixture=new CampAuditNativeFixture(data)){
|
||||
var f=fixture.f;var actor=fixture.actor;f.activate(actor);
|
||||
actor.allowed=false;f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","place",kind,"10","Denied"});actor.allowed=true;
|
||||
for(var dimensions:java.util.List.of(new String[]{"3"},new String[]{"65"},new String[]{"20","7"},new String[]{"20","129"},new String[]{"1.5"},new String[]{"--mode","invalid","10"})){
|
||||
var args=new java.util.ArrayList<>(java.util.List.of("adventure-site","place",kind));args.addAll(java.util.List.of(dimensions));f.adminCommand.execute(actor.player,"fstickadmin",args.toArray(String[]::new));
|
||||
}
|
||||
assertTrue(fixture.directory.snapshot().sites().isEmpty());
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","place",kind,"10","Original"});ReskillEventsNativeTest.await(f,()->fixture.directory.snapshot().sites().size()==1);
|
||||
var original=fixture.directory.snapshot().sites().getFirst();var file=data.resolve("adventure-quests.yml");var bytes=java.nio.file.Files.readAllBytes(file);java.nio.file.Files.delete(file);java.nio.file.Files.createDirectory(file);
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","place",kind,"20","30","Retry"});ReskillEventsNativeTest.await(f,()->actor.messages.contains("Adventure site save failed; desired state is unchanged."));
|
||||
assertEquals(java.util.List.of(original),fixture.directory.snapshot().sites());java.nio.file.Files.delete(file);java.nio.file.Files.write(file,bytes);
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","place",kind,"20","30","Retry"});ReskillEventsNativeTest.await(f,()->fixture.directory.snapshot().sites().size()==2);
|
||||
f.adminCommand.execute(actor.player,"fstickadmin",new String[]{"adventure-site","list"});fixture.pulse();fixture.pulse();
|
||||
ReskillEventsNativeTest.await(f,()->{try{return rows(kind).size()==2;}catch(Exception failure){throw new AssertionError(failure);}});f.plugin.setEnabled(false);f.awaitWorkers();drain(f,kind.equals("camp")?"campAuditDrain":"championAuditDrain");
|
||||
var exported=rows(kind);assertEquals(2,exported.size());assertEquals(java.util.Set.of("Original","Retry"),exported.stream().map(row->row.getAsJsonObject("data").get("site_name").getAsString()).collect(java.util.stream.Collectors.toSet()));
|
||||
assertEquals(fixture.directory.snapshot().sites().stream().map(site->site.id().toString()).collect(java.util.stream.Collectors.toSet()),exported.stream().map(row->row.getAsJsonObject("data").get("site_id").getAsString()).collect(java.util.stream.Collectors.toSet()));
|
||||
assertTrue(fixture.spawned.isEmpty());
|
||||
}
|
||||
}
|
||||
private java.util.List<com.google.gson.JsonObject> rows(String kind)throws Exception{
|
||||
return ReskillEventsNativeTest.records(data).stream().filter(row->row.get("type").getAsString().endsWith("adventure.site.registered")
|
||||
&&row.getAsJsonObject("data").get("site_kind").getAsString().equals(kind)).toList();
|
||||
}
|
||||
private java.util.List<com.google.gson.JsonObject> rows()throws Exception{
|
||||
return ReskillEventsNativeTest.records(data).stream().filter(row->row.get("type").getAsString().endsWith("adventure.site.registered")
|
||||
&&row.getAsJsonObject("data").get("site_kind").getAsString().equals("champion")).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package games.dmg.fantasystick.events;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import games.dmg.fantasystick.adventure.AdventureQuestState;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ArenaAuditContextTest {
|
||||
@Test void compatibleLegacyDefaultGeometryReportsCapturedRadiusWithoutInventingPersistedRevision(){
|
||||
var site=new AdventureQuestState.Site(UUID.randomUUID(),AdventureQuestState.SiteType.CAMP,"Legacy",UUID.randomUUID(),12,70,-8);
|
||||
var context=CampEventBridge.arenaData(site,AdventureQuestState.Arena.circle(24));
|
||||
assertEquals("unavailable",context.get("arena_revision_capture_quality"));assertFalse(context.containsKey("arena_revision"));assertNull(site.registrationRevision());assertNull(site.arena());
|
||||
assertEquals(Map.of("shape","circle","source","configured_default","radius",24,"x_blocks",24,"z_blocks",24),context.get("arena"));
|
||||
}
|
||||
@Test void explicitFullRectangleUsesKnownDefinitionRevisionAndNeverClaimsCircleRadius(){
|
||||
var revision=UUID.randomUUID();var arena=AdventureQuestState.Arena.rectangle(20,30);
|
||||
var site=new AdventureQuestState.Site(UUID.randomUUID(),AdventureQuestState.SiteType.CHAMPION,"Explicit",UUID.randomUUID(),12,70,-8,arena,AdventureQuestState.ParticipationMode.INDIVIDUAL_ONLY,null,revision);
|
||||
var context=CampEventBridge.arenaData(site,arena);
|
||||
assertEquals(revision.toString(),context.get("arena_revision"));assertEquals("persisted_site_definition",context.get("arena_revision_capture_quality"));
|
||||
assertEquals(Map.of("shape","rectangle","source","explicit","x_blocks",20,"z_blocks",30),context.get("arena"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user