Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ public void bindItemMatcher(ItemMatcher matcher) {
brushItem.setMatcher(bound);
sketchSupplies.setMatcher(bound);
digTools.setMatcher(bound);
handPick.toolsChanged();
recoveredFindItem.setMatcher(bound);
sketch.setMatcher(bound);
museum.setMatcher(bound);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,23 @@ public void onDrop(PlayerDropItemEvent event) {
*/
@EventHandler(priority = EventPriority.MONITOR)
public void onQuit(PlayerQuitEvent event) {
pick.finish(event.getPlayer());
pick.syncHeldTool(event.getPlayer(), null);
try {
pick.finish(event.getPlayer());
pick.syncHeldTool(event.getPlayer(), null);
} finally {
pick.forget(event.getPlayer());
}
}

/**
* Defensive reset on a character switch: the next tick reads the mining lock again instead
* of trusting what it saw for the previous character.
*
* @param event character session end
*/
@EventHandler(priority = EventPriority.MONITOR)
public void onCharacterEnd(net.tfminecraft.tlibs.character.CharacterSessionEndEvent event) {
pick.forget(event.getPlayer());
}

/**
Expand Down
131 changes: 126 additions & 5 deletions src/main/java/net/tfminecraft/archaeo/excavation/HandPickService.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ public class HandPickService {
* the ray hits.
*/
private static final int REACH_CELLS = REACH + 2;
/**
* Ticks a per-tick shortcut is trusted before the server is asked again: the held tool's
* whitelist match (pack registries can reload under an unchanged stack) and a confirmed
* mining unlock (another plugin could restore the modifier).
*/
private static final int RECHECK_TICKS = 20;

private final JavaPlugin plugin;
private final net.tfminecraft.archaeo.ownership.CharacterWork identity;
Expand All @@ -75,6 +81,7 @@ public class HandPickService {
private int gameTick;
private final Map<UUID, Cycle> cycles = new ConcurrentHashMap<>();
private final Map<UUID, Long> lastWarn = new ConcurrentHashMap<>();
private final Map<UUID, HeldState> held = new ConcurrentHashMap<>();
/** Wall clock in milliseconds for the warning cooldown; tests replace it to step past the cooldown. */
LongSupplier clock = System::currentTimeMillis;

Expand Down Expand Up @@ -108,6 +115,15 @@ public HandPickService(
public void setSettings(PickSettings settings) {
this.settings = settings;
this.tools.setProfiles(settings.profiles());
toolsChanged();
}

/**
* Forgets every cached held-tool match, so the next tick matches again. Call after the
* whitelist or the pack-plugin lookup behind it changes.
*/
public void toolsChanged() {
held.clear();
}

/**
Expand All @@ -131,6 +147,16 @@ public void stop() {
setVanillaMineLocked(player, false);
}
cycles.clear();
held.clear();
}

/**
* Drops the per-tick shortcuts kept for a player who is leaving.
*
* @param player quitting player
*/
public void forget(Player player) {
held.remove(player.getUniqueId());
}

/**
Expand Down Expand Up @@ -352,7 +378,7 @@ private void tick() {
continue;
}
if (!net.tfminecraft.archaeo.ownership.CharacterActors.current(cycle.actor, player)) { finish(player); continue; }
ExcavationTool held = tools.match(player.getInventory().getItemInMainHand()).orElse(null);
ExcavationTool held = heldTool(player, player.getInventory().getItemInMainHand(), false);
if (held == null || !held.id().equals(cycle.tool.id())) {
finish(player);
continue;
Expand Down Expand Up @@ -403,11 +429,10 @@ private void tick() {
*/
private void syncAndShowHud(Player player) {
ItemStack held = player.getInventory().getItemInMainHand();
if (!tools.isAllowed(held)) {
setVanillaMineLocked(player, false);
if (heldTool(player, held, true) == null) {
keepVanillaMineUnlocked(player);
return;
}
tools.restoreAllowedTool(held);
Cycle cycle = cycles.get(player.getUniqueId());
if (cycle != null) {
setVanillaMineLocked(player, true);
Expand All @@ -417,12 +442,82 @@ private void syncAndShowHud(Player player) {
}
Block target = aimedNearSite(player);
Site site = target == null ? null : establishedAt(target);
setVanillaMineLocked(player, site != null && PrismFill.isTerrainFill(target.getType()));
if (site != null && PrismFill.isTerrainFill(target.getType())) {
setVanillaMineLocked(player, true);
} else {
keepVanillaMineUnlocked(player);
}
if (site != null) {
showHud(player, null, target, site);
}
}

/**
* The whitelist profile for the main-hand stack, matched again only when the stack differs
* from the one matched last (any change to type, amount, or data) or after
* {@link #RECHECK_TICKS}. Matching asks every whitelist entry and the pack plugins, which is
* too slow to repeat for every online player on every tick.
*
* @param player holder
* @param stack current main-hand stack
* @param restore whether to strip a legacy mine lock from a whitelisted stack, as
* {@link DigTools#restoreAllowedTool} does; skipped while the stack is the one
* already restored, since a restored stack has nothing left to strip
* @return matching profile, or {@code null} when the stack is not an excavation tool
*/
private ExcavationTool heldTool(Player player, ItemStack stack, boolean restore) {
HeldState state = state(player);
boolean same = stack != null
&& state.stack != null
&& gameTick - state.matchedTick < RECHECK_TICKS
&& state.stack.equals(stack);
if (!same) {
state.tool = tools.match(stack).orElse(null);
state.matchedTick = gameTick;
state.restored = false;
}
if (restore && !state.restored) {
if (state.tool != null) {
tools.restoreAllowedTool(stack);
}
state.restored = true;
// Restoring may have changed the stack, so remember it as it is now.
same = false;
}
if (!same) {
state.stack = stack == null ? null : stack.clone();
}
return state.tool;
}

/**
* Unlocks vanilla mining, skipping the attribute read while an unlock was already confirmed
* within {@link #RECHECK_TICKS}. Only {@link #setVanillaMineLocked} adds the lock, and it
* clears that confirmation when it does.
*
* @param player holder
*/
private void keepVanillaMineUnlocked(Player player) {
HeldState state = state(player);
if (state.unlocked && gameTick - state.unlockedTick < RECHECK_TICKS) {
return;
}
setVanillaMineLocked(player, false);
}

/**
* @param player online player
* @return shortcuts for this player object; a rejoin gets a fresh one
*/
private HeldState state(Player player) {
HeldState state = held.get(player.getUniqueId());
if (state == null || state.player != player) {
state = new HeldState(player);
held.put(player.getUniqueId(), state);
}
return state;
}

/**
* @param player viewer
* @return whether the crosshair is on prism fill
Expand Down Expand Up @@ -766,6 +861,9 @@ private void setVanillaMineLocked(Player player, boolean locked) {
break;
}
}
HeldState state = state(player);
state.unlocked = !locked;
state.unlockedTick = gameTick;
if (locked && found == null) {
speed.addModifier(new AttributeModifier(
noVanillaMineKey,
Expand Down Expand Up @@ -857,6 +955,29 @@ private void warn(Player player, String message) {
player.sendMessage(message);
}

/**
* Per-tick shortcuts for one online player object.
*/
private static final class HeldState {
private final Player player;
/** Copy of the main-hand stack last matched, or {@code null} before the first match. */
private ItemStack stack;
private ExcavationTool tool;
private int matchedTick;
/** Whether {@link DigTools#restoreAllowedTool} already ran on {@link #stack}. */
private boolean restored;
/** Whether the mining lock was last seen absent, at {@link #unlockedTick}. */
private boolean unlocked;
private int unlockedTick;

/**
* @param player online player object
*/
private HeldState(Player player) {
this.player = player;
}
}

/**
* One player's current left-click hold on a cell.
*/
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/net/tfminecraft/archaeo/ownership/SiteEstates.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,19 @@ private static void supersede(SiteOwnership store,Path path,Map<String,Object> j
if(!Objects.equals(SiteDocuments.read(source),journal.get("before"))||!Estates.guardHolds(current)||!Files.readString(path).equals(original))throw new SiteOwnership.Conflict("Untouched plan changed during supersession");Files.delete(path);SiteDocuments.forceDirectory(path.getParent());
}
private static Outcome done(Set<String> moved){return Outcome.done(Map.of("sites",moved.size()),List.copyOf(moved));}
public static void start(Plugin plugin,SiteRepository repository){SiteEstates participant=new SiteEstates(repository);Bukkit.getServicesManager().register(EstateParticipant.class,participant,plugin,ServicePriority.Normal);Bukkit.getScheduler().runTaskTimer(plugin,()->{for(EstateDecision decision:Estates.decided())try{Estates.report(decision.deathId(),participant.id(),decision.revision(),participant.settle(decision));}catch(Exception failure){plugin.getLogger().warning("Site estate "+decision.deathId()+" revision "+decision.revision()+": "+failure.getMessage());}},3L,20L*60*60);}
/** Ticks between estate passes. */
static final long PASS_PERIOD_TICKS=20L*60*60;
/** Delay of the startup pass. */
static final long STARTUP_PASS_TICKS=3L;
/**
* First hourly pass, a quarter hour later than a plain hourly timer would run it, so it does
* not land on the same tick as the hourly jobs other plugins start at enable time.
*/
static final long FIRST_HOURLY_PASS_TICKS=STARTUP_PASS_TICKS+PASS_PERIOD_TICKS+20L*60*15;
public static void start(Plugin plugin,SiteRepository repository){
SiteEstates participant=new SiteEstates(repository);Bukkit.getServicesManager().register(EstateParticipant.class,participant,plugin,ServicePriority.Normal);
Runnable pass=()->{for(EstateDecision decision:Estates.decided())try{Estates.report(decision.deathId(),participant.id(),decision.revision(),participant.settle(decision));}catch(Exception failure){plugin.getLogger().warning("Site estate "+decision.deathId()+" revision "+decision.revision()+": "+failure.getMessage());}};
Bukkit.getScheduler().runTaskLater(plugin,pass,STARTUP_PASS_TICKS);
Bukkit.getScheduler().runTaskTimer(plugin,pass,FIRST_HOURLY_PASS_TICKS,PASS_PERIOD_TICKS);
}
}
Loading
Loading