Skip to content

Commit cd2e380

Browse files
committed
Raise item labels and add GitHub build
1 parent 46928ae commit cd2e380

5 files changed

Lines changed: 119 additions & 7 deletions

File tree

.github/workflows/build.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
build:
15+
name: Java 25 build
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 20
18+
19+
steps:
20+
- name: Check out repository
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Java 25
24+
uses: actions/setup-java@v4
25+
with:
26+
distribution: temurin
27+
java-version: "25"
28+
29+
- name: Set up Gradle
30+
uses: gradle/actions/setup-gradle@v4
31+
32+
- name: Run checks and build production jar
33+
run: ./gradlew clean check shadowJar --no-daemon --no-build-cache --rerun-tasks
34+
35+
- name: Upload production jar
36+
if: success()
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: InteractionVisualizer-${{ github.sha }}
40+
path: build/libs/InteractionVisualizer-*.jar
41+
if-no-files-found: error
42+
retention-days: 14

common/src/main/java/com/loohp/interactionvisualizer/entities/DroppedItemDisplay.java

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
3333
import org.bukkit.Bukkit;
3434
import org.bukkit.Color;
35+
import org.bukkit.Location;
3536
import org.bukkit.Material;
3637
import org.bukkit.NamespacedKey;
3738
import org.bukkit.World;
@@ -51,6 +52,9 @@
5152
import org.bukkit.inventory.meta.Damageable;
5253
import org.bukkit.inventory.meta.ItemMeta;
5354
import org.bukkit.persistence.PersistentDataType;
55+
import org.bukkit.util.Transformation;
56+
import org.joml.Quaternionf;
57+
import org.joml.Vector3f;
5458

5559
import java.util.HashMap;
5660
import java.util.HashSet;
@@ -79,6 +83,7 @@ public final class DroppedItemDisplay extends VisualizerRunnableDisplay implemen
7983
private String mediumColor = "";
8084
private String lowColor = "";
8185
private int cramp = 6;
86+
private double labelYOffset = 0.8D;
8287
private int updateRate = 20;
8388
private int ticksUntilUpdate;
8489
private int despawnTicks = 6000;
@@ -98,6 +103,9 @@ public void onReload(InteractionVisualizerReloadEvent event) {
98103
mediumColor = configString("Entities.Item.Options.Color.Medium");
99104
lowColor = configString("Entities.Item.Options.Color.Low");
100105
cramp = InteractionVisualizer.plugin.getConfiguration().getInt("Entities.Item.Options.Cramping");
106+
double configuredLabelYOffset = InteractionVisualizer.plugin.getConfiguration()
107+
.getDouble("Entities.Item.Options.LabelYOffset");
108+
labelYOffset = Double.isFinite(configuredLabelYOffset) ? configuredLabelYOffset : 0.8D;
101109
updateRate = Math.max(1, InteractionVisualizer.plugin.getConfiguration().getInt("Entities.Item.Options.UpdateRate"));
102110
int configuredDespawnTicks = InteractionVisualizer.plugin.getConfiguration().getInt("Entities.Item.Options.DespawnTicks");
103111
despawnTicks = configuredDespawnTicks > 0 ? configuredDespawnTicks : 6000;
@@ -229,14 +237,10 @@ private void update(Item item) {
229237
label.text(text);
230238
}
231239
boolean mounted = item.equals(label.getVehicle()) || item.addPassenger(label);
232-
if (created) {
233-
// Mount before revealing the label so Paper can pair both entities
234-
// with their passenger relationship in the initial tracking bundle.
235-
showToEligibleViewers(label);
236-
}
237240
if (mounted) {
238241
// A mounted display follows the item on every client render frame.
239242
// Text refreshes stay low-frequency without sampling item positions.
243+
setLabelVerticalTranslation(label, mountedLabelTranslation(labelYOffset, item.getHeight()));
240244
if (label.getInterpolationDuration() != 0) {
241245
label.setInterpolationDuration(0);
242246
}
@@ -246,16 +250,22 @@ private void update(Item item) {
246250
} else {
247251
// Preserve a safe fallback if another plugin cancels the mount or
248252
// changes either entity during the update.
253+
setLabelVerticalTranslation(label, 0.0F);
249254
int transitionTicks = Math.min(59, updateRate);
250255
if (label.getTeleportDuration() != transitionTicks) {
251256
label.setTeleportDuration(transitionTicks);
252257
}
253-
label.teleport(item.getLocation().add(0.0, item.getHeight() * 1.7, 0.0));
258+
label.teleport(labelLocation(item));
259+
}
260+
if (created) {
261+
// Mount and configure the final render height before revealing the
262+
// label so the first tracking bundle cannot flash at item height.
263+
showToEligibleViewers(label);
254264
}
255265
}
256266

257267
private TextDisplay spawnLabel(Item item) {
258-
return item.getWorld().spawn(item.getLocation().add(0.0, item.getHeight() * 1.7, 0.0),
268+
return item.getWorld().spawn(labelLocation(item),
259269
TextDisplay.class, display -> {
260270
display.setPersistent(false);
261271
display.setVisibleByDefault(false);
@@ -277,6 +287,27 @@ private TextDisplay spawnLabel(Item item) {
277287
});
278288
}
279289

290+
private Location labelLocation(Item item) {
291+
return item.getLocation().add(0.0, labelYOffset, 0.0);
292+
}
293+
294+
static float mountedLabelTranslation(double yOffset, double itemHeight) {
295+
return (float) (yOffset - itemHeight);
296+
}
297+
298+
private static void setLabelVerticalTranslation(TextDisplay label, float targetY) {
299+
Transformation current = label.getTransformation();
300+
Vector3f translation = current.getTranslation();
301+
if (Math.abs(translation.y - targetY) <= 1.0E-4F) {
302+
return;
303+
}
304+
label.setTransformation(new Transformation(
305+
new Vector3f(translation.x, targetY, translation.z),
306+
new Quaternionf(current.getLeftRotation()),
307+
new Vector3f(current.getScale()),
308+
new Quaternionf(current.getRightRotation())));
309+
}
310+
280311
private void reconcileEligibleViewers() {
281312
Map<UUID, Player> desired = new HashMap<>();
282313
for (Player player : InteractionVisualizerAPI.getPlayerModuleList(Modules.HOLOGRAM, KEY)) {

common/src/main/resources/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ Entities:
273273
RegularFormat: "{Item} &bx{Amount} &6[{Timer}&6]"
274274
SingularFormat: "{Item} &6[{Timer}&6]"
275275
ToolsFormat: "{Item} &6[{Durability}&6]"
276+
#Vertical distance from the dropped item entity origin to its text label
277+
LabelYOffset: 0.8
276278
#Hide if there is more than this defined amount of item entities in one block
277279
Cramping: 6
278280
#How often the item display updates

common/src/test/java/com/loohp/interactionvisualizer/config/SparrowConfigurationTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,17 @@ void preservesDotsInImmediateSectionKeys() throws Exception {
105105
Map.of("myplugin:life.steal", "Life Steal", "myplugin:frost", "Frost"),
106106
configuration.getConfigurationSection("enchantments").getValues(false));
107107
}
108+
109+
@Test
110+
void defaultConfigKeepsDroppedItemLabelsAboveTheItem() throws Exception {
111+
Path file = temporaryDirectory.resolve("plugin-config.yml");
112+
Files.writeString(file, "", StandardCharsets.UTF_8);
113+
114+
SparrowConfiguration configuration = new SparrowConfiguration(
115+
file.toFile(),
116+
SparrowConfigurationTest.class.getClassLoader().getResourceAsStream("config.yml"),
117+
false);
118+
119+
assertEquals(0.8D, configuration.getDouble("Entities.Item.Options.LabelYOffset"));
120+
}
108121
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* This file is part of InteractionVisualizer.
3+
*
4+
* Copyright (C) 2026. Contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*/
11+
12+
package com.loohp.interactionvisualizer.entities;
13+
14+
import org.junit.jupiter.api.Test;
15+
16+
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
18+
class DroppedItemDisplayPositionTest {
19+
20+
@Test
21+
void compensatesForTheItemsPassengerAttachmentHeight() {
22+
assertEquals(0.55F, DroppedItemDisplay.mountedLabelTranslation(0.8D, 0.25D), 1.0E-6F);
23+
}
24+
}

0 commit comments

Comments
 (0)