@@ -354,6 +354,7 @@ function activitiesSurface(e: ReactLike["createElement"], data: SCUMSurfaceData,
return view . activityStatus === "all" || status === view . activityStatus ;
} ) ;
const statuses = unique ( data . events . map ( ( event ) = > textField ( runsByEvent . get ( textField ( event , "id" , "eventId" ) ) , "status" , "state" ) || textField ( event , "status" , "state" ) ) . filter ( Boolean ) ) ;
const tradeEvents = recentRows ( data . tradeEvents , "observedAt" , "occurredAt" , "createdAt" ) ;
const saveEvent = ( ) = > runAction ( view . setAction , "正在保存活动定义…" , async ( ) = > {
const id = view . eventId . trim ( ) ;
const name = view . eventName . trim ( ) ;
@@ -380,7 +381,7 @@ function activitiesSurface(e: ReactLike["createElement"], data: SCUMSurfaceData,
} ) ;
const activityHistory = data . activityEvents . filter ( ( event ) = > Boolean ( textField ( event , "occurredAt" , "createdAt" ) ) && textField ( event , "taskKind" ) . toLowerCase ( ) !== "active-task" ) ;
return e ( "div" , { className : "console-record-list" } ,
statsStrip ( e , [ [ "活动定义" , data . events . length ] , [ "运行记录" , data . eventRuns . length ] , [ "原生赛事轮次 " , data . nativeEventRounds . length ] , [ "任务 " , data . tasks . length ] ] ) ,
statsStrip ( e , [ [ "活动定义" , data . events . length ] , [ "运行记录" , data . eventRuns . length ] , [ "物品列表 " , data . tradeGoods . length ] , [ "交易记录 " , data . tradeEvents . length ] ] ) ,
e ( "div" , { className : "overview-two-col" } ,
e ( "details" , { className : "console-module scum-editor" , open : view.eventEditorOpen , onToggle : ( event : InputEvent ) = > view . setEventEditorOpen ( detailOpen ( event ) ) } , e ( "summary" , null , e ( "strong" , null , "新建或更新活动" ) , e ( "span" , { className : "page-status" } , view . eventId || view . eventName ? "编辑中" : "点击展开" ) ) ,
labeledField ( e , "活动编号" , e ( "input" , { value : view.eventId , "aria-label" : "活动编号" , placeholder : "例如 event_supply_drop" , onChange : ( event : InputEvent ) = > view . setEventId ( inputValue ( event ) ) } ) ) ,
@@ -416,7 +417,7 @@ function activitiesSurface(e: ReactLike["createElement"], data: SCUMSurfaceData,
labeledField ( e , "Z" , e ( "input" , { value : view.produceZ , "aria-label" : "生成 Z" , type : "number" , placeholder : "0" , onChange : ( event : InputEvent ) = > view . setProduceZ ( inputValue ( event ) ) } ) ) ,
e ( "button" , { type : "button" , className : "primary-command" , disabled : ! actions ? . pluginData , onClick : saveProduce } , "保存生成项" ) ) ,
e ( "div" , { className : "console-row-list" } , data . eventProduces . length ? data . eventProduces . map ( ( produce , index ) = > e ( "div" , { key : idOf ( produce , ` produce- ${ index } ` ) , className : "console-row" } ,
e ( "span" , null , ` ${ textField ( produce , "eventId" , "event" ) } / ${ textField ( produce , "tradeGoodsId" , "trade_goods_id" ) } ` ) ,
e ( "span" , null , ` ${ textField ( produce , "eventId" , "event" ) } / ${ tradeGoodsLabel ( data . tradeGoods , textField ( produce , "tradeGoodsId" , "trade_goods_id" ) ) } ` ) ,
e ( "strong" , null , ` ${ numField ( produce , "percent" ) } % × ${ numField ( produce , "value" ) } ` ) ,
e ( "strong" , null , ` R ${ numField ( produce , "r" ) } · ${ coords ( produce ) } ` ) ,
e ( "button" , { type : "button" , className : "icon-command" , onClick : ( ) = > { view . setProduceEditorOpen ( true ) ; view . setProduceEventId ( textField ( produce , "eventId" , "event" ) ) ; view . setProduceId ( textField ( produce , "id" , "produceId" ) ) ; view . setProduceTradeGoodsId ( textField ( produce , "tradeGoodsId" , "trade_goods_id" ) ) ; view . setProducePercent ( numField ( produce , "percent" ) ) ; view . setProduceValue ( numField ( produce , "value" ) ) ; view . setProduceRadius ( numField ( produce , "r" ) ) ; view . setProduceX ( numField ( produce , "x" ) ) ; view . setProduceY ( numField ( produce , "y" ) ) ; view . setProduceZ ( numField ( produce , "z" ) ) ; } } , "编辑" ) ,
@@ -440,6 +441,10 @@ function activitiesSurface(e: ReactLike["createElement"], data: SCUMSurfaceData,
tablePanel ( e , "原生赛事轮次" , data . nativeEventRounds , ( event ) = > [ textField ( event , "eventId" ) || "event" , textField ( event , "state" ) || "unknown" , ` Kills ${ numField ( event , "enemyKills" ) } ` , dateField ( event , "startTime" ) ] ) ,
tablePanel ( e , "Quest / Task" , data . tasks , ( task ) = > [ textField ( task , "taskKind" ) || "task" , textField ( task , "dataAssetPath" ) || "unknown" , textField ( task , "state" ) || "unknown" , textField ( task , "userProfileId" ) || "unknown" ] )
) ,
e ( "div" , { className : "overview-two-col" } ,
itemCatalogPanel ( e , data . tradeGoods ) ,
tablePanel ( e , "最近商人交易" , tradeEvents , ( event ) = > [ tradeGoodsLabel ( data . tradeGoods , textField ( event , "itemCode" , "code" , "tradeGoodsId" ) ) , ` ${ tradeActionLabel ( event ) } × ${ numField ( event , "quantity" , "itemCount" ) } ` , ` 玩家 ${ shortHash ( textField ( event , "steamId" , "playerId" ) ) } ` , ` 价格 ${ numField ( event , "price" , "currencyDelta" ) } ` , dateField ( event , "observedAt" , "occurredAt" , "createdAt" ) ] )
) ,
tablePanel ( e , "最近活动记录" , [ . . . data . eventRuns , . . . activityHistory ] , ( event ) = > [ textField ( event , "eventName" , "type" , "kind" , "activityType" ) || "event" , textField ( event , "subjectName" , "eventId" , "subjectId" , "subject" ) || "unknown" , textField ( event , "status" , "result" , "state" ) || "unknown" , dateField ( event , "startedAt" , "occurredAt" , "createdAt" ) ] )
) ;
}
@@ -472,7 +477,7 @@ function giftsSurface(e: ReactLike["createElement"], data: SCUMSurfaceData, inpu
return "礼包发放命令已进入执行队列。" ;
} ) ;
return e ( "div" , { className : "console-record-list" } ,
statsStrip ( e , [ [ "礼包定义" , data . gifts . length ] , [ "领取/待领" , data . giftClaims . length + data . pendingGifts . length ] , [ "发放记录" , data . giftDeliveries . length ] , [ "原生定时记录 " , data . timedGiftEvents . length ] ] ) ,
statsStrip ( e , [ [ "礼包定义" , data . gifts . length ] , [ "领取/待领" , data . giftClaims . length + data . pendingGifts . length ] , [ "发放记录" , data . giftDeliveries . length ] , [ "物品列表 " , data . tradeGoods . length ] ] ) ,
e ( "div" , { className : "console-row-actions" , role : "tablist" , "aria-label" : "礼包视图" } ,
giftTabButton ( e , view , "definitions" , "礼包定义" ) , giftTabButton ( e , view , "claims" , "领取/待领" ) , giftTabButton ( e , view , "deliveries" , "发放记录" ) , giftTabButton ( e , view , "timed" , "游戏定时记录" )
) ,
@@ -499,6 +504,7 @@ function giftsSurface(e: ReactLike["createElement"], data: SCUMSurfaceData, inpu
) ;
} ) : e ( "p" , { className : "page-status" } , "暂无礼包定义。" ) )
) : null ,
view . giftTab === "definitions" ? itemCatalogPanel ( e , data . tradeGoods ) : null ,
view . giftTab === "claims" ? e ( "div" , { className : "overview-two-col" } ,
resettableGiftPanel ( e , "领取记录" , data . giftClaims , "重置领取" , ( claim ) = > runAction ( view . setAction , "正在重置领取记录…" , async ( ) = > { await resetGiftClaim ( actions ? ? { } , claim ) ; view . refresh ( ) ; return "领取记录已重置。" ; } ) , actions ) ,
resettableGiftPanel ( e , "待领礼包" , data . pendingGifts , "重置待领" , ( pending ) = > runAction ( view . setAction , "正在重置待领记录…" , async ( ) = > { await resetPendingGift ( actions ? ? { } , pending ) ; view . refresh ( ) ; return "待领状态已重置。" ; } ) , actions )
@@ -521,6 +527,7 @@ function giftsSurface(e: ReactLike["createElement"], data: SCUMSurfaceData, inpu
function mapSurface ( e : ReactLike [ "createElement" ] , data : SCUMSurfaceData , input : SCUMPageContext , view : ViewState ) {
const actions = input . workspaceActions ;
const points = collectMapPoints ( data ) ;
const vehicleCatalog = data . tradeGoods . filter ( ( item ) = > isVehicleCatalogItem ( item ) ) ;
const settings = data . mapSettings . find ( ( value ) = > textField ( value , "_recordKey" , "id" ) === "current" ) ? ? data . mapSettings [ 0 ] ;
const bounds = resolveMapBounds ( settings ) ;
const customEnabled = view . mapCustomEnabled ? ? Boolean ( settings && boolField ( settings , "customMapEnabled" ) ) ;
@@ -534,7 +541,7 @@ function mapSurface(e: ReactLike["createElement"], data: SCUMSurfaceData, input:
const trails = visibleTrajectoryPoints ( data . trajectories , view . mapLayers , search ) . sort ( ( left , right ) = > trajectoryOrder ( right ) - trajectoryOrder ( left ) ) . slice ( 0 , 180 ) . reverse ( ) ;
const selectedTrails = selected ? trajectoryRecordsForPoint ( data . trajectories , selected ) . sort ( ( left , right ) = > trajectoryOrder ( right ) - trajectoryOrder ( left ) ) . slice ( 0 , 8 ) : [ ] ;
return e ( "div" , { className : "console-record-list" } ,
statsStrip ( e , [ [ "地图点" , points . length ] , [ "用户" , data . players . length ] , [ "载具" , data . vehicles . length ] , [ "轨迹采样 " , data . trajectories . length ] ] ) ,
statsStrip ( e , [ [ "地图点" , points . length ] , [ "用户" , data . players . length ] , [ "载具" , data . vehicles . length ] , [ "载具目录 " , vehicleCatalog . length ] ] ) ,
e ( "div" , { className : "resource-filter-bar scum-filter-bar" } ,
labeledField ( e , "筛选地图点" , e ( "input" , { value : view.mapSearch , "aria-label" : "筛选地图点" , placeholder : "名称 / 类型 / ID" , onChange : ( event : InputEvent ) = > view . setMapSearch ( inputValue ( event ) ) } ) ) ,
( [ "players" , "vehicles" , "flags" , "regions" , "other" ] as MapLayer [ ] ) . map ( ( layer ) = > e ( "label" , { key : layer , className : "scum-layer-toggle" } , e ( "input" , { type : "checkbox" , checked : view.mapLayers [ layer ] , onChange : ( event : InputEvent ) = > view . setMapLayers ( ( previous ) = > ( { . . . previous , [ layer ] : Boolean ( event . target ? . checked ) } ) ) } ) , layerLabel ( layer ) ) )
@@ -550,7 +557,7 @@ function mapSurface(e: ReactLike["createElement"], data: SCUMSurfaceData, input:
) ,
e ( "div" , { className : "overview-two-col" } ,
e ( "div" , { className : "map-projection-board" , "aria-label" : "SCUM 地图图层" , style : { backgroundImage : ` url( ${ scumMapBackground } ) ` } } , mapGridOverlay ( e ) , trails . map ( ( point , index ) = > e ( "span" , { key : ` trail- ${ index } - ${ idOf ( point , "sample" ) } ` , className : ` map-trajectory-dot map-layer- ${ layerOf ( point ) } ` , title : ` ${ pointTitle ( point ) } ${ dateField ( point , "sampledAt" , "observedAt" ) } ` , style : mapPointStyle ( point , bounds ) } ) ) , visible . map ( ( point , index ) = > { const ride = layerOf ( point ) === "players" ? nearbyVehicle ( point , data . vehicles ) : undefined ; return e ( "button" , { key : idOf ( point , ` point- ${ index } ` ) , type : "button" , className : ` map-projection-dot map-layer- ${ layerOf ( point ) } ${ ride ? " map-projection-dot-riding" : "" } ` , title : ` ${ pointTitle ( point ) } ${ coords ( point ) } ${ ride ? ` · 疑似乘坐 ${ pointTitle ( ride ) } ` : "" } ` , "aria-label" : pointTitle ( point ) , style : mapPointStyle ( point , bounds ) , onClick : ( ) = > view . setSelectedMapPoint ( idOf ( point , ` point- ${ index } ` ) ) } , vehicleIconFor ( point ) ? e ( "img" , { src : vehicleIconFor ( point ) , alt : "" } ) : "" ) ; } ) ) ,
e ( "article" , { className : "console-module" } , e ( "div" , { className : "panel-header" } , e ( "h2" , null , "地图点详情" ) , e ( "span" , { className : "page-status" } , ` ${ visible . length } 个可见点 ` ) ) , selected ? e ( "div" , { className : "console-record" } , e ( "strong" , null , pointTitle ( selected ) ) , e ( "span" , { className : "status-pill status-active" } , layerLabel ( layerOf ( selected ) ) ) , e ( "span" , { className : "provider-id" } , coords ( selected ) ) , e ( "div" , { className : "console-record-meta" } , e ( "span" , null , ` ID ${ textField ( selected , "subjectId" , "id" , "_recordKey" ) || "unknown" } ` ) , e ( "span" , null , ` 来源 ${ textField ( selected , "source" ) || "plugin collection" } ` ) , e ( "span" , null , freshness ( selected ) ) ) , selectedTrails . length ? e ( "div" , { className : "console-row-list" } , selectedTrails . map ( ( row , index ) = > e ( "div" , { key : ` selected-trail- ${ index } ` , className : "console-row" } , e ( "span" , null , dateField ( row , "sampledAt" , "observedAt" ) ) , e ( "strong" , null , coords ( row ) ) , e ( "strong" , null , textField ( row , "source" ) || "run.sqlite" ) ) ) ) : null ) : e ( "p" , { className : "page-status" } , "当前图层和筛选条件下没有真实地图点。" ) )
e ( "article" , { className : "console-module" } , e ( "div" , { className : "panel-header" } , e ( "h2" , null , "地图点详情" ) , e ( "span" , { className : "page-status" } , ` ${ visible . length } 个可见点 ` ) ) , selected ? e ( "div" , { className : "console-record" } , e ( "strong" , null , pointTitle ( selected ) ) , e ( "span" , { className : "status-pill status-active" } , layerLabel ( layerOf ( selected ) ) ) , e ( "span" , { className : "provider-id" } , coords ( selected ) ) , e ( "div" , { className : "console-record-meta" } , e ( "span" , null , ` ID ${ textField ( selected , "subjectId" , "id" , "_recordKey" ) || "unknown" } ` ) , e ( "span" , null , ` 来源 ${ textField ( selected , "source" ) || "plugin collection" } ` ) , e ( "span" , null , freshness ( selected ) ) ) , layerOf ( selected ) === "vehicles" ? e ( "div" , { className : "console-record-meta" } , e ( "span" , null , ` 类型 ${ textField ( selected , "className" , "vehicleClass" , "vehicleType" ) || "unknown" } ` ) , e ( "span" , null , ` 状态 ${ textField ( selected , "status" , "state" , "isFunctional" ) || "unknown" } ` ) , e ( "span" , null , ` 访问 ${ dateField ( selected , "lastAccessTime" , "vehicleObservedAt" , "sampledAt" ) } ` ) ) : null , selectedTrails . length ? e ( "div" , { className : "console-row-list" } , selectedTrails . map ( ( row , index ) = > e ( "div" , { key : ` selected-trail- ${ index } ` , className : "console-row" } , e ( "span" , null , dateField ( row , "sampledAt" , "observedAt" ) ) , e ( "strong" , null , coords ( row ) ) , e ( "strong" , null , textField ( row , "source" ) || "run.sqlite" ) ) ) ) : null ) : e ( "p" , { className : "page-status" } , "当前图层和筛选条件下没有真实地图点。" ) )
)
) ;
}
@@ -561,11 +568,24 @@ function giftTabButton(e: ReactLike["createElement"], view: ViewState, tab: Gift
function statsStrip ( e : ReactLike [ "createElement" ] , items : Array < [ string , number ] > ) { return e ( "div" , { className : "console-stat-strip" } , items . map ( ( [ label , value ] ) = > e ( "span" , { key : label , className : "server-card-stat" } , e ( "span" , null , label ) , e ( "strong" , null , String ( value ) ) ) ) ) ; }
function tablePanel ( e : ReactLike [ "createElement" ] , title : string , rows : RecordMap [ ] , render : ( row : RecordMap ) = > unknown [ ] ) { return e ( "article" , { className : "console-module" } , e ( "div" , { className : "panel-header" } , e ( "h2" , null , title ) , e ( "span" , { className : "page-status" } , ` ${ rows . length } 条 ` ) ) , e ( "div" , { className : "console-row-list" } , rows . length ? rows . map ( ( row , index ) = > e ( "div" , { key : idOf ( row , ` ${ title } - ${ index } ` ) , className : "console-row" } , render ( row ) . map ( ( part , partIndex ) = > partIndex === 0 ? e ( "span" , { key : partIndex } , String ( part ? ? "unknown" ) ) : e ( "strong" , { key : partIndex } , String ( part ? ? "unknown" ) ) ) ) ) : e ( "p" , { className : "page-status" } , "暂无真实记录。" ) ) ) ; }
function resettableGiftPanel ( e : ReactLike [ "createElement" ] , title : string , rows : RecordMap [ ] , actionLabel : string , onReset : ( row : RecordMap ) = > void , actions : SCUMWorkspaceActions | undefined ) { return e ( "article" , { className : "console-module" } , e ( "div" , { className : "panel-header" } , e ( "h2" , null , title ) , e ( "span" , { className : "page-status" } , ` ${ rows . length } 条 ` ) ) , e ( "div" , { className : "console-row-list" } , rows . length ? rows . map ( ( row , index ) = > e ( "div" , { key : idOf ( row , ` ${ title } - ${ index } ` ) , className : "console-row" } , e ( "span" , null , textField ( row , "playerName" , "playerId" , "displayName" , "userProfileId" ) || "unknown" ) , e ( "strong" , null , textField ( row , "giftName" , "giftCode" , "giftType" ) || "unknown" ) , e ( "strong" , null , textField ( row , "status" ) || "unknown" ) , e ( "strong" , null , dateField ( row , "claimedAt" , "receivedAt" , "createdAt" ) ) , e ( "button" , { type : "button" , className : "icon-command" , disabled : ! actions ? . pluginData , onClick : ( ) = > onReset ( row ) } , actionLabel ) ) ) : e ( "p" , { className : "page-status" } , "暂无真实记录。" ) ) ) ; }
function itemCatalogPanel ( e : ReactLike [ "createElement" ] , rows : RecordMap [ ] ) { return tablePanel ( e , "物品列表" , recentRows ( rows , "lastSeenAt" , "updatedAt" , "createdAt" ) , ( item ) = > [ tradeGoodsName ( item ) , tradeGoodsTypeLabel ( item ) , textField ( item , "code" , "itemCode" , "className" ) || "unknown" , dateField ( item , "lastSeenAt" , "updatedAt" , "createdAt" ) ] ) ; }
function tradeGoodsLabel ( rows : RecordMap [ ] , code : string ) : string { const item = tradeGoodsIndex ( rows ) . get ( code ) ? ? tradeGoodsIndex ( rows ) . get ( code . replace ( /^#spawnvehicle\s+/i , "" ) ) ; return item ? ` ${ tradeGoodsName ( item ) } ( ${ code } ) ` : code || "unknown" ; }
function tradeGoodsName ( item : RecordMap ) : string { return textField ( item , "nameCn" , "name_cn" , "name" , "className" ) || textField ( item , "code" , "itemCode" ) || "未命名物品" ; }
function tradeGoodsTypeLabel ( item : RecordMap ) : string { return textField ( item , "typeName" , "type_name" ) || ( textField ( item , "type" ) === "21" || isVehicleCatalogItem ( item ) ? "其他载具" : "未知类型" ) ; }
function tradeActionLabel ( event : RecordMap ) : string { const value = textField ( event , "tradeVerb" , "tradeKind" , "action" ) . toLowerCase ( ) ; return value === "purchased" || value === "purchase" ? "买入" : value === "sold" || value === "sale" ? "卖出" : value || "交易" ; }
function tradeGoodsIndex ( rows : RecordMap [ ] ) : Map < string , RecordMap > { const result = new Map < string , RecordMap > ( ) ; for ( const row of rows ) for ( const key of [ textField ( row , "code" ) , textField ( row , "itemCode" ) , textField ( row , "className" ) ] . filter ( Boolean ) ) result . set ( key , row ) ; return result ; }
function recentRows ( rows : RecordMap [ ] , . . . keys : string [ ] ) : RecordMap [ ] { return [ . . . rows ] . sort ( ( left , right ) = > rowTime ( right , keys ) - rowTime ( left , keys ) ) . slice ( 0 , 24 ) ; }
function rowTime ( row : RecordMap , keys : string [ ] ) : number { for ( const key of keys ) { const stamp = Date . parse ( textField ( row , key ) ) ; if ( ! Number . isNaN ( stamp ) ) return stamp ; } return 0 ; }
function isVehicleCatalogItem ( item : RecordMap ) : boolean { const type = textField ( item , "catalogType" , "kind" , "typeName" , "type_name" , "type" ) . toLowerCase ( ) ; const code = textField ( item , "code" , "spawnCommand" ) . toLowerCase ( ) ; return type . includes ( "vehicle" ) || type . includes ( "载具" ) || textField ( item , "type" ) === "21" || code . startsWith ( "#spawnvehicle" ) ; }
function vehicleCatalogIndex ( rows : RecordMap [ ] ) : Map < string , RecordMap > { const result = new Map < string , RecordMap > ( ) ; for ( const row of rows ) if ( isVehicleCatalogItem ( row ) ) for ( const key of vehicleCatalogKeys ( row ) ) result . set ( key , row ) ; return result ; }
function vehicleCatalogKeys ( row : RecordMap ) : string [ ] { return [ "className" , "vehicleClass" , "entityClass" , "vehicleType" , "name" , "code" , "spawnCommand" ] . map ( ( key ) = > vehicleClassKey ( textField ( row , key ) ) ) . filter ( Boolean ) ; }
function enrichVehicleFromCatalog ( vehicle : RecordMap , catalog : Map < string , RecordMap > ) : RecordMap { const found = vehicleCatalogKeys ( vehicle ) . map ( ( key ) = > catalog . get ( key ) ) . find ( Boolean ) ; return found ? { . . . found , . . . vehicle , imagePath : textField ( vehicle , "imagePath" , "image_path" ) || textField ( found , "imagePath" , "image_path" ) , catalogCode : textField ( found , "code" ) , spawnCommand : textField ( found , "spawnCommand" , "code" ) } : vehicle ; }
export function collectMapPoints ( data : SCUMSurfaceData ) : RecordMap [ ] {
const direct = data . mapPoints . map ( ( point ) = > ( { . . . point , layer : textField ( point , "layer" , "subjectType" , "type" ) || "other" } ) ) ;
const players = data . players . flatMap ( ( player ) = > withPosition ( player , "players" , textField ( player , "displayName" ) , textField ( player , "steamId" , "gamePlayerId" , "id" ) ) ) ;
const vehicles = data . vehicles . flatMap ( ( vehicle ) = > withPosition ( vehicle , "vehicles" , textField ( vehicle , "label" , "name" ) , textField ( vehicle , "vehicleId" , "id" ) ) ) ;
const catalog = vehicleCatalogIndex ( data . tradeGoods ) ;
const vehicles = data . vehicles . flatMap ( ( vehicle ) = > { const enriched = enrichVehicleFromCatalog ( vehicle , catalog ) ; return withPosition ( enriched , "vehicles" , textField ( enriched , "label" , "name" , "className" ) , textField ( enriched , "vehicleId" , "id" ) ) ; } ) ;
const flags = data . flags . flatMap ( ( flag ) = > withPosition ( flag , "flags" , textField ( flag , "name" ) , textField ( flag , "flagId" , "id" ) ) ) ;
const regions = data . mapRegions . flatMap ( ( region ) = > withPosition ( region , "regions" , textField ( region , "name" ) , textField ( region , "id" , "regionId" ) ) ) ;
const uniquePoints = new Map < string , RecordMap > ( ) ;
@@ -605,7 +625,8 @@ function trajectoryIdentity(row: RecordMap): string[] { return [textField(row, "
function trajectoryOrder ( row : RecordMap ) : number { const stamp = Date . parse ( textField ( row , "sampledAt" , "observedAt" , "createdAt" ) ) ; return Number . isNaN ( stamp ) ? 0 : stamp ; }
function nearbyVehicle ( point : RecordMap , vehicles : RecordMap [ ] ) : RecordMap | undefined { if ( ! hasCoordinates ( positionOf ( point ) ) ) return undefined ; let best : { vehicle : RecordMap ; distance : number } | undefined ; for ( const vehicle of vehicles ) { if ( ! hasCoordinates ( positionOf ( vehicle ) ) ) continue ; const distance = distance2D ( positionOf ( point ) ! , positionOf ( vehicle ) ! ) ; if ( distance <= rideDistanceThreshold && ( ! best || distance < best . distance ) ) best = { vehicle , distance } ; } return best ? . vehicle ; }
function distance2D ( left : RecordMap , right : RecordMap ) : number { const dx = Number ( field ( left , "x" , "locationX" ) ) - Number ( field ( right , "x" , "locationX" ) ) ; const dy = Number ( field ( left , "y" , "locationY" ) ) - Number ( field ( right , "y" , "locationY" ) ) ; return Math . sqrt ( dx * dx + dy * dy ) ; }
function vehicleIconFor ( point : RecordMap ) : string { return vehicleIconByClass [ normalizedVehicleClass ( textField ( point , "className" , "vehicleClass" , "entityClass" ) ) ] ? ? "" ; }
function vehicleIconFor ( point : RecordMap ) : string { const explicit = textField ( point , "imagePath" , "image_path" ) ; if ( explicit ) return explicit . startsWith ( "/" ) ? explicit : ` / ${ explicit } ` ; return vehicleIconByClass [ normalizedVehicleClass ( textField ( point , "className" , "vehicleClass" , "entityClass" , "vehicleType" ) ) ] ? ? "" ; }
function vehicleClassKey ( value : string ) : string { return normalizedVehicleClass ( value . replace ( /^#spawnvehicle\s+/i , "" ) ) ; }
function normalizedVehicleClass ( value : string ) : string { return value . replace ( /_C$/i , "" ) . split ( "." ) . pop ( ) ? . trim ( ) ? ? value . trim ( ) ; }
function shortHash ( value : string ) : string { return value ? ` ${ value . slice ( 0 , 10 ) } … ${ value . slice ( - 6 ) } ` : "" ; }