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
22 changes: 22 additions & 0 deletions .github/compatibility/tlibs-2.3.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.tfminecraft</groupId>
<artifactId>magic-compatibility-fixture</artifactId>
<version>main-SNAPSHOT</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<tlibs.version>2.3.0</tlibs.version>
</properties>
<dependencies>
<dependency>
<groupId>me.plugins</groupId>
<artifactId>tlibs</artifactId>
<version>${tlibs.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ jobs:
with:
mode: pinned

- name: Install older API compatibility fixture
uses: TF-Minecraft/TLibs/.github/actions/setup-plugins@8ef41de589f121c98ff892b10696e7c1feb4ca74
with:
mode: pinned
pom: .github/compatibility/tlibs-2.3.xml
lock: .build/legacy-plugin-dependencies.json

- name: Prepare pinned dependencies
env:
GH_TOKEN: ${{ secrets.DEPS_TOKEN }}
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/maven-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ jobs:
with:
mode: pinned

- name: Install older API compatibility fixture
uses: TF-Minecraft/TLibs/.github/actions/setup-plugins@8ef41de589f121c98ff892b10696e7c1feb4ca74
with:
mode: pinned
pom: .github/compatibility/tlibs-2.3.xml
lock: .build/legacy-plugin-dependencies.json

- name: Prepare pinned dependencies
env:
GH_TOKEN: ${{ secrets.DEPS_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<maven.compiler.proc>full</maven.compiler.proc>
<interactiblefurniture.version>0.2.0</interactiblefurniture.version>
<rpcharacters.version>2.16.0</rpcharacters.version>
<tlibs.version>2.3.0</tlibs.version>
<tlibs.version>2.6.0</tlibs.version>
</properties>

<dependencies>
Expand Down
1 change: 1 addition & 0 deletions src/main/java/net/tfminecraft/magic/Magic.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public void onEnable() {
getServer().getScheduler().runTask(this, ArtifactDisplayIndex::rebuildFromSaved);
getServer().getScheduler().runTask(this, GearStationStore::load);
GearOrbService.start();
net.tfminecraft.magic.gear.ownership.GearOwnershipLifecycle.start(this);
if (ItemScanService.get() != null) {
ItemScanService.get().subscribe(attuneScanHandler);
}
Expand Down
42 changes: 22 additions & 20 deletions src/main/java/net/tfminecraft/magic/gear/GearChargeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ private GearChargeService() {}
private static final class Confirm {
private final String stationKey;
private final long expiresAt;
private net.tfminecraft.magic.gear.ownership.GearIdentity.Token token;
private UUID generation;
private String itemHash;

private Confirm(String stationKey, long expiresAt) {
this.stationKey = stationKey;
Expand All @@ -52,7 +55,8 @@ public static void tryApply(Player player, Location station, ItemStack hand) {
player.sendMessage(Messages.get("gear.charge.blank"));
return;
}
if (!SessionGuard.ready(player)) {
var token = GearStationStore.property().identity().current(player);
if (token.isEmpty()) {
player.sendMessage(Messages.get("gear.charge.no_character"));
return;
}
Expand All @@ -72,14 +76,16 @@ public static void tryApply(Player player, Location station, ItemStack hand) {
}
String stationKey = GearStationStore.key(station);
List<String> over = overResonance(player, charge);
if (!over.isEmpty() && !confirmed(player, stationKey)) {
if (!over.isEmpty() && !confirmed(player, stationKey, occupancy, token.orElseThrow())) {
player.sendMessage(Messages.get("gear.charge.warn"));
for (String line : over) {
player.sendMessage(line);
}
player.sendMessage(Messages.get("gear.charge.warn_again"));
CONFIRMS.put(player.getUniqueId(), new Confirm(
stationKey, System.currentTimeMillis() + GearCache.confirmMillis));
Confirm confirm = new Confirm(stationKey, System.currentTimeMillis() + GearCache.confirmMillis);
confirm.token = token.orElseThrow(); confirm.generation = occupancy.generation();
confirm.itemHash = net.tfminecraft.magic.gear.ownership.GearPropertyStore.hash(net.tfminecraft.magic.gear.ownership.GearItems.bytes(occupancy.getItem()));
CONFIRMS.put(player.getUniqueId(), confirm);
player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 0.8f);
return;
}
Expand All @@ -91,24 +97,20 @@ public static void tryApply(Player player, Location station, ItemStack hand) {
snapshot.put(elementId, fill);
}
}
// The charge is spent up front. What reaches the weapon is decided by the orbs.
occupancy.setOrbSessionActive(true);
if (!GearOrbService.begin(player, station, snapshot, charge.primaryElementId(), charge.getTier())) {
occupancy.setOrbSessionActive(false);
player.sendMessage(Messages.get("gear.orbs.busy"));
return;
if (!net.tfminecraft.magic.gear.ownership.GearEffects.charge(player, token.orElseThrow(), station, hand, snapshot, charge.primaryElementId(), charge.getTier())) {
player.sendMessage(Messages.get("gear.orbs.busy")); return;
}
consumeOne(player, hand);
player.sendMessage(Messages.get("gear.charge.applied"));
player.playSound(player.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, 1f, 1.2f);
}

private static boolean confirmed(Player player, String stationKey) {
private static boolean confirmed(Player player, String stationKey, GearStationStore.Occupancy occupancy, net.tfminecraft.magic.gear.ownership.GearIdentity.Token token) {
Confirm confirm = CONFIRMS.get(player.getUniqueId());
if (confirm == null || System.currentTimeMillis() > confirm.expiresAt) {
return false;
}
return confirm.stationKey.equals(stationKey);
return confirm.stationKey.equals(stationKey) && token.equals(confirm.token) && java.util.Objects.equals(occupancy.generation(), confirm.generation)
&& net.tfminecraft.magic.gear.ownership.GearPropertyStore.hash(net.tfminecraft.magic.gear.ownership.GearItems.bytes(occupancy.getItem())).equals(confirm.itemHash);
}

private static List<String> overResonance(Player player, Charge charge) {
Expand Down Expand Up @@ -138,12 +140,12 @@ private static List<String> overResonance(Player player, Charge charge) {
return lines;
}

private static void consumeOne(Player player, ItemStack hand) {
if (hand.getAmount() <= 1) {
player.getInventory().setItemInMainHand(null);
} else {
hand.setAmount(hand.getAmount() - 1);
}
player.updateInventory();
public static void clear(Player player, net.tfminecraft.magic.gear.ownership.GearIdentity.Token outgoing) {
Confirm confirm = CONFIRMS.get(player.getUniqueId());
if (confirm != null && outgoing.equals(confirm.token)) clear(player);
}
public static void clearStale(Player player) {
Confirm confirm = CONFIRMS.get(player.getUniqueId());
if (confirm != null && !GearStationStore.property().identity().current(confirm.token, player)) clear(player);
}
}
18 changes: 18 additions & 0 deletions src/main/java/net/tfminecraft/magic/gear/GearCosts.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ public static void take(Player player, Collection<PartDef> parts) {
player.updateInventory();
}

/** Plans removal on clones; a deposit checks the captured frame again before commit. */
public static ItemStack[] plan(Player player, Collection<PartDef> parts) {
ItemStack[] after = net.tfminecraft.magic.gear.ownership.GearEffects.cloneContents(player.getInventory().getContents());
if (bypasses(player)) return after;
for (var cost : total(parts).entrySet()) {
int remaining = cost.getValue();
for (int i = 0; i < after.length && remaining > 0; i++) {
ItemStack item = after[i]; if (item == null) continue;
try { if (!TLibs.getItemAPI().getChecker().checkItemWithPath(item, cost.getKey())) continue; }
catch (RuntimeException ex) { continue; }
int remove = Math.min(remaining, item.getAmount()); remaining -= remove;
if (remove == item.getAmount()) after[i] = null; else item.setAmount(item.getAmount() - remove);
}
if (remaining > 0) return null;
}
return after;
}

public static void refund(Player player, Map<String, Integer> costs, Location drop) {
if (player == null || costs == null || costs.isEmpty()) {
return;
Expand Down
56 changes: 20 additions & 36 deletions src/main/java/net/tfminecraft/magic/gear/GearStationListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,12 @@ public void onFurnitureBroken(FurnitureBreakEvent event) {
if (station == null) {
return;
}
GearStationStore.Occupancy captured = GearStationStore.get(station);
GearOrbService.abort(station);
ItemStack weapon = GearStationStore.takeForAbort(station);
if (weapon != null) {
station.getWorld().dropItem(station.clone().add(0.5, 1.0, 0.5), weapon);
GearStationStore.acknowledgeRelease(station, captured.generation());
Magic.plugin.getLogger().warning("[Magic] Gear station at " + GearStationStore.key(station)
+ " lost its furniture. Dropped the weapon and removed its display.");
}
Expand Down Expand Up @@ -142,6 +144,7 @@ public void onInteract(PlayerInteractEvent event) {
}
event.setCancelled(true);
Player player = event.getPlayer();
if (GearStationStore.property().identity().current(player).isEmpty()) return;
Location location = block.getLocation();
ItemStack hand = player.getInventory().getItemInMainHand();
boolean emptyHand = hand.getType().isAir();
Expand Down Expand Up @@ -190,13 +193,14 @@ public void onInteract(PlayerInteractEvent event) {
private static void rest(Player player, Location location, ItemStack hand) {
ItemStack placed = hand.clone();
placed.setAmount(1);
if (hand.getAmount() <= 1) {
player.getInventory().setItemInMainHand(null);
} else {
hand.setAmount(hand.getAmount() - 1);
}
player.updateInventory();
GearStationStore.rest(location, placed, player.getUniqueId());
var token = GearStationStore.property().identity().current(player);
if (token.isEmpty()) return;
List<String> before = net.tfminecraft.magic.gear.ownership.GearEffects.frame(player.getInventory().getContents());
ItemStack[] after = net.tfminecraft.magic.gear.ownership.GearEffects.cloneContents(player.getInventory().getContents());
int slot = player.getInventory().getHeldItemSlot();
if (after[slot] == null || !java.util.Arrays.equals(net.tfminecraft.magic.gear.ownership.GearItems.bytes(hand), net.tfminecraft.magic.gear.ownership.GearItems.bytes(after[slot]))) return;
if (after[slot].getAmount() <= 1) after[slot] = null; else after[slot].setAmount(after[slot].getAmount() - 1);
if (GearStationStore.deposit(player, token.orElseThrow(), location, placed, Map.of(), true, after, before) == null) return;
player.sendMessage(Messages.get("gear.station.rested"));
player.playSound(location, Sound.BLOCK_ANVIL_USE, 1f, 1.2f);
}
Expand Down Expand Up @@ -258,12 +262,14 @@ private static void eject(Player player, Location location) {
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
if (GearStationStore.property().identity().current(player).isEmpty()) return;
ItemStack item = GearStationStore.eject(location);
if (item == null) {
return;
}
Location drop = location.clone().add(0.5, 1.0, 0.5);
drop.getWorld().dropItem(drop, item);
GearStationStore.acknowledgeRelease(location, occupancy.generation());
player.playSound(location, Sound.ENTITY_ITEM_PICKUP, 1f, 1f);
}

Expand All @@ -272,24 +278,12 @@ private static void eject(Player player, Location location) {
* weapon. An idle rested weapon stays put; an empty hand picks it up.
*/
private void returnRested(Player player, Location location, GearStationStore.Occupancy occupancy) {
UUID owner = occupancy.getOwner() != null ? occupancy.getOwner() : GearOrbService.sessionOwner(location);
if (owner != null && !owner.equals(player.getUniqueId()) && !player.hasPermission("magic.admin")) {
player.sendMessage(Messages.get("gear.abort.not_yours"));
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
GearOrbService.abort(location);
ItemStack weapon = GearStationStore.takeForAbort(location);
if (weapon == null) {
return;
if (!net.tfminecraft.magic.gear.ownership.GearEffects.authorize(player, location, occupancy)) {
player.sendMessage(Messages.get("gear.abort.not_yours")); return;
}
if (!net.tfminecraft.magic.gear.ownership.GearEffects.abortReturn(player, location, occupancy, Map.of())) return;
recentAborts.values().removeIf(at -> System.currentTimeMillis() - at >= ABORT_BREAK_GUARD_MILLIS);
recentAborts.put(GearStationStore.key(location), System.currentTimeMillis());
Location drop = location.clone().add(0.5, 1.0, 0.5);
for (ItemStack leftover : player.getInventory().addItem(weapon).values()) {
drop.getWorld().dropItem(drop, leftover);
}
player.updateInventory();
player.sendMessage(Messages.get("gear.abort.returned"));
player.playSound(location, Sound.ENTITY_ITEM_PICKUP, 1f, 1f);
}
Expand Down Expand Up @@ -329,23 +323,13 @@ private void tryAbort(PlayerInteractEvent event) {
returnRested(player, location, occupancy);
return;
}
UUID owner = occupancy.getOwner() != null ? occupancy.getOwner() : GearOrbService.sessionOwner(location);
if (owner != null && !owner.equals(player.getUniqueId()) && !player.hasPermission("magic.admin")) {
player.sendMessage(Messages.get("gear.abort.not_yours"));
player.playSound(player.getLocation(), Sound.ENTITY_VILLAGER_NO, 1f, 1f);
return;
}
GearOrbService.abort(location);
ItemStack weapon = GearStationStore.takeForAbort(location);
if (weapon == null) {
return;
if (!net.tfminecraft.magic.gear.ownership.GearEffects.authorize(player, location, occupancy)) {
player.sendMessage(Messages.get("gear.abort.not_yours")); return;
}
Map<String, Integer> charged = occupancy.getCharged();
if (!net.tfminecraft.magic.gear.ownership.GearEffects.abortReturn(player, location, occupancy, charged)) return;
recentAborts.values().removeIf(at -> System.currentTimeMillis() - at >= ABORT_BREAK_GUARD_MILLIS);
recentAborts.put(GearStationStore.key(location), System.currentTimeMillis());
Map<String, Integer> charged = occupancy.getCharged() != null
? occupancy.getCharged()
: GearCosts.total(GearProvenance.resolveParts(weapon));
GearCosts.refund(player, charged, location);
player.sendMessage(Messages.get("gear.abort.done"));
player.playSound(location, Sound.BLOCK_ANVIL_LAND, 0.6f, 1.4f);
}
Expand Down
Loading
Loading