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 - 9 a - f A - 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' ) {
0 commit comments