feat(scum): add map trajectory projection

This commit is contained in:
npc0-hue
2026-07-28 17:13:48 +08:00
parent 7f64765c1c
commit 5b15bd50cb
33 changed files with 1073 additions and 49 deletions
+69
View File
@@ -0,0 +1,69 @@
package dto
import (
"browser.local/platform/domain"
"time"
)
type GameMapTrajectoryPointResponse struct {
MapX float64 `json:"mapX"`
MapY float64 `json:"mapY"`
OccurredAt time.Time `json:"occurredAt"`
CollectedAt time.Time `json:"collectedAt"`
Source string `json:"source"`
}
type GameMapTrajectoryEntityResponse struct {
Kind string `json:"kind"`
EntityID string `json:"entityId"`
GamePlayerRecordID string `json:"gamePlayerRecordId,omitempty"`
Label string `json:"label"`
Points []GameMapTrajectoryPointResponse `json:"points"`
CollectedAt time.Time `json:"collectedAt,omitempty"`
Sources []string `json:"sources"`
}
type GameMapTrajectorySegmentResponse struct {
GamePlayerRecordID string `json:"gamePlayerRecordId"`
VehicleID string `json:"vehicleId"`
StartedAt time.Time `json:"startedAt"`
EndedAt time.Time `json:"endedAt,omitempty"`
}
type GameMapTrajectoryMapResponse struct {
MapID string `json:"mapId"`
MapVersion string `json:"mapVersion"`
ImageWidth float64 `json:"imageWidth"`
ImageHeight float64 `json:"imageHeight"`
Precision float64 `json:"precision"`
}
type GameMapTrajectoryResponse struct {
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
Map *GameMapTrajectoryMapResponse `json:"map,omitempty"`
From time.Time `json:"from,omitempty"`
To time.Time `json:"to,omitempty"`
Players []GameMapTrajectoryEntityResponse `json:"players"`
Vehicles []GameMapTrajectoryEntityResponse `json:"vehicles"`
RideSegments []GameMapTrajectorySegmentResponse `json:"rideSegments"`
}
func GameMapTrajectoryFromDomain(value domain.GameMapTrajectoryView) GameMapTrajectoryResponse {
value = domain.CopyGameMapTrajectoryView(value)
response := GameMapTrajectoryResponse{Status: value.Status, Reason: value.Reason, From: value.From, To: value.To, Players: mapTrajectoryEntitiesFromDomain(value.Players), Vehicles: mapTrajectoryEntitiesFromDomain(value.Vehicles), RideSegments: make([]GameMapTrajectorySegmentResponse, len(value.RideSegments))}
if value.Status != "missing-map" {
response.Map = &GameMapTrajectoryMapResponse{MapID: value.Map.MapID, MapVersion: value.Map.MapVersion, ImageWidth: value.Map.ImageWidth, ImageHeight: value.Map.ImageHeight, Precision: value.Map.Precision}
}
for i, segment := range value.RideSegments {
response.RideSegments[i] = GameMapTrajectorySegmentResponse{GamePlayerRecordID: segment.GamePlayerRecordID, VehicleID: segment.VehicleID, StartedAt: segment.StartedAt, EndedAt: segment.EndedAt}
}
return response
}
func mapTrajectoryEntitiesFromDomain(values []domain.GameMapTrajectoryEntity) []GameMapTrajectoryEntityResponse {
result := make([]GameMapTrajectoryEntityResponse, len(values))
for i, value := range values {
points := make([]GameMapTrajectoryPointResponse, len(value.Points))
for j, point := range value.Points {
points[j] = GameMapTrajectoryPointResponse{MapX: point.MapX, MapY: point.MapY, OccurredAt: point.OccurredAt, CollectedAt: point.CollectedAt, Source: point.Source}
}
result[i] = GameMapTrajectoryEntityResponse{Kind: string(value.Kind), EntityID: value.EntityID, GamePlayerRecordID: value.GamePlayerRecordID, Label: value.Label, Points: points, CollectedAt: value.CollectedAt, Sources: domain.CopyStringSlice(value.Sources)}
}
return result
}
+36
View File
@@ -321,6 +321,20 @@ type GameClientBridgeManifestBody struct {
Pages []GameClientBridgePageContractBody `json:"pages,omitempty"`
Companion *GameClientBridgeCompanionDeclarationBody `json:"companion,omitempty"`
}
type GameMapTrajectoryDeclarationBody struct {
MapID string `json:"mapId"`
MapVersion string `json:"mapVersion"`
WorldMinX float64 `json:"worldMinX"`
WorldMinY float64 `json:"worldMinY"`
WorldMaxX float64 `json:"worldMaxX"`
WorldMaxY float64 `json:"worldMaxY"`
ImageWidth float64 `json:"imageWidth"`
ImageHeight float64 `json:"imageHeight"`
Precision float64 `json:"precision"`
SampleDistance float64 `json:"sampleDistance"`
SampleIntervalSeconds int `json:"sampleIntervalSeconds"`
RetentionSeconds int `json:"retentionSeconds"`
}
type GamePluginManifestBody struct {
ID string `json:"id"`
@@ -341,6 +355,7 @@ type GamePluginManifestBody struct {
RemoteAccess GamePluginRemoteAccessBody `json:"remoteAccess,omitempty"`
RuntimeProfiles GamePluginRuntimeProfilesBody `json:"runtimeProfiles,omitempty"`
GameClientBridge GameClientBridgeManifestBody `json:"gameClientBridge,omitempty"`
MapTrajectories *GameMapTrajectoryDeclarationBody `json:"mapTrajectories,omitempty"`
}
type GamePluginManifestRegistrationRequest struct {
@@ -372,6 +387,7 @@ type GamePluginCreateRequest struct {
RemoteAccess GamePluginRemoteAccessBody `json:"remoteAccess,omitempty"`
RuntimeProfiles GamePluginRuntimeProfilesBody `json:"runtimeProfiles,omitempty"`
GameClientBridge GameClientBridgeManifestBody `json:"gameClientBridge,omitempty"`
MapTrajectories *GameMapTrajectoryDeclarationBody `json:"mapTrajectories,omitempty"`
ValidationViolations []string `json:"validationViolations,omitempty"`
}
@@ -399,6 +415,7 @@ type GamePluginResponse struct {
RemoteAccess GamePluginRemoteAccessBody `json:"remoteAccess,omitempty"`
RuntimeProfiles GamePluginRuntimeProfilesResponseBody `json:"runtimeProfiles,omitempty"`
GameClientBridge GameClientBridgeManifestBody `json:"gameClientBridge,omitempty"`
MapTrajectories *GameMapTrajectoryDeclarationBody `json:"mapTrajectories,omitempty"`
ValidationViolations []string `json:"validationViolations,omitempty"`
Status domain.GamePluginStatus `json:"status"`
}
@@ -430,6 +447,7 @@ type MarketplacePluginResponse struct {
RemoteAccess GamePluginRemoteAccessBody `json:"remoteAccess,omitempty"`
RuntimeProfiles GamePluginRuntimeProfilesResponseBody `json:"runtimeProfiles,omitempty"`
GameClientBridge GameClientBridgeManifestBody `json:"gameClientBridge,omitempty"`
MapTrajectories *GameMapTrajectoryDeclarationBody `json:"mapTrajectories,omitempty"`
ValidationViolations []string `json:"validationViolations,omitempty"`
Status domain.GamePluginStatus `json:"status"`
Source string `json:"source"`
@@ -974,10 +992,25 @@ func (request GamePluginManifestRegistrationRequest) ToDomain() domain.GamePlugi
RemoteAccess: request.Manifest.RemoteAccess.ToDomain(),
RuntimeProfiles: request.Manifest.RuntimeProfiles.ToDomain(),
GameClientBridge: request.Manifest.GameClientBridge.ToDomain(),
MapTrajectories: mapTrajectoryDeclarationToDomain(request.Manifest.MapTrajectories),
},
}
}
func mapTrajectoryDeclarationToDomain(value *GameMapTrajectoryDeclarationBody) *domain.GameMapTrajectoryDeclaration {
if value == nil {
return nil
}
result := domain.GameMapTrajectoryDeclaration{MapID: value.MapID, MapVersion: value.MapVersion, WorldMinX: value.WorldMinX, WorldMinY: value.WorldMinY, WorldMaxX: value.WorldMaxX, WorldMaxY: value.WorldMaxY, ImageWidth: value.ImageWidth, ImageHeight: value.ImageHeight, Precision: value.Precision, SampleDistance: value.SampleDistance, SampleIntervalSeconds: value.SampleIntervalSeconds, RetentionSeconds: value.RetentionSeconds}
return &result
}
func mapTrajectoryDeclarationFromDomain(value *domain.GameMapTrajectoryDeclaration) *GameMapTrajectoryDeclarationBody {
if value == nil {
return nil
}
return &GameMapTrajectoryDeclarationBody{MapID: value.MapID, MapVersion: value.MapVersion, WorldMinX: value.WorldMinX, WorldMinY: value.WorldMinY, WorldMaxX: value.WorldMaxX, WorldMaxY: value.WorldMaxY, ImageWidth: value.ImageWidth, ImageHeight: value.ImageHeight, Precision: value.Precision, SampleDistance: value.SampleDistance, SampleIntervalSeconds: value.SampleIntervalSeconds, RetentionSeconds: value.RetentionSeconds}
}
func fileWorkspaceToDomain(body PluginFileWorkspaceBody) domain.PluginFileWorkspace {
workspace := domain.PluginFileWorkspace{DefaultDirectoryKey: body.DefaultDirectoryKey}
for _, item := range body.Directories {
@@ -1119,6 +1152,7 @@ func (request GamePluginCreateRequest) ToDomain() domain.GamePlugin {
RemoteAccess: request.RemoteAccess.ToDomain(),
RuntimeProfiles: request.RuntimeProfiles.ToDomain(),
GameClientBridge: request.GameClientBridge.ToDomain(),
MapTrajectories: mapTrajectoryDeclarationToDomain(request.MapTrajectories),
ValidationViolations: domain.CopyStringSlice(request.ValidationViolations),
}
}
@@ -1367,6 +1401,7 @@ func GamePluginFromDomain(plugin domain.GamePlugin) GamePluginResponse {
RemoteAccess: remoteAccessFromDomain(plugin.RemoteAccess),
RuntimeProfiles: runtimeProfilesFromDomain(plugin.RuntimeProfiles),
GameClientBridge: gameClientBridgeManifestFromDomain(plugin.GameClientBridge),
MapTrajectories: mapTrajectoryDeclarationFromDomain(plugin.MapTrajectories),
ValidationViolations: plugin.ValidationViolations,
Status: plugin.Status,
}
@@ -1458,6 +1493,7 @@ func MarketplacePluginFromDomain(plugin domain.PluginMarketplacePlugin) Marketpl
RemoteAccess: remoteAccessFromDomain(plugin.RemoteAccess),
RuntimeProfiles: runtimeProfilesFromDomain(plugin.RuntimeProfiles),
GameClientBridge: gameClientBridgeManifestFromDomain(plugin.GameClientBridge),
MapTrajectories: mapTrajectoryDeclarationFromDomain(plugin.MapTrajectories),
ValidationViolations: plugin.ValidationViolations,
Status: plugin.Status,
Source: plugin.Source,