fix(adventure): retract owned hook after quest catch
Release / release (push) Failing after 13m17s

This commit is contained in:
dmg committed 2026-09-25 16:39:35 -04:00
1 parent c13c3093cf
commit 897a0606ba
2 files changed
+35 -3

No files matched your search

@@ -50,17 +50,18 @@ public final class FishingController implements Listener,AutoCloseable,Adventure
case LURED,BITE->{} // Approach and bite are not reels; retain the original owned hook.
case CAUGHT_ENTITY->{event.setCancelled(true);hooks.remove(player.getUniqueId());event.getHook().remove();player.sendMessage("Quest Fishing Rods cannot pull entities.");}
case CAUGHT_FISH->{
var claim=hooks.remove(player.getUniqueId());
var claim=hooks.get(player.getUniqueId());
if(claim==null||!claim.session().equals(context.active().session())||!claim.character().equals(context.record().id())||!claim.quest().equals(quest.id())||!claim.hook().equals(event.getHook().getUniqueId())){
refuseCatch(event,"Quest catch refused: the cast is no longer valid; cast again.");return;
}
hooks.remove(player.getUniqueId(),claim); // A late callback for another hook must not consume the current cast.
if(!(event.getCaught() instanceof Item caught)){
refuseCatch(event,"Quest catch refused: no native fish item was caught.");return;
}
if(!inside(player.getLocation(),quest)||!inside(event.getHook().getLocation(),quest)){
refuseCatch(event,"Quest catch refused: keep yourself and the bobber inside the fishing area.");return;
}
event.setCancelled(true);event.setExpToDrop(0);caught.remove();
event.setCancelled(true);event.setExpToDrop(0);caught.remove();event.getHook().remove();
BigInteger xp=claim.sampledXp();boolean awarded=random.nextDouble(100d)<quest.fishing().chancePercent().doubleValue();
UUID operation=UUID.nameUUIDFromBytes((quest.id()+":"+claim.hook()).getBytes(StandardCharsets.UTF_8));
var plan=new FishingState.Catch(operation,context.record().id(),quest.id(),quest.site(),quest.fishing().itemId(),quest.fishing().itemName(),xp,awarded);
@@ -46,7 +46,7 @@ final class FishingQuestNativeTest {
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,hook,EquipmentSlot.HAND,PlayerFishEvent.State.FISHING));
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,hook,EquipmentSlot.HAND,PlayerFishEvent.State.LURED));
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,hook,EquipmentSlot.HAND,PlayerFishEvent.State.BITE));
var caughtEvent=new PlayerFishEvent(actor.player,caught,hook,EquipmentSlot.HAND,PlayerFishEvent.State.CAUGHT_FISH);caughtEvent.setExpToDrop(7);f.dispatcher.accept(caughtEvent);assertTrue(caughtEvent.isCancelled());assertEquals(0,caughtEvent.getExpToDrop());verify(caught).remove();await(f,()->fishing.quantity(actor.core.record.id(),site.fishing().itemId()).equals(BigInteger.ONE));var storage=(CharacterStorage)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"characters");await(f,()->storage.find(actor.core.record.id()).join().orElseThrow().experience().equals(BigInteger.TWO));assertTrue(actor.messages.stream().anyMatch(message->message.contains("Caught Silver Scale")));
var caughtEvent=new PlayerFishEvent(actor.player,caught,hook,EquipmentSlot.HAND,PlayerFishEvent.State.CAUGHT_FISH);caughtEvent.setExpToDrop(7);f.dispatcher.accept(caughtEvent);assertTrue(caughtEvent.isCancelled());assertEquals(0,caughtEvent.getExpToDrop());verify(caught).remove();verify(hook).remove();await(f,()->fishing.quantity(actor.core.record.id(),site.fishing().itemId()).equals(BigInteger.ONE));var storage=(CharacterStorage)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"characters");await(f,()->storage.find(actor.core.record.id()).join().orElseThrow().experience().equals(BigInteger.TWO));assertTrue(actor.messages.stream().anyMatch(message->message.contains("Caught Silver Scale")));
var missedHook=mock(FishHook.class);when(missedHook.getUniqueId()).thenReturn(UUID.randomUUID());when(missedHook.getLocation()).thenReturn(actor.core.location.clone());
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,missedHook,EquipmentSlot.HAND,PlayerFishEvent.State.FISHING));
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,missedHook,EquipmentSlot.HAND,PlayerFishEvent.State.FAILED_ATTEMPT));
@@ -77,6 +77,37 @@ final class FishingQuestNativeTest {
}
}
@Test void ordinaryQuestCatchRetractsHookAndNextCastSurvivesOldHookCallback() throws Exception {
java.nio.file.Files.writeString(data.resolve("config.yml"),"adventure-daily-site-count: 1\nfishing-catch-xp-min: 2\nfishing-catch-xp-max: 2\n");
try(var f=new FantasyStickLifecycleNativeTest.Fixture(data)){
f.plugin.setEnabled(true);var actor=new FantasyStickLifecycleNativeTest.Actor(data);f.players.put(actor.core.id,actor.player);UUID world=UUID.randomUUID();when(actor.world.getUID()).thenReturn(world);f.activate(actor);doReturn(null).when(actor.player).getFishHook();
var adventures=(AdventureQuestDirectory)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"adventureQuests");var fishing=(FishingDirectory)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"fishing");
var site=adventures.placeFishingSite("Stillwater","Rare Pearl",world,0,64,0,16,java.math.BigDecimal.ZERO,BigInteger.ONE,BigInteger.TEN,BigInteger.ZERO,AdventureQuestState.ParticipationMode.INDIVIDUAL_ONLY).get(5,TimeUnit.SECONDS);
var before=adventures.snapshot().catalog();var roll=adventures.rerollToday(before==null?null:before.revision()).get(5,TimeUnit.SECONDS);if(adventures.snapshot().reset()!=null)adventures.completeReset(adventures.snapshot().reset().operation()).get(5,TimeUnit.SECONDS);
adventures.accept(roll.catalog().orElseThrow().revision(),site.id(),AdventureQuestState.UnitType.SOLO,actor.core.record.id(),Set.of(actor.core.record.id())).get(5,TimeUnit.SECONDS);
for(int retry=0;retry<20&&actor.core.inventory.getItem(0).getType()!=Material.FISHING_ROD;retry++){pulse(f);drain(f);}
assertEquals(Material.FISHING_ROD,actor.core.inventory.getItem(0).getType());
var first=mock(FishHook.class);when(first.getUniqueId()).thenReturn(UUID.randomUUID());when(first.getLocation()).thenReturn(actor.core.location.clone());var caught=mock(Item.class);
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,first,EquipmentSlot.HAND,PlayerFishEvent.State.FISHING));
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,first,EquipmentSlot.HAND,PlayerFishEvent.State.LURED));
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,first,EquipmentSlot.HAND,PlayerFishEvent.State.BITE));
var vetoed=new PlayerFishEvent(actor.player,caught,first,EquipmentSlot.HAND,PlayerFishEvent.State.CAUGHT_FISH);vetoed.setCancelled(true);f.dispatcher.accept(vetoed);verify(first,never()).remove();
var firstCatch=new PlayerFishEvent(actor.player,caught,first,EquipmentSlot.HAND,PlayerFishEvent.State.CAUGHT_FISH);firstCatch.setExpToDrop(6);f.dispatcher.accept(firstCatch);
assertTrue(firstCatch.isCancelled());assertEquals(0,firstCatch.getExpToDrop());verify(caught).remove();verify(first).remove();
var storage=(CharacterStorage)FantasyStickLifecycleNativeTest.Fixture.field(f.plugin,"characters");await(f,()->storage.find(actor.core.record.id()).join().orElseThrow().experience().equals(BigInteger.TWO));
assertEquals(BigInteger.ZERO,fishing.quantity(actor.core.record.id(),site.fishing().itemId()));assertTrue(actor.messages.stream().anyMatch(message->message.contains("no quest item")));
var next=mock(FishHook.class);when(next.getUniqueId()).thenReturn(UUID.randomUUID());when(next.getLocation()).thenReturn(actor.core.location.clone());
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,next,EquipmentSlot.HAND,PlayerFishEvent.State.FISHING));
f.dispatcher.accept(new PlayerFishEvent(actor.player,caught,first,EquipmentSlot.HAND,PlayerFishEvent.State.CAUGHT_FISH));
verify(next,never()).remove();
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,next,EquipmentSlot.HAND,PlayerFishEvent.State.LURED));
f.dispatcher.accept(new PlayerFishEvent(actor.player,null,next,EquipmentSlot.HAND,PlayerFishEvent.State.BITE));
f.dispatcher.accept(new PlayerFishEvent(actor.player,caught,next,EquipmentSlot.HAND,PlayerFishEvent.State.CAUGHT_FISH));
verify(next).remove();await(f,()->storage.find(actor.core.record.id()).join().orElseThrow().experience().equals(BigInteger.valueOf(4)));
assertEquals(BigInteger.ZERO,fishing.quantity(actor.core.record.id(),site.fishing().itemId()));
}
}
@Test void nonleaderMemberSuppliesWholeRequirementAndOfflinePartySharePersists() throws Exception {
java.nio.file.Files.writeString(data.resolve("config.yml"),"adventure-daily-site-count: 1\n");
try(var f=new FantasyStickLifecycleNativeTest.Fixture(data)){