Skip to content

Commit d78c79d

Browse files
committed
支持查看插旗信息
1 parent d1c6762 commit d78c79d

7 files changed

Lines changed: 135 additions & 12 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "MCTeamViewer-map-projection",
3-
"version": "0.4.3",
3+
"version": "0.4.4",
44
"private": true,
55
"type": "module",
66
"scripts": {

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const DEFAULT_CONFIG = {
1212
ROOM_CODE: 'default',
1313
RECONNECT_INTERVAL_MS: 1000,
1414
TARGET_DIMENSION: 'minecraft:overworld',
15+
SHOW_CAPTURE_INFO: true,
1516
SHOW_PLAYER_ICON: true,
1617
SHOW_PLAYER_TEXT: true,
1718
SHOW_HORSE_TEXT: true,

src/core/mapProjection.ts

Lines changed: 123 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
normalizeDimension,
55
normalizeMarkSource,
66
normalizeTeam,
7+
parseMcDisplayName,
78
readNumber,
89
} from '../utils/overlayUtils';
910

@@ -758,14 +759,17 @@ export function createMapProjection(deps: MapProjectionDeps) {
758759

759760
function getMarkerVisualConfig(markerKind: string) {
760761
const isHorse = markerKind === 'horse';
762+
const isArmorStandPair = markerKind === 'armor-stand-pair';
761763
const iconSizeRaw = Number(isHorse ? CONFIG.HORSE_ICON_SIZE : CONFIG.PLAYER_ICON_SIZE);
762764
const textSizeRaw = Number(isHorse ? CONFIG.HORSE_TEXT_SIZE : CONFIG.PLAYER_TEXT_SIZE);
763-
const iconSize = Number.isFinite(iconSizeRaw) ? Math.max(6, Math.min(40, Math.round(iconSizeRaw))) : (isHorse ? 14 : 10);
765+
const iconSizeFallback = isHorse ? 14 : (isArmorStandPair ? 12 : 10);
766+
const iconSizeBase = Number.isFinite(iconSizeRaw) ? Math.max(6, Math.min(40, Math.round(iconSizeRaw))) : iconSizeFallback;
767+
const iconSize = isArmorStandPair ? Math.max(10, iconSizeBase + 2) : iconSizeBase;
764768
const textSize = Number.isFinite(textSizeRaw) ? Math.max(8, Math.min(32, Math.round(textSizeRaw))) : 12;
765769
return {
766770
iconSize,
767771
textSize,
768-
labelOffset: iconSize,
772+
labelOffset: isArmorStandPair ? iconSize + 4 : iconSize,
769773
};
770774
}
771775

@@ -811,6 +815,94 @@ export function createMapProjection(deps: MapProjectionDeps) {
811815
return String(value || '').trim().toLowerCase();
812816
}
813817

818+
function hasHalfBlockFraction(value: number) {
819+
if (!Number.isFinite(value)) return false;
820+
const fraction = value - Math.floor(value);
821+
return Math.abs(fraction - 0.5) < 1e-6;
822+
}
823+
824+
function buildArmorStandPairKey(dimension: string, x: number, z: number) {
825+
return `${dimension}|${x.toFixed(3)}|${z.toFixed(3)}`;
826+
}
827+
828+
function collectRenderableArmorStandPairs(snapshot: any, wantedDim: string) {
829+
const entities = snapshot && typeof snapshot === 'object' ? snapshot.entities : null;
830+
if (!entities || typeof entities !== 'object') return [];
831+
832+
const labelRegex = /^§[0-9a-fA-F]\[[^\]]+\]$/;
833+
const timerRegex = /^\[\d{2}:\d{2}\]$/;
834+
const grouped = new Map<string, {
835+
x: number;
836+
z: number;
837+
labels: Array<{ name: string; color: string | null; y: number }>;
838+
timers: Array<{ name: string; y: number }>;
839+
}>();
840+
841+
for (const rawNode of Object.values(entities)) {
842+
const data = getPlayerDataNode(rawNode);
843+
if (!data) continue;
844+
845+
const entityType = String(data.entityType || '').trim().toLowerCase();
846+
if (entityType !== 'entity.minecraft.armor_stand') continue;
847+
848+
const dim = normalizeDimension(data.dimension);
849+
if (wantedDim && dim !== wantedDim) continue;
850+
851+
const x = readNumber(data.x);
852+
const y = readNumber(data.y);
853+
const z = readNumber(data.z);
854+
if (x === null || z === null) continue;
855+
if (!hasHalfBlockFraction(x) || !hasHalfBlockFraction(z)) continue;
856+
857+
const rawName = String(data.entityName || '').trim();
858+
if (!rawName) continue;
859+
860+
const parsedName = parseMcDisplayName(rawName);
861+
const plainName = String(parsedName.plain || '').trim();
862+
const key = buildArmorStandPairKey(dim, x, z);
863+
let group = grouped.get(key);
864+
if (!group) {
865+
group = { x, z, labels: [], timers: [] };
866+
grouped.set(key, group);
867+
}
868+
869+
if (labelRegex.test(rawName) && /^\[[^\]]+\]$/.test(plainName)) {
870+
group.labels.push({
871+
name: plainName,
872+
color: parsedName.color || null,
873+
y: y === null ? 0 : y,
874+
});
875+
continue;
876+
}
877+
878+
if (timerRegex.test(plainName)) {
879+
group.timers.push({
880+
name: plainName,
881+
y: y === null ? 0 : y,
882+
});
883+
}
884+
}
885+
886+
const markers: Array<{ markerId: string; x: number; z: number; name: string; color: string }> = [];
887+
for (const [key, group] of grouped.entries()) {
888+
if (!group.labels.length || !group.timers.length) continue;
889+
890+
const labelEntry = group.labels.sort((left, right) => right.y - left.y)[0];
891+
const timerEntry = group.timers.sort((left, right) => right.y - left.y)[0];
892+
const color = normalizeColor(labelEntry.color, deps.getConfiguredTeamColor('neutral'));
893+
894+
markers.push({
895+
markerId: `entity:armor-stand-pair:${key}`,
896+
x: group.x,
897+
z: group.z,
898+
name: `[🚩插旗] ${labelEntry.name} ${timerEntry.name}`,
899+
color,
900+
});
901+
}
902+
903+
return markers;
904+
}
905+
814906
function addReporterName(target: Set<string>, value: unknown) {
815907
const normalized = normalizeReporterName(value);
816908
if (normalized) target.add(normalized);
@@ -1012,10 +1104,12 @@ export function createMapProjection(deps: MapProjectionDeps) {
10121104
isReporter = false,
10131105
isRiding = false
10141106
) {
1107+
const isHorse = markerKind === 'horse';
1108+
const isArmorStandPair = markerKind === 'armor-stand-pair';
10151109
const team = mark ? normalizeTeam(mark.team) : 'neutral';
10161110
const color = mark ? normalizeColor(mark.color, deps.getConfiguredTeamColor(team)) : deps.getConfiguredTeamColor(team);
1017-
const showIcon = Boolean(CONFIG.SHOW_PLAYER_ICON);
1018-
const showText = markerKind === 'horse' ? Boolean(CONFIG.SHOW_HORSE_TEXT) : Boolean(CONFIG.SHOW_PLAYER_TEXT);
1111+
const showIcon = isArmorStandPair ? true : Boolean(CONFIG.SHOW_PLAYER_ICON);
1112+
const showText = isHorse ? Boolean(CONFIG.SHOW_HORSE_TEXT) : (isArmorStandPair ? true : Boolean(CONFIG.SHOW_PLAYER_TEXT));
10191113
if (!showIcon && !showText) {
10201114
return '';
10211115
}
@@ -1042,15 +1136,15 @@ export function createMapProjection(deps: MapProjectionDeps) {
10421136
const useReporterHighlight = markerKind === 'player' && isReporter && Boolean(CONFIG.REPORTER_STAR_ICON);
10431137
const iconSize = useReporterHighlight ? Math.max(15, visual.iconSize + 3) : visual.iconSize;
10441138

1045-
const iconContent = markerKind === 'horse' ? '🐎' : '';
1046-
const iconExtraClass = useReporterHighlight ? ' is-reporter-highlight' : '';
1139+
const iconContent = isHorse ? '🐎' : (isArmorStandPair ? '' : '');
1140+
const iconExtraClass = `${isHorse ? ' is-horse' : ''}${useReporterHighlight ? ' is-reporter-highlight' : ''}${isArmorStandPair ? ' is-armor-stand-pair' : ''}`;
10471141
const iconVisualStyle = useReporterHighlight
10481142
? `--reporter-accent-color:${color};background:${color};border:2px solid rgba(255,255,255,0.98);box-shadow:0 0 0 1px rgba(15,23,42,.88),0 0 0 3px ${color}42;`
1049-
: `background:${markerKind === 'horse' ? 'rgba(15,23,42,.92)' : color};box-shadow:0 0 0 2px ${color}55,0 0 0 1px rgba(15,23,42,.95) inset;`;
1143+
: `background:${isHorse ? 'rgba(15,23,42,.92)' : (isArmorStandPair ? 'rgba(15,23,42,.88)' : color)};color:${isArmorStandPair ? color : '#ffffff'};border:${isArmorStandPair ? `1.5px solid ${color}` : '1px solid rgba(255,255,255,0.9)'};box-shadow:0 0 0 2px ${color}55,0 0 0 1px rgba(15,23,42,.95) inset;`;
10501144
const iconHtml = showIcon
1051-
? `<span class="n-icon ${markerKind === 'horse' ? 'is-horse' : ''}${iconExtraClass}" style="${iconVisualStyle}width:${iconSize}px;height:${iconSize}px;line-height:${iconSize}px;font-size:${Math.max(9, Math.round(iconSize * 0.75))}px;">${iconContent}</span>`
1145+
? `<span class="n-icon${iconExtraClass}" style="${iconVisualStyle}width:${iconSize}px;height:${iconSize}px;line-height:${iconSize}px;font-size:${Math.max(9, Math.round(iconSize * 0.75))}px;">${iconContent}</span>`
10521146
: '';
1053-
const teamHtml = CONFIG.SHOW_LABEL_TEAM_INFO && markerKind !== 'horse'
1147+
const teamHtml = CONFIG.SHOW_LABEL_TEAM_INFO && !isHorse && !isArmorStandPair
10541148
? `<span class="n-team">[${safeTeam}]</span>`
10551149
: '';
10561150
const townHtml = CONFIG.SHOW_LABEL_TOWN_INFO && safeTown
@@ -1066,6 +1160,7 @@ export function createMapProjection(deps: MapProjectionDeps) {
10661160

10671161
function getMarkerZIndexOffset(markerKind: string) {
10681162
if (markerKind === 'horse') return -1000;
1163+
if (markerKind === 'armor-stand-pair') return 2000;
10691164
return 1000;
10701165
}
10711166

@@ -1506,6 +1601,25 @@ export function createMapProjection(deps: MapProjectionDeps) {
15061601
}
15071602
}
15081603

1604+
if (Boolean(CONFIG.SHOW_CAPTURE_INFO)) {
1605+
for (const armorStandPair of collectRenderableArmorStandPairs(snapshot, wantedDim)) {
1606+
nextIds.add(armorStandPair.markerId);
1607+
upsertMarker(map, armorStandPair.markerId, {
1608+
x: armorStandPair.x,
1609+
z: armorStandPair.z,
1610+
health: null,
1611+
name: armorStandPair.name,
1612+
mark: {
1613+
team: 'neutral',
1614+
color: armorStandPair.color,
1615+
label: '',
1616+
},
1617+
townInfo: null,
1618+
kind: 'armor-stand-pair',
1619+
});
1620+
}
1621+
}
1622+
15091623
const waypoints = snapshot && typeof snapshot === 'object' ? snapshot.waypoints : null;
15101624
const nextWaypointIds = new Set<string>();
15111625
if (waypoints && typeof waypoints === 'object') {

src/meta.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export const USERSCRIPT_META = {
22
name: '地图玩家投影 - squaremap 版',
33
namespace: 'https://map.nodemc.cc/',
4-
version: '0.4.3',
4+
version: '0.4.4',
55
description: '将远程玩家信息投影到 squaremap 地图',
66
author: 'Prof. Chen',
77
match: [

src/ui/components/OverlaySettingsPanel.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type OverlayUiState = {
5353
ROOM_CODE: string;
5454
RECONNECT_INTERVAL_MS: string;
5555
TARGET_DIMENSION: string;
56+
SHOW_CAPTURE_INFO: boolean;
5657
SHOW_PLAYER_ICON: boolean;
5758
SHOW_PLAYER_TEXT: boolean;
5859
SHOW_HORSE_TEXT: boolean;
@@ -453,13 +454,13 @@ function applyQuickLabel(label: string) {
453454
<div class="n-nav-row">
454455
<div>
455456
<div class="n-subtitle" style="margin: 0">显示设置</div>
456-
<div class="n-section-copy">把尺寸、颜色和特殊显示集中在一页处理</div>
457457
</div>
458458
<button id="nodemc-overlay-back-main-from-display" type="button" class="n-link-btn" @click="setPage('main')">返回概览</button>
459459
</div>
460460

461461
<div class="n-card">
462462
<div class="n-subtitle">显示开关</div>
463+
<label class="n-check"><input v-model="state.form.SHOW_CAPTURE_INFO" @change="triggerAutoApply" id="nodemc-overlay-show-capture-info" type="checkbox" />显示插旗信息</label>
463464
<label class="n-check"><input v-model="state.form.SHOW_PLAYER_ICON" @change="triggerAutoApply" id="nodemc-overlay-show-icon" type="checkbox" />显示玩家图标(图标中心对准玩家坐标)</label>
464465
<label class="n-check"><input v-model="state.form.SHOW_PLAYER_TEXT" @change="triggerAutoApply" id="nodemc-overlay-show-text" type="checkbox" />显示玩家文字信息(仅文字时左端对准玩家坐标)</label>
465466
<label class="n-check"><input v-model="state.form.SHOW_WAYPOINT_ICON" @change="triggerAutoApply" id="nodemc-overlay-show-waypoint-icon" type="checkbox" />显示报点图标(图标中心对准报点坐标)</label>

src/ui/settingsUi.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type OverlayFormState = {
4848
ROOM_CODE: string;
4949
RECONNECT_INTERVAL_MS: string;
5050
TARGET_DIMENSION: string;
51+
SHOW_CAPTURE_INFO: boolean;
5152
SHOW_PLAYER_ICON: boolean;
5253
SHOW_PLAYER_TEXT: boolean;
5354
SHOW_HORSE_TEXT: boolean;
@@ -88,6 +89,7 @@ function createDefaultFormState(): OverlayFormState {
8889
ROOM_CODE: '',
8990
RECONNECT_INTERVAL_MS: '1000',
9091
TARGET_DIMENSION: 'minecraft:overworld',
92+
SHOW_CAPTURE_INFO: true,
9193
SHOW_PLAYER_ICON: true,
9294
SHOW_PLAYER_TEXT: true,
9395
SHOW_HORSE_TEXT: true,
@@ -446,6 +448,7 @@ export function createSettingsUi(deps: SettingsUiDeps) {
446448
state.form.TARGET_DIMENSION = String(config.TARGET_DIMENSION ?? 'minecraft:overworld');
447449
state.overview.roomCode = state.form.ROOM_CODE || 'default';
448450
state.overview.targetDimension = state.form.TARGET_DIMENSION || 'minecraft:overworld';
451+
state.form.SHOW_CAPTURE_INFO = Boolean(config.SHOW_CAPTURE_INFO);
449452
state.form.SHOW_PLAYER_ICON = Boolean(config.SHOW_PLAYER_ICON);
450453
state.form.SHOW_PLAYER_TEXT = Boolean(config.SHOW_PLAYER_TEXT);
451454
state.form.SHOW_HORSE_TEXT = Boolean(config.SHOW_HORSE_TEXT);
@@ -491,6 +494,7 @@ export function createSettingsUi(deps: SettingsUiDeps) {
491494
ROOM_CODE: state.form.ROOM_CODE || config.ROOM_CODE,
492495
RECONNECT_INTERVAL_MS: state.form.RECONNECT_INTERVAL_MS || config.RECONNECT_INTERVAL_MS,
493496
TARGET_DIMENSION: state.form.TARGET_DIMENSION || config.TARGET_DIMENSION,
497+
SHOW_CAPTURE_INFO: state.form.SHOW_CAPTURE_INFO,
494498
SHOW_PLAYER_ICON: state.form.SHOW_PLAYER_ICON,
495499
SHOW_PLAYER_TEXT: state.form.SHOW_PLAYER_TEXT,
496500
SHOW_HORSE_TEXT: state.form.SHOW_HORSE_TEXT,

src/utils/overlayUtils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ export function sanitizeConfig(candidate: Record<string, unknown> | null | undef
124124
next.SHOW_PLAYER_ICON = candidate.SHOW_PLAYER_ICON === undefined
125125
? DEFAULT_CONFIG.SHOW_PLAYER_ICON
126126
: Boolean(candidate.SHOW_PLAYER_ICON);
127+
next.SHOW_CAPTURE_INFO = candidate.SHOW_CAPTURE_INFO === undefined
128+
? DEFAULT_CONFIG.SHOW_CAPTURE_INFO
129+
: Boolean(candidate.SHOW_CAPTURE_INFO);
127130
next.SHOW_PLAYER_TEXT = candidate.SHOW_PLAYER_TEXT === undefined
128131
? DEFAULT_CONFIG.SHOW_PLAYER_TEXT
129132
: Boolean(candidate.SHOW_PLAYER_TEXT);

0 commit comments

Comments
 (0)