Skip to content

Commit 8274e02

Browse files
cryptobenchclaude
andcommitted
Add text rendering for claim owner and trusted players on map
- Add BitmapFont class with 5x7 pixel font for crisp text rendering - Render owner name (white) and trusted players (yellow) on map tiles - Text has black outline for visibility against colored claim overlays - Add helper methods in EasyClaimsAccess for owner/trusted names Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e46d918 commit 8274e02

3 files changed

Lines changed: 293 additions & 0 deletions

File tree

src/main/java/com/easyclaims/EasyClaimsAccess.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package com.easyclaims;
22

33
import com.easyclaims.data.ClaimStorage;
4+
import com.easyclaims.data.PlayerClaims;
5+
import com.easyclaims.data.TrustedPlayer;
46

7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Map;
510
import java.util.UUID;
611

712
/**
@@ -47,4 +52,43 @@ public static String getPlayerName(UUID playerId) {
4752
}
4853
return claimStorage.getPlayerName(playerId);
4954
}
55+
56+
/**
57+
* Gets the owner name for a claimed chunk.
58+
*/
59+
public static String getOwnerName(String worldName, int chunkX, int chunkZ) {
60+
UUID owner = getClaimOwner(worldName, chunkX, chunkZ);
61+
if (owner == null) {
62+
return null;
63+
}
64+
return getPlayerName(owner);
65+
}
66+
67+
/**
68+
* Gets the list of trusted player names for a claimed chunk.
69+
* Returns an empty list if unclaimed or no trusted players.
70+
*/
71+
public static List<String> getTrustedPlayerNames(String worldName, int chunkX, int chunkZ) {
72+
List<String> names = new ArrayList<>();
73+
if (claimStorage == null) {
74+
return names;
75+
}
76+
77+
UUID owner = getClaimOwner(worldName, chunkX, chunkZ);
78+
if (owner == null) {
79+
return names;
80+
}
81+
82+
PlayerClaims playerClaims = claimStorage.getPlayerClaims(owner);
83+
if (playerClaims == null) {
84+
return names;
85+
}
86+
87+
Map<UUID, TrustedPlayer> trustedMap = playerClaims.getTrustedPlayersMap();
88+
for (TrustedPlayer trusted : trustedMap.values()) {
89+
names.add(trusted.getName());
90+
}
91+
92+
return names;
93+
}
5094
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
package com.easyclaims.map;
2+
3+
/**
4+
* 5x7 bitmap font for rendering text on map tiles.
5+
* Each character is stored as an array of 7 rows, where each row is a 5-bit bitmask.
6+
*/
7+
public class BitmapFont {
8+
9+
public static final int CHAR_WIDTH = 5;
10+
public static final int CHAR_HEIGHT = 7;
11+
public static final int CHAR_SPACING = 1;
12+
13+
// Font data: 5x7 pixels per character
14+
private static final int[][] GLYPHS = new int[128][];
15+
16+
static {
17+
// Letters A-Z (5x7)
18+
GLYPHS['A'] = new int[]{0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001};
19+
GLYPHS['B'] = new int[]{0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110};
20+
GLYPHS['C'] = new int[]{0b01110, 0b10001, 0b10000, 0b10000, 0b10000, 0b10001, 0b01110};
21+
GLYPHS['D'] = new int[]{0b11110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11110};
22+
GLYPHS['E'] = new int[]{0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111};
23+
GLYPHS['F'] = new int[]{0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000};
24+
GLYPHS['G'] = new int[]{0b01110, 0b10001, 0b10000, 0b10111, 0b10001, 0b10001, 0b01110};
25+
GLYPHS['H'] = new int[]{0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001};
26+
GLYPHS['I'] = new int[]{0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b11111};
27+
GLYPHS['J'] = new int[]{0b00111, 0b00010, 0b00010, 0b00010, 0b00010, 0b10010, 0b01100};
28+
GLYPHS['K'] = new int[]{0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001};
29+
GLYPHS['L'] = new int[]{0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111};
30+
GLYPHS['M'] = new int[]{0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001};
31+
GLYPHS['N'] = new int[]{0b10001, 0b11001, 0b10101, 0b10101, 0b10011, 0b10001, 0b10001};
32+
GLYPHS['O'] = new int[]{0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110};
33+
GLYPHS['P'] = new int[]{0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000};
34+
GLYPHS['Q'] = new int[]{0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101};
35+
GLYPHS['R'] = new int[]{0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001};
36+
GLYPHS['S'] = new int[]{0b01110, 0b10001, 0b10000, 0b01110, 0b00001, 0b10001, 0b01110};
37+
GLYPHS['T'] = new int[]{0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100};
38+
GLYPHS['U'] = new int[]{0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110};
39+
GLYPHS['V'] = new int[]{0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100};
40+
GLYPHS['W'] = new int[]{0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b11011, 0b10001};
41+
GLYPHS['X'] = new int[]{0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001};
42+
GLYPHS['Y'] = new int[]{0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100};
43+
GLYPHS['Z'] = new int[]{0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111};
44+
45+
// Lowercase maps to uppercase
46+
for (char c = 'a'; c <= 'z'; c++) {
47+
GLYPHS[c] = GLYPHS[Character.toUpperCase(c)];
48+
}
49+
50+
// Numbers 0-9
51+
GLYPHS['0'] = new int[]{0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110};
52+
GLYPHS['1'] = new int[]{0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110};
53+
GLYPHS['2'] = new int[]{0b01110, 0b10001, 0b00001, 0b00110, 0b01000, 0b10000, 0b11111};
54+
GLYPHS['3'] = new int[]{0b01110, 0b10001, 0b00001, 0b00110, 0b00001, 0b10001, 0b01110};
55+
GLYPHS['4'] = new int[]{0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010};
56+
GLYPHS['5'] = new int[]{0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110};
57+
GLYPHS['6'] = new int[]{0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110};
58+
GLYPHS['7'] = new int[]{0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000};
59+
GLYPHS['8'] = new int[]{0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110};
60+
GLYPHS['9'] = new int[]{0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100};
61+
62+
// Special characters
63+
GLYPHS[' '] = new int[]{0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000};
64+
GLYPHS['.'] = new int[]{0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b01100, 0b01100};
65+
GLYPHS[','] = new int[]{0b00000, 0b00000, 0b00000, 0b00000, 0b00100, 0b00100, 0b01000};
66+
GLYPHS[':'] = new int[]{0b00000, 0b01100, 0b01100, 0b00000, 0b01100, 0b01100, 0b00000};
67+
GLYPHS['-'] = new int[]{0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000};
68+
GLYPHS['_'] = new int[]{0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111};
69+
GLYPHS['+'] = new int[]{0b00000, 0b00100, 0b00100, 0b11111, 0b00100, 0b00100, 0b00000};
70+
GLYPHS['\''] = new int[]{0b00100, 0b00100, 0b01000, 0b00000, 0b00000, 0b00000, 0b00000};
71+
GLYPHS['!'] = new int[]{0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000, 0b00100};
72+
GLYPHS['?'] = new int[]{0b01110, 0b10001, 0b00010, 0b00100, 0b00100, 0b00000, 0b00100};
73+
}
74+
75+
/**
76+
* Calculate the width in pixels needed to render a string.
77+
*/
78+
public static int getTextWidth(String text) {
79+
if (text == null || text.isEmpty()) return 0;
80+
return text.length() * CHAR_WIDTH + (text.length() - 1) * CHAR_SPACING;
81+
}
82+
83+
/**
84+
* Draw text onto an image data array.
85+
*/
86+
public static void drawText(int[] imageData, int imageWidth, int imageHeight,
87+
String text, int startX, int startY, int color) {
88+
if (text == null) return;
89+
90+
int x = startX;
91+
for (int i = 0; i < text.length(); i++) {
92+
char c = text.charAt(i);
93+
drawChar(imageData, imageWidth, imageHeight, c, x, startY, color);
94+
x += CHAR_WIDTH + CHAR_SPACING;
95+
}
96+
}
97+
98+
/**
99+
* Draw text centered horizontally within the image.
100+
*/
101+
public static void drawTextCentered(int[] imageData, int imageWidth, int imageHeight,
102+
String text, int centerY, int color) {
103+
int textWidth = getTextWidth(text);
104+
int startX = (imageWidth - textWidth) / 2;
105+
drawText(imageData, imageWidth, imageHeight, text, startX, centerY, color);
106+
}
107+
108+
/**
109+
* Draw text with an outline for better visibility.
110+
*/
111+
public static void drawTextWithOutline(int[] imageData, int imageWidth, int imageHeight,
112+
String text, int startX, int startY,
113+
int textColor, int outlineColor) {
114+
// Draw outline in all 8 directions
115+
for (int dx = -1; dx <= 1; dx++) {
116+
for (int dy = -1; dy <= 1; dy++) {
117+
if (dx != 0 || dy != 0) {
118+
drawText(imageData, imageWidth, imageHeight, text, startX + dx, startY + dy, outlineColor);
119+
}
120+
}
121+
}
122+
// Draw main text on top
123+
drawText(imageData, imageWidth, imageHeight, text, startX, startY, textColor);
124+
}
125+
126+
/**
127+
* Draw text with a shadow/outline for better visibility.
128+
*/
129+
public static void drawTextWithShadow(int[] imageData, int imageWidth, int imageHeight,
130+
String text, int startX, int startY,
131+
int textColor, int shadowColor) {
132+
// Draw shadow offset by 1 pixel
133+
drawText(imageData, imageWidth, imageHeight, text, startX + 1, startY + 1, shadowColor);
134+
// Draw main text on top
135+
drawText(imageData, imageWidth, imageHeight, text, startX, startY, textColor);
136+
}
137+
138+
/**
139+
* Draw text centered with outline.
140+
*/
141+
public static void drawTextCenteredWithOutline(int[] imageData, int imageWidth, int imageHeight,
142+
String text, int centerY,
143+
int textColor, int outlineColor) {
144+
int textWidth = getTextWidth(text);
145+
int startX = (imageWidth - textWidth) / 2;
146+
drawTextWithOutline(imageData, imageWidth, imageHeight, text, startX, centerY, textColor, outlineColor);
147+
}
148+
149+
/**
150+
* Draw text centered with shadow.
151+
*/
152+
public static void drawTextCenteredWithShadow(int[] imageData, int imageWidth, int imageHeight,
153+
String text, int centerY,
154+
int textColor, int shadowColor) {
155+
int textWidth = getTextWidth(text);
156+
int startX = (imageWidth - textWidth) / 2;
157+
drawTextWithShadow(imageData, imageWidth, imageHeight, text, startX, centerY, textColor, shadowColor);
158+
}
159+
160+
/**
161+
* Draw a single character.
162+
*/
163+
private static void drawChar(int[] imageData, int imageWidth, int imageHeight,
164+
char c, int x, int y, int color) {
165+
int[] glyph = (c < GLYPHS.length) ? GLYPHS[c] : null;
166+
if (glyph == null) {
167+
glyph = GLYPHS[' ']; // Default to space for unknown chars
168+
}
169+
170+
for (int row = 0; row < CHAR_HEIGHT; row++) {
171+
int rowBits = glyph[row];
172+
for (int col = 0; col < CHAR_WIDTH; col++) {
173+
// Check if this pixel should be drawn
174+
boolean pixelOn = (rowBits & (1 << (CHAR_WIDTH - 1 - col))) != 0;
175+
if (pixelOn) {
176+
int px = x + col;
177+
int py = y + row;
178+
if (px >= 0 && px < imageWidth && py >= 0 && py < imageHeight) {
179+
imageData[py * imageWidth + px] = color;
180+
}
181+
}
182+
}
183+
}
184+
}
185+
186+
/**
187+
* Pack RGBA values into an int.
188+
*/
189+
public static int packColor(int r, int g, int b, int a) {
190+
return (r & 255) << 24 | (g & 255) << 16 | (b & 255) << 8 | (a & 255);
191+
}
192+
193+
/**
194+
* Common colors for map text.
195+
*/
196+
public static final int WHITE = packColor(255, 255, 255, 255);
197+
public static final int BLACK = packColor(0, 0, 0, 255);
198+
public static final int SHADOW = packColor(30, 30, 30, 255);
199+
public static final int YELLOW = packColor(255, 255, 100, 255);
200+
public static final int GREEN = packColor(100, 255, 100, 255);
201+
public static final int RED = packColor(255, 100, 100, 255);
202+
}

src/main/java/com/easyclaims/map/ClaimImageBuilder.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import javax.annotation.Nonnull;
1717
import javax.annotation.Nullable;
1818
import java.awt.Color;
19+
import java.util.List;
1920
import java.util.Objects;
2021
import java.util.UUID;
2122
import java.util.concurrent.CompletableFuture;
@@ -356,9 +357,55 @@ private ClaimImageBuilder generateImageAsync() {
356357
}
357358
}
358359

360+
// Draw owner name and trusted players text on claimed chunks
361+
if (claimOwner != null) {
362+
drawClaimText(worldName, chunkX, chunkZ);
363+
}
364+
359365
return this;
360366
}
361367

368+
/**
369+
* Draws owner name and trusted player names on the map tile.
370+
* Text is centered and may extend beyond tile boundaries.
371+
*/
372+
private void drawClaimText(String worldName, int chunkX, int chunkZ) {
373+
String ownerName = EasyClaimsAccess.getOwnerName(worldName, chunkX, chunkZ);
374+
List<String> trustedNames = EasyClaimsAccess.getTrustedPlayerNames(worldName, chunkX, chunkZ);
375+
376+
if (ownerName == null) {
377+
return;
378+
}
379+
380+
// Calculate vertical positioning
381+
int lineHeight = BitmapFont.CHAR_HEIGHT + 2; // 7 + 2 = 9 pixels per line
382+
int totalLines = 1 + Math.min(trustedNames.size(), 2); // Owner + up to 2 trusted
383+
int startY = (this.image.height - (totalLines * lineHeight)) / 2;
384+
385+
// Draw owner name (white text with black outline for crisp visibility)
386+
BitmapFont.drawTextCenteredWithOutline(
387+
this.image.data, this.image.width, this.image.height,
388+
ownerName, startY,
389+
BitmapFont.WHITE, BitmapFont.BLACK
390+
);
391+
392+
// Draw trusted players (yellow)
393+
int trustedY = startY + lineHeight;
394+
int trustedCount = 0;
395+
for (String trustedName : trustedNames) {
396+
if (trustedCount >= 2) break;
397+
398+
BitmapFont.drawTextCenteredWithOutline(
399+
this.image.data, this.image.width, this.image.height,
400+
trustedName, trustedY,
401+
BitmapFont.YELLOW, BitmapFont.BLACK
402+
);
403+
404+
trustedY += lineHeight;
405+
trustedCount++;
406+
}
407+
}
408+
362409
private static float shadeFromHeights(int blockPixelX, int blockPixelZ, int blockPixelWidth, int blockPixelHeight,
363410
short height, short north, short south, short west, short east,
364411
short northWest, short northEast, short southWest, short southEast) {

0 commit comments

Comments
 (0)