diff --git a/go.mod b/go.mod index 666667e7b..891ba06f5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/OCAP2/web go 1.26.0 require ( + github.com/OCAP2/extension/v5 v5.0.0-alpha.1.0.20260216221044-4932fc4f0a04 github.com/gorilla/websocket v1.5.3 github.com/labstack/echo/v4 v4.15.0 github.com/mattn/go-sqlite3 v1.14.34 @@ -31,9 +32,9 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.46.0 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/sys v0.39.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/time v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 6b9772331..7ae0936b5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/OCAP2/extension/v5 v5.0.0-alpha.1.0.20260216221044-4932fc4f0a04 h1:D+cP4mCigM7dCgPwcS+YcJPbE/tH2BqB6+85N9WbcIs= +github.com/OCAP2/extension/v5 v5.0.0-alpha.1.0.20260216221044-4932fc4f0a04/go.mod h1:vhxiM92vYBjJ/J6zrwbYDiaPrUNwMRyWZ05duCGjHXg= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -52,17 +54,17 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= diff --git a/internal/ingestion/chunk_flusher.go b/internal/ingestion/chunk_flusher.go new file mode 100644 index 000000000..43c7ba501 --- /dev/null +++ b/internal/ingestion/chunk_flusher.go @@ -0,0 +1,173 @@ +package ingestion + +import ( + "fmt" + "os" + "path/filepath" + "sort" + + "google.golang.org/protobuf/proto" + + pbv2 "github.com/OCAP2/web/pkg/schemas/protobuf/v2" +) + +// ChunkFlusher writes v2 protobuf chunks incrementally during streaming. +// Every chunkSize frames, accumulated states are flushed to disk. +// All methods are called from a single goroutine (the WebSocket read loop). +type ChunkFlusher struct { + chunksDir string + chunkSize uint32 + currentChunkIdx uint32 + chunkStartFrame uint32 + + // Buffered states keyed by frame number. + soldierStates map[uint32][]*pbv2.SoldierState + vehicleStates map[uint32][]*pbv2.VehicleState + + flushedChunks uint32 +} + +// NewChunkFlusher creates a flusher that writes chunks of chunkSize frames +// to the given output directory. +func NewChunkFlusher(outputDir string, chunkSize uint32) (*ChunkFlusher, error) { + chunksDir := filepath.Join(outputDir, "chunks") + if err := os.MkdirAll(chunksDir, 0755); err != nil { + return nil, fmt.Errorf("create chunks dir: %w", err) + } + return &ChunkFlusher{ + chunksDir: chunksDir, + chunkSize: chunkSize, + soldierStates: make(map[uint32][]*pbv2.SoldierState), + vehicleStates: make(map[uint32][]*pbv2.VehicleState), + }, nil +} + +// AddSoldierState buffers a soldier state and flushes if a chunk boundary is crossed. +func (cf *ChunkFlusher) AddSoldierState(frameNum uint32, state *pbv2.SoldierState) error { + cf.soldierStates[frameNum] = append(cf.soldierStates[frameNum], state) + return cf.maybeFlush(frameNum) +} + +// AddVehicleState buffers a vehicle state and flushes if a chunk boundary is crossed. +func (cf *ChunkFlusher) AddVehicleState(frameNum uint32, state *pbv2.VehicleState) error { + cf.vehicleStates[frameNum] = append(cf.vehicleStates[frameNum], state) + return cf.maybeFlush(frameNum) +} + +// Flush writes any remaining buffered frames as the final chunk. +func (cf *ChunkFlusher) Flush() error { + if len(cf.soldierStates) == 0 && len(cf.vehicleStates) == 0 { + return nil + } + return cf.writeCurrentChunk() +} + +// ChunkCount returns the total number of chunks written (including pending). +func (cf *ChunkFlusher) ChunkCount() uint32 { + return cf.flushedChunks +} + +func (cf *ChunkFlusher) maybeFlush(frameNum uint32) error { + // Check if this frame crosses the next chunk boundary. + chunkEnd := cf.chunkStartFrame + cf.chunkSize + if frameNum >= chunkEnd { + return cf.writeCurrentChunk() + } + return nil +} + +func (cf *ChunkFlusher) writeCurrentChunk() error { + // Collect all frame numbers in this chunk. + frameSet := make(map[uint32]bool) + for f := range cf.soldierStates { + frameSet[f] = true + } + for f := range cf.vehicleStates { + frameSet[f] = true + } + + if len(frameSet) == 0 { + return nil + } + + // Sort frame numbers. + frames := make([]uint32, 0, len(frameSet)) + for f := range frameSet { + frames = append(frames, f) + } + sort.Slice(frames, func(i, j int) bool { return frames[i] < frames[j] }) + + // Determine which frames belong to the current chunk vs next. + chunkEnd := cf.chunkStartFrame + cf.chunkSize + var currentFrames, nextFrames []uint32 + for _, f := range frames { + if f < chunkEnd { + currentFrames = append(currentFrames, f) + } else { + nextFrames = append(nextFrames, f) + } + } + + // Build and write the current chunk from currentFrames. + if len(currentFrames) > 0 { + chunk := cf.buildChunk(cf.currentChunkIdx, cf.chunkStartFrame, currentFrames) + if err := cf.writeChunkFile(chunk); err != nil { + return err + } + + // Remove flushed frames from buffers. + for _, f := range currentFrames { + delete(cf.soldierStates, f) + delete(cf.vehicleStates, f) + } + + cf.flushedChunks++ + cf.currentChunkIdx++ + cf.chunkStartFrame = chunkEnd + } + + // If we have frames that spill into the next chunk, check again. + // (Typically only one chunk boundary is crossed per state message.) + if len(nextFrames) > 0 { + // The next frames are already in the buffer, check if they cross another boundary. + maxNext := nextFrames[len(nextFrames)-1] + if maxNext >= cf.chunkStartFrame+cf.chunkSize { + return cf.writeCurrentChunk() + } + } + + return nil +} + +func (cf *ChunkFlusher) buildChunk(idx, startFrame uint32, frameNums []uint32) *pbv2.Chunk { + chunk := &pbv2.Chunk{ + Index: idx, + StartFrame: startFrame, + FrameCount: cf.chunkSize, + } + + for _, fn := range frameNums { + frame := &pbv2.Frame{ + FrameNum: fn, + Soldiers: cf.soldierStates[fn], + Vehicles: cf.vehicleStates[fn], + } + chunk.Frames = append(chunk.Frames, frame) + } + + return chunk +} + +func (cf *ChunkFlusher) writeChunkFile(chunk *pbv2.Chunk) error { + data, err := proto.Marshal(chunk) + if err != nil { + return fmt.Errorf("marshal chunk %d: %w", chunk.Index, err) + } + + path := filepath.Join(cf.chunksDir, fmt.Sprintf("%04d.pb", chunk.Index)) + if err := os.WriteFile(path, data, 0644); err != nil { + return fmt.Errorf("write chunk %d: %w", chunk.Index, err) + } + + return nil +} diff --git a/internal/ingestion/chunk_flusher_test.go b/internal/ingestion/chunk_flusher_test.go new file mode 100644 index 000000000..ee2f68af6 --- /dev/null +++ b/internal/ingestion/chunk_flusher_test.go @@ -0,0 +1,120 @@ +package ingestion + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + pbv2 "github.com/OCAP2/web/pkg/schemas/protobuf/v2" +) + +func TestChunkFlusher_FlushesAtBoundary(t *testing.T) { + dir := t.TempDir() + cf, err := NewChunkFlusher(dir, 3) // chunk size = 3 frames + require.NoError(t, err) + + // Add states for frames 0, 1, 2 (fills chunk 0). + for frame := uint32(0); frame < 3; frame++ { + err := cf.AddSoldierState(frame, &pbv2.SoldierState{ + Id: 1, + Bearing: frame * 10, + Position: &pbv2.Position3D{X: float32(frame), Y: 0, Z: 0}, + }) + require.NoError(t, err) + } + + // Frame 3 crosses boundary → chunk 0 should be flushed. + err = cf.AddSoldierState(3, &pbv2.SoldierState{Id: 1, Bearing: 30}) + require.NoError(t, err) + assert.Equal(t, uint32(1), cf.ChunkCount()) + + // Verify chunk 0 file exists and is valid. + chunkPath := filepath.Join(dir, "chunks", "0000.pb") + data, err := os.ReadFile(chunkPath) + require.NoError(t, err) + + var chunk pbv2.Chunk + require.NoError(t, proto.Unmarshal(data, &chunk)) + assert.Equal(t, uint32(0), chunk.Index) + assert.Equal(t, uint32(0), chunk.StartFrame) + assert.Equal(t, uint32(3), chunk.FrameCount) + assert.Len(t, chunk.Frames, 3) + + // Flush remaining (frame 3). + require.NoError(t, cf.Flush()) + assert.Equal(t, uint32(2), cf.ChunkCount()) + + chunkPath1 := filepath.Join(dir, "chunks", "0001.pb") + data1, err := os.ReadFile(chunkPath1) + require.NoError(t, err) + + var chunk1 pbv2.Chunk + require.NoError(t, proto.Unmarshal(data1, &chunk1)) + assert.Equal(t, uint32(1), chunk1.Index) + assert.Len(t, chunk1.Frames, 1) +} + +func TestChunkFlusher_VehicleStates(t *testing.T) { + dir := t.TempDir() + cf, err := NewChunkFlusher(dir, 5) + require.NoError(t, err) + + for frame := uint32(0); frame < 4; frame++ { + err := cf.AddVehicleState(frame, &pbv2.VehicleState{ + Id: 10, + Fuel: 0.8, + }) + require.NoError(t, err) + } + assert.Equal(t, uint32(0), cf.ChunkCount()) // Not yet flushed. + + require.NoError(t, cf.Flush()) + assert.Equal(t, uint32(1), cf.ChunkCount()) +} + +func TestChunkFlusher_EmptyFlush(t *testing.T) { + dir := t.TempDir() + cf, err := NewChunkFlusher(dir, 10) + require.NoError(t, err) + + // Flushing with no data should succeed silently. + require.NoError(t, cf.Flush()) + assert.Equal(t, uint32(0), cf.ChunkCount()) +} + +func TestChunkFlusher_MixedSoldierVehicle(t *testing.T) { + dir := t.TempDir() + cf, err := NewChunkFlusher(dir, 2) + require.NoError(t, err) + + // Frame 0: soldier + vehicle. + require.NoError(t, cf.AddSoldierState(0, &pbv2.SoldierState{Id: 1})) + require.NoError(t, cf.AddVehicleState(0, &pbv2.VehicleState{Id: 10})) + + // Frame 1: only soldier. + require.NoError(t, cf.AddSoldierState(1, &pbv2.SoldierState{Id: 1})) + + // Frame 2 crosses boundary. + require.NoError(t, cf.AddSoldierState(2, &pbv2.SoldierState{Id: 1})) + assert.Equal(t, uint32(1), cf.ChunkCount()) + + // Verify chunk 0 has both soldiers and vehicles. + data, err := os.ReadFile(filepath.Join(dir, "chunks", "0000.pb")) + require.NoError(t, err) + + var chunk pbv2.Chunk + require.NoError(t, proto.Unmarshal(data, &chunk)) + assert.Len(t, chunk.Frames, 2) // frames 0 and 1 + + // Frame 0 has both soldier and vehicle. + assert.Len(t, chunk.Frames[0].Soldiers, 1) + assert.Len(t, chunk.Frames[0].Vehicles, 1) + + // Frame 1 has only soldier. + assert.Len(t, chunk.Frames[1].Soldiers, 1) + assert.Len(t, chunk.Frames[1].Vehicles, 0) +} diff --git a/internal/ingestion/session.go b/internal/ingestion/session.go new file mode 100644 index 000000000..7914ac202 --- /dev/null +++ b/internal/ingestion/session.go @@ -0,0 +1,848 @@ +// Package ingestion accumulates streaming mission data and produces +// v1 JSON recordings for the conversion pipeline. +package ingestion + +import ( + "compress/gzip" + "encoding/json" + "fmt" + "math" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/OCAP2/extension/v5/pkg/core" +) + +// soldierRecord tracks a soldier and their accumulated states/events. +type soldierRecord struct { + Soldier core.Soldier + States []core.SoldierState + FiredEvents []core.FiredEvent + // bulletFireLines is populated at write time from session.projectiles. + bulletFireLines []core.ProjectileEvent +} + +// vehicleRecord tracks a vehicle and their accumulated states. +type vehicleRecord struct { + Vehicle core.Vehicle + States []core.VehicleState +} + +// markerRecord tracks a marker and its accumulated states. +type markerRecord struct { + Marker core.Marker + States []core.MarkerState +} + +// eventRecord stores a generic event with its type for serialization. +type eventRecord struct { + eventType string + frame uint + kill *core.KillEvent + hit *core.HitEvent + general *core.GeneralEvent + chat *core.ChatEvent +} + +// Session accumulates streaming mission data in memory. +// All methods are called sequentially from a single goroutine (the WebSocket read loop). +type Session struct { + mission *core.Mission + world *core.World + + soldiers map[uint16]*soldierRecord + vehicles map[uint16]*vehicleRecord + markers map[string]*markerRecord + markersByID map[uint]*markerRecord // reverse index for MarkerState routing + + events []eventRecord + projectiles []core.ProjectileEvent // raw 1:1 storage, derived at write time + telemetry []core.TelemetryEvent + times []core.TimeState + + frameCount uint // highest CaptureFrame seen + 1 + + projectileMarkerSeq uint // counter for unique projectile marker names + projectilesDerived bool // guard against double-derivation + + // v2 chunk flusher (optional, nil for v1-only sessions). + chunkFlusher *ChunkFlusher +} + +// NewSession creates an empty ingestion session. +func NewSession() *Session { + return &Session{ + soldiers: make(map[uint16]*soldierRecord), + vehicles: make(map[uint16]*vehicleRecord), + markers: make(map[string]*markerRecord), + markersByID: make(map[uint]*markerRecord), + } +} + +// Mission returns the stored mission metadata. +func (s *Session) Mission() *core.Mission { return s.mission } + +// World returns the stored world metadata. +func (s *Session) World() *core.World { return s.world } + +// FrameCount returns the number of frames accumulated. +func (s *Session) FrameCount() uint { return s.frameCount } + +// SetMission stores mission and world metadata from start_mission. +func (s *Session) SetMission(mission *core.Mission, world *core.World) { + s.mission = mission + s.world = world +} + +// SetChunkFlusher attaches a v2 chunk flusher to this session. +// When set, state updates are also routed to the flusher for incremental writing. +func (s *Session) SetChunkFlusher(cf *ChunkFlusher) { + s.chunkFlusher = cf +} + +// ChunkFlusher returns the attached chunk flusher, or nil. +func (s *Session) ChunkFlusher() *ChunkFlusher { + return s.chunkFlusher +} + +// Finalize flushes remaining chunks and writes the v2 manifest. +// outputDir is the directory where chunks/ and manifest files are written. +func (s *Session) Finalize(outputDir string) error { + if s.chunkFlusher == nil { + return nil + } + if err := s.chunkFlusher.Flush(); err != nil { + return fmt.Errorf("flush final chunk: %w", err) + } + return WriteV2Manifest(s, outputDir, s.chunkFlusher.ChunkCount()) +} + +// HandleAddSoldier registers a new soldier entity. +func (s *Session) HandleAddSoldier(sol core.Soldier) { + s.soldiers[sol.ID] = &soldierRecord{Soldier: sol} +} + +// HandleSoldierState appends a state snapshot for a soldier. +func (s *Session) HandleSoldierState(state core.SoldierState) { + rec, ok := s.soldiers[state.SoldierID] + if !ok { + rec = &soldierRecord{Soldier: core.Soldier{ID: state.SoldierID}} + s.soldiers[state.SoldierID] = rec + } + rec.States = append(rec.States, state) + s.trackFrame(state.CaptureFrame) + + if s.chunkFlusher != nil { + s.chunkFlusher.AddSoldierState(uint32(state.CaptureFrame), SoldierStateToProto(state)) + } +} + +// HandleAddVehicle registers a new vehicle entity. +func (s *Session) HandleAddVehicle(veh core.Vehicle) { + s.vehicles[veh.ID] = &vehicleRecord{Vehicle: veh} +} + +// HandleVehicleState appends a state snapshot for a vehicle. +func (s *Session) HandleVehicleState(state core.VehicleState) { + rec, ok := s.vehicles[state.VehicleID] + if !ok { + rec = &vehicleRecord{Vehicle: core.Vehicle{ID: state.VehicleID}} + s.vehicles[state.VehicleID] = rec + } + rec.States = append(rec.States, state) + s.trackFrame(state.CaptureFrame) + + if s.chunkFlusher != nil { + s.chunkFlusher.AddVehicleState(uint32(state.CaptureFrame), VehicleStateToProto(state)) + } +} + +// HandleAddMarker registers a new marker. +func (s *Session) HandleAddMarker(marker core.Marker) { + rec := &markerRecord{Marker: marker} + s.markers[marker.MarkerName] = rec + s.markersByID[marker.ID] = rec +} + +// HandleMarkerState appends a state snapshot for a marker, routed by MarkerID. +func (s *Session) HandleMarkerState(state core.MarkerState) { + rec, ok := s.markersByID[state.MarkerID] + if !ok { + return + } + rec.States = append(rec.States, state) +} + +// HandleDeleteMarker sets the end frame for a marker. +func (s *Session) HandleDeleteMarker(name string, endFrame uint) { + rec, ok := s.markers[name] + if !ok { + return + } + rec.Marker.EndFrame = int(endFrame) + rec.Marker.IsDeleted = true +} + +// HandleFiredEvent appends a fired event to the corresponding soldier. +func (s *Session) HandleFiredEvent(fe core.FiredEvent) { + rec, ok := s.soldiers[fe.SoldierID] + if !ok { + return + } + rec.FiredEvents = append(rec.FiredEvents, fe) + s.trackFrame(fe.CaptureFrame) +} + +// HandleKillEvent stores a kill event. +func (s *Session) HandleKillEvent(evt core.KillEvent) { + s.events = append(s.events, eventRecord{ + eventType: "killed", + frame: evt.CaptureFrame, + kill: &evt, + }) + s.trackFrame(evt.CaptureFrame) +} + +// HandleProjectileEvent stores a raw projectile event for later derivation. +// Raw events are stored 1:1; fire lines, markers, and hit events are derived at write time. +func (s *Session) HandleProjectileEvent(evt core.ProjectileEvent) { + s.projectiles = append(s.projectiles, evt) + s.trackFrame(evt.CaptureFrame) +} + +// HandleHitEvent stores a hit event. +func (s *Session) HandleHitEvent(evt core.HitEvent) { + s.events = append(s.events, eventRecord{ + eventType: "hit", + frame: evt.CaptureFrame, + hit: &evt, + }) + s.trackFrame(evt.CaptureFrame) +} + +// HandleGeneralEvent stores a general event. +func (s *Session) HandleGeneralEvent(evt core.GeneralEvent) { + s.events = append(s.events, eventRecord{ + eventType: evt.Name, + frame: evt.CaptureFrame, + general: &evt, + }) + s.trackFrame(evt.CaptureFrame) +} + +// HandleChatEvent stores a chat event. +func (s *Session) HandleChatEvent(evt core.ChatEvent) { + s.events = append(s.events, eventRecord{ + eventType: "chat", + frame: evt.CaptureFrame, + chat: &evt, + }) + s.trackFrame(evt.CaptureFrame) +} + +// HandleTelemetry stores a telemetry snapshot (FPS, entity counts, scripts, weather, player network). +func (s *Session) HandleTelemetry(evt core.TelemetryEvent) { + s.telemetry = append(s.telemetry, evt) + s.trackFrame(evt.CaptureFrame) +} + +// HandleTimeState appends a time synchronization record. +func (s *Session) HandleTimeState(ts core.TimeState) { + s.times = append(s.times, ts) + s.trackFrame(ts.CaptureFrame) +} + +// deriveProjectileData processes raw projectile events into fire lines, markers, +// and hit events. Called once at write time — never during ingestion. +func (s *Session) deriveProjectileData() { + if s.projectilesDerived { + return + } + s.projectilesDerived = true + + for _, evt := range s.projectiles { + if !isProjectileMarker(evt.SimulationType) { + // Bullets → fire lines on the soldier entity (need >= 2 trajectory points). + if len(evt.Trajectory) >= 2 { + if rec, ok := s.soldiers[evt.FirerObjectID]; ok { + rec.bulletFireLines = append(rec.bulletFireLines, evt) + } + } + } else { + // Non-bullet projectiles → moving markers. + s.addProjectileMarker(evt) + } + + // All projectiles with hits → hit events. + if len(evt.Hits) > 0 { + s.addProjectileHits(evt) + } + } +} + +// addProjectileMarker creates a moving marker from a non-bullet projectile. +// Matches the extension's builder.go logic for marker creation. +func (s *Session) addProjectileMarker(evt core.ProjectileEvent) { + if len(evt.Trajectory) == 0 { + return + } + + // Determine icon and color. + iconFilename := extractFilename(evt.MagazineIcon) + var markerType, color string + if iconFilename != "" { + markerType = "magIcons/" + iconFilename + color = "ColorWhite" + } else { + markerType = "mil_triangle" + color = "ColorRed" + } + + // Determine text. + var text string + switch { + case evt.VehicleObjectID != nil && *evt.VehicleObjectID != evt.FirerObjectID: + vehicleName := "" + if vr, ok := s.vehicles[*evt.VehicleObjectID]; ok { + vehicleName = vr.Vehicle.DisplayName + } + text = fmt.Sprintf("%s %s - %s", vehicleName, evt.MuzzleDisplay, evt.MagazineDisplay) + case evt.SimulationType == "shotGrenade": + text = evt.MagazineDisplay + default: + text = fmt.Sprintf("%s - %s", evt.MuzzleDisplay, evt.MagazineDisplay) + } + + // EndFrame is the last trajectory point's frame. + endFrame := int(evt.Trajectory[len(evt.Trajectory)-1].Frame) + + // Generate unique marker name. + s.projectileMarkerSeq++ + name := fmt.Sprintf("_projectile_%d", s.projectileMarkerSeq) + + firstTP := evt.Trajectory[0] + marker := core.Marker{ + CaptureFrame: firstTP.Frame, + EndFrame: endFrame, + MarkerName: name, + MarkerType: markerType, + Text: text, + OwnerID: int(evt.FirerObjectID), + Color: color, + Size: "[1,1]", + Shape: "ICON", + Alpha: 1.0, + Brush: "Solid", + Position: firstTP.Position, + } + + rec := &markerRecord{Marker: marker} + for i := 1; i < len(evt.Trajectory); i++ { + tp := evt.Trajectory[i] + rec.States = append(rec.States, core.MarkerState{ + CaptureFrame: tp.Frame, + Position: tp.Position, + Alpha: 1.0, + }) + } + + s.markers[name] = rec +} + +// addProjectileHits extracts hit events from a projectile. +// Matches the extension's builder.go hit event extraction. +func (s *Session) addProjectileHits(evt core.ProjectileEvent) { + weaponName := evt.MuzzleDisplay + if weaponName == "" { + weaponName = evt.WeaponDisplay + } + eventText := formatWeaponText(weaponName, evt.MagazineDisplay) + + var startPos core.Position3D + if len(evt.Trajectory) > 0 { + startPos = evt.Trajectory[0].Position + } + + shooterID := uint(evt.FirerObjectID) + + for _, hit := range evt.Hits { + dx := float64(startPos.X - hit.Position.X) + dy := float64(startPos.Y - hit.Position.Y) + dist := float32(math.Sqrt(dx*dx + dy*dy)) + + hitEvt := core.HitEvent{ + CaptureFrame: hit.CaptureFrame, + ShooterSoldierID: &shooterID, + EventText: eventText, + Distance: dist, + WeaponName: weaponName, + WeaponMagazine: evt.MagazineDisplay, + } + + if hit.SoldierID != nil { + v := uint(*hit.SoldierID) + hitEvt.VictimSoldierID = &v + } + if hit.VehicleID != nil { + v := uint(*hit.VehicleID) + hitEvt.VictimVehicleID = &v + } + if evt.VehicleObjectID != nil { + v := uint(*evt.VehicleObjectID) + hitEvt.ShooterVehicleID = &v + } + + s.events = append(s.events, eventRecord{ + eventType: "hit", + frame: hit.CaptureFrame, + hit: &hitEvt, + }) + s.trackFrame(hit.CaptureFrame) + } +} + +// projectileEndPos returns the best end position for a projectile fire line: +// last hit position if any, otherwise last trajectory point. +func projectileEndPos(pe core.ProjectileEvent) core.Position3D { + if len(pe.Hits) > 0 { + return pe.Hits[len(pe.Hits)-1].Position + } + if len(pe.Trajectory) > 0 { + return pe.Trajectory[len(pe.Trajectory)-1].Position + } + return core.Position3D{} +} + +// isProjectileMarker returns true if the projectile should be rendered as a +// moving marker rather than a fire-line. Bullets are fire-lines; everything +// else (grenades, rockets, missiles, shells, etc.) becomes a marker. +func isProjectileMarker(sim string) bool { + return sim != "shotBullet" +} + +// extractFilename returns the last path component from a file path. +// Handles both forward and backslash separators (Arma uses backslashes). +func extractFilename(path string) string { + for i := len(path) - 1; i >= 0; i-- { + if path[i] == '/' || path[i] == '\\' { + return path[i+1:] + } + } + return path +} + +// formatWeaponText formats weapon and magazine into display text: "weapon [magazine]". +func formatWeaponText(weapon, magazine string) string { + if magazine == "" { + return weapon + } + return weapon + " [" + magazine + "]" +} + +// trackFrame updates frameCount to be max(current, frame+1). +func (s *Session) trackFrame(frame uint) { + if frame+1 > s.frameCount { + s.frameCount = frame + 1 + } +} + +// --- V1 JSON Serialization --- +// Produces the same format as the extension's internal/storage/memory/export/v1/builder.go +// which parser_v1.go in internal/storage/ is proven to parse. + +// ToV1JSON converts accumulated session data to the v1 JSON map structure. +func (s *Session) ToV1JSON() map[string]any { + // Derive fire lines, markers, and hit events from raw projectile data. + s.deriveProjectileData() + + result := map[string]any{ + "worldName": "", + "missionName": "", + "endFrame": s.frameCount, + "captureDelay": float32(0), + "entities": s.entitiesToV1(), + "events": s.eventsToV1(), + "Markers": s.markersToV1(), + "times": s.timesToV1(), + } + + if s.world != nil { + result["worldName"] = s.world.WorldName + } + if s.mission != nil { + result["missionName"] = s.mission.MissionName + result["captureDelay"] = s.mission.CaptureDelay + if s.mission.ExtensionVersion != "" { + result["extensionVersion"] = s.mission.ExtensionVersion + } + if s.mission.AddonVersion != "" { + result["addonVersion"] = s.mission.AddonVersion + } + if s.mission.ExtensionBuild != "" { + result["extensionBuild"] = s.mission.ExtensionBuild + } + if s.mission.Author != "" { + result["missionAuthor"] = s.mission.Author + } + if s.mission.Tag != "" { + result["tags"] = s.mission.Tag + } + } + + return result +} + +// entitiesToV1 converts soldiers and vehicles to v1 entity format. +// Matches the extension's v1 builder: entities array indexed by ID. +func (s *Session) entitiesToV1() []any { + // Find max entity ID to size array correctly (JS frontend uses entities[id]) + var maxID uint16 + for _, rec := range s.soldiers { + if rec.Soldier.ID > maxID { + maxID = rec.Soldier.ID + } + } + for _, rec := range s.vehicles { + if rec.Vehicle.ID > maxID { + maxID = rec.Vehicle.ID + } + } + + if len(s.soldiers) == 0 && len(s.vehicles) == 0 { + return []any{} + } + + entities := make([]any, maxID+1) + // Fill with placeholder maps for empty slots + for i := range entities { + entities[i] = map[string]any{ + "id": i, + "type": "", + "name": "", + "side": "", + "isPlayer": 0, + "startFrameNum": 0, + "positions": []any{}, + "framesFired": []any{}, + } + } + + for _, rec := range s.soldiers { + sol := rec.Soldier + + positions := make([][]any, 0, len(rec.States)) + for _, st := range rec.States { + var inVehicleID any = 0 + if st.InVehicleObjectID != nil { + inVehicleID = *st.InVehicleObjectID + } + + pos := []any{ + []float64{st.Position.X, st.Position.Y, st.Position.Z}, + st.Bearing, + st.Lifestate, + inVehicleID, + st.UnitName, + boolToInt(st.IsPlayer), + st.CurrentRole, + } + positions = append(positions, pos) + } + + firedFrames := make([][]any, 0, len(rec.FiredEvents)+len(rec.bulletFireLines)) + for _, fe := range rec.FiredEvents { + firedFrames = append(firedFrames, []any{ + fe.CaptureFrame, + []float64{fe.EndPos.X, fe.EndPos.Y, fe.EndPos.Z}, + }) + } + for _, pe := range rec.bulletFireLines { + endPos := projectileEndPos(pe) + firedFrames = append(firedFrames, []any{ + pe.CaptureFrame, + []float64{endPos.X, endPos.Y, endPos.Z}, + }) + } + + entity := map[string]any{ + "id": sol.ID, + "type": "unit", + "name": sol.UnitName, + "side": sol.Side, + "group": sol.GroupID, + "isPlayer": boolToInt(sol.IsPlayer), + "role": sol.RoleDescription, + "startFrameNum": sol.JoinFrame, + "positions": positions, + "framesFired": firedFrames, + } + entities[sol.ID] = entity + } + + for _, rec := range s.vehicles { + veh := rec.Vehicle + + positions := make([][]any, 0, len(rec.States)) + for _, st := range rec.States { + // Parse crew JSON string into actual JSON array + var crew any + if st.Crew != "" { + if err := json.Unmarshal([]byte(st.Crew), &crew); err != nil { + crew = []any{} + } + } else { + crew = []any{} + } + + pos := []any{ + []float64{st.Position.X, st.Position.Y, st.Position.Z}, + st.Bearing, + boolToInt(st.IsAlive), + crew, + []uint{st.CaptureFrame, st.CaptureFrame}, + } + positions = append(positions, pos) + } + + entity := map[string]any{ + "id": veh.ID, + "type": "vehicle", + "name": veh.DisplayName, + "side": "UNKNOWN", + "class": veh.OcapType, + "isPlayer": 0, + "startFrameNum": veh.JoinFrame, + "positions": positions, + "framesFired": []any{}, + } + entities[veh.ID] = entity + } + + return entities +} + +// eventsToV1 converts events to v1 format. +// Uses the extension's "old" format: [frame, "killed", victimId, [killerId, weapon], distance] +func (s *Session) eventsToV1() []any { + events := make([]any, 0, len(s.events)) + + for _, e := range s.events { + switch e.eventType { + case "killed": + evt := e.kill + var victimID uint + if evt.VictimVehicleID != nil { + victimID = *evt.VictimVehicleID + } else if evt.VictimSoldierID != nil { + victimID = *evt.VictimSoldierID + } + var killerID uint + if evt.KillerVehicleID != nil { + killerID = *evt.KillerVehicleID + } else if evt.KillerSoldierID != nil { + killerID = *evt.KillerSoldierID + } + events = append(events, []any{ + evt.CaptureFrame, "killed", + victimID, + []any{killerID, evt.EventText}, + evt.Distance, + }) + + case "hit": + evt := e.hit + var victimID uint + if evt.VictimVehicleID != nil { + victimID = *evt.VictimVehicleID + } else if evt.VictimSoldierID != nil { + victimID = *evt.VictimSoldierID + } + var sourceID uint + if evt.ShooterVehicleID != nil { + sourceID = *evt.ShooterVehicleID + } else if evt.ShooterSoldierID != nil { + sourceID = *evt.ShooterSoldierID + } + events = append(events, []any{ + evt.CaptureFrame, "hit", + victimID, + []any{sourceID, evt.EventText}, + evt.Distance, + }) + + default: + // generalEvent, chat, and other event types + if e.general != nil { + events = append(events, []any{ + e.general.CaptureFrame, e.eventType, e.general.Message, + }) + } else if e.chat != nil { + events = append(events, []any{ + e.chat.CaptureFrame, "chat", e.chat.Message, + }) + } + } + } + + return events +} + +// markersToV1 converts markers to v1 format. +// Format: [type, text, startFrame, endFrame, playerId, color, sideIndex, positions, size, shape, brush] +func (s *Session) markersToV1() []any { + markers := make([]any, 0, len(s.markers)) + + for _, rec := range s.markers { + m := rec.Marker + + posArray := make([][]any, 0, 1+len(rec.States)) + + if m.Shape == "POLYLINE" && len(m.Polyline) > 0 { + coords := make([][]float64, len(m.Polyline)) + for i, pt := range m.Polyline { + coords[i] = []float64{pt.X, pt.Y} + } + posArray = append(posArray, []any{ + m.CaptureFrame, coords, m.Direction, m.Alpha, + }) + } else { + posArray = append(posArray, []any{ + m.CaptureFrame, + []float64{m.Position.X, m.Position.Y, m.Position.Z}, + m.Direction, m.Alpha, + }) + + for _, st := range rec.States { + posArray = append(posArray, []any{ + st.CaptureFrame, + []float64{st.Position.X, st.Position.Y, st.Position.Z}, + st.Direction, st.Alpha, + }) + } + } + + // Strip "#" prefix from hex colors for URL compatibility + markerColor := strings.TrimPrefix(m.Color, "#") + + endFrame := m.EndFrame + if endFrame == 0 { + endFrame = -1 + } + + markers = append(markers, []any{ + m.MarkerType, + m.Text, + m.CaptureFrame, + endFrame, + m.OwnerID, + markerColor, + sideToIndex(m.Side), + posArray, + parseMarkerSize(m.Size), + m.Shape, + m.Brush, + }) + } + + return markers +} + +// timesToV1 converts time states to v1 format. +func (s *Session) timesToV1() []any { + times := make([]any, 0, len(s.times)) + for _, ts := range s.times { + times = append(times, map[string]any{ + "frameNum": ts.CaptureFrame, + "systemTimeUTC": ts.SystemTimeUTC, + "date": ts.MissionDate, + "timeMultiplier": ts.TimeMultiplier, + "time": ts.MissionTime, + }) + } + return times +} + +// sanitizeFilename replaces non-filesystem-safe characters with underscores. +var unsafeChars = regexp.MustCompile(`[^a-zA-Z0-9._-]`) + +func sanitizeFilename(name string) string { + return unsafeChars.ReplaceAllString(strings.TrimSpace(name), "_") +} + +// MakeFilename creates a sanitized timestamped filename from a mission name. +func MakeFilename(missionName string) string { + name := "unknown" + if missionName != "" { + name = sanitizeFilename(missionName) + } + return name + "_" + time.Now().Format("20060102_150405") +} + +// WriteJSONGz serializes the session to v1 JSON, gzip-compresses it, +// and writes it to dataDir/{filename}.json.gz. Returns the sanitized filename (without extension). +func (s *Session) WriteJSONGz(dataDir string) (string, error) { + missionName := "unknown" + if s.mission != nil && s.mission.MissionName != "" { + missionName = s.mission.MissionName + } + + filename := sanitizeFilename(missionName) + "_" + time.Now().Format("20060102_150405") + + data := s.ToV1JSON() + + outPath := filepath.Join(dataDir, filename+".json.gz") + f, err := os.Create(outPath) + if err != nil { + return "", fmt.Errorf("create file: %w", err) + } + + gw := gzip.NewWriter(f) + encodeErr := json.NewEncoder(gw).Encode(data) + gzipErr := gw.Close() + fileErr := f.Close() + + if encodeErr != nil { + return "", fmt.Errorf("encode JSON: %w", encodeErr) + } + if gzipErr != nil { + return "", fmt.Errorf("close gzip: %w", gzipErr) + } + if fileErr != nil { + return "", fmt.Errorf("close file: %w", fileErr) + } + + return filename, nil +} + +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} + +func sideToIndex(side string) int { + switch strings.ToUpper(side) { + case "EAST", "OPFOR": + return 0 + case "WEST", "BLUFOR": + return 1 + case "GUER", "INDEPENDENT": + return 2 + case "CIV", "CIVILIAN": + return 3 + default: + return -1 + } +} + +func parseMarkerSize(sizeStr string) []float64 { + var size []float64 + if err := json.Unmarshal([]byte(sizeStr), &size); err != nil || len(size) != 2 { + return []float64{1.0, 1.0} + } + return size +} diff --git a/internal/ingestion/session_test.go b/internal/ingestion/session_test.go new file mode 100644 index 000000000..9cce2773c --- /dev/null +++ b/internal/ingestion/session_test.go @@ -0,0 +1,480 @@ +package ingestion + +import ( + "compress/gzip" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/OCAP2/extension/v5/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSession_AddSoldierAndStates(t *testing.T) { + s := NewSession() + + s.HandleAddSoldier(core.Soldier{ + ID: 1, JoinFrame: 0, UnitName: "Player1", Side: "WEST", + GroupID: "Alpha", IsPlayer: true, DisplayName: "Player1", OcapType: "unit", + }) + + s.HandleSoldierState(core.SoldierState{ + SoldierID: 1, CaptureFrame: 0, + Position: core.Position3D{X: 100, Y: 200, Z: 10}, + Bearing: 90, Lifestate: 0, IsPlayer: true, + UnitName: "Player1", GroupID: "Alpha", Side: "WEST", + }) + s.HandleSoldierState(core.SoldierState{ + SoldierID: 1, CaptureFrame: 1, + Position: core.Position3D{X: 101, Y: 201, Z: 10}, + Bearing: 95, Lifestate: 0, IsPlayer: true, + UnitName: "Player1", GroupID: "Alpha", Side: "WEST", + }) + + rec, ok := s.soldiers[1] + require.True(t, ok) + assert.Equal(t, "Player1", rec.Soldier.DisplayName) + assert.Len(t, rec.States, 2) + assert.Equal(t, uint(1), rec.States[1].CaptureFrame) +} + +func TestSession_SoldierStatePlaceholder(t *testing.T) { + s := NewSession() + + // State arrives before add_soldier — should create placeholder + s.HandleSoldierState(core.SoldierState{ + SoldierID: 5, CaptureFrame: 0, + Position: core.Position3D{X: 100, Y: 200, Z: 10}, + }) + + rec, ok := s.soldiers[5] + require.True(t, ok) + assert.Equal(t, uint16(5), rec.Soldier.ID) + assert.Len(t, rec.States, 1) +} + +func TestSession_AddVehicleAndStates(t *testing.T) { + s := NewSession() + + s.HandleAddVehicle(core.Vehicle{ + ID: 100, JoinFrame: 5, ClassName: "B_MRAP_01_F", + DisplayName: "Hunter", OcapType: "vehicle", + }) + + s.HandleVehicleState(core.VehicleState{ + VehicleID: 100, CaptureFrame: 5, + Position: core.Position3D{X: 500, Y: 600, Z: 0}, + Bearing: 180, IsAlive: true, Crew: "[1,2]", + }) + + rec, ok := s.vehicles[100] + require.True(t, ok) + assert.Equal(t, "Hunter", rec.Vehicle.DisplayName) + assert.Len(t, rec.States, 1) +} + +func TestSession_Events(t *testing.T) { + s := NewSession() + + s.HandleKillEvent(core.KillEvent{ + CaptureFrame: 50, + VictimSoldierID: ptrUint(2), + KillerSoldierID: ptrUint(1), + EventText: "arifle_MX_F", + Distance: 150.5, + }) + + s.HandleGeneralEvent(core.GeneralEvent{ + CaptureFrame: 100, Name: "generalEvent", Message: "Objective captured", + }) + + assert.Len(t, s.events, 2) +} + +func TestSession_Markers(t *testing.T) { + s := NewSession() + + s.HandleAddMarker(core.Marker{ + ID: 1, MarkerName: "marker1", CaptureFrame: 10, EndFrame: -1, + MarkerType: "hd_dot", Text: "HQ", Color: "ColorBlue", + Side: "WEST", Position: core.Position3D{X: 300, Y: 400, Z: 0}, + Shape: "ICON", Alpha: 1.0, Brush: "Solid", + }) + + s.HandleMarkerState(core.MarkerState{ + MarkerID: 1, CaptureFrame: 20, + Position: core.Position3D{X: 310, Y: 410, Z: 0}, + Direction: 45, Alpha: 0.8, + }) + + rec, ok := s.markers["marker1"] + require.True(t, ok) + assert.Equal(t, "HQ", rec.Marker.Text) + assert.Len(t, rec.States, 1) +} + +func TestSession_DeleteMarker(t *testing.T) { + s := NewSession() + + s.HandleAddMarker(core.Marker{ + ID: 1, MarkerName: "m1", CaptureFrame: 0, EndFrame: -1, + }) + + s.HandleDeleteMarker("m1", 50) + + rec := s.markers["m1"] + assert.Equal(t, 50, rec.Marker.EndFrame) + assert.True(t, rec.Marker.IsDeleted) +} + +func TestSession_FiredEvents(t *testing.T) { + s := NewSession() + + s.HandleAddSoldier(core.Soldier{ID: 1}) + s.HandleFiredEvent(core.FiredEvent{ + SoldierID: 1, CaptureFrame: 30, + StartPos: core.Position3D{X: 100, Y: 200, Z: 10}, + EndPos: core.Position3D{X: 150, Y: 250, Z: 8}, + }) + + rec := s.soldiers[1] + assert.Len(t, rec.FiredEvents, 1) +} + +func TestSession_FiredEvent_UnknownSoldier(t *testing.T) { + s := NewSession() + + // Fired event for unknown soldier should be silently dropped + s.HandleFiredEvent(core.FiredEvent{SoldierID: 99, CaptureFrame: 0}) + + assert.Empty(t, s.soldiers) +} + +func TestSession_TimeStates(t *testing.T) { + s := NewSession() + + s.HandleTimeState(core.TimeState{ + CaptureFrame: 0, + SystemTimeUTC: "2026-02-16T12:00:00Z", + MissionDate: "2035-06-15", + TimeMultiplier: 1.0, + MissionTime: 360, + }) + + assert.Len(t, s.times, 1) +} + +func TestSession_FrameCount(t *testing.T) { + s := NewSession() + + s.HandleAddSoldier(core.Soldier{ID: 1}) + s.HandleSoldierState(core.SoldierState{SoldierID: 1, CaptureFrame: 0}) + s.HandleSoldierState(core.SoldierState{SoldierID: 1, CaptureFrame: 1}) + s.HandleSoldierState(core.SoldierState{SoldierID: 1, CaptureFrame: 2}) + + s.HandleAddVehicle(core.Vehicle{ID: 100}) + s.HandleVehicleState(core.VehicleState{VehicleID: 100, CaptureFrame: 5}) + + assert.Equal(t, uint(6), s.frameCount) // max(2,5) + 1 = 6 +} + +func TestSession_Telemetry(t *testing.T) { + s := NewSession() + + s.HandleTelemetry(core.TelemetryEvent{ + CaptureFrame: 10, + FpsAverage: 45.2, + FpsMin: 30.1, + GlobalCounts: core.GlobalEntityCount{UnitsAlive: 20, PlayersConnected: 8}, + Players: []core.PlayerNetworkData{ + {UID: "123", Name: "Player1", Ping: 42, BW: 1024, Desync: 0.5}, + }, + }) + + assert.Len(t, s.events, 0, "telemetry should not be stored as gameplay events") + assert.Len(t, s.telemetry, 1) + assert.InDelta(t, 45.2, s.telemetry[0].FpsAverage, 0.01) + assert.InDelta(t, 30.1, s.telemetry[0].FpsMin, 0.01) + assert.Equal(t, uint(20), s.telemetry[0].GlobalCounts.UnitsAlive) + assert.Equal(t, uint(8), s.telemetry[0].GlobalCounts.PlayersConnected) + assert.Len(t, s.telemetry[0].Players, 1) + assert.Equal(t, "Player1", s.telemetry[0].Players[0].Name) + assert.Equal(t, uint(11), s.frameCount) // frame 10 + 1 +} + +func TestSession_Finalize(t *testing.T) { + s := NewSession() + s.SetMission( + &core.Mission{MissionName: "Test Mission", CaptureDelay: 1.0, Tag: "TvT", + ExtensionVersion: "1.0.0", AddonVersion: "2.0.0"}, + &core.World{WorldName: "altis"}, + ) + + // Add a soldier with 3 frames of state + s.HandleAddSoldier(core.Soldier{ + ID: 1, JoinFrame: 0, UnitName: "Player1", Side: "WEST", + GroupID: "Alpha", IsPlayer: true, OcapType: "unit", RoleDescription: "Rifleman", + }) + for i := uint(0); i < 3; i++ { + s.HandleSoldierState(core.SoldierState{ + SoldierID: 1, CaptureFrame: i, + Position: core.Position3D{X: float64(100 + i), Y: 200, Z: 10}, + Bearing: 90, Lifestate: 0, IsPlayer: true, + UnitName: "Player1", GroupID: "Alpha", Side: "WEST", + }) + } + + // Add a vehicle + s.HandleAddVehicle(core.Vehicle{ + ID: 100, JoinFrame: 0, ClassName: "B_MRAP_01_F", + DisplayName: "Hunter", OcapType: "vehicle", + }) + s.HandleVehicleState(core.VehicleState{ + VehicleID: 100, CaptureFrame: 0, + Position: core.Position3D{X: 500, Y: 600, Z: 0}, + Bearing: 180, IsAlive: true, Crew: "[1]", + }) + + // Add a kill event + s.HandleKillEvent(core.KillEvent{ + CaptureFrame: 2, + VictimSoldierID: ptrUint(2), + KillerSoldierID: ptrUint(1), + EventText: "arifle_MX_F", + Distance: 100.5, + }) + + // Add a time state + s.HandleTimeState(core.TimeState{ + CaptureFrame: 0, SystemTimeUTC: "2026-02-16T12:00:00Z", MissionDate: "2035-06-15", + }) + + // Fired event + s.HandleFiredEvent(core.FiredEvent{ + SoldierID: 1, CaptureFrame: 1, + EndPos: core.Position3D{X: 150, Y: 250, Z: 8}, + }) + + data := s.ToV1JSON() + + // Verify top-level fields + assert.Equal(t, "altis", data["worldName"]) + assert.Equal(t, "Test Mission", data["missionName"]) + assert.Equal(t, uint(3), data["endFrame"]) + assert.Equal(t, float32(1.0), data["captureDelay"]) + assert.Equal(t, "1.0.0", data["extensionVersion"]) + assert.Equal(t, "2.0.0", data["addonVersion"]) + + // Entities array is indexed by ID — entity 1 at index 1, entity 100 at index 100 + entities, ok := data["entities"].([]any) + require.True(t, ok) + assert.Len(t, entities, 101) // maxID=100, so 101 entries + + // Check soldier at index 1 + solEnt, ok := entities[1].(map[string]any) + require.True(t, ok) + assert.Equal(t, "unit", solEnt["type"]) + assert.Equal(t, "Player1", solEnt["name"]) + positions := solEnt["positions"].([][]any) + assert.Len(t, positions, 3) + + // Check vehicle at index 100 + vehEnt, ok := entities[100].(map[string]any) + require.True(t, ok) + assert.Equal(t, "vehicle", vehEnt["type"]) + + // Verify events + events, ok := data["events"].([]any) + require.True(t, ok) + assert.Len(t, events, 1) // 1 kill + + // Verify kill event uses old extension format: [frame, "killed", victimId, [killerId, weapon], distance] + killEvt := events[0].([]any) + assert.Equal(t, uint(2), killEvt[0]) + assert.Equal(t, "killed", killEvt[1]) + assert.Equal(t, uint(2), killEvt[2]) // victimId + killerArr := killEvt[3].([]any) + assert.Equal(t, uint(1), killerArr[0]) // killerId + assert.Equal(t, "arifle_MX_F", killerArr[1]) // weapon + assert.Equal(t, float32(100.5), killEvt[4]) // distance + + // Verify markers + markers, ok := data["Markers"].([]any) + require.True(t, ok) + assert.Len(t, markers, 0) + + // Verify times + times, ok := data["times"].([]any) + require.True(t, ok) + assert.Len(t, times, 1) +} + +func TestSession_FinalizeWritesFile(t *testing.T) { + s := NewSession() + s.SetMission( + &core.Mission{MissionName: "Test Op", CaptureDelay: 1.0, Tag: "coop"}, + &core.World{WorldName: "altis"}, + ) + + s.HandleAddSoldier(core.Soldier{ID: 1, JoinFrame: 0, OcapType: "unit", IsPlayer: true, UnitName: "P1", Side: "WEST"}) + s.HandleSoldierState(core.SoldierState{SoldierID: 1, CaptureFrame: 0, Position: core.Position3D{X: 1, Y: 2, Z: 3}}) + + dir := t.TempDir() + filename, err := s.WriteJSONGz(dir) + require.NoError(t, err) + assert.Contains(t, filename, "Test_Op_") + + // Verify file exists and is valid gzip JSON + fullPath := filepath.Join(dir, filename+".json.gz") + f, err := os.Open(fullPath) + require.NoError(t, err) + defer f.Close() + + gr, err := gzip.NewReader(f) + require.NoError(t, err) + + var data map[string]any + err = json.NewDecoder(gr).Decode(&data) + require.NoError(t, err) + assert.Equal(t, "altis", data["worldName"]) +} + +func TestSession_RoundTrip_ParserV1(t *testing.T) { + // Build a session with representative data + s := NewSession() + s.SetMission( + &core.Mission{MissionName: "RoundTrip Test", CaptureDelay: 1.0, Tag: "coop", + ExtensionVersion: "1.0", AddonVersion: "2.0"}, + &core.World{WorldName: "stratis"}, + ) + + // 2 soldiers, 1 vehicle, states, events, markers, times + s.HandleAddSoldier(core.Soldier{ID: 1, JoinFrame: 0, OcapType: "unit", + UnitName: "Alpha1", Side: "WEST", GroupID: "Alpha", + IsPlayer: true, RoleDescription: "SL"}) + s.HandleAddSoldier(core.Soldier{ID: 2, JoinFrame: 0, OcapType: "unit", + UnitName: "Alpha2", Side: "WEST", GroupID: "Alpha"}) + + for i := uint(0); i < 5; i++ { + s.HandleSoldierState(core.SoldierState{ + SoldierID: 1, CaptureFrame: i, + Position: core.Position3D{X: float64(100 + i), Y: 200, Z: 10}, + Bearing: 90, Side: "WEST", GroupID: "Alpha", + UnitName: "Alpha1", IsPlayer: true, + }) + s.HandleSoldierState(core.SoldierState{ + SoldierID: 2, CaptureFrame: i, + Position: core.Position3D{X: float64(110 + i), Y: 210, Z: 10}, + Bearing: 180, Side: "WEST", GroupID: "Alpha", + UnitName: "Alpha2", + }) + } + + s.HandleAddVehicle(core.Vehicle{ID: 100, JoinFrame: 0, + ClassName: "B_MRAP_01_F", DisplayName: "Hunter", OcapType: "vehicle"}) + s.HandleVehicleState(core.VehicleState{VehicleID: 100, CaptureFrame: 0, + Position: core.Position3D{X: 500, Y: 600, Z: 0}, IsAlive: true, Crew: "[1]"}) + + s.HandleFiredEvent(core.FiredEvent{SoldierID: 1, CaptureFrame: 2, + EndPos: core.Position3D{X: 150, Y: 250, Z: 8}}) + + s.HandleKillEvent(core.KillEvent{CaptureFrame: 3, + KillerSoldierID: ptrUint(1), VictimSoldierID: ptrUint(2), + EventText: "arifle_MX_F", Distance: 50}) + + s.HandleAddMarker(core.Marker{ID: 1, MarkerName: "m1", CaptureFrame: 0, + EndFrame: -1, MarkerType: "hd_dot", Text: "HQ", + Color: "ColorBlue", Side: "WEST", Shape: "ICON", Alpha: 1.0, + Position: core.Position3D{X: 300, Y: 400, Z: 0}}) + + s.HandleTimeState(core.TimeState{CaptureFrame: 0, + SystemTimeUTC: "2026-02-16T12:00:00Z", MissionDate: "2035-06-15", + TimeMultiplier: 1.0, MissionTime: 360}) + + // Get v1 JSON and round-trip through JSON marshal/unmarshal + data := s.ToV1JSON() + jsonBytes, err := json.Marshal(data) + require.NoError(t, err) + + var parsed map[string]interface{} + err = json.Unmarshal(jsonBytes, &parsed) + require.NoError(t, err) + + // Verify top-level fields + assert.Equal(t, "stratis", parsed["worldName"]) + assert.Equal(t, "RoundTrip Test", parsed["missionName"]) + assert.NotNil(t, parsed["endFrame"]) + assert.NotNil(t, parsed["captureDelay"]) + + // Entities indexed by ID: 0..100 + entities := parsed["entities"].([]interface{}) + assert.Len(t, entities, 101) // maxID=100 + + // Verify soldier entity at index 1 + ent := entities[1].(map[string]interface{}) + assert.Equal(t, "unit", ent["type"]) + assert.Contains(t, ent, "positions") + assert.Contains(t, ent, "startFrameNum") + assert.Contains(t, ent, "framesFired") + + positions := ent["positions"].([]interface{}) + assert.Len(t, positions, 5) + + // Each position: [[x,y,z], bearing, lifestate, inVehicleID, name, isPlayer, role] + pos := positions[0].([]interface{}) + assert.Len(t, pos, 7) + + // Events + events := parsed["events"].([]interface{}) + assert.Len(t, events, 1) // 1 kill + + // Kill event uses old format: [frame, "killed", victimId, [killerId, weapon], distance] + killEvt := events[0].([]interface{}) + assert.Len(t, killEvt, 5) + assert.Equal(t, "killed", killEvt[1]) + killerArr := killEvt[3].([]interface{}) + assert.Len(t, killerArr, 2) // [killerId, weapon] + + // Markers + markers := parsed["Markers"].([]interface{}) + assert.Len(t, markers, 1) + + // Marker format: [type, text, startFrame, endFrame, playerId, color, side, positions, size, shape, brush] + marker := markers[0].([]interface{}) + assert.Len(t, marker, 11) + assert.Equal(t, "hd_dot", marker[0]) + assert.Equal(t, "HQ", marker[1]) + + // Times + times := parsed["times"].([]interface{}) + assert.Len(t, times, 1) +} + +func TestSession_EmptySession(t *testing.T) { + s := NewSession() + data := s.ToV1JSON() + + assert.Equal(t, "", data["worldName"]) + assert.Equal(t, "", data["missionName"]) + assert.Equal(t, uint(0), data["endFrame"]) + + entities := data["entities"].([]any) + assert.Empty(t, entities) +} + +func TestSession_Accessors(t *testing.T) { + s := NewSession() + assert.Nil(t, s.Mission()) + assert.Nil(t, s.World()) + assert.Equal(t, uint(0), s.FrameCount()) + + s.SetMission(&core.Mission{MissionName: "Test"}, &core.World{WorldName: "altis"}) + assert.Equal(t, "Test", s.Mission().MissionName) + assert.Equal(t, "altis", s.World().WorldName) +} + +func ptrUint(v uint) *uint { + return &v +} diff --git a/internal/ingestion/writer_v2.go b/internal/ingestion/writer_v2.go new file mode 100644 index 000000000..8a66ba68c --- /dev/null +++ b/internal/ingestion/writer_v2.go @@ -0,0 +1,639 @@ +package ingestion + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "google.golang.org/protobuf/proto" + + "github.com/OCAP2/extension/v5/pkg/core" + pbv2 "github.com/OCAP2/web/pkg/schemas/protobuf/v2" +) + +const defaultChunkSize = 300 + +// WriteV2Manifest writes the v2 protobuf manifest and a JSON archive from session data. +// Chunks are expected to have been written already by the ChunkFlusher. +func WriteV2Manifest(session *Session, outputDir string, chunkCount uint32) error { + manifest := buildManifest(session, chunkCount) + + // Write protobuf manifest. + data, err := proto.Marshal(manifest) + if err != nil { + return fmt.Errorf("marshal manifest: %w", err) + } + if err := os.WriteFile(filepath.Join(outputDir, "manifest.pb"), data, 0644); err != nil { + return fmt.Errorf("write manifest.pb: %w", err) + } + + // Write JSON archive for debugging. + jsonData, err := json.MarshalIndent(manifestToJSON(manifest), "", " ") + if err != nil { + return fmt.Errorf("marshal JSON archive: %w", err) + } + if err := os.WriteFile(filepath.Join(outputDir, "manifest.json"), jsonData, 0644); err != nil { + return fmt.Errorf("write manifest.json: %w", err) + } + + return nil +} + +func buildManifest(session *Session, chunkCount uint32) *pbv2.Manifest { + // Derive fire lines, markers, and hit events from raw projectile data. + session.deriveProjectileData() + + chunkSize := uint32(defaultChunkSize) + + manifest := &pbv2.Manifest{ + Version: 2, + FrameCount: uint32(session.frameCount), + ChunkSize: chunkSize, + CaptureDelayMs: captureDelayMs(session), + ChunkCount: chunkCount, + } + + // World metadata. + if session.world != nil { + manifest.World = &pbv2.WorldMeta{ + WorldName: session.world.WorldName, + WorldSize: session.world.WorldSize, + Latitude: session.world.Latitude, + Longitude: session.world.Longitude, + Author: session.world.Author, + DisplayName: session.world.DisplayName, + } + } + + // Mission metadata. + if session.mission != nil { + m := session.mission + manifest.Mission = &pbv2.MissionMeta{ + MissionName: m.MissionName, + BriefingName: m.BriefingName, + Author: m.Author, + ServerName: m.ServerName, + ExtensionVersion: m.ExtensionVersion, + AddonVersion: m.AddonVersion, + ExtensionBuild: m.ExtensionBuild, + Tag: m.Tag, + PlayableSlots: &pbv2.PlayableSlots{ + West: uint32(m.PlayableSlots.West), + East: uint32(m.PlayableSlots.East), + Independent: uint32(m.PlayableSlots.Independent), + Civilian: uint32(m.PlayableSlots.Civilian), + }, + SideFriendly: &pbv2.SideFriendly{ + EastWest: m.SideFriendly.EastWest, + EastIndependent: m.SideFriendly.EastIndependent, + WestIndependent: m.SideFriendly.WestIndependent, + }, + } + for _, addon := range m.Addons { + manifest.Mission.Addons = append(manifest.Mission.Addons, &pbv2.Addon{ + Name: addon.Name, + WorkshopId: addon.WorkshopID, + }) + } + } + + // Soldier definitions. + for _, rec := range session.soldiers { + manifest.Soldiers = append(manifest.Soldiers, soldierDefToProto(rec)) + } + + // Vehicle definitions. + for _, rec := range session.vehicles { + manifest.Vehicles = append(manifest.Vehicles, vehicleDefToProto(rec)) + } + + // Events. + for _, rec := range session.events { + if evt := eventToProto(rec); evt != nil { + manifest.Events = append(manifest.Events, evt) + } + } + + // Raw projectile events (1:1 storage for future access). + for _, pe := range session.projectiles { + manifest.Events = append(manifest.Events, projectileToProtoEvent(pe)) + } + + // Telemetry (performance + entity counts + weather + player network). + for _, te := range session.telemetry { + manifest.Events = append(manifest.Events, telemetryToProtoEvent(te)) + } + + // Markers. + for _, rec := range session.markers { + manifest.Markers = append(manifest.Markers, markerToProto(rec)) + } + + // Times. + for _, ts := range session.times { + manifest.Times = append(manifest.Times, timeToProto(ts)) + } + + return manifest +} + +func soldierDefToProto(rec *soldierRecord) *pbv2.SoldierDef { + sol := rec.Soldier + def := &pbv2.SoldierDef{ + Id: uint32(sol.ID), + Name: sol.UnitName, + Side: sideStringToProto(sol.Side), + GroupName: sol.GroupID, + Role: sol.RoleDescription, + StartFrame: uint32(sol.JoinFrame), + IsPlayer: sol.IsPlayer, + ClassName: sol.ClassName, + PlayerUid: sol.PlayerUID, + } + if len(rec.States) > 0 { + lastFrame := rec.States[len(rec.States)-1].CaptureFrame + def.EndFrame = uint32(lastFrame) + } + + for _, fe := range rec.FiredEvents { + def.FramesFired = append(def.FramesFired, &pbv2.FiredFrame{ + FrameNum: uint32(fe.CaptureFrame), + StartPos: pos3DToProto(fe.StartPos), + EndPos: pos3DToProto(fe.EndPos), + Weapon: fe.Weapon, + Magazine: fe.Magazine, + FiringMode: fe.FiringMode, + }) + } + for _, pe := range rec.bulletFireLines { + endPos := projectileEndPos(pe) + startPos := core.Position3D{} + if len(pe.Trajectory) > 0 { + startPos = pe.Trajectory[0].Position + } + def.FramesFired = append(def.FramesFired, &pbv2.FiredFrame{ + FrameNum: uint32(pe.CaptureFrame), + StartPos: pos3DToProto(startPos), + EndPos: pos3DToProto(endPos), + Weapon: pe.WeaponDisplay, + Magazine: pe.MagazineDisplay, + }) + } + + return def +} + +func vehicleDefToProto(rec *vehicleRecord) *pbv2.VehicleDef { + veh := rec.Vehicle + def := &pbv2.VehicleDef{ + Id: uint32(veh.ID), + Name: veh.DisplayName, + VehicleClass: veh.OcapType, + ClassName: veh.ClassName, + StartFrame: uint32(veh.JoinFrame), + Customization: veh.Customization, + } + if len(rec.States) > 0 { + lastFrame := rec.States[len(rec.States)-1].CaptureFrame + def.EndFrame = uint32(lastFrame) + } + return def +} + +func eventToProto(rec eventRecord) *pbv2.Event { + evt := &pbv2.Event{ + FrameNum: uint32(rec.frame), + } + + switch rec.eventType { + case "killed": + if rec.kill == nil { + return nil + } + k := rec.kill + kill := &pbv2.KillEvent{ + WeaponName: k.WeaponName, + WeaponMagazine: k.WeaponMagazine, + EventText: k.EventText, + Distance: k.Distance, + } + if k.VictimSoldierID != nil { + kill.VictimSoldierId = uint32(*k.VictimSoldierID) + } + if k.VictimVehicleID != nil { + kill.VictimVehicleId = uint32(*k.VictimVehicleID) + kill.VictimIsVehicle = true + } + if k.KillerSoldierID != nil { + kill.KillerSoldierId = uint32(*k.KillerSoldierID) + } + if k.KillerVehicleID != nil { + kill.KillerVehicleId = uint32(*k.KillerVehicleID) + kill.KillerIsVehicle = true + } + evt.Event = &pbv2.Event_Kill{Kill: kill} + + case "hit": + if rec.hit == nil { + return nil + } + h := rec.hit + hit := &pbv2.HitEvent{ + WeaponName: h.WeaponName, + WeaponMagazine: h.WeaponMagazine, + EventText: h.EventText, + Distance: h.Distance, + } + if h.VictimSoldierID != nil { + hit.VictimSoldierId = uint32(*h.VictimSoldierID) + } + if h.VictimVehicleID != nil { + hit.VictimVehicleId = uint32(*h.VictimVehicleID) + hit.VictimIsVehicle = true + } + if h.ShooterSoldierID != nil { + hit.ShooterSoldierId = uint32(*h.ShooterSoldierID) + } + if h.ShooterVehicleID != nil { + hit.ShooterVehicleId = uint32(*h.ShooterVehicleID) + hit.ShooterIsVehicle = true + } + evt.Event = &pbv2.Event_Hit{Hit: hit} + + case "chat": + if rec.chat == nil { + return nil + } + c := rec.chat + chat := &pbv2.ChatEvent{ + Channel: c.Channel, + FromName: c.FromName, + Message: c.Message, + PlayerUid: c.PlayerUID, + } + if c.SoldierID != nil { + chat.SoldierId = uint32(*c.SoldierID) + } + evt.Event = &pbv2.Event_Chat{Chat: chat} + + default: + if rec.general != nil { + evt.Event = &pbv2.Event_General{General: &pbv2.GeneralEvent{ + Name: rec.eventType, + Message: rec.general.Message, + }} + } else { + return nil + } + } + + return evt +} + +func markerToProto(rec *markerRecord) *pbv2.MarkerDef { + m := rec.Marker + endFrame := uint32(0) + if m.EndFrame > 0 { + endFrame = uint32(m.EndFrame) + } + + marker := &pbv2.MarkerDef{ + Type: m.MarkerType, + Text: m.Text, + StartFrame: uint32(m.CaptureFrame), + EndFrame: endFrame, + PlayerId: int32(m.OwnerID), + Color: strings.TrimPrefix(m.Color, "#"), + Side: sideStringToProto(m.Side), + Shape: m.Shape, + Brush: m.Brush, + Size: parseMarkerSizeF32(m.Size), + } + + // Initial position. + if m.Shape == "POLYLINE" && len(m.Polyline) > 0 { + coords := make([]float32, 0, len(m.Polyline)*2) + for _, pt := range m.Polyline { + coords = append(coords, float32(pt.X), float32(pt.Y)) + } + marker.Positions = append(marker.Positions, &pbv2.MarkerPosition{ + FrameNum: uint32(m.CaptureFrame), + Direction: m.Direction, + Alpha: m.Alpha, + LineCoords: coords, + }) + } else { + marker.Positions = append(marker.Positions, &pbv2.MarkerPosition{ + FrameNum: uint32(m.CaptureFrame), + Position: &pbv2.Position3D{ + X: float32(m.Position.X), + Y: float32(m.Position.Y), + Z: float32(m.Position.Z), + }, + Direction: m.Direction, + Alpha: m.Alpha, + }) + + for _, st := range rec.States { + marker.Positions = append(marker.Positions, &pbv2.MarkerPosition{ + FrameNum: uint32(st.CaptureFrame), + Position: &pbv2.Position3D{ + X: float32(st.Position.X), + Y: float32(st.Position.Y), + Z: float32(st.Position.Z), + }, + Direction: st.Direction, + Alpha: st.Alpha, + }) + } + } + + return marker +} + +func timeToProto(ts core.TimeState) *pbv2.TimeSample { + return &pbv2.TimeSample{ + FrameNum: uint32(ts.CaptureFrame), + SystemTimeUtc: ts.SystemTimeUTC, + Date: ts.MissionDate, + TimeMultiplier: ts.TimeMultiplier, + Time: ts.MissionTime, + } +} + +// --- Helpers --- + +func pos3DToProto(p core.Position3D) *pbv2.Position3D { + return &pbv2.Position3D{ + X: float32(p.X), + Y: float32(p.Y), + Z: float32(p.Z), + } +} + +func sideStringToProto(s string) pbv2.Side { + switch strings.ToUpper(s) { + case "WEST", "BLUFOR": + return pbv2.Side_SIDE_WEST + case "EAST", "OPFOR": + return pbv2.Side_SIDE_EAST + case "GUER", "INDEPENDENT": + return pbv2.Side_SIDE_GUER + case "CIV", "CIVILIAN": + return pbv2.Side_SIDE_CIV + case "GLOBAL": + return pbv2.Side_SIDE_GLOBAL + default: + return pbv2.Side_SIDE_UNKNOWN + } +} + +func captureDelayMs(session *Session) uint32 { + if session.mission != nil && session.mission.CaptureDelay > 0 { + return uint32(session.mission.CaptureDelay * 1000) + } + return 1000 +} + +// SoldierStateToProto converts a core.SoldierState to a v2 protobuf SoldierState. +func SoldierStateToProto(st core.SoldierState) *pbv2.SoldierState { + state := &pbv2.SoldierState{ + Id: uint32(st.SoldierID), + Position: pos3DToProto(st.Position), + Bearing: uint32(st.Bearing), + Lifestate: uint32(st.Lifestate), + InVehicle: st.InVehicle, + Name: st.UnitName, + IsPlayer: st.IsPlayer, + GroupName: st.GroupID, + Side: st.Side, + Role: st.CurrentRole, + VehicleRole: st.VehicleRole, + Stance: st.Stance, + HasStableVitals: st.HasStableVitals, + IsDraggedCarried: st.IsDraggedCarried, + Scores: &pbv2.SoldierScores{ + InfantryKills: uint32(st.Scores.InfantryKills), + VehicleKills: uint32(st.Scores.VehicleKills), + ArmorKills: uint32(st.Scores.ArmorKills), + AirKills: uint32(st.Scores.AirKills), + Deaths: uint32(st.Scores.Deaths), + TotalScore: uint32(st.Scores.TotalScore), + }, + } + if st.InVehicleObjectID != nil { + state.VehicleId = uint32(*st.InVehicleObjectID) + } + return state +} + +// VehicleStateToProto converts a core.VehicleState to a v2 protobuf VehicleState. +func VehicleStateToProto(st core.VehicleState) *pbv2.VehicleState { + state := &pbv2.VehicleState{ + Id: uint32(st.VehicleID), + Position: pos3DToProto(st.Position), + Bearing: uint32(st.Bearing), + Alive: st.IsAlive, + Fuel: st.Fuel, + Damage: st.Damage, + Locked: st.Locked, + EngineOn: st.EngineOn, + Side: st.Side, + TurretAzimuth: st.TurretAzimuth, + TurretElevation: st.TurretElevation, + } + + // Parse crew JSON string to extract crew IDs. + if st.Crew != "" { + var crewIDs []uint32 + if err := json.Unmarshal([]byte(st.Crew), &crewIDs); err == nil { + state.CrewIds = crewIDs + } + } + + return state +} + +func parseMarkerSizeF32(sizeStr string) []float32 { + f64 := parseMarkerSize(sizeStr) + f32 := make([]float32, len(f64)) + for i, v := range f64 { + f32[i] = float32(v) + } + return f32 +} + +// manifestToJSON creates a JSON-serializable representation of the manifest for debugging. +func manifestToJSON(m *pbv2.Manifest) map[string]any { + result := map[string]any{ + "version": m.Version, + "frameCount": m.FrameCount, + "chunkSize": m.ChunkSize, + "captureDelayMs": m.CaptureDelayMs, + "chunkCount": m.ChunkCount, + } + + if m.World != nil { + result["world"] = map[string]any{ + "worldName": m.World.WorldName, + "worldSize": m.World.WorldSize, + "latitude": m.World.Latitude, + "longitude": m.World.Longitude, + "author": m.World.Author, + "displayName": m.World.DisplayName, + } + } + + if m.Mission != nil { + mission := map[string]any{ + "missionName": m.Mission.MissionName, + "briefingName": m.Mission.BriefingName, + "author": m.Mission.Author, + "serverName": m.Mission.ServerName, + "extensionVersion": m.Mission.ExtensionVersion, + "addonVersion": m.Mission.AddonVersion, + "extensionBuild": m.Mission.ExtensionBuild, + "tag": m.Mission.Tag, + } + if m.Mission.PlayableSlots != nil { + mission["playableSlots"] = m.Mission.PlayableSlots + } + if m.Mission.SideFriendly != nil { + mission["sideFriendly"] = m.Mission.SideFriendly + } + if len(m.Mission.Addons) > 0 { + mission["addons"] = m.Mission.Addons + } + result["mission"] = mission + } + + result["soldierCount"] = len(m.Soldiers) + result["vehicleCount"] = len(m.Vehicles) + result["eventCount"] = len(m.Events) + result["markerCount"] = len(m.Markers) + result["timeCount"] = len(m.Times) + + return result +} + +// telemetryToProtoEvent converts a core.TelemetryEvent to a v2 proto Event. +func telemetryToProtoEvent(te core.TelemetryEvent) *pbv2.Event { + t := &pbv2.TelemetryEvent{ + FpsAverage: te.FpsAverage, + FpsMin: te.FpsMin, + SideEntityCounts: &pbv2.SideEntityCounts{ + East: sideEntityCountToProto(te.SideEntityCounts.East), + West: sideEntityCountToProto(te.SideEntityCounts.West), + Independent: sideEntityCountToProto(te.SideEntityCounts.Independent), + Civilian: sideEntityCountToProto(te.SideEntityCounts.Civilian), + }, + GlobalCounts: &pbv2.GlobalEntityCount{ + UnitsAlive: uint32(te.GlobalCounts.UnitsAlive), + UnitsDead: uint32(te.GlobalCounts.UnitsDead), + Groups: uint32(te.GlobalCounts.Groups), + Vehicles: uint32(te.GlobalCounts.Vehicles), + WeaponHolders: uint32(te.GlobalCounts.WeaponHolders), + PlayersAlive: uint32(te.GlobalCounts.PlayersAlive), + PlayersDead: uint32(te.GlobalCounts.PlayersDead), + PlayersConnected: uint32(te.GlobalCounts.PlayersConnected), + }, + Scripts: &pbv2.ScriptCounts{ + Spawn: uint32(te.Scripts.Spawn), + ExecVm: uint32(te.Scripts.ExecVM), + Exec: uint32(te.Scripts.Exec), + ExecFsm: uint32(te.Scripts.ExecFSM), + Pfh: uint32(te.Scripts.PFH), + }, + Weather: &pbv2.WeatherData{ + Fog: te.Weather.Fog, + Overcast: te.Weather.Overcast, + Rain: te.Weather.Rain, + Humidity: te.Weather.Humidity, + Waves: te.Weather.Waves, + WindDir: te.Weather.WindDir, + WindStr: te.Weather.WindStr, + Gusts: te.Weather.Gusts, + Lightnings: te.Weather.Lightnings, + MoonIntensity: te.Weather.MoonIntensity, + MoonPhase: te.Weather.MoonPhase, + SunOrMoon: te.Weather.SunOrMoon, + }, + } + for _, p := range te.Players { + t.Players = append(t.Players, &pbv2.PlayerNetworkData{ + Uid: p.UID, + Name: p.Name, + Ping: p.Ping, + Bw: p.BW, + Desync: p.Desync, + }) + } + return &pbv2.Event{ + FrameNum: uint32(te.CaptureFrame), + Event: &pbv2.Event_Telemetry{Telemetry: t}, + } +} + +func sideEntityCountToProto(sec core.SideEntityCount) *pbv2.SideEntityCount { + return &pbv2.SideEntityCount{ + Local: &pbv2.EntityLocality{ + UnitsTotal: uint32(sec.Local.UnitsTotal), + UnitsAlive: uint32(sec.Local.UnitsAlive), + UnitsDead: uint32(sec.Local.UnitsDead), + Groups: uint32(sec.Local.Groups), + Vehicles: uint32(sec.Local.Vehicles), + WeaponHolders: uint32(sec.Local.WeaponHolders), + }, + Remote: &pbv2.EntityLocality{ + UnitsTotal: uint32(sec.Remote.UnitsTotal), + UnitsAlive: uint32(sec.Remote.UnitsAlive), + UnitsDead: uint32(sec.Remote.UnitsDead), + Groups: uint32(sec.Remote.Groups), + Vehicles: uint32(sec.Remote.Vehicles), + WeaponHolders: uint32(sec.Remote.WeaponHolders), + }, + } +} + +// projectileToProtoEvent converts a raw core.ProjectileEvent to a v2 proto Event. +func projectileToProtoEvent(pe core.ProjectileEvent) *pbv2.Event { + proj := &pbv2.ProjectileEvent{ + FirerId: uint32(pe.FirerObjectID), + Weapon: pe.WeaponDisplay, + Magazine: pe.MagazineDisplay, + Muzzle: pe.MuzzleDisplay, + MagazineIcon: pe.MagazineIcon, + SimulationType: pe.SimulationType, + } + if pe.VehicleObjectID != nil { + proj.VehicleId = uint32(*pe.VehicleObjectID) + } + for _, tp := range pe.Trajectory { + proj.Trajectory = append(proj.Trajectory, &pbv2.TrajectoryPoint{ + Position: pos3DToProto(tp.Position), + FrameNum: uint32(tp.Frame), + }) + } + for _, h := range pe.Hits { + hit := &pbv2.ProjectileHit{ + FrameNum: uint32(h.CaptureFrame), + Position: pos3DToProto(h.Position), + ComponentsHit: h.ComponentsHit, + } + if h.SoldierID != nil { + hit.SoldierId = uint32(*h.SoldierID) + hit.HitSoldier = true + } + if h.VehicleID != nil { + hit.VehicleId = uint32(*h.VehicleID) + hit.HitVehicle = true + } + proj.Hits = append(proj.Hits, hit) + } + return &pbv2.Event{ + FrameNum: uint32(pe.CaptureFrame), + Event: &pbv2.Event_Projectile{Projectile: proj}, + } +} diff --git a/internal/ingestion/writer_v2_test.go b/internal/ingestion/writer_v2_test.go new file mode 100644 index 000000000..cbde23d46 --- /dev/null +++ b/internal/ingestion/writer_v2_test.go @@ -0,0 +1,283 @@ +package ingestion + +import ( + "os" + "path/filepath" + "testing" + + "github.com/OCAP2/extension/v5/pkg/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + pbv2 "github.com/OCAP2/web/pkg/schemas/protobuf/v2" +) + +func TestWriteV2Manifest_Roundtrip(t *testing.T) { + session := NewSession() + + session.SetMission( + &core.Mission{ + MissionName: "Test Mission", + BriefingName: "Briefing", + Author: "TestAuthor", + ServerName: "TestServer", + ExtensionVersion: "5.0.0", + AddonVersion: "1.2.3", + ExtensionBuild: "abc123", + Tag: "test", + CaptureDelay: 1.0, + PlayableSlots: core.PlayableSlots{West: 10, East: 8, Independent: 4, Civilian: 2}, + SideFriendly: core.SideFriendly{EastWest: false, EastIndependent: true, WestIndependent: false}, + Addons: []core.Addon{{Name: "CBA_A3", WorkshopID: "450814997"}}, + }, + &core.World{ + WorldName: "Altis", + WorldSize: 30720, + Latitude: -35.09, + Longitude: 16.81, + Author: "BIS", + DisplayName: "Altis", + }, + ) + + // Add a soldier. + session.HandleAddSoldier(core.Soldier{ + ID: 1, + UnitName: "TestUnit", + Side: "WEST", + GroupID: "Alpha", + RoleDescription: "Rifleman", + JoinFrame: 0, + IsPlayer: true, + ClassName: "B_Soldier_F", + PlayerUID: "76561198000000000", + }) + + // Add soldier states. + session.HandleSoldierState(core.SoldierState{ + SoldierID: 1, + CaptureFrame: 0, + Position: core.Position3D{X: 100, Y: 200, Z: 10}, + Bearing: 90, + Lifestate: 1, + IsPlayer: true, + UnitName: "TestUnit", + GroupID: "Alpha", + Side: "WEST", + CurrentRole: "Rifleman", + Stance: "STAND", + }) + + // Add a vehicle. + session.HandleAddVehicle(core.Vehicle{ + ID: 10, + DisplayName: "Hunter", + OcapType: "car", + ClassName: "B_MRAP_01_F", + JoinFrame: 0, + }) + + // Add vehicle state. + session.HandleVehicleState(core.VehicleState{ + VehicleID: 10, + CaptureFrame: 0, + Position: core.Position3D{X: 150, Y: 250, Z: 5}, + Bearing: 180, + IsAlive: true, + Fuel: 0.75, + Damage: 0.1, + Side: "WEST", + }) + + // Add events. + victimID := uint(1) + killerID := uint(2) + session.HandleKillEvent(core.KillEvent{ + CaptureFrame: 50, + VictimSoldierID: &victimID, + KillerSoldierID: &killerID, + WeaponName: "arifle_MX_F", + WeaponMagazine: "30Rnd_65x39_caseless_mag", + EventText: "Player1 killed Player2", + Distance: 150.5, + }) + + session.HandleChatEvent(core.ChatEvent{ + CaptureFrame: 10, + Channel: "side", + FromName: "TestUnit", + Message: "Hello world", + PlayerUID: "76561198000000000", + }) + + session.HandleTimeState(core.TimeState{ + CaptureFrame: 0, + SystemTimeUTC: "2025-01-15T10:30:00Z", + MissionDate: "2035/6/15", + TimeMultiplier: 1.0, + MissionTime: 28800, + }) + + // Add a fired event. + session.HandleFiredEvent(core.FiredEvent{ + SoldierID: 1, + CaptureFrame: 45, + Weapon: "arifle_MX_F", + Magazine: "30Rnd_65x39_caseless_mag", + FiringMode: "Single", + StartPos: core.Position3D{X: 100, Y: 200, Z: 11}, + EndPos: core.Position3D{X: 200, Y: 300, Z: 10}, + }) + + // Write. + dir := t.TempDir() + require.NoError(t, WriteV2Manifest(session, dir, 1)) + + // Read back protobuf manifest. + data, err := os.ReadFile(filepath.Join(dir, "manifest.pb")) + require.NoError(t, err) + + var manifest pbv2.Manifest + require.NoError(t, proto.Unmarshal(data, &manifest)) + + // Verify top-level fields. + assert.Equal(t, uint32(2), manifest.Version) + assert.Equal(t, uint32(1), manifest.ChunkCount) + + // Verify world. + require.NotNil(t, manifest.World) + assert.Equal(t, "Altis", manifest.World.WorldName) + assert.InDelta(t, float32(30720), manifest.World.WorldSize, 0.01) + assert.InDelta(t, float32(-35.09), manifest.World.Latitude, 0.01) + + // Verify mission. + require.NotNil(t, manifest.Mission) + assert.Equal(t, "Test Mission", manifest.Mission.MissionName) + assert.Equal(t, "TestServer", manifest.Mission.ServerName) + assert.Equal(t, "5.0.0", manifest.Mission.ExtensionVersion) + require.NotNil(t, manifest.Mission.PlayableSlots) + assert.Equal(t, uint32(10), manifest.Mission.PlayableSlots.West) + require.Len(t, manifest.Mission.Addons, 1) + assert.Equal(t, "CBA_A3", manifest.Mission.Addons[0].Name) + + // Verify soldiers. + require.Len(t, manifest.Soldiers, 1) + sol := manifest.Soldiers[0] + assert.Equal(t, uint32(1), sol.Id) + assert.Equal(t, "TestUnit", sol.Name) + assert.Equal(t, pbv2.Side_SIDE_WEST, sol.Side) + assert.True(t, sol.IsPlayer) + assert.Equal(t, "B_Soldier_F", sol.ClassName) + require.Len(t, sol.FramesFired, 1) + assert.Equal(t, "arifle_MX_F", sol.FramesFired[0].Weapon) + + // Verify vehicles. + require.Len(t, manifest.Vehicles, 1) + veh := manifest.Vehicles[0] + assert.Equal(t, uint32(10), veh.Id) + assert.Equal(t, "Hunter", veh.Name) + assert.Equal(t, "car", veh.VehicleClass) + + // Verify events. + require.GreaterOrEqual(t, len(manifest.Events), 2) + var foundKill, foundChat bool + for _, evt := range manifest.Events { + if kill := evt.GetKill(); kill != nil { + foundKill = true + assert.Equal(t, uint32(1), kill.VictimSoldierId) + assert.Equal(t, uint32(2), kill.KillerSoldierId) + assert.Equal(t, "arifle_MX_F", kill.WeaponName) + assert.InDelta(t, float32(150.5), kill.Distance, 0.1) + } + if chat := evt.GetChat(); chat != nil { + foundChat = true + assert.Equal(t, "side", chat.Channel) + assert.Equal(t, "Hello world", chat.Message) + } + } + assert.True(t, foundKill, "expected kill event in manifest") + assert.True(t, foundChat, "expected chat event in manifest") + + // Verify times. + require.Len(t, manifest.Times, 1) + assert.Equal(t, "2025-01-15T10:30:00Z", manifest.Times[0].SystemTimeUtc) + + // Verify JSON archive also exists. + jsonData, err := os.ReadFile(filepath.Join(dir, "manifest.json")) + require.NoError(t, err) + assert.Contains(t, string(jsonData), "Test Mission") +} + +func TestSoldierStateToProto(t *testing.T) { + vehicleID := uint16(10) + st := core.SoldierState{ + SoldierID: 1, + CaptureFrame: 5, + Position: core.Position3D{X: 100, Y: 200, Z: 10}, + Bearing: 90, + Lifestate: 1, + InVehicle: true, + InVehicleObjectID: &vehicleID, + VehicleRole: "driver", + UnitName: "TestUnit", + IsPlayer: true, + GroupID: "Alpha", + Side: "WEST", + CurrentRole: "Rifleman", + Stance: "CROUCH", + HasStableVitals: true, + IsDraggedCarried: false, + Scores: core.SoldierScores{ + InfantryKills: 2, + VehicleKills: 1, + Deaths: 0, + TotalScore: 5, + }, + } + + pb := SoldierStateToProto(st) + assert.Equal(t, uint32(1), pb.Id) + assert.InDelta(t, float32(100), pb.Position.X, 0.01) + assert.Equal(t, uint32(90), pb.Bearing) + assert.Equal(t, uint32(1), pb.Lifestate) + assert.True(t, pb.InVehicle) + assert.Equal(t, uint32(10), pb.VehicleId) + assert.Equal(t, "driver", pb.VehicleRole) + assert.Equal(t, "CROUCH", pb.Stance) + assert.True(t, pb.HasStableVitals) + require.NotNil(t, pb.Scores) + assert.Equal(t, uint32(2), pb.Scores.InfantryKills) + assert.Equal(t, uint32(5), pb.Scores.TotalScore) +} + +func TestVehicleStateToProto(t *testing.T) { + st := core.VehicleState{ + VehicleID: 10, + CaptureFrame: 5, + Position: core.Position3D{X: 150, Y: 250, Z: 5}, + Bearing: 180, + IsAlive: true, + Crew: "[1,2,3]", + Fuel: 0.6, + Damage: 0.2, + Locked: true, + EngineOn: true, + Side: "EAST", + TurretAzimuth: 45.0, + TurretElevation: -5.0, + } + + pb := VehicleStateToProto(st) + assert.Equal(t, uint32(10), pb.Id) + assert.InDelta(t, float32(150), pb.Position.X, 0.01) + assert.Equal(t, uint32(180), pb.Bearing) + assert.True(t, pb.Alive) + assert.Equal(t, []uint32{1, 2, 3}, pb.CrewIds) + assert.InDelta(t, float32(0.6), pb.Fuel, 0.01) + assert.InDelta(t, float32(0.2), pb.Damage, 0.01) + assert.True(t, pb.Locked) + assert.True(t, pb.EngineOn) + assert.Equal(t, "EAST", pb.Side) + assert.InDelta(t, float32(45.0), pb.TurretAzimuth, 0.1) +} diff --git a/internal/server/handler_stream.go b/internal/server/handler_stream.go index ee367b83c..13e4f3d76 100644 --- a/internal/server/handler_stream.go +++ b/internal/server/handler_stream.go @@ -1,12 +1,20 @@ package server import ( + "context" "encoding/json" + "fmt" "log/slog" "net/http" + "os" + "path/filepath" "sync" "time" + "github.com/OCAP2/extension/v5/pkg/core" + "github.com/OCAP2/extension/v5/pkg/streaming" + "github.com/OCAP2/web/internal/ingestion" + "github.com/OCAP2/web/internal/storage" "github.com/gorilla/websocket" "github.com/labstack/echo/v4" ) @@ -75,23 +83,40 @@ func (h *Handler) streamLoop(ws *websocket.Conn) { }() defer close(done) - // Message counts for logging + var session *ingestion.Session + var v2OutputDir string // set on start_mission when data dir is available + var streamingOpID int64 // DB row created at start_mission, updated at finalize counts := make(map[string]int) + writeAck := func(msgType string) { + ack, _ := json.Marshal(streaming.AckMessage{Type: "ack", For: msgType}) + mu.Lock() + ws.WriteMessage(websocket.TextMessage, ack) + mu.Unlock() + } + + finalize := func(tag string) { + if session == nil { + return + } + if err := h.finalizeSession(session, tag, v2OutputDir, streamingOpID); err != nil { + slog.Error("stream: finalization failed", "error", err) + } + } + for { _, msg, err := ws.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) { - slog.Warn("stream: unexpected disconnect", "error", err, "counts", counts) + slog.Warn("stream: unexpected disconnect, finalizing partial data", "error", err, "counts", counts) + finalize("partial") } else { slog.Info("stream: connection closed", "counts", counts) } return } - var envelope struct { - Type string `json:"type"` - } + var envelope streaming.Envelope if err := json.Unmarshal(msg, &envelope); err != nil { slog.Warn("stream: invalid message", "error", err) continue @@ -99,21 +124,314 @@ func (h *Handler) streamLoop(ws *websocket.Conn) { counts[envelope.Type]++ + // All messages except start_mission and end_mission require an active session. + if session == nil && envelope.Type != streaming.TypeStartMission && envelope.Type != streaming.TypeEndMission { + slog.Warn("stream: message received before start_mission, ignoring", "type", envelope.Type) + continue + } + switch envelope.Type { - case "start_mission": - slog.Info("stream: mission started", "message", string(msg)) - ack, _ := json.Marshal(map[string]string{"type": "ack", "for": "start_mission"}) - mu.Lock() - ws.WriteMessage(websocket.TextMessage, ack) - mu.Unlock() - - case "end_mission": + case streaming.TypeStartMission: + var payload streaming.StartMissionPayload + if err := json.Unmarshal(envelope.Payload, &payload); err != nil { + slog.Warn("stream: invalid start_mission payload", "error", err) + continue + } + session = ingestion.NewSession() + session.SetMission(payload.Mission, payload.World) + + // Set up v2 protobuf output directory and chunk flusher. + if h.setting.Data != "" { + v2OutputDir = filepath.Join(h.setting.Data, ingestion.MakeFilename(payload.Mission.MissionName)) + if err := os.MkdirAll(v2OutputDir, 0755); err != nil { + slog.Error("stream: failed to create v2 output dir", "error", err) + } else { + cf, err := ingestion.NewChunkFlusher(v2OutputDir, 300) + if err != nil { + slog.Error("stream: failed to create chunk flusher", "error", err) + } else { + session.SetChunkFlusher(cf) + } + } + } + + slog.Info("stream: mission started", + "mission", payload.Mission.MissionName, + "world", payload.World.WorldName) + + // Insert a "streaming" operation so it appears in the operations list. + filename := ingestion.MakeFilename(payload.Mission.MissionName) + op := Operation{ + WorldName: payload.World.WorldName, + MissionName: payload.Mission.MissionName, + Filename: filename, + Date: time.Now().Format("2006-01-02"), + Tag: payload.Mission.Tag, + StorageFormat: "protobuf", + ConversionStatus: ConversionStatusStreaming, + SchemaVersion: uint32(storage.SchemaVersionV2), + } + if err := h.repoOperation.Store(context.TODO(), &op); err != nil { + slog.Error("stream: failed to store streaming operation", "error", err) + } else { + streamingOpID = op.ID + } + + writeAck(streaming.TypeStartMission) + + case streaming.TypeEndMission: + tag := "" + if session != nil && session.Mission() != nil { + tag = session.Mission().Tag + } slog.Info("stream: mission ended", "counts", counts) - ack, _ := json.Marshal(map[string]string{"type": "ack", "for": "end_mission"}) - mu.Lock() - ws.WriteMessage(websocket.TextMessage, ack) - mu.Unlock() + finalize(tag) + writeAck(streaming.TypeEndMission) return + + case streaming.TypeAddSoldier: + var sol core.Soldier + if err := json.Unmarshal(envelope.Payload, &sol); err != nil { + slog.Warn("stream: invalid add_soldier", "error", err) + continue + } + session.HandleAddSoldier(sol) + + case streaming.TypeSoldierState: + var st core.SoldierState + if err := json.Unmarshal(envelope.Payload, &st); err != nil { + slog.Warn("stream: invalid soldier_state", "error", err) + continue + } + session.HandleSoldierState(st) + + case streaming.TypeAddVehicle: + var veh core.Vehicle + if err := json.Unmarshal(envelope.Payload, &veh); err != nil { + slog.Warn("stream: invalid add_vehicle", "error", err) + continue + } + session.HandleAddVehicle(veh) + + case streaming.TypeVehicleState: + var st core.VehicleState + if err := json.Unmarshal(envelope.Payload, &st); err != nil { + slog.Warn("stream: invalid vehicle_state", "error", err) + continue + } + session.HandleVehicleState(st) + + case streaming.TypeAddMarker: + var m core.Marker + if err := json.Unmarshal(envelope.Payload, &m); err != nil { + slog.Warn("stream: invalid add_marker", "error", err) + continue + } + session.HandleAddMarker(m) + + case streaming.TypeMarkerState: + var st core.MarkerState + if err := json.Unmarshal(envelope.Payload, &st); err != nil { + slog.Warn("stream: invalid marker_state", "error", err) + continue + } + session.HandleMarkerState(st) + + case streaming.TypeDeleteMarker: + var dm core.DeleteMarker + if err := json.Unmarshal(envelope.Payload, &dm); err != nil { + slog.Warn("stream: invalid delete_marker", "error", err) + continue + } + session.HandleDeleteMarker(dm.Name, dm.EndFrame) + + case streaming.TypeFiredEvent: + var fe core.FiredEvent + if err := json.Unmarshal(envelope.Payload, &fe); err != nil { + slog.Warn("stream: invalid fired_event", "error", err) + continue + } + session.HandleFiredEvent(fe) + + case streaming.TypeKillEvent: + var evt core.KillEvent + if err := json.Unmarshal(envelope.Payload, &evt); err != nil { + slog.Warn("stream: invalid kill_event", "error", err) + continue + } + session.HandleKillEvent(evt) + + case streaming.TypeHitEvent: + var evt core.HitEvent + if err := json.Unmarshal(envelope.Payload, &evt); err != nil { + slog.Warn("stream: invalid hit_event", "error", err) + continue + } + session.HandleHitEvent(evt) + + case streaming.TypeGeneralEvent: + var evt core.GeneralEvent + if err := json.Unmarshal(envelope.Payload, &evt); err != nil { + slog.Warn("stream: invalid general_event", "error", err) + continue + } + session.HandleGeneralEvent(evt) + + case streaming.TypeChatEvent: + var evt core.ChatEvent + if err := json.Unmarshal(envelope.Payload, &evt); err != nil { + slog.Warn("stream: invalid chat_event", "error", err) + continue + } + session.HandleChatEvent(evt) + + case streaming.TypeTelemetry: + var evt core.TelemetryEvent + if err := json.Unmarshal(envelope.Payload, &evt); err != nil { + slog.Warn("stream: invalid telemetry", "error", err) + continue + } + session.HandleTelemetry(evt) + + // Update operation metadata so the operations list stays current. + if streamingOpID > 0 { + captureDelay := float64(1.0) + if session.Mission() != nil && session.Mission().CaptureDelay > 0 { + captureDelay = float64(session.Mission().CaptureDelay) + } + duration := float64(session.FrameCount()) * captureDelay + playerCount := int(evt.GlobalCounts.PlayersConnected) + if err := h.repoOperation.UpdateStreamingMeta(context.TODO(), streamingOpID, duration, playerCount); err != nil { + slog.Warn("stream: failed to update streaming meta", "error", err) + } + } + + case streaming.TypeProjectileEvent: + var evt core.ProjectileEvent + if err := json.Unmarshal(envelope.Payload, &evt); err != nil { + slog.Warn("stream: invalid projectile_event", "error", err) + continue + } + session.HandleProjectileEvent(evt) + + case streaming.TypeTimeState: + var ts core.TimeState + if err := json.Unmarshal(envelope.Payload, &ts); err != nil { + slog.Warn("stream: invalid time_state", "error", err) + continue + } + session.HandleTimeState(ts) + + default: + slog.Warn("stream: unknown message type", "type", envelope.Type) } } } + +// finalizeSession writes v2 protobuf + v1 JSON, stores an Operation, and triggers conversion. +// If streamingOpID > 0, updates the existing "streaming" row instead of inserting a new one. +func (h *Handler) finalizeSession(session *ingestion.Session, tag string, v2OutputDir string, streamingOpID int64) error { + if h.setting.Data == "" { + return nil + } + + if tag == "partial" && session.Mission() != nil && session.Mission().Tag != "" { + tag = session.Mission().Tag + ",partial" + } + + worldName := "" + missionName := "" + if session.World() != nil { + worldName = session.World().WorldName + } + if session.Mission() != nil { + missionName = session.Mission().MissionName + } + + // Compute duration from frame count and capture delay. + captureDelay := float32(1.0) + if session.Mission() != nil && session.Mission().CaptureDelay > 0 { + captureDelay = session.Mission().CaptureDelay + } + duration := float64(session.FrameCount()) * float64(captureDelay) + + // Write v2 protobuf (manifest + finalize chunks). + var chunkCount int + hasV2 := session.ChunkFlusher() != nil && v2OutputDir != "" + if hasV2 { + if err := session.Finalize(v2OutputDir); err != nil { + slog.Error("stream: v2 finalization failed, falling back to v1 only", "error", err) + hasV2 = false + } else { + chunkCount = int(session.ChunkFlusher().ChunkCount()) + } + } + + // Always write v1 JSON.gz as backup/fallback. + v1Filename, err := session.WriteJSONGz(h.setting.Data) + if err != nil { + return fmt.Errorf("write JSON.gz: %w", err) + } + + // Use v2 output directory name as filename if v2 succeeded, otherwise v1. + filename := v1Filename + storageFormat := "json" + schemaVersion := uint32(storage.SchemaVersionV1) + conversionStatus := ConversionStatusPending + + if hasV2 { + filename = filepath.Base(v2OutputDir) + storageFormat = "protobuf" + schemaVersion = uint32(storage.SchemaVersionV2) + conversionStatus = ConversionStatusCompleted // v2 is already in final format + } + + op := Operation{ + WorldName: worldName, + MissionName: missionName, + MissionDuration: duration, + Filename: filename, + Date: time.Now().Format("2006-01-02"), + Tag: tag, + StorageFormat: storageFormat, + ConversionStatus: conversionStatus, + SchemaVersion: schemaVersion, + ChunkCount: chunkCount, + } + ctx := context.TODO() + + if streamingOpID > 0 { + // Update the existing "streaming" row created at start_mission. + op.ID = streamingOpID + if err := h.repoOperation.Update(ctx, &op); err != nil { + return fmt.Errorf("update operation: %w", err) + } + } else { + if err := h.repoOperation.Store(ctx, &op); err != nil { + return fmt.Errorf("store operation: %w", err) + } + } + + slog.Info("stream: finalized", + "id", op.ID, + "filename", filename, + "format", storageFormat, + "schemaVersion", schemaVersion, + "frames", session.FrameCount(), + "chunks", chunkCount, + "duration", duration, + "tag", tag) + + // Only trigger conversion for v1 JSON (v2 is already complete). + if !hasV2 { + if h.conversionTrigger != nil { + h.conversionTrigger.TriggerConversion(op.ID, op.Filename) + } else { + if err := h.repoOperation.UpdateConversionStatus(ctx, op.ID, ConversionStatusCompleted); err != nil { + slog.Error("stream: failed to mark completed", "error", err) + } + } + } + + return nil +} diff --git a/internal/server/handler_stream_test.go b/internal/server/handler_stream_test.go index 81e824aab..6d9940074 100644 --- a/internal/server/handler_stream_test.go +++ b/internal/server/handler_stream_test.go @@ -1,6 +1,8 @@ package server import ( + "context" + "encoding/json" "net/http" "net/http/httptest" "path/filepath" @@ -38,6 +40,49 @@ func newTestStreamHandler(enabled bool) (*Handler, *echo.Echo) { return hdlr, e } +func newTestStreamHandlerWithRepo(t *testing.T) (*Handler, *echo.Echo, string) { + t.Helper() + dir := t.TempDir() + pathDB := filepath.Join(dir, "test.db") + repo, err := NewRepoOperation(pathDB) + require.NoError(t, err) + t.Cleanup(func() { repo.db.Close() }) + + e := echo.New() + hdlr := &Handler{ + repoOperation: repo, + setting: Setting{ + Secret: "test-secret", + Data: dir, + Streaming: Streaming{ + Enabled: true, + PingInterval: 30 * time.Second, + PingTimeout: 10 * time.Second, + }, + }, + } + e.GET("/api/v1/stream", hdlr.HandleStream) + return hdlr, e, dir +} + +// sendStartMission sends a properly formatted start_mission envelope and reads the ack. +func sendStartMission(t *testing.T, conn *websocket.Conn, missionName, worldName string) { + t.Helper() + startPayload, _ := json.Marshal(map[string]any{ + "mission": map[string]any{"MissionName": missionName, "CaptureDelay": 1.0}, + "world": map[string]any{"WorldName": worldName}, + }) + err := conn.WriteJSON(map[string]any{ + "type": "start_mission", + "payload": json.RawMessage(startPayload), + }) + require.NoError(t, err) + var ack map[string]string + err = conn.ReadJSON(&ack) + require.NoError(t, err) + require.Equal(t, "start_mission", ack["for"]) +} + func TestHandleStream_Disabled(t *testing.T) { _, e := newTestStreamHandler(false) srv := httptest.NewServer(e) @@ -93,20 +138,7 @@ func TestHandleStream_StartMissionAck(t *testing.T) { require.NoError(t, err) defer conn.Close() - // Send start_mission - err = conn.WriteJSON(map[string]any{ - "type": "start_mission", - "missionName": "Test Mission", - "worldName": "altis", - }) - require.NoError(t, err) - - // Read ack - var ack map[string]string - err = conn.ReadJSON(&ack) - require.NoError(t, err) - assert.Equal(t, "ack", ack["type"]) - assert.Equal(t, "start_mission", ack["for"]) + sendStartMission(t, conn, "Test Mission", "altis") } func TestHandleStream_EndMissionAckAndClose(t *testing.T) { @@ -119,16 +151,11 @@ func TestHandleStream_EndMissionAckAndClose(t *testing.T) { require.NoError(t, err) defer conn.Close() - // Send start_mission and consume ack - err = conn.WriteJSON(map[string]string{"type": "start_mission"}) - require.NoError(t, err) - var startAck map[string]string - err = conn.ReadJSON(&startAck) - require.NoError(t, err) + sendStartMission(t, conn, "Test", "altis") - // Send some state messages + // Send some state messages (no payload — will be skipped with a warning) for i := 0; i < 5; i++ { - conn.WriteJSON(map[string]any{"type": "soldier_state", "id": i}) + conn.WriteJSON(map[string]any{"type": "soldier_state"}) } // Send end_mission @@ -158,11 +185,9 @@ func TestHandleStream_UnknownTypesAccepted(t *testing.T) { defer conn.Close() // Send unknown message types — should not error - err = conn.WriteJSON(map[string]string{"type": "add_soldier"}) + err = conn.WriteJSON(map[string]string{"type": "unknown_type_1"}) require.NoError(t, err) - err = conn.WriteJSON(map[string]string{"type": "vehicle_state"}) - require.NoError(t, err) - err = conn.WriteJSON(map[string]string{"type": "fired_event"}) + err = conn.WriteJSON(map[string]string{"type": "unknown_type_2"}) require.NoError(t, err) // Send end_mission to cleanly close @@ -187,13 +212,8 @@ func TestHandleStream_InvalidJSON(t *testing.T) { err = conn.WriteMessage(websocket.TextMessage, []byte("not json")) require.NoError(t, err) - // Server should still be alive — send valid message and get ack - err = conn.WriteJSON(map[string]string{"type": "start_mission"}) - require.NoError(t, err) - var ack map[string]string - err = conn.ReadJSON(&ack) - require.NoError(t, err) - assert.Equal(t, "start_mission", ack["for"]) + // Server should still be alive — send valid start_mission + sendStartMission(t, conn, "Test", "altis") } func TestHandleStream_NormalClose(t *testing.T) { @@ -260,3 +280,110 @@ func TestNewHandler_StreamRouteRegistered(t *testing.T) { } assert.Contains(t, routePaths, "/sub/api/v1/stream") } + +func TestHandleStream_FullLifecycle(t *testing.T) { + hdlr, e, dir := newTestStreamHandlerWithRepo(t) + srv := httptest.NewServer(e) + defer srv.Close() + + wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/api/v1/stream?secret=test-secret" + conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + require.NoError(t, err) + defer conn.Close() + + // Send start_mission with envelope format + startPayload, _ := json.Marshal(map[string]any{ + "mission": map[string]any{ + "MissionName": "Lifecycle Test", + "CaptureDelay": 1.0, + "Tag": "TvT", + }, + "world": map[string]any{ + "WorldName": "altis", + }, + }) + conn.WriteJSON(map[string]any{ + "type": "start_mission", + "payload": json.RawMessage(startPayload), + }) + var ack map[string]string + conn.ReadJSON(&ack) + require.Equal(t, "start_mission", ack["for"]) + + // Send add_soldier + solPayload, _ := json.Marshal(map[string]any{ + "ID": 1, "JoinFrame": 0, "OcapType": "unit", + "UnitName": "TestPlayer", "Side": "WEST", + "GroupID": "Alpha", "IsPlayer": true, + }) + conn.WriteJSON(map[string]any{"type": "add_soldier", "payload": json.RawMessage(solPayload)}) + + // Send soldier states + for i := 0; i < 3; i++ { + stPayload, _ := json.Marshal(map[string]any{ + "SoldierID": 1, "CaptureFrame": i, + "Position": map[string]any{"x": 100 + i, "y": 200, "z": 10}, + "Bearing": 90, "Lifestate": 0, "IsPlayer": true, + "UnitName": "TestPlayer", "GroupID": "Alpha", "Side": "WEST", + }) + conn.WriteJSON(map[string]any{"type": "soldier_state", "payload": json.RawMessage(stPayload)}) + } + + // Send end_mission + conn.WriteJSON(map[string]any{"type": "end_mission", "payload": json.RawMessage("{}")}) + conn.ReadJSON(&ack) + require.Equal(t, "end_mission", ack["for"]) + + // Verify a .json.gz file was written + files, _ := filepath.Glob(filepath.Join(dir, "*.json.gz")) + assert.NotEmpty(t, files, "expected a .json.gz file to be written") + + // Verify operation was stored in DB + ops, err := hdlr.repoOperation.Select(context.Background(), Filter{}) + require.NoError(t, err) + found := false + for _, op := range ops { + if op.MissionName == "Lifecycle Test" { + found = true + assert.Equal(t, "altis", op.WorldName) + assert.Equal(t, ConversionStatusCompleted, op.ConversionStatus) + break + } + } + assert.True(t, found, "expected finalized operation in DB") +} + +func TestHandleStream_CrashDisconnectFinalizes(t *testing.T) { + _, e, dir := newTestStreamHandlerWithRepo(t) + srv := httptest.NewServer(e) + defer srv.Close() + + wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/api/v1/stream?secret=test-secret" + conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil) + require.NoError(t, err) + + // Start mission + sendStartMission(t, conn, "Crash Test", "altis") + + // Send some data + solPayload, _ := json.Marshal(map[string]any{ + "ID": 1, "JoinFrame": 0, "OcapType": "unit", "UnitName": "P1", "Side": "WEST", + }) + conn.WriteJSON(map[string]any{"type": "add_soldier", "payload": json.RawMessage(solPayload)}) + + stPayload, _ := json.Marshal(map[string]any{ + "SoldierID": 1, "CaptureFrame": 0, + "Position": map[string]any{"x": 100, "y": 200, "z": 10}, + }) + conn.WriteJSON(map[string]any{"type": "soldier_state", "payload": json.RawMessage(stPayload)}) + + // Simulate crash: just close without end_mission + conn.Close() + + // Give server time to detect close and finalize + time.Sleep(200 * time.Millisecond) + + // Verify a file was written (partial data) + files, _ := filepath.Glob(filepath.Join(dir, "*.json.gz")) + assert.NotEmpty(t, files, "expected partial .json.gz file after crash disconnect") +} diff --git a/internal/server/operation.go b/internal/server/operation.go index d238178a2..28a68485b 100644 --- a/internal/server/operation.go +++ b/internal/server/operation.go @@ -454,6 +454,14 @@ func (r *RepoOperation) UpdateSchemaVersion(ctx context.Context, id int64, versi return err } +// UpdateStreamingMeta updates duration and player count for a live streaming operation. +func (r *RepoOperation) UpdateStreamingMeta(ctx context.Context, id int64, duration float64, playerCount int) error { + _, err := r.db.ExecContext(ctx, + `UPDATE operations SET mission_duration = ?, player_count = ? WHERE id = ?`, + duration, playerCount, id) + return err +} + // UpdateMissionDuration updates the mission duration for an operation func (r *RepoOperation) UpdateMissionDuration(ctx context.Context, id int64, duration float64) error { _, err := r.db.ExecContext(ctx, @@ -510,6 +518,23 @@ func unmarshalSideComposition(raw string) SideComposition { return sc } +// Update replaces all mutable fields for an existing operation by ID. +func (r *RepoOperation) Update(ctx context.Context, op *Operation) error { + sideJSON := marshalSideComposition(op.SideComposition) + _, err := r.db.ExecContext(ctx, ` + UPDATE operations SET + world_name = ?, mission_name = ?, mission_duration = ?, filename = ?, + date = ?, tag = ?, storage_format = ?, conversion_status = ?, + schema_version = ?, chunk_count = ?, player_count = ?, kill_count = ?, + side_composition = ?, player_kill_count = ? + WHERE id = ?`, + op.WorldName, op.MissionName, op.MissionDuration, op.Filename, + op.Date, op.Tag, op.StorageFormat, op.ConversionStatus, + op.SchemaVersion, op.ChunkCount, op.PlayerCount, op.KillCount, + sideJSON, op.PlayerKillCount, op.ID) + return err +} + // SelectStatsBackfill returns completed protobuf operations that have no stats yet func (r *RepoOperation) SelectStatsBackfill(ctx context.Context) ([]Operation, error) { rows, err := r.db.QueryContext(ctx, diff --git a/internal/storage/version.go b/internal/storage/version.go index 547d4ded1..6f73f1687 100644 --- a/internal/storage/version.go +++ b/internal/storage/version.go @@ -8,7 +8,8 @@ type SchemaVersion uint32 const ( SchemaVersionUnknown SchemaVersion = 0 SchemaVersionV1 SchemaVersion = 1 - CurrentSchemaVersion = SchemaVersionV1 + SchemaVersionV2 SchemaVersion = 2 + CurrentSchemaVersion = SchemaVersionV2 ) func (v SchemaVersion) String() string { diff --git a/internal/storage/version_test.go b/internal/storage/version_test.go index 13036a798..6d61c2df3 100644 --- a/internal/storage/version_test.go +++ b/internal/storage/version_test.go @@ -22,10 +22,15 @@ func TestSchemaVersion_String(t *testing.T) { version: SchemaVersionV1, want: "v1", }, + { + name: "version 2", + version: SchemaVersionV2, + want: "v2", + }, { name: "current version", version: CurrentSchemaVersion, - want: "v1", + want: "v2", }, } diff --git a/pkg/schemas/protobuf/v2/generate.go b/pkg/schemas/protobuf/v2/generate.go new file mode 100644 index 000000000..f5fe8d0c1 --- /dev/null +++ b/pkg/schemas/protobuf/v2/generate.go @@ -0,0 +1,3 @@ +package v2 + +//go:generate protoc --go_out=. --go_opt=paths=source_relative ocap_v2.proto diff --git a/pkg/schemas/protobuf/v2/ocap_v2.pb.go b/pkg/schemas/protobuf/v2/ocap_v2.pb.go new file mode 100644 index 000000000..716e5181d --- /dev/null +++ b/pkg/schemas/protobuf/v2/ocap_v2.pb.go @@ -0,0 +1,4110 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v4.25.1 +// source: ocap_v2.proto + +package v2 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Side int32 + +const ( + Side_SIDE_UNKNOWN Side = 0 + Side_SIDE_WEST Side = 1 + Side_SIDE_EAST Side = 2 + Side_SIDE_GUER Side = 3 + Side_SIDE_CIV Side = 4 + Side_SIDE_GLOBAL Side = 5 +) + +// Enum value maps for Side. +var ( + Side_name = map[int32]string{ + 0: "SIDE_UNKNOWN", + 1: "SIDE_WEST", + 2: "SIDE_EAST", + 3: "SIDE_GUER", + 4: "SIDE_CIV", + 5: "SIDE_GLOBAL", + } + Side_value = map[string]int32{ + "SIDE_UNKNOWN": 0, + "SIDE_WEST": 1, + "SIDE_EAST": 2, + "SIDE_GUER": 3, + "SIDE_CIV": 4, + "SIDE_GLOBAL": 5, + } +) + +func (x Side) Enum() *Side { + p := new(Side) + *p = x + return p +} + +func (x Side) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Side) Descriptor() protoreflect.EnumDescriptor { + return file_ocap_v2_proto_enumTypes[0].Descriptor() +} + +func (Side) Type() protoreflect.EnumType { + return &file_ocap_v2_proto_enumTypes[0] +} + +func (x Side) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Side.Descriptor instead. +func (Side) EnumDescriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{0} +} + +type Position3D struct { + state protoimpl.MessageState `protogen:"open.v1"` + X float32 `protobuf:"fixed32,1,opt,name=x,proto3" json:"x,omitempty"` + Y float32 `protobuf:"fixed32,2,opt,name=y,proto3" json:"y,omitempty"` + Z float32 `protobuf:"fixed32,3,opt,name=z,proto3" json:"z,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Position3D) Reset() { + *x = Position3D{} + mi := &file_ocap_v2_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Position3D) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Position3D) ProtoMessage() {} + +func (x *Position3D) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Position3D.ProtoReflect.Descriptor instead. +func (*Position3D) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{0} +} + +func (x *Position3D) GetX() float32 { + if x != nil { + return x.X + } + return 0 +} + +func (x *Position3D) GetY() float32 { + if x != nil { + return x.Y + } + return 0 +} + +func (x *Position3D) GetZ() float32 { + if x != nil { + return x.Z + } + return 0 +} + +type Manifest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` // = 2 + World *WorldMeta `protobuf:"bytes,2,opt,name=world,proto3" json:"world,omitempty"` + Mission *MissionMeta `protobuf:"bytes,3,opt,name=mission,proto3" json:"mission,omitempty"` + FrameCount uint32 `protobuf:"varint,4,opt,name=frame_count,json=frameCount,proto3" json:"frame_count,omitempty"` + ChunkSize uint32 `protobuf:"varint,5,opt,name=chunk_size,json=chunkSize,proto3" json:"chunk_size,omitempty"` + CaptureDelayMs uint32 `protobuf:"varint,6,opt,name=capture_delay_ms,json=captureDelayMs,proto3" json:"capture_delay_ms,omitempty"` + ChunkCount uint32 `protobuf:"varint,7,opt,name=chunk_count,json=chunkCount,proto3" json:"chunk_count,omitempty"` + Soldiers []*SoldierDef `protobuf:"bytes,10,rep,name=soldiers,proto3" json:"soldiers,omitempty"` + Vehicles []*VehicleDef `protobuf:"bytes,11,rep,name=vehicles,proto3" json:"vehicles,omitempty"` + Events []*Event `protobuf:"bytes,12,rep,name=events,proto3" json:"events,omitempty"` + Markers []*MarkerDef `protobuf:"bytes,13,rep,name=markers,proto3" json:"markers,omitempty"` + Times []*TimeSample `protobuf:"bytes,14,rep,name=times,proto3" json:"times,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Manifest) Reset() { + *x = Manifest{} + mi := &file_ocap_v2_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Manifest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Manifest) ProtoMessage() {} + +func (x *Manifest) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Manifest.ProtoReflect.Descriptor instead. +func (*Manifest) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{1} +} + +func (x *Manifest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Manifest) GetWorld() *WorldMeta { + if x != nil { + return x.World + } + return nil +} + +func (x *Manifest) GetMission() *MissionMeta { + if x != nil { + return x.Mission + } + return nil +} + +func (x *Manifest) GetFrameCount() uint32 { + if x != nil { + return x.FrameCount + } + return 0 +} + +func (x *Manifest) GetChunkSize() uint32 { + if x != nil { + return x.ChunkSize + } + return 0 +} + +func (x *Manifest) GetCaptureDelayMs() uint32 { + if x != nil { + return x.CaptureDelayMs + } + return 0 +} + +func (x *Manifest) GetChunkCount() uint32 { + if x != nil { + return x.ChunkCount + } + return 0 +} + +func (x *Manifest) GetSoldiers() []*SoldierDef { + if x != nil { + return x.Soldiers + } + return nil +} + +func (x *Manifest) GetVehicles() []*VehicleDef { + if x != nil { + return x.Vehicles + } + return nil +} + +func (x *Manifest) GetEvents() []*Event { + if x != nil { + return x.Events + } + return nil +} + +func (x *Manifest) GetMarkers() []*MarkerDef { + if x != nil { + return x.Markers + } + return nil +} + +func (x *Manifest) GetTimes() []*TimeSample { + if x != nil { + return x.Times + } + return nil +} + +type WorldMeta struct { + state protoimpl.MessageState `protogen:"open.v1"` + WorldName string `protobuf:"bytes,1,opt,name=world_name,json=worldName,proto3" json:"world_name,omitempty"` + WorldSize float32 `protobuf:"fixed32,2,opt,name=world_size,json=worldSize,proto3" json:"world_size,omitempty"` + Latitude float32 `protobuf:"fixed32,3,opt,name=latitude,proto3" json:"latitude,omitempty"` + Longitude float32 `protobuf:"fixed32,4,opt,name=longitude,proto3" json:"longitude,omitempty"` + Author string `protobuf:"bytes,5,opt,name=author,proto3" json:"author,omitempty"` + DisplayName string `protobuf:"bytes,6,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WorldMeta) Reset() { + *x = WorldMeta{} + mi := &file_ocap_v2_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WorldMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorldMeta) ProtoMessage() {} + +func (x *WorldMeta) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorldMeta.ProtoReflect.Descriptor instead. +func (*WorldMeta) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{2} +} + +func (x *WorldMeta) GetWorldName() string { + if x != nil { + return x.WorldName + } + return "" +} + +func (x *WorldMeta) GetWorldSize() float32 { + if x != nil { + return x.WorldSize + } + return 0 +} + +func (x *WorldMeta) GetLatitude() float32 { + if x != nil { + return x.Latitude + } + return 0 +} + +func (x *WorldMeta) GetLongitude() float32 { + if x != nil { + return x.Longitude + } + return 0 +} + +func (x *WorldMeta) GetAuthor() string { + if x != nil { + return x.Author + } + return "" +} + +func (x *WorldMeta) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +type MissionMeta struct { + state protoimpl.MessageState `protogen:"open.v1"` + MissionName string `protobuf:"bytes,1,opt,name=mission_name,json=missionName,proto3" json:"mission_name,omitempty"` + BriefingName string `protobuf:"bytes,2,opt,name=briefing_name,json=briefingName,proto3" json:"briefing_name,omitempty"` + Author string `protobuf:"bytes,3,opt,name=author,proto3" json:"author,omitempty"` + ServerName string `protobuf:"bytes,4,opt,name=server_name,json=serverName,proto3" json:"server_name,omitempty"` + ExtensionVersion string `protobuf:"bytes,5,opt,name=extension_version,json=extensionVersion,proto3" json:"extension_version,omitempty"` + AddonVersion string `protobuf:"bytes,6,opt,name=addon_version,json=addonVersion,proto3" json:"addon_version,omitempty"` + ExtensionBuild string `protobuf:"bytes,7,opt,name=extension_build,json=extensionBuild,proto3" json:"extension_build,omitempty"` + Tag string `protobuf:"bytes,8,opt,name=tag,proto3" json:"tag,omitempty"` + PlayableSlots *PlayableSlots `protobuf:"bytes,9,opt,name=playable_slots,json=playableSlots,proto3" json:"playable_slots,omitempty"` + SideFriendly *SideFriendly `protobuf:"bytes,10,opt,name=side_friendly,json=sideFriendly,proto3" json:"side_friendly,omitempty"` + Addons []*Addon `protobuf:"bytes,11,rep,name=addons,proto3" json:"addons,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MissionMeta) Reset() { + *x = MissionMeta{} + mi := &file_ocap_v2_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MissionMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MissionMeta) ProtoMessage() {} + +func (x *MissionMeta) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MissionMeta.ProtoReflect.Descriptor instead. +func (*MissionMeta) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{3} +} + +func (x *MissionMeta) GetMissionName() string { + if x != nil { + return x.MissionName + } + return "" +} + +func (x *MissionMeta) GetBriefingName() string { + if x != nil { + return x.BriefingName + } + return "" +} + +func (x *MissionMeta) GetAuthor() string { + if x != nil { + return x.Author + } + return "" +} + +func (x *MissionMeta) GetServerName() string { + if x != nil { + return x.ServerName + } + return "" +} + +func (x *MissionMeta) GetExtensionVersion() string { + if x != nil { + return x.ExtensionVersion + } + return "" +} + +func (x *MissionMeta) GetAddonVersion() string { + if x != nil { + return x.AddonVersion + } + return "" +} + +func (x *MissionMeta) GetExtensionBuild() string { + if x != nil { + return x.ExtensionBuild + } + return "" +} + +func (x *MissionMeta) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *MissionMeta) GetPlayableSlots() *PlayableSlots { + if x != nil { + return x.PlayableSlots + } + return nil +} + +func (x *MissionMeta) GetSideFriendly() *SideFriendly { + if x != nil { + return x.SideFriendly + } + return nil +} + +func (x *MissionMeta) GetAddons() []*Addon { + if x != nil { + return x.Addons + } + return nil +} + +type PlayableSlots struct { + state protoimpl.MessageState `protogen:"open.v1"` + West uint32 `protobuf:"varint,1,opt,name=west,proto3" json:"west,omitempty"` + East uint32 `protobuf:"varint,2,opt,name=east,proto3" json:"east,omitempty"` + Independent uint32 `protobuf:"varint,3,opt,name=independent,proto3" json:"independent,omitempty"` + Civilian uint32 `protobuf:"varint,4,opt,name=civilian,proto3" json:"civilian,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PlayableSlots) Reset() { + *x = PlayableSlots{} + mi := &file_ocap_v2_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PlayableSlots) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlayableSlots) ProtoMessage() {} + +func (x *PlayableSlots) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlayableSlots.ProtoReflect.Descriptor instead. +func (*PlayableSlots) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{4} +} + +func (x *PlayableSlots) GetWest() uint32 { + if x != nil { + return x.West + } + return 0 +} + +func (x *PlayableSlots) GetEast() uint32 { + if x != nil { + return x.East + } + return 0 +} + +func (x *PlayableSlots) GetIndependent() uint32 { + if x != nil { + return x.Independent + } + return 0 +} + +func (x *PlayableSlots) GetCivilian() uint32 { + if x != nil { + return x.Civilian + } + return 0 +} + +type SideFriendly struct { + state protoimpl.MessageState `protogen:"open.v1"` + EastWest bool `protobuf:"varint,1,opt,name=east_west,json=eastWest,proto3" json:"east_west,omitempty"` + EastIndependent bool `protobuf:"varint,2,opt,name=east_independent,json=eastIndependent,proto3" json:"east_independent,omitempty"` + WestIndependent bool `protobuf:"varint,3,opt,name=west_independent,json=westIndependent,proto3" json:"west_independent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SideFriendly) Reset() { + *x = SideFriendly{} + mi := &file_ocap_v2_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SideFriendly) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SideFriendly) ProtoMessage() {} + +func (x *SideFriendly) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SideFriendly.ProtoReflect.Descriptor instead. +func (*SideFriendly) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{5} +} + +func (x *SideFriendly) GetEastWest() bool { + if x != nil { + return x.EastWest + } + return false +} + +func (x *SideFriendly) GetEastIndependent() bool { + if x != nil { + return x.EastIndependent + } + return false +} + +func (x *SideFriendly) GetWestIndependent() bool { + if x != nil { + return x.WestIndependent + } + return false +} + +type Addon struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + WorkshopId string `protobuf:"bytes,2,opt,name=workshop_id,json=workshopId,proto3" json:"workshop_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Addon) Reset() { + *x = Addon{} + mi := &file_ocap_v2_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Addon) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Addon) ProtoMessage() {} + +func (x *Addon) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Addon.ProtoReflect.Descriptor instead. +func (*Addon) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{6} +} + +func (x *Addon) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Addon) GetWorkshopId() string { + if x != nil { + return x.WorkshopId + } + return "" +} + +type SoldierDef struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Side Side `protobuf:"varint,3,opt,name=side,proto3,enum=ocap.v2.Side" json:"side,omitempty"` + GroupName string `protobuf:"bytes,4,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"` + Role string `protobuf:"bytes,5,opt,name=role,proto3" json:"role,omitempty"` + StartFrame uint32 `protobuf:"varint,6,opt,name=start_frame,json=startFrame,proto3" json:"start_frame,omitempty"` + EndFrame uint32 `protobuf:"varint,7,opt,name=end_frame,json=endFrame,proto3" json:"end_frame,omitempty"` + IsPlayer bool `protobuf:"varint,8,opt,name=is_player,json=isPlayer,proto3" json:"is_player,omitempty"` + ClassName string `protobuf:"bytes,9,opt,name=class_name,json=className,proto3" json:"class_name,omitempty"` + PlayerUid string `protobuf:"bytes,10,opt,name=player_uid,json=playerUid,proto3" json:"player_uid,omitempty"` + FramesFired []*FiredFrame `protobuf:"bytes,11,rep,name=frames_fired,json=framesFired,proto3" json:"frames_fired,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SoldierDef) Reset() { + *x = SoldierDef{} + mi := &file_ocap_v2_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SoldierDef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SoldierDef) ProtoMessage() {} + +func (x *SoldierDef) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SoldierDef.ProtoReflect.Descriptor instead. +func (*SoldierDef) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{7} +} + +func (x *SoldierDef) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *SoldierDef) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SoldierDef) GetSide() Side { + if x != nil { + return x.Side + } + return Side_SIDE_UNKNOWN +} + +func (x *SoldierDef) GetGroupName() string { + if x != nil { + return x.GroupName + } + return "" +} + +func (x *SoldierDef) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *SoldierDef) GetStartFrame() uint32 { + if x != nil { + return x.StartFrame + } + return 0 +} + +func (x *SoldierDef) GetEndFrame() uint32 { + if x != nil { + return x.EndFrame + } + return 0 +} + +func (x *SoldierDef) GetIsPlayer() bool { + if x != nil { + return x.IsPlayer + } + return false +} + +func (x *SoldierDef) GetClassName() string { + if x != nil { + return x.ClassName + } + return "" +} + +func (x *SoldierDef) GetPlayerUid() string { + if x != nil { + return x.PlayerUid + } + return "" +} + +func (x *SoldierDef) GetFramesFired() []*FiredFrame { + if x != nil { + return x.FramesFired + } + return nil +} + +type VehicleDef struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + VehicleClass string `protobuf:"bytes,3,opt,name=vehicle_class,json=vehicleClass,proto3" json:"vehicle_class,omitempty"` // ocapType: car, tank, heli, etc. + ClassName string `protobuf:"bytes,4,opt,name=class_name,json=className,proto3" json:"class_name,omitempty"` + StartFrame uint32 `protobuf:"varint,5,opt,name=start_frame,json=startFrame,proto3" json:"start_frame,omitempty"` + EndFrame uint32 `protobuf:"varint,6,opt,name=end_frame,json=endFrame,proto3" json:"end_frame,omitempty"` + Customization string `protobuf:"bytes,7,opt,name=customization,proto3" json:"customization,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VehicleDef) Reset() { + *x = VehicleDef{} + mi := &file_ocap_v2_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VehicleDef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VehicleDef) ProtoMessage() {} + +func (x *VehicleDef) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VehicleDef.ProtoReflect.Descriptor instead. +func (*VehicleDef) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{8} +} + +func (x *VehicleDef) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *VehicleDef) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *VehicleDef) GetVehicleClass() string { + if x != nil { + return x.VehicleClass + } + return "" +} + +func (x *VehicleDef) GetClassName() string { + if x != nil { + return x.ClassName + } + return "" +} + +func (x *VehicleDef) GetStartFrame() uint32 { + if x != nil { + return x.StartFrame + } + return 0 +} + +func (x *VehicleDef) GetEndFrame() uint32 { + if x != nil { + return x.EndFrame + } + return 0 +} + +func (x *VehicleDef) GetCustomization() string { + if x != nil { + return x.Customization + } + return "" +} + +type FiredFrame struct { + state protoimpl.MessageState `protogen:"open.v1"` + FrameNum uint32 `protobuf:"varint,1,opt,name=frame_num,json=frameNum,proto3" json:"frame_num,omitempty"` + StartPos *Position3D `protobuf:"bytes,2,opt,name=start_pos,json=startPos,proto3" json:"start_pos,omitempty"` + EndPos *Position3D `protobuf:"bytes,3,opt,name=end_pos,json=endPos,proto3" json:"end_pos,omitempty"` + Weapon string `protobuf:"bytes,4,opt,name=weapon,proto3" json:"weapon,omitempty"` + Magazine string `protobuf:"bytes,5,opt,name=magazine,proto3" json:"magazine,omitempty"` + FiringMode string `protobuf:"bytes,6,opt,name=firing_mode,json=firingMode,proto3" json:"firing_mode,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *FiredFrame) Reset() { + *x = FiredFrame{} + mi := &file_ocap_v2_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *FiredFrame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FiredFrame) ProtoMessage() {} + +func (x *FiredFrame) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FiredFrame.ProtoReflect.Descriptor instead. +func (*FiredFrame) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{9} +} + +func (x *FiredFrame) GetFrameNum() uint32 { + if x != nil { + return x.FrameNum + } + return 0 +} + +func (x *FiredFrame) GetStartPos() *Position3D { + if x != nil { + return x.StartPos + } + return nil +} + +func (x *FiredFrame) GetEndPos() *Position3D { + if x != nil { + return x.EndPos + } + return nil +} + +func (x *FiredFrame) GetWeapon() string { + if x != nil { + return x.Weapon + } + return "" +} + +func (x *FiredFrame) GetMagazine() string { + if x != nil { + return x.Magazine + } + return "" +} + +func (x *FiredFrame) GetFiringMode() string { + if x != nil { + return x.FiringMode + } + return "" +} + +type Event struct { + state protoimpl.MessageState `protogen:"open.v1"` + FrameNum uint32 `protobuf:"varint,1,opt,name=frame_num,json=frameNum,proto3" json:"frame_num,omitempty"` + // Types that are valid to be assigned to Event: + // + // *Event_Kill + // *Event_Hit + // *Event_Projectile + // *Event_Chat + // *Event_Radio + // *Event_Ace3Death + // *Event_Ace3Unconscious + // *Event_General + // *Event_Connect + // *Event_EndMission + // *Event_Telemetry + Event isEvent_Event `protobuf_oneof:"event"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Event) Reset() { + *x = Event{} + mi := &file_ocap_v2_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{10} +} + +func (x *Event) GetFrameNum() uint32 { + if x != nil { + return x.FrameNum + } + return 0 +} + +func (x *Event) GetEvent() isEvent_Event { + if x != nil { + return x.Event + } + return nil +} + +func (x *Event) GetKill() *KillEvent { + if x != nil { + if x, ok := x.Event.(*Event_Kill); ok { + return x.Kill + } + } + return nil +} + +func (x *Event) GetHit() *HitEvent { + if x != nil { + if x, ok := x.Event.(*Event_Hit); ok { + return x.Hit + } + } + return nil +} + +func (x *Event) GetProjectile() *ProjectileEvent { + if x != nil { + if x, ok := x.Event.(*Event_Projectile); ok { + return x.Projectile + } + } + return nil +} + +func (x *Event) GetChat() *ChatEvent { + if x != nil { + if x, ok := x.Event.(*Event_Chat); ok { + return x.Chat + } + } + return nil +} + +func (x *Event) GetRadio() *RadioEvent { + if x != nil { + if x, ok := x.Event.(*Event_Radio); ok { + return x.Radio + } + } + return nil +} + +func (x *Event) GetAce3Death() *Ace3DeathEvent { + if x != nil { + if x, ok := x.Event.(*Event_Ace3Death); ok { + return x.Ace3Death + } + } + return nil +} + +func (x *Event) GetAce3Unconscious() *Ace3UnconsciousEvent { + if x != nil { + if x, ok := x.Event.(*Event_Ace3Unconscious); ok { + return x.Ace3Unconscious + } + } + return nil +} + +func (x *Event) GetGeneral() *GeneralEvent { + if x != nil { + if x, ok := x.Event.(*Event_General); ok { + return x.General + } + } + return nil +} + +func (x *Event) GetConnect() *ConnectEvent { + if x != nil { + if x, ok := x.Event.(*Event_Connect); ok { + return x.Connect + } + } + return nil +} + +func (x *Event) GetEndMission() *EndMissionEvent { + if x != nil { + if x, ok := x.Event.(*Event_EndMission); ok { + return x.EndMission + } + } + return nil +} + +func (x *Event) GetTelemetry() *TelemetryEvent { + if x != nil { + if x, ok := x.Event.(*Event_Telemetry); ok { + return x.Telemetry + } + } + return nil +} + +type isEvent_Event interface { + isEvent_Event() +} + +type Event_Kill struct { + Kill *KillEvent `protobuf:"bytes,10,opt,name=kill,proto3,oneof"` +} + +type Event_Hit struct { + Hit *HitEvent `protobuf:"bytes,11,opt,name=hit,proto3,oneof"` +} + +type Event_Projectile struct { + Projectile *ProjectileEvent `protobuf:"bytes,12,opt,name=projectile,proto3,oneof"` +} + +type Event_Chat struct { + Chat *ChatEvent `protobuf:"bytes,13,opt,name=chat,proto3,oneof"` +} + +type Event_Radio struct { + Radio *RadioEvent `protobuf:"bytes,14,opt,name=radio,proto3,oneof"` +} + +type Event_Ace3Death struct { + Ace3Death *Ace3DeathEvent `protobuf:"bytes,15,opt,name=ace3_death,json=ace3Death,proto3,oneof"` +} + +type Event_Ace3Unconscious struct { + Ace3Unconscious *Ace3UnconsciousEvent `protobuf:"bytes,16,opt,name=ace3_unconscious,json=ace3Unconscious,proto3,oneof"` +} + +type Event_General struct { + General *GeneralEvent `protobuf:"bytes,17,opt,name=general,proto3,oneof"` +} + +type Event_Connect struct { + Connect *ConnectEvent `protobuf:"bytes,18,opt,name=connect,proto3,oneof"` +} + +type Event_EndMission struct { + EndMission *EndMissionEvent `protobuf:"bytes,19,opt,name=end_mission,json=endMission,proto3,oneof"` +} + +type Event_Telemetry struct { + Telemetry *TelemetryEvent `protobuf:"bytes,20,opt,name=telemetry,proto3,oneof"` +} + +func (*Event_Kill) isEvent_Event() {} + +func (*Event_Hit) isEvent_Event() {} + +func (*Event_Projectile) isEvent_Event() {} + +func (*Event_Chat) isEvent_Event() {} + +func (*Event_Radio) isEvent_Event() {} + +func (*Event_Ace3Death) isEvent_Event() {} + +func (*Event_Ace3Unconscious) isEvent_Event() {} + +func (*Event_General) isEvent_Event() {} + +func (*Event_Connect) isEvent_Event() {} + +func (*Event_EndMission) isEvent_Event() {} + +func (*Event_Telemetry) isEvent_Event() {} + +type KillEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + VictimSoldierId uint32 `protobuf:"varint,1,opt,name=victim_soldier_id,json=victimSoldierId,proto3" json:"victim_soldier_id,omitempty"` + VictimVehicleId uint32 `protobuf:"varint,2,opt,name=victim_vehicle_id,json=victimVehicleId,proto3" json:"victim_vehicle_id,omitempty"` + KillerSoldierId uint32 `protobuf:"varint,3,opt,name=killer_soldier_id,json=killerSoldierId,proto3" json:"killer_soldier_id,omitempty"` + KillerVehicleId uint32 `protobuf:"varint,4,opt,name=killer_vehicle_id,json=killerVehicleId,proto3" json:"killer_vehicle_id,omitempty"` + WeaponName string `protobuf:"bytes,5,opt,name=weapon_name,json=weaponName,proto3" json:"weapon_name,omitempty"` + WeaponMagazine string `protobuf:"bytes,6,opt,name=weapon_magazine,json=weaponMagazine,proto3" json:"weapon_magazine,omitempty"` + EventText string `protobuf:"bytes,7,opt,name=event_text,json=eventText,proto3" json:"event_text,omitempty"` + Distance float32 `protobuf:"fixed32,8,opt,name=distance,proto3" json:"distance,omitempty"` + VictimIsVehicle bool `protobuf:"varint,9,opt,name=victim_is_vehicle,json=victimIsVehicle,proto3" json:"victim_is_vehicle,omitempty"` + KillerIsVehicle bool `protobuf:"varint,10,opt,name=killer_is_vehicle,json=killerIsVehicle,proto3" json:"killer_is_vehicle,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *KillEvent) Reset() { + *x = KillEvent{} + mi := &file_ocap_v2_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *KillEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*KillEvent) ProtoMessage() {} + +func (x *KillEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use KillEvent.ProtoReflect.Descriptor instead. +func (*KillEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{11} +} + +func (x *KillEvent) GetVictimSoldierId() uint32 { + if x != nil { + return x.VictimSoldierId + } + return 0 +} + +func (x *KillEvent) GetVictimVehicleId() uint32 { + if x != nil { + return x.VictimVehicleId + } + return 0 +} + +func (x *KillEvent) GetKillerSoldierId() uint32 { + if x != nil { + return x.KillerSoldierId + } + return 0 +} + +func (x *KillEvent) GetKillerVehicleId() uint32 { + if x != nil { + return x.KillerVehicleId + } + return 0 +} + +func (x *KillEvent) GetWeaponName() string { + if x != nil { + return x.WeaponName + } + return "" +} + +func (x *KillEvent) GetWeaponMagazine() string { + if x != nil { + return x.WeaponMagazine + } + return "" +} + +func (x *KillEvent) GetEventText() string { + if x != nil { + return x.EventText + } + return "" +} + +func (x *KillEvent) GetDistance() float32 { + if x != nil { + return x.Distance + } + return 0 +} + +func (x *KillEvent) GetVictimIsVehicle() bool { + if x != nil { + return x.VictimIsVehicle + } + return false +} + +func (x *KillEvent) GetKillerIsVehicle() bool { + if x != nil { + return x.KillerIsVehicle + } + return false +} + +type HitEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + VictimSoldierId uint32 `protobuf:"varint,1,opt,name=victim_soldier_id,json=victimSoldierId,proto3" json:"victim_soldier_id,omitempty"` + VictimVehicleId uint32 `protobuf:"varint,2,opt,name=victim_vehicle_id,json=victimVehicleId,proto3" json:"victim_vehicle_id,omitempty"` + ShooterSoldierId uint32 `protobuf:"varint,3,opt,name=shooter_soldier_id,json=shooterSoldierId,proto3" json:"shooter_soldier_id,omitempty"` + ShooterVehicleId uint32 `protobuf:"varint,4,opt,name=shooter_vehicle_id,json=shooterVehicleId,proto3" json:"shooter_vehicle_id,omitempty"` + WeaponName string `protobuf:"bytes,5,opt,name=weapon_name,json=weaponName,proto3" json:"weapon_name,omitempty"` + WeaponMagazine string `protobuf:"bytes,6,opt,name=weapon_magazine,json=weaponMagazine,proto3" json:"weapon_magazine,omitempty"` + EventText string `protobuf:"bytes,7,opt,name=event_text,json=eventText,proto3" json:"event_text,omitempty"` + Distance float32 `protobuf:"fixed32,8,opt,name=distance,proto3" json:"distance,omitempty"` + VictimIsVehicle bool `protobuf:"varint,9,opt,name=victim_is_vehicle,json=victimIsVehicle,proto3" json:"victim_is_vehicle,omitempty"` + ShooterIsVehicle bool `protobuf:"varint,10,opt,name=shooter_is_vehicle,json=shooterIsVehicle,proto3" json:"shooter_is_vehicle,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HitEvent) Reset() { + *x = HitEvent{} + mi := &file_ocap_v2_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HitEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HitEvent) ProtoMessage() {} + +func (x *HitEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HitEvent.ProtoReflect.Descriptor instead. +func (*HitEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{12} +} + +func (x *HitEvent) GetVictimSoldierId() uint32 { + if x != nil { + return x.VictimSoldierId + } + return 0 +} + +func (x *HitEvent) GetVictimVehicleId() uint32 { + if x != nil { + return x.VictimVehicleId + } + return 0 +} + +func (x *HitEvent) GetShooterSoldierId() uint32 { + if x != nil { + return x.ShooterSoldierId + } + return 0 +} + +func (x *HitEvent) GetShooterVehicleId() uint32 { + if x != nil { + return x.ShooterVehicleId + } + return 0 +} + +func (x *HitEvent) GetWeaponName() string { + if x != nil { + return x.WeaponName + } + return "" +} + +func (x *HitEvent) GetWeaponMagazine() string { + if x != nil { + return x.WeaponMagazine + } + return "" +} + +func (x *HitEvent) GetEventText() string { + if x != nil { + return x.EventText + } + return "" +} + +func (x *HitEvent) GetDistance() float32 { + if x != nil { + return x.Distance + } + return 0 +} + +func (x *HitEvent) GetVictimIsVehicle() bool { + if x != nil { + return x.VictimIsVehicle + } + return false +} + +func (x *HitEvent) GetShooterIsVehicle() bool { + if x != nil { + return x.ShooterIsVehicle + } + return false +} + +type ProjectileEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + FirerId uint32 `protobuf:"varint,1,opt,name=firer_id,json=firerId,proto3" json:"firer_id,omitempty"` + VehicleId uint32 `protobuf:"varint,2,opt,name=vehicle_id,json=vehicleId,proto3" json:"vehicle_id,omitempty"` + Weapon string `protobuf:"bytes,3,opt,name=weapon,proto3" json:"weapon,omitempty"` // WeaponDisplay + Magazine string `protobuf:"bytes,4,opt,name=magazine,proto3" json:"magazine,omitempty"` // MagazineDisplay + SimulationType string `protobuf:"bytes,5,opt,name=simulation_type,json=simulationType,proto3" json:"simulation_type,omitempty"` + Trajectory []*TrajectoryPoint `protobuf:"bytes,6,rep,name=trajectory,proto3" json:"trajectory,omitempty"` + Hits []*ProjectileHit `protobuf:"bytes,7,rep,name=hits,proto3" json:"hits,omitempty"` + Muzzle string `protobuf:"bytes,8,opt,name=muzzle,proto3" json:"muzzle,omitempty"` // MuzzleDisplay + MagazineIcon string `protobuf:"bytes,9,opt,name=magazine_icon,json=magazineIcon,proto3" json:"magazine_icon,omitempty"` // MagazineIcon (Arma path for ammo icon) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProjectileEvent) Reset() { + *x = ProjectileEvent{} + mi := &file_ocap_v2_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProjectileEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProjectileEvent) ProtoMessage() {} + +func (x *ProjectileEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProjectileEvent.ProtoReflect.Descriptor instead. +func (*ProjectileEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{13} +} + +func (x *ProjectileEvent) GetFirerId() uint32 { + if x != nil { + return x.FirerId + } + return 0 +} + +func (x *ProjectileEvent) GetVehicleId() uint32 { + if x != nil { + return x.VehicleId + } + return 0 +} + +func (x *ProjectileEvent) GetWeapon() string { + if x != nil { + return x.Weapon + } + return "" +} + +func (x *ProjectileEvent) GetMagazine() string { + if x != nil { + return x.Magazine + } + return "" +} + +func (x *ProjectileEvent) GetSimulationType() string { + if x != nil { + return x.SimulationType + } + return "" +} + +func (x *ProjectileEvent) GetTrajectory() []*TrajectoryPoint { + if x != nil { + return x.Trajectory + } + return nil +} + +func (x *ProjectileEvent) GetHits() []*ProjectileHit { + if x != nil { + return x.Hits + } + return nil +} + +func (x *ProjectileEvent) GetMuzzle() string { + if x != nil { + return x.Muzzle + } + return "" +} + +func (x *ProjectileEvent) GetMagazineIcon() string { + if x != nil { + return x.MagazineIcon + } + return "" +} + +type TrajectoryPoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + Position *Position3D `protobuf:"bytes,1,opt,name=position,proto3" json:"position,omitempty"` + FrameNum uint32 `protobuf:"varint,2,opt,name=frame_num,json=frameNum,proto3" json:"frame_num,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TrajectoryPoint) Reset() { + *x = TrajectoryPoint{} + mi := &file_ocap_v2_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TrajectoryPoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TrajectoryPoint) ProtoMessage() {} + +func (x *TrajectoryPoint) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TrajectoryPoint.ProtoReflect.Descriptor instead. +func (*TrajectoryPoint) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{14} +} + +func (x *TrajectoryPoint) GetPosition() *Position3D { + if x != nil { + return x.Position + } + return nil +} + +func (x *TrajectoryPoint) GetFrameNum() uint32 { + if x != nil { + return x.FrameNum + } + return 0 +} + +type ProjectileHit struct { + state protoimpl.MessageState `protogen:"open.v1"` + FrameNum uint32 `protobuf:"varint,1,opt,name=frame_num,json=frameNum,proto3" json:"frame_num,omitempty"` + Position *Position3D `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + SoldierId uint32 `protobuf:"varint,3,opt,name=soldier_id,json=soldierId,proto3" json:"soldier_id,omitempty"` + VehicleId uint32 `protobuf:"varint,4,opt,name=vehicle_id,json=vehicleId,proto3" json:"vehicle_id,omitempty"` + HitSoldier bool `protobuf:"varint,5,opt,name=hit_soldier,json=hitSoldier,proto3" json:"hit_soldier,omitempty"` + HitVehicle bool `protobuf:"varint,6,opt,name=hit_vehicle,json=hitVehicle,proto3" json:"hit_vehicle,omitempty"` + ComponentsHit []string `protobuf:"bytes,7,rep,name=components_hit,json=componentsHit,proto3" json:"components_hit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProjectileHit) Reset() { + *x = ProjectileHit{} + mi := &file_ocap_v2_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProjectileHit) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProjectileHit) ProtoMessage() {} + +func (x *ProjectileHit) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProjectileHit.ProtoReflect.Descriptor instead. +func (*ProjectileHit) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{15} +} + +func (x *ProjectileHit) GetFrameNum() uint32 { + if x != nil { + return x.FrameNum + } + return 0 +} + +func (x *ProjectileHit) GetPosition() *Position3D { + if x != nil { + return x.Position + } + return nil +} + +func (x *ProjectileHit) GetSoldierId() uint32 { + if x != nil { + return x.SoldierId + } + return 0 +} + +func (x *ProjectileHit) GetVehicleId() uint32 { + if x != nil { + return x.VehicleId + } + return 0 +} + +func (x *ProjectileHit) GetHitSoldier() bool { + if x != nil { + return x.HitSoldier + } + return false +} + +func (x *ProjectileHit) GetHitVehicle() bool { + if x != nil { + return x.HitVehicle + } + return false +} + +func (x *ProjectileHit) GetComponentsHit() []string { + if x != nil { + return x.ComponentsHit + } + return nil +} + +type ChatEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + SoldierId uint32 `protobuf:"varint,1,opt,name=soldier_id,json=soldierId,proto3" json:"soldier_id,omitempty"` + Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty"` + FromName string `protobuf:"bytes,3,opt,name=from_name,json=fromName,proto3" json:"from_name,omitempty"` + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + PlayerUid string `protobuf:"bytes,5,opt,name=player_uid,json=playerUid,proto3" json:"player_uid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ChatEvent) Reset() { + *x = ChatEvent{} + mi := &file_ocap_v2_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ChatEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChatEvent) ProtoMessage() {} + +func (x *ChatEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChatEvent.ProtoReflect.Descriptor instead. +func (*ChatEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{16} +} + +func (x *ChatEvent) GetSoldierId() uint32 { + if x != nil { + return x.SoldierId + } + return 0 +} + +func (x *ChatEvent) GetChannel() string { + if x != nil { + return x.Channel + } + return "" +} + +func (x *ChatEvent) GetFromName() string { + if x != nil { + return x.FromName + } + return "" +} + +func (x *ChatEvent) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ChatEvent) GetPlayerUid() string { + if x != nil { + return x.PlayerUid + } + return "" +} + +type RadioEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + SoldierId uint32 `protobuf:"varint,1,opt,name=soldier_id,json=soldierId,proto3" json:"soldier_id,omitempty"` + Radio string `protobuf:"bytes,2,opt,name=radio,proto3" json:"radio,omitempty"` + RadioType string `protobuf:"bytes,3,opt,name=radio_type,json=radioType,proto3" json:"radio_type,omitempty"` + StartEnd string `protobuf:"bytes,4,opt,name=start_end,json=startEnd,proto3" json:"start_end,omitempty"` + Channel int32 `protobuf:"varint,5,opt,name=channel,proto3" json:"channel,omitempty"` + IsAdditional bool `protobuf:"varint,6,opt,name=is_additional,json=isAdditional,proto3" json:"is_additional,omitempty"` + Frequency float32 `protobuf:"fixed32,7,opt,name=frequency,proto3" json:"frequency,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RadioEvent) Reset() { + *x = RadioEvent{} + mi := &file_ocap_v2_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RadioEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RadioEvent) ProtoMessage() {} + +func (x *RadioEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RadioEvent.ProtoReflect.Descriptor instead. +func (*RadioEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{17} +} + +func (x *RadioEvent) GetSoldierId() uint32 { + if x != nil { + return x.SoldierId + } + return 0 +} + +func (x *RadioEvent) GetRadio() string { + if x != nil { + return x.Radio + } + return "" +} + +func (x *RadioEvent) GetRadioType() string { + if x != nil { + return x.RadioType + } + return "" +} + +func (x *RadioEvent) GetStartEnd() string { + if x != nil { + return x.StartEnd + } + return "" +} + +func (x *RadioEvent) GetChannel() int32 { + if x != nil { + return x.Channel + } + return 0 +} + +func (x *RadioEvent) GetIsAdditional() bool { + if x != nil { + return x.IsAdditional + } + return false +} + +func (x *RadioEvent) GetFrequency() float32 { + if x != nil { + return x.Frequency + } + return 0 +} + +type Ace3DeathEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + SoldierId uint32 `protobuf:"varint,1,opt,name=soldier_id,json=soldierId,proto3" json:"soldier_id,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` + LastDamageSourceId uint32 `protobuf:"varint,3,opt,name=last_damage_source_id,json=lastDamageSourceId,proto3" json:"last_damage_source_id,omitempty"` + HasDamageSource bool `protobuf:"varint,4,opt,name=has_damage_source,json=hasDamageSource,proto3" json:"has_damage_source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Ace3DeathEvent) Reset() { + *x = Ace3DeathEvent{} + mi := &file_ocap_v2_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Ace3DeathEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ace3DeathEvent) ProtoMessage() {} + +func (x *Ace3DeathEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ace3DeathEvent.ProtoReflect.Descriptor instead. +func (*Ace3DeathEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{18} +} + +func (x *Ace3DeathEvent) GetSoldierId() uint32 { + if x != nil { + return x.SoldierId + } + return 0 +} + +func (x *Ace3DeathEvent) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *Ace3DeathEvent) GetLastDamageSourceId() uint32 { + if x != nil { + return x.LastDamageSourceId + } + return 0 +} + +func (x *Ace3DeathEvent) GetHasDamageSource() bool { + if x != nil { + return x.HasDamageSource + } + return false +} + +type Ace3UnconsciousEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + SoldierId uint32 `protobuf:"varint,1,opt,name=soldier_id,json=soldierId,proto3" json:"soldier_id,omitempty"` + IsUnconscious bool `protobuf:"varint,2,opt,name=is_unconscious,json=isUnconscious,proto3" json:"is_unconscious,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Ace3UnconsciousEvent) Reset() { + *x = Ace3UnconsciousEvent{} + mi := &file_ocap_v2_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Ace3UnconsciousEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Ace3UnconsciousEvent) ProtoMessage() {} + +func (x *Ace3UnconsciousEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Ace3UnconsciousEvent.ProtoReflect.Descriptor instead. +func (*Ace3UnconsciousEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{19} +} + +func (x *Ace3UnconsciousEvent) GetSoldierId() uint32 { + if x != nil { + return x.SoldierId + } + return 0 +} + +func (x *Ace3UnconsciousEvent) GetIsUnconscious() bool { + if x != nil { + return x.IsUnconscious + } + return false +} + +type TelemetryEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + FpsAverage float32 `protobuf:"fixed32,1,opt,name=fps_average,json=fpsAverage,proto3" json:"fps_average,omitempty"` + FpsMin float32 `protobuf:"fixed32,2,opt,name=fps_min,json=fpsMin,proto3" json:"fps_min,omitempty"` + SideEntityCounts *SideEntityCounts `protobuf:"bytes,3,opt,name=side_entity_counts,json=sideEntityCounts,proto3" json:"side_entity_counts,omitempty"` + GlobalCounts *GlobalEntityCount `protobuf:"bytes,4,opt,name=global_counts,json=globalCounts,proto3" json:"global_counts,omitempty"` + Scripts *ScriptCounts `protobuf:"bytes,5,opt,name=scripts,proto3" json:"scripts,omitempty"` + Weather *WeatherData `protobuf:"bytes,6,opt,name=weather,proto3" json:"weather,omitempty"` + Players []*PlayerNetworkData `protobuf:"bytes,7,rep,name=players,proto3" json:"players,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TelemetryEvent) Reset() { + *x = TelemetryEvent{} + mi := &file_ocap_v2_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TelemetryEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TelemetryEvent) ProtoMessage() {} + +func (x *TelemetryEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TelemetryEvent.ProtoReflect.Descriptor instead. +func (*TelemetryEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{20} +} + +func (x *TelemetryEvent) GetFpsAverage() float32 { + if x != nil { + return x.FpsAverage + } + return 0 +} + +func (x *TelemetryEvent) GetFpsMin() float32 { + if x != nil { + return x.FpsMin + } + return 0 +} + +func (x *TelemetryEvent) GetSideEntityCounts() *SideEntityCounts { + if x != nil { + return x.SideEntityCounts + } + return nil +} + +func (x *TelemetryEvent) GetGlobalCounts() *GlobalEntityCount { + if x != nil { + return x.GlobalCounts + } + return nil +} + +func (x *TelemetryEvent) GetScripts() *ScriptCounts { + if x != nil { + return x.Scripts + } + return nil +} + +func (x *TelemetryEvent) GetWeather() *WeatherData { + if x != nil { + return x.Weather + } + return nil +} + +func (x *TelemetryEvent) GetPlayers() []*PlayerNetworkData { + if x != nil { + return x.Players + } + return nil +} + +type SideEntityCounts struct { + state protoimpl.MessageState `protogen:"open.v1"` + East *SideEntityCount `protobuf:"bytes,1,opt,name=east,proto3" json:"east,omitempty"` + West *SideEntityCount `protobuf:"bytes,2,opt,name=west,proto3" json:"west,omitempty"` + Independent *SideEntityCount `protobuf:"bytes,3,opt,name=independent,proto3" json:"independent,omitempty"` + Civilian *SideEntityCount `protobuf:"bytes,4,opt,name=civilian,proto3" json:"civilian,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SideEntityCounts) Reset() { + *x = SideEntityCounts{} + mi := &file_ocap_v2_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SideEntityCounts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SideEntityCounts) ProtoMessage() {} + +func (x *SideEntityCounts) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SideEntityCounts.ProtoReflect.Descriptor instead. +func (*SideEntityCounts) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{21} +} + +func (x *SideEntityCounts) GetEast() *SideEntityCount { + if x != nil { + return x.East + } + return nil +} + +func (x *SideEntityCounts) GetWest() *SideEntityCount { + if x != nil { + return x.West + } + return nil +} + +func (x *SideEntityCounts) GetIndependent() *SideEntityCount { + if x != nil { + return x.Independent + } + return nil +} + +func (x *SideEntityCounts) GetCivilian() *SideEntityCount { + if x != nil { + return x.Civilian + } + return nil +} + +type SideEntityCount struct { + state protoimpl.MessageState `protogen:"open.v1"` + Local *EntityLocality `protobuf:"bytes,1,opt,name=local,proto3" json:"local,omitempty"` + Remote *EntityLocality `protobuf:"bytes,2,opt,name=remote,proto3" json:"remote,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SideEntityCount) Reset() { + *x = SideEntityCount{} + mi := &file_ocap_v2_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SideEntityCount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SideEntityCount) ProtoMessage() {} + +func (x *SideEntityCount) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SideEntityCount.ProtoReflect.Descriptor instead. +func (*SideEntityCount) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{22} +} + +func (x *SideEntityCount) GetLocal() *EntityLocality { + if x != nil { + return x.Local + } + return nil +} + +func (x *SideEntityCount) GetRemote() *EntityLocality { + if x != nil { + return x.Remote + } + return nil +} + +type EntityLocality struct { + state protoimpl.MessageState `protogen:"open.v1"` + UnitsTotal uint32 `protobuf:"varint,1,opt,name=units_total,json=unitsTotal,proto3" json:"units_total,omitempty"` + UnitsAlive uint32 `protobuf:"varint,2,opt,name=units_alive,json=unitsAlive,proto3" json:"units_alive,omitempty"` + UnitsDead uint32 `protobuf:"varint,3,opt,name=units_dead,json=unitsDead,proto3" json:"units_dead,omitempty"` + Groups uint32 `protobuf:"varint,4,opt,name=groups,proto3" json:"groups,omitempty"` + Vehicles uint32 `protobuf:"varint,5,opt,name=vehicles,proto3" json:"vehicles,omitempty"` + WeaponHolders uint32 `protobuf:"varint,6,opt,name=weapon_holders,json=weaponHolders,proto3" json:"weapon_holders,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EntityLocality) Reset() { + *x = EntityLocality{} + mi := &file_ocap_v2_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EntityLocality) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EntityLocality) ProtoMessage() {} + +func (x *EntityLocality) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EntityLocality.ProtoReflect.Descriptor instead. +func (*EntityLocality) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{23} +} + +func (x *EntityLocality) GetUnitsTotal() uint32 { + if x != nil { + return x.UnitsTotal + } + return 0 +} + +func (x *EntityLocality) GetUnitsAlive() uint32 { + if x != nil { + return x.UnitsAlive + } + return 0 +} + +func (x *EntityLocality) GetUnitsDead() uint32 { + if x != nil { + return x.UnitsDead + } + return 0 +} + +func (x *EntityLocality) GetGroups() uint32 { + if x != nil { + return x.Groups + } + return 0 +} + +func (x *EntityLocality) GetVehicles() uint32 { + if x != nil { + return x.Vehicles + } + return 0 +} + +func (x *EntityLocality) GetWeaponHolders() uint32 { + if x != nil { + return x.WeaponHolders + } + return 0 +} + +type GlobalEntityCount struct { + state protoimpl.MessageState `protogen:"open.v1"` + UnitsAlive uint32 `protobuf:"varint,1,opt,name=units_alive,json=unitsAlive,proto3" json:"units_alive,omitempty"` + UnitsDead uint32 `protobuf:"varint,2,opt,name=units_dead,json=unitsDead,proto3" json:"units_dead,omitempty"` + Groups uint32 `protobuf:"varint,3,opt,name=groups,proto3" json:"groups,omitempty"` + Vehicles uint32 `protobuf:"varint,4,opt,name=vehicles,proto3" json:"vehicles,omitempty"` + WeaponHolders uint32 `protobuf:"varint,5,opt,name=weapon_holders,json=weaponHolders,proto3" json:"weapon_holders,omitempty"` + PlayersAlive uint32 `protobuf:"varint,6,opt,name=players_alive,json=playersAlive,proto3" json:"players_alive,omitempty"` + PlayersDead uint32 `protobuf:"varint,7,opt,name=players_dead,json=playersDead,proto3" json:"players_dead,omitempty"` + PlayersConnected uint32 `protobuf:"varint,8,opt,name=players_connected,json=playersConnected,proto3" json:"players_connected,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GlobalEntityCount) Reset() { + *x = GlobalEntityCount{} + mi := &file_ocap_v2_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GlobalEntityCount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GlobalEntityCount) ProtoMessage() {} + +func (x *GlobalEntityCount) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GlobalEntityCount.ProtoReflect.Descriptor instead. +func (*GlobalEntityCount) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{24} +} + +func (x *GlobalEntityCount) GetUnitsAlive() uint32 { + if x != nil { + return x.UnitsAlive + } + return 0 +} + +func (x *GlobalEntityCount) GetUnitsDead() uint32 { + if x != nil { + return x.UnitsDead + } + return 0 +} + +func (x *GlobalEntityCount) GetGroups() uint32 { + if x != nil { + return x.Groups + } + return 0 +} + +func (x *GlobalEntityCount) GetVehicles() uint32 { + if x != nil { + return x.Vehicles + } + return 0 +} + +func (x *GlobalEntityCount) GetWeaponHolders() uint32 { + if x != nil { + return x.WeaponHolders + } + return 0 +} + +func (x *GlobalEntityCount) GetPlayersAlive() uint32 { + if x != nil { + return x.PlayersAlive + } + return 0 +} + +func (x *GlobalEntityCount) GetPlayersDead() uint32 { + if x != nil { + return x.PlayersDead + } + return 0 +} + +func (x *GlobalEntityCount) GetPlayersConnected() uint32 { + if x != nil { + return x.PlayersConnected + } + return 0 +} + +type ScriptCounts struct { + state protoimpl.MessageState `protogen:"open.v1"` + Spawn uint32 `protobuf:"varint,1,opt,name=spawn,proto3" json:"spawn,omitempty"` + ExecVm uint32 `protobuf:"varint,2,opt,name=exec_vm,json=execVm,proto3" json:"exec_vm,omitempty"` + Exec uint32 `protobuf:"varint,3,opt,name=exec,proto3" json:"exec,omitempty"` + ExecFsm uint32 `protobuf:"varint,4,opt,name=exec_fsm,json=execFsm,proto3" json:"exec_fsm,omitempty"` + Pfh uint32 `protobuf:"varint,5,opt,name=pfh,proto3" json:"pfh,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScriptCounts) Reset() { + *x = ScriptCounts{} + mi := &file_ocap_v2_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScriptCounts) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScriptCounts) ProtoMessage() {} + +func (x *ScriptCounts) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScriptCounts.ProtoReflect.Descriptor instead. +func (*ScriptCounts) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{25} +} + +func (x *ScriptCounts) GetSpawn() uint32 { + if x != nil { + return x.Spawn + } + return 0 +} + +func (x *ScriptCounts) GetExecVm() uint32 { + if x != nil { + return x.ExecVm + } + return 0 +} + +func (x *ScriptCounts) GetExec() uint32 { + if x != nil { + return x.Exec + } + return 0 +} + +func (x *ScriptCounts) GetExecFsm() uint32 { + if x != nil { + return x.ExecFsm + } + return 0 +} + +func (x *ScriptCounts) GetPfh() uint32 { + if x != nil { + return x.Pfh + } + return 0 +} + +type WeatherData struct { + state protoimpl.MessageState `protogen:"open.v1"` + Fog float32 `protobuf:"fixed32,1,opt,name=fog,proto3" json:"fog,omitempty"` + Overcast float32 `protobuf:"fixed32,2,opt,name=overcast,proto3" json:"overcast,omitempty"` + Rain float32 `protobuf:"fixed32,3,opt,name=rain,proto3" json:"rain,omitempty"` + Humidity float32 `protobuf:"fixed32,4,opt,name=humidity,proto3" json:"humidity,omitempty"` + Waves float32 `protobuf:"fixed32,5,opt,name=waves,proto3" json:"waves,omitempty"` + WindDir float32 `protobuf:"fixed32,6,opt,name=wind_dir,json=windDir,proto3" json:"wind_dir,omitempty"` + WindStr float32 `protobuf:"fixed32,7,opt,name=wind_str,json=windStr,proto3" json:"wind_str,omitempty"` + Gusts float32 `protobuf:"fixed32,8,opt,name=gusts,proto3" json:"gusts,omitempty"` + Lightnings float32 `protobuf:"fixed32,9,opt,name=lightnings,proto3" json:"lightnings,omitempty"` + MoonIntensity float32 `protobuf:"fixed32,10,opt,name=moon_intensity,json=moonIntensity,proto3" json:"moon_intensity,omitempty"` + MoonPhase float32 `protobuf:"fixed32,11,opt,name=moon_phase,json=moonPhase,proto3" json:"moon_phase,omitempty"` + SunOrMoon float32 `protobuf:"fixed32,12,opt,name=sun_or_moon,json=sunOrMoon,proto3" json:"sun_or_moon,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WeatherData) Reset() { + *x = WeatherData{} + mi := &file_ocap_v2_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WeatherData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WeatherData) ProtoMessage() {} + +func (x *WeatherData) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WeatherData.ProtoReflect.Descriptor instead. +func (*WeatherData) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{26} +} + +func (x *WeatherData) GetFog() float32 { + if x != nil { + return x.Fog + } + return 0 +} + +func (x *WeatherData) GetOvercast() float32 { + if x != nil { + return x.Overcast + } + return 0 +} + +func (x *WeatherData) GetRain() float32 { + if x != nil { + return x.Rain + } + return 0 +} + +func (x *WeatherData) GetHumidity() float32 { + if x != nil { + return x.Humidity + } + return 0 +} + +func (x *WeatherData) GetWaves() float32 { + if x != nil { + return x.Waves + } + return 0 +} + +func (x *WeatherData) GetWindDir() float32 { + if x != nil { + return x.WindDir + } + return 0 +} + +func (x *WeatherData) GetWindStr() float32 { + if x != nil { + return x.WindStr + } + return 0 +} + +func (x *WeatherData) GetGusts() float32 { + if x != nil { + return x.Gusts + } + return 0 +} + +func (x *WeatherData) GetLightnings() float32 { + if x != nil { + return x.Lightnings + } + return 0 +} + +func (x *WeatherData) GetMoonIntensity() float32 { + if x != nil { + return x.MoonIntensity + } + return 0 +} + +func (x *WeatherData) GetMoonPhase() float32 { + if x != nil { + return x.MoonPhase + } + return 0 +} + +func (x *WeatherData) GetSunOrMoon() float32 { + if x != nil { + return x.SunOrMoon + } + return 0 +} + +type PlayerNetworkData struct { + state protoimpl.MessageState `protogen:"open.v1"` + Uid string `protobuf:"bytes,1,opt,name=uid,proto3" json:"uid,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Ping float32 `protobuf:"fixed32,3,opt,name=ping,proto3" json:"ping,omitempty"` + Bw float32 `protobuf:"fixed32,4,opt,name=bw,proto3" json:"bw,omitempty"` + Desync float32 `protobuf:"fixed32,5,opt,name=desync,proto3" json:"desync,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PlayerNetworkData) Reset() { + *x = PlayerNetworkData{} + mi := &file_ocap_v2_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PlayerNetworkData) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PlayerNetworkData) ProtoMessage() {} + +func (x *PlayerNetworkData) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PlayerNetworkData.ProtoReflect.Descriptor instead. +func (*PlayerNetworkData) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{27} +} + +func (x *PlayerNetworkData) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + +func (x *PlayerNetworkData) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PlayerNetworkData) GetPing() float32 { + if x != nil { + return x.Ping + } + return 0 +} + +func (x *PlayerNetworkData) GetBw() float32 { + if x != nil { + return x.Bw + } + return 0 +} + +func (x *PlayerNetworkData) GetDesync() float32 { + if x != nil { + return x.Desync + } + return 0 +} + +type GeneralEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GeneralEvent) Reset() { + *x = GeneralEvent{} + mi := &file_ocap_v2_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GeneralEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneralEvent) ProtoMessage() {} + +func (x *GeneralEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneralEvent.ProtoReflect.Descriptor instead. +func (*GeneralEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{28} +} + +func (x *GeneralEvent) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *GeneralEvent) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type ConnectEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + UnitName string `protobuf:"bytes,1,opt,name=unit_name,json=unitName,proto3" json:"unit_name,omitempty"` + IsConnect bool `protobuf:"varint,2,opt,name=is_connect,json=isConnect,proto3" json:"is_connect,omitempty"` // true = connected, false = disconnected + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConnectEvent) Reset() { + *x = ConnectEvent{} + mi := &file_ocap_v2_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConnectEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConnectEvent) ProtoMessage() {} + +func (x *ConnectEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConnectEvent.ProtoReflect.Descriptor instead. +func (*ConnectEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{29} +} + +func (x *ConnectEvent) GetUnitName() string { + if x != nil { + return x.UnitName + } + return "" +} + +func (x *ConnectEvent) GetIsConnect() bool { + if x != nil { + return x.IsConnect + } + return false +} + +type EndMissionEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Side string `protobuf:"bytes,1,opt,name=side,proto3" json:"side,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EndMissionEvent) Reset() { + *x = EndMissionEvent{} + mi := &file_ocap_v2_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EndMissionEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EndMissionEvent) ProtoMessage() {} + +func (x *EndMissionEvent) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EndMissionEvent.ProtoReflect.Descriptor instead. +func (*EndMissionEvent) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{30} +} + +func (x *EndMissionEvent) GetSide() string { + if x != nil { + return x.Side + } + return "" +} + +func (x *EndMissionEvent) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type Chunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + Index uint32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` + StartFrame uint32 `protobuf:"varint,2,opt,name=start_frame,json=startFrame,proto3" json:"start_frame,omitempty"` + FrameCount uint32 `protobuf:"varint,3,opt,name=frame_count,json=frameCount,proto3" json:"frame_count,omitempty"` + Frames []*Frame `protobuf:"bytes,4,rep,name=frames,proto3" json:"frames,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Chunk) Reset() { + *x = Chunk{} + mi := &file_ocap_v2_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Chunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Chunk) ProtoMessage() {} + +func (x *Chunk) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Chunk.ProtoReflect.Descriptor instead. +func (*Chunk) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{31} +} + +func (x *Chunk) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Chunk) GetStartFrame() uint32 { + if x != nil { + return x.StartFrame + } + return 0 +} + +func (x *Chunk) GetFrameCount() uint32 { + if x != nil { + return x.FrameCount + } + return 0 +} + +func (x *Chunk) GetFrames() []*Frame { + if x != nil { + return x.Frames + } + return nil +} + +type Frame struct { + state protoimpl.MessageState `protogen:"open.v1"` + FrameNum uint32 `protobuf:"varint,1,opt,name=frame_num,json=frameNum,proto3" json:"frame_num,omitempty"` + Soldiers []*SoldierState `protobuf:"bytes,2,rep,name=soldiers,proto3" json:"soldiers,omitempty"` + Vehicles []*VehicleState `protobuf:"bytes,3,rep,name=vehicles,proto3" json:"vehicles,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Frame) Reset() { + *x = Frame{} + mi := &file_ocap_v2_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Frame) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Frame) ProtoMessage() {} + +func (x *Frame) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Frame.ProtoReflect.Descriptor instead. +func (*Frame) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{32} +} + +func (x *Frame) GetFrameNum() uint32 { + if x != nil { + return x.FrameNum + } + return 0 +} + +func (x *Frame) GetSoldiers() []*SoldierState { + if x != nil { + return x.Soldiers + } + return nil +} + +func (x *Frame) GetVehicles() []*VehicleState { + if x != nil { + return x.Vehicles + } + return nil +} + +type SoldierState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Position *Position3D `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + Bearing uint32 `protobuf:"varint,3,opt,name=bearing,proto3" json:"bearing,omitempty"` + Lifestate uint32 `protobuf:"varint,4,opt,name=lifestate,proto3" json:"lifestate,omitempty"` // 0=dead, 1=alive, 2=unconscious + VehicleId uint32 `protobuf:"varint,5,opt,name=vehicle_id,json=vehicleId,proto3" json:"vehicle_id,omitempty"` + InVehicle bool `protobuf:"varint,6,opt,name=in_vehicle,json=inVehicle,proto3" json:"in_vehicle,omitempty"` + Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"` + IsPlayer bool `protobuf:"varint,8,opt,name=is_player,json=isPlayer,proto3" json:"is_player,omitempty"` + GroupName string `protobuf:"bytes,9,opt,name=group_name,json=groupName,proto3" json:"group_name,omitempty"` + Side string `protobuf:"bytes,10,opt,name=side,proto3" json:"side,omitempty"` + Role string `protobuf:"bytes,11,opt,name=role,proto3" json:"role,omitempty"` + VehicleRole string `protobuf:"bytes,12,opt,name=vehicle_role,json=vehicleRole,proto3" json:"vehicle_role,omitempty"` + Stance string `protobuf:"bytes,13,opt,name=stance,proto3" json:"stance,omitempty"` + HasStableVitals bool `protobuf:"varint,14,opt,name=has_stable_vitals,json=hasStableVitals,proto3" json:"has_stable_vitals,omitempty"` + IsDraggedCarried bool `protobuf:"varint,15,opt,name=is_dragged_carried,json=isDraggedCarried,proto3" json:"is_dragged_carried,omitempty"` + Scores *SoldierScores `protobuf:"bytes,16,opt,name=scores,proto3" json:"scores,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SoldierState) Reset() { + *x = SoldierState{} + mi := &file_ocap_v2_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SoldierState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SoldierState) ProtoMessage() {} + +func (x *SoldierState) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SoldierState.ProtoReflect.Descriptor instead. +func (*SoldierState) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{33} +} + +func (x *SoldierState) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *SoldierState) GetPosition() *Position3D { + if x != nil { + return x.Position + } + return nil +} + +func (x *SoldierState) GetBearing() uint32 { + if x != nil { + return x.Bearing + } + return 0 +} + +func (x *SoldierState) GetLifestate() uint32 { + if x != nil { + return x.Lifestate + } + return 0 +} + +func (x *SoldierState) GetVehicleId() uint32 { + if x != nil { + return x.VehicleId + } + return 0 +} + +func (x *SoldierState) GetInVehicle() bool { + if x != nil { + return x.InVehicle + } + return false +} + +func (x *SoldierState) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SoldierState) GetIsPlayer() bool { + if x != nil { + return x.IsPlayer + } + return false +} + +func (x *SoldierState) GetGroupName() string { + if x != nil { + return x.GroupName + } + return "" +} + +func (x *SoldierState) GetSide() string { + if x != nil { + return x.Side + } + return "" +} + +func (x *SoldierState) GetRole() string { + if x != nil { + return x.Role + } + return "" +} + +func (x *SoldierState) GetVehicleRole() string { + if x != nil { + return x.VehicleRole + } + return "" +} + +func (x *SoldierState) GetStance() string { + if x != nil { + return x.Stance + } + return "" +} + +func (x *SoldierState) GetHasStableVitals() bool { + if x != nil { + return x.HasStableVitals + } + return false +} + +func (x *SoldierState) GetIsDraggedCarried() bool { + if x != nil { + return x.IsDraggedCarried + } + return false +} + +func (x *SoldierState) GetScores() *SoldierScores { + if x != nil { + return x.Scores + } + return nil +} + +type SoldierScores struct { + state protoimpl.MessageState `protogen:"open.v1"` + InfantryKills uint32 `protobuf:"varint,1,opt,name=infantry_kills,json=infantryKills,proto3" json:"infantry_kills,omitempty"` + VehicleKills uint32 `protobuf:"varint,2,opt,name=vehicle_kills,json=vehicleKills,proto3" json:"vehicle_kills,omitempty"` + ArmorKills uint32 `protobuf:"varint,3,opt,name=armor_kills,json=armorKills,proto3" json:"armor_kills,omitempty"` + AirKills uint32 `protobuf:"varint,4,opt,name=air_kills,json=airKills,proto3" json:"air_kills,omitempty"` + Deaths uint32 `protobuf:"varint,5,opt,name=deaths,proto3" json:"deaths,omitempty"` + TotalScore uint32 `protobuf:"varint,6,opt,name=total_score,json=totalScore,proto3" json:"total_score,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SoldierScores) Reset() { + *x = SoldierScores{} + mi := &file_ocap_v2_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SoldierScores) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SoldierScores) ProtoMessage() {} + +func (x *SoldierScores) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[34] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SoldierScores.ProtoReflect.Descriptor instead. +func (*SoldierScores) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{34} +} + +func (x *SoldierScores) GetInfantryKills() uint32 { + if x != nil { + return x.InfantryKills + } + return 0 +} + +func (x *SoldierScores) GetVehicleKills() uint32 { + if x != nil { + return x.VehicleKills + } + return 0 +} + +func (x *SoldierScores) GetArmorKills() uint32 { + if x != nil { + return x.ArmorKills + } + return 0 +} + +func (x *SoldierScores) GetAirKills() uint32 { + if x != nil { + return x.AirKills + } + return 0 +} + +func (x *SoldierScores) GetDeaths() uint32 { + if x != nil { + return x.Deaths + } + return 0 +} + +func (x *SoldierScores) GetTotalScore() uint32 { + if x != nil { + return x.TotalScore + } + return 0 +} + +type VehicleState struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Position *Position3D `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + Bearing uint32 `protobuf:"varint,3,opt,name=bearing,proto3" json:"bearing,omitempty"` + Alive bool `protobuf:"varint,4,opt,name=alive,proto3" json:"alive,omitempty"` + CrewIds []uint32 `protobuf:"varint,5,rep,packed,name=crew_ids,json=crewIds,proto3" json:"crew_ids,omitempty"` + Fuel float32 `protobuf:"fixed32,6,opt,name=fuel,proto3" json:"fuel,omitempty"` + Damage float32 `protobuf:"fixed32,7,opt,name=damage,proto3" json:"damage,omitempty"` + Locked bool `protobuf:"varint,8,opt,name=locked,proto3" json:"locked,omitempty"` + EngineOn bool `protobuf:"varint,9,opt,name=engine_on,json=engineOn,proto3" json:"engine_on,omitempty"` + Side string `protobuf:"bytes,10,opt,name=side,proto3" json:"side,omitempty"` + TurretAzimuth float32 `protobuf:"fixed32,11,opt,name=turret_azimuth,json=turretAzimuth,proto3" json:"turret_azimuth,omitempty"` + TurretElevation float32 `protobuf:"fixed32,12,opt,name=turret_elevation,json=turretElevation,proto3" json:"turret_elevation,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *VehicleState) Reset() { + *x = VehicleState{} + mi := &file_ocap_v2_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *VehicleState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VehicleState) ProtoMessage() {} + +func (x *VehicleState) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use VehicleState.ProtoReflect.Descriptor instead. +func (*VehicleState) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{35} +} + +func (x *VehicleState) GetId() uint32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *VehicleState) GetPosition() *Position3D { + if x != nil { + return x.Position + } + return nil +} + +func (x *VehicleState) GetBearing() uint32 { + if x != nil { + return x.Bearing + } + return 0 +} + +func (x *VehicleState) GetAlive() bool { + if x != nil { + return x.Alive + } + return false +} + +func (x *VehicleState) GetCrewIds() []uint32 { + if x != nil { + return x.CrewIds + } + return nil +} + +func (x *VehicleState) GetFuel() float32 { + if x != nil { + return x.Fuel + } + return 0 +} + +func (x *VehicleState) GetDamage() float32 { + if x != nil { + return x.Damage + } + return 0 +} + +func (x *VehicleState) GetLocked() bool { + if x != nil { + return x.Locked + } + return false +} + +func (x *VehicleState) GetEngineOn() bool { + if x != nil { + return x.EngineOn + } + return false +} + +func (x *VehicleState) GetSide() string { + if x != nil { + return x.Side + } + return "" +} + +func (x *VehicleState) GetTurretAzimuth() float32 { + if x != nil { + return x.TurretAzimuth + } + return 0 +} + +func (x *VehicleState) GetTurretElevation() float32 { + if x != nil { + return x.TurretElevation + } + return 0 +} + +type MarkerDef struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Text string `protobuf:"bytes,2,opt,name=text,proto3" json:"text,omitempty"` + StartFrame uint32 `protobuf:"varint,3,opt,name=start_frame,json=startFrame,proto3" json:"start_frame,omitempty"` + EndFrame uint32 `protobuf:"varint,4,opt,name=end_frame,json=endFrame,proto3" json:"end_frame,omitempty"` + PlayerId int32 `protobuf:"varint,5,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"` + Color string `protobuf:"bytes,6,opt,name=color,proto3" json:"color,omitempty"` + Side Side `protobuf:"varint,7,opt,name=side,proto3,enum=ocap.v2.Side" json:"side,omitempty"` + Positions []*MarkerPosition `protobuf:"bytes,8,rep,name=positions,proto3" json:"positions,omitempty"` + Size []float32 `protobuf:"fixed32,9,rep,packed,name=size,proto3" json:"size,omitempty"` + Shape string `protobuf:"bytes,10,opt,name=shape,proto3" json:"shape,omitempty"` + Brush string `protobuf:"bytes,11,opt,name=brush,proto3" json:"brush,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MarkerDef) Reset() { + *x = MarkerDef{} + mi := &file_ocap_v2_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MarkerDef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MarkerDef) ProtoMessage() {} + +func (x *MarkerDef) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MarkerDef.ProtoReflect.Descriptor instead. +func (*MarkerDef) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{36} +} + +func (x *MarkerDef) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *MarkerDef) GetText() string { + if x != nil { + return x.Text + } + return "" +} + +func (x *MarkerDef) GetStartFrame() uint32 { + if x != nil { + return x.StartFrame + } + return 0 +} + +func (x *MarkerDef) GetEndFrame() uint32 { + if x != nil { + return x.EndFrame + } + return 0 +} + +func (x *MarkerDef) GetPlayerId() int32 { + if x != nil { + return x.PlayerId + } + return 0 +} + +func (x *MarkerDef) GetColor() string { + if x != nil { + return x.Color + } + return "" +} + +func (x *MarkerDef) GetSide() Side { + if x != nil { + return x.Side + } + return Side_SIDE_UNKNOWN +} + +func (x *MarkerDef) GetPositions() []*MarkerPosition { + if x != nil { + return x.Positions + } + return nil +} + +func (x *MarkerDef) GetSize() []float32 { + if x != nil { + return x.Size + } + return nil +} + +func (x *MarkerDef) GetShape() string { + if x != nil { + return x.Shape + } + return "" +} + +func (x *MarkerDef) GetBrush() string { + if x != nil { + return x.Brush + } + return "" +} + +type MarkerPosition struct { + state protoimpl.MessageState `protogen:"open.v1"` + FrameNum uint32 `protobuf:"varint,1,opt,name=frame_num,json=frameNum,proto3" json:"frame_num,omitempty"` + Position *Position3D `protobuf:"bytes,2,opt,name=position,proto3" json:"position,omitempty"` + Direction float32 `protobuf:"fixed32,3,opt,name=direction,proto3" json:"direction,omitempty"` + Alpha float32 `protobuf:"fixed32,4,opt,name=alpha,proto3" json:"alpha,omitempty"` + LineCoords []float32 `protobuf:"fixed32,5,rep,packed,name=line_coords,json=lineCoords,proto3" json:"line_coords,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MarkerPosition) Reset() { + *x = MarkerPosition{} + mi := &file_ocap_v2_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MarkerPosition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MarkerPosition) ProtoMessage() {} + +func (x *MarkerPosition) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[37] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MarkerPosition.ProtoReflect.Descriptor instead. +func (*MarkerPosition) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{37} +} + +func (x *MarkerPosition) GetFrameNum() uint32 { + if x != nil { + return x.FrameNum + } + return 0 +} + +func (x *MarkerPosition) GetPosition() *Position3D { + if x != nil { + return x.Position + } + return nil +} + +func (x *MarkerPosition) GetDirection() float32 { + if x != nil { + return x.Direction + } + return 0 +} + +func (x *MarkerPosition) GetAlpha() float32 { + if x != nil { + return x.Alpha + } + return 0 +} + +func (x *MarkerPosition) GetLineCoords() []float32 { + if x != nil { + return x.LineCoords + } + return nil +} + +type TimeSample struct { + state protoimpl.MessageState `protogen:"open.v1"` + FrameNum uint32 `protobuf:"varint,1,opt,name=frame_num,json=frameNum,proto3" json:"frame_num,omitempty"` + SystemTimeUtc string `protobuf:"bytes,2,opt,name=system_time_utc,json=systemTimeUtc,proto3" json:"system_time_utc,omitempty"` + Date string `protobuf:"bytes,3,opt,name=date,proto3" json:"date,omitempty"` + TimeMultiplier float32 `protobuf:"fixed32,4,opt,name=time_multiplier,json=timeMultiplier,proto3" json:"time_multiplier,omitempty"` + Time float32 `protobuf:"fixed32,5,opt,name=time,proto3" json:"time,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TimeSample) Reset() { + *x = TimeSample{} + mi := &file_ocap_v2_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TimeSample) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TimeSample) ProtoMessage() {} + +func (x *TimeSample) ProtoReflect() protoreflect.Message { + mi := &file_ocap_v2_proto_msgTypes[38] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TimeSample.ProtoReflect.Descriptor instead. +func (*TimeSample) Descriptor() ([]byte, []int) { + return file_ocap_v2_proto_rawDescGZIP(), []int{38} +} + +func (x *TimeSample) GetFrameNum() uint32 { + if x != nil { + return x.FrameNum + } + return 0 +} + +func (x *TimeSample) GetSystemTimeUtc() string { + if x != nil { + return x.SystemTimeUtc + } + return "" +} + +func (x *TimeSample) GetDate() string { + if x != nil { + return x.Date + } + return "" +} + +func (x *TimeSample) GetTimeMultiplier() float32 { + if x != nil { + return x.TimeMultiplier + } + return 0 +} + +func (x *TimeSample) GetTime() float32 { + if x != nil { + return x.Time + } + return 0 +} + +var File_ocap_v2_proto protoreflect.FileDescriptor + +const file_ocap_v2_proto_rawDesc = "" + + "\n" + + "\rocap_v2.proto\x12\aocap.v2\"6\n" + + "\n" + + "Position3D\x12\f\n" + + "\x01x\x18\x01 \x01(\x02R\x01x\x12\f\n" + + "\x01y\x18\x02 \x01(\x02R\x01y\x12\f\n" + + "\x01z\x18\x03 \x01(\x02R\x01z\"\xec\x03\n" + + "\bManifest\x12\x18\n" + + "\aversion\x18\x01 \x01(\rR\aversion\x12(\n" + + "\x05world\x18\x02 \x01(\v2\x12.ocap.v2.WorldMetaR\x05world\x12.\n" + + "\amission\x18\x03 \x01(\v2\x14.ocap.v2.MissionMetaR\amission\x12\x1f\n" + + "\vframe_count\x18\x04 \x01(\rR\n" + + "frameCount\x12\x1d\n" + + "\n" + + "chunk_size\x18\x05 \x01(\rR\tchunkSize\x12(\n" + + "\x10capture_delay_ms\x18\x06 \x01(\rR\x0ecaptureDelayMs\x12\x1f\n" + + "\vchunk_count\x18\a \x01(\rR\n" + + "chunkCount\x12/\n" + + "\bsoldiers\x18\n" + + " \x03(\v2\x13.ocap.v2.SoldierDefR\bsoldiers\x12/\n" + + "\bvehicles\x18\v \x03(\v2\x13.ocap.v2.VehicleDefR\bvehicles\x12&\n" + + "\x06events\x18\f \x03(\v2\x0e.ocap.v2.EventR\x06events\x12,\n" + + "\amarkers\x18\r \x03(\v2\x12.ocap.v2.MarkerDefR\amarkers\x12)\n" + + "\x05times\x18\x0e \x03(\v2\x13.ocap.v2.TimeSampleR\x05times\"\xbe\x01\n" + + "\tWorldMeta\x12\x1d\n" + + "\n" + + "world_name\x18\x01 \x01(\tR\tworldName\x12\x1d\n" + + "\n" + + "world_size\x18\x02 \x01(\x02R\tworldSize\x12\x1a\n" + + "\blatitude\x18\x03 \x01(\x02R\blatitude\x12\x1c\n" + + "\tlongitude\x18\x04 \x01(\x02R\tlongitude\x12\x16\n" + + "\x06author\x18\x05 \x01(\tR\x06author\x12!\n" + + "\fdisplay_name\x18\x06 \x01(\tR\vdisplayName\"\xbe\x03\n" + + "\vMissionMeta\x12!\n" + + "\fmission_name\x18\x01 \x01(\tR\vmissionName\x12#\n" + + "\rbriefing_name\x18\x02 \x01(\tR\fbriefingName\x12\x16\n" + + "\x06author\x18\x03 \x01(\tR\x06author\x12\x1f\n" + + "\vserver_name\x18\x04 \x01(\tR\n" + + "serverName\x12+\n" + + "\x11extension_version\x18\x05 \x01(\tR\x10extensionVersion\x12#\n" + + "\raddon_version\x18\x06 \x01(\tR\faddonVersion\x12'\n" + + "\x0fextension_build\x18\a \x01(\tR\x0eextensionBuild\x12\x10\n" + + "\x03tag\x18\b \x01(\tR\x03tag\x12=\n" + + "\x0eplayable_slots\x18\t \x01(\v2\x16.ocap.v2.PlayableSlotsR\rplayableSlots\x12:\n" + + "\rside_friendly\x18\n" + + " \x01(\v2\x15.ocap.v2.SideFriendlyR\fsideFriendly\x12&\n" + + "\x06addons\x18\v \x03(\v2\x0e.ocap.v2.AddonR\x06addons\"u\n" + + "\rPlayableSlots\x12\x12\n" + + "\x04west\x18\x01 \x01(\rR\x04west\x12\x12\n" + + "\x04east\x18\x02 \x01(\rR\x04east\x12 \n" + + "\vindependent\x18\x03 \x01(\rR\vindependent\x12\x1a\n" + + "\bcivilian\x18\x04 \x01(\rR\bcivilian\"\x81\x01\n" + + "\fSideFriendly\x12\x1b\n" + + "\teast_west\x18\x01 \x01(\bR\beastWest\x12)\n" + + "\x10east_independent\x18\x02 \x01(\bR\x0feastIndependent\x12)\n" + + "\x10west_independent\x18\x03 \x01(\bR\x0fwestIndependent\"<\n" + + "\x05Addon\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" + + "\vworkshop_id\x18\x02 \x01(\tR\n" + + "workshopId\"\xd7\x02\n" + + "\n" + + "SoldierDef\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12!\n" + + "\x04side\x18\x03 \x01(\x0e2\r.ocap.v2.SideR\x04side\x12\x1d\n" + + "\n" + + "group_name\x18\x04 \x01(\tR\tgroupName\x12\x12\n" + + "\x04role\x18\x05 \x01(\tR\x04role\x12\x1f\n" + + "\vstart_frame\x18\x06 \x01(\rR\n" + + "startFrame\x12\x1b\n" + + "\tend_frame\x18\a \x01(\rR\bendFrame\x12\x1b\n" + + "\tis_player\x18\b \x01(\bR\bisPlayer\x12\x1d\n" + + "\n" + + "class_name\x18\t \x01(\tR\tclassName\x12\x1d\n" + + "\n" + + "player_uid\x18\n" + + " \x01(\tR\tplayerUid\x126\n" + + "\fframes_fired\x18\v \x03(\v2\x13.ocap.v2.FiredFrameR\vframesFired\"\xd8\x01\n" + + "\n" + + "VehicleDef\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12#\n" + + "\rvehicle_class\x18\x03 \x01(\tR\fvehicleClass\x12\x1d\n" + + "\n" + + "class_name\x18\x04 \x01(\tR\tclassName\x12\x1f\n" + + "\vstart_frame\x18\x05 \x01(\rR\n" + + "startFrame\x12\x1b\n" + + "\tend_frame\x18\x06 \x01(\rR\bendFrame\x12$\n" + + "\rcustomization\x18\a \x01(\tR\rcustomization\"\xde\x01\n" + + "\n" + + "FiredFrame\x12\x1b\n" + + "\tframe_num\x18\x01 \x01(\rR\bframeNum\x120\n" + + "\tstart_pos\x18\x02 \x01(\v2\x13.ocap.v2.Position3DR\bstartPos\x12,\n" + + "\aend_pos\x18\x03 \x01(\v2\x13.ocap.v2.Position3DR\x06endPos\x12\x16\n" + + "\x06weapon\x18\x04 \x01(\tR\x06weapon\x12\x1a\n" + + "\bmagazine\x18\x05 \x01(\tR\bmagazine\x12\x1f\n" + + "\vfiring_mode\x18\x06 \x01(\tR\n" + + "firingMode\"\xf3\x04\n" + + "\x05Event\x12\x1b\n" + + "\tframe_num\x18\x01 \x01(\rR\bframeNum\x12(\n" + + "\x04kill\x18\n" + + " \x01(\v2\x12.ocap.v2.KillEventH\x00R\x04kill\x12%\n" + + "\x03hit\x18\v \x01(\v2\x11.ocap.v2.HitEventH\x00R\x03hit\x12:\n" + + "\n" + + "projectile\x18\f \x01(\v2\x18.ocap.v2.ProjectileEventH\x00R\n" + + "projectile\x12(\n" + + "\x04chat\x18\r \x01(\v2\x12.ocap.v2.ChatEventH\x00R\x04chat\x12+\n" + + "\x05radio\x18\x0e \x01(\v2\x13.ocap.v2.RadioEventH\x00R\x05radio\x128\n" + + "\n" + + "ace3_death\x18\x0f \x01(\v2\x17.ocap.v2.Ace3DeathEventH\x00R\tace3Death\x12J\n" + + "\x10ace3_unconscious\x18\x10 \x01(\v2\x1d.ocap.v2.Ace3UnconsciousEventH\x00R\x0face3Unconscious\x121\n" + + "\ageneral\x18\x11 \x01(\v2\x15.ocap.v2.GeneralEventH\x00R\ageneral\x121\n" + + "\aconnect\x18\x12 \x01(\v2\x15.ocap.v2.ConnectEventH\x00R\aconnect\x12;\n" + + "\vend_mission\x18\x13 \x01(\v2\x18.ocap.v2.EndMissionEventH\x00R\n" + + "endMission\x127\n" + + "\ttelemetry\x18\x14 \x01(\v2\x17.ocap.v2.TelemetryEventH\x00R\ttelemetryB\a\n" + + "\x05event\"\x98\x03\n" + + "\tKillEvent\x12*\n" + + "\x11victim_soldier_id\x18\x01 \x01(\rR\x0fvictimSoldierId\x12*\n" + + "\x11victim_vehicle_id\x18\x02 \x01(\rR\x0fvictimVehicleId\x12*\n" + + "\x11killer_soldier_id\x18\x03 \x01(\rR\x0fkillerSoldierId\x12*\n" + + "\x11killer_vehicle_id\x18\x04 \x01(\rR\x0fkillerVehicleId\x12\x1f\n" + + "\vweapon_name\x18\x05 \x01(\tR\n" + + "weaponName\x12'\n" + + "\x0fweapon_magazine\x18\x06 \x01(\tR\x0eweaponMagazine\x12\x1d\n" + + "\n" + + "event_text\x18\a \x01(\tR\teventText\x12\x1a\n" + + "\bdistance\x18\b \x01(\x02R\bdistance\x12*\n" + + "\x11victim_is_vehicle\x18\t \x01(\bR\x0fvictimIsVehicle\x12*\n" + + "\x11killer_is_vehicle\x18\n" + + " \x01(\bR\x0fkillerIsVehicle\"\x9d\x03\n" + + "\bHitEvent\x12*\n" + + "\x11victim_soldier_id\x18\x01 \x01(\rR\x0fvictimSoldierId\x12*\n" + + "\x11victim_vehicle_id\x18\x02 \x01(\rR\x0fvictimVehicleId\x12,\n" + + "\x12shooter_soldier_id\x18\x03 \x01(\rR\x10shooterSoldierId\x12,\n" + + "\x12shooter_vehicle_id\x18\x04 \x01(\rR\x10shooterVehicleId\x12\x1f\n" + + "\vweapon_name\x18\x05 \x01(\tR\n" + + "weaponName\x12'\n" + + "\x0fweapon_magazine\x18\x06 \x01(\tR\x0eweaponMagazine\x12\x1d\n" + + "\n" + + "event_text\x18\a \x01(\tR\teventText\x12\x1a\n" + + "\bdistance\x18\b \x01(\x02R\bdistance\x12*\n" + + "\x11victim_is_vehicle\x18\t \x01(\bR\x0fvictimIsVehicle\x12,\n" + + "\x12shooter_is_vehicle\x18\n" + + " \x01(\bR\x10shooterIsVehicle\"\xcb\x02\n" + + "\x0fProjectileEvent\x12\x19\n" + + "\bfirer_id\x18\x01 \x01(\rR\afirerId\x12\x1d\n" + + "\n" + + "vehicle_id\x18\x02 \x01(\rR\tvehicleId\x12\x16\n" + + "\x06weapon\x18\x03 \x01(\tR\x06weapon\x12\x1a\n" + + "\bmagazine\x18\x04 \x01(\tR\bmagazine\x12'\n" + + "\x0fsimulation_type\x18\x05 \x01(\tR\x0esimulationType\x128\n" + + "\n" + + "trajectory\x18\x06 \x03(\v2\x18.ocap.v2.TrajectoryPointR\n" + + "trajectory\x12*\n" + + "\x04hits\x18\a \x03(\v2\x16.ocap.v2.ProjectileHitR\x04hits\x12\x16\n" + + "\x06muzzle\x18\b \x01(\tR\x06muzzle\x12#\n" + + "\rmagazine_icon\x18\t \x01(\tR\fmagazineIcon\"_\n" + + "\x0fTrajectoryPoint\x12/\n" + + "\bposition\x18\x01 \x01(\v2\x13.ocap.v2.Position3DR\bposition\x12\x1b\n" + + "\tframe_num\x18\x02 \x01(\rR\bframeNum\"\x84\x02\n" + + "\rProjectileHit\x12\x1b\n" + + "\tframe_num\x18\x01 \x01(\rR\bframeNum\x12/\n" + + "\bposition\x18\x02 \x01(\v2\x13.ocap.v2.Position3DR\bposition\x12\x1d\n" + + "\n" + + "soldier_id\x18\x03 \x01(\rR\tsoldierId\x12\x1d\n" + + "\n" + + "vehicle_id\x18\x04 \x01(\rR\tvehicleId\x12\x1f\n" + + "\vhit_soldier\x18\x05 \x01(\bR\n" + + "hitSoldier\x12\x1f\n" + + "\vhit_vehicle\x18\x06 \x01(\bR\n" + + "hitVehicle\x12%\n" + + "\x0ecomponents_hit\x18\a \x03(\tR\rcomponentsHit\"\x9a\x01\n" + + "\tChatEvent\x12\x1d\n" + + "\n" + + "soldier_id\x18\x01 \x01(\rR\tsoldierId\x12\x18\n" + + "\achannel\x18\x02 \x01(\tR\achannel\x12\x1b\n" + + "\tfrom_name\x18\x03 \x01(\tR\bfromName\x12\x18\n" + + "\amessage\x18\x04 \x01(\tR\amessage\x12\x1d\n" + + "\n" + + "player_uid\x18\x05 \x01(\tR\tplayerUid\"\xda\x01\n" + + "\n" + + "RadioEvent\x12\x1d\n" + + "\n" + + "soldier_id\x18\x01 \x01(\rR\tsoldierId\x12\x14\n" + + "\x05radio\x18\x02 \x01(\tR\x05radio\x12\x1d\n" + + "\n" + + "radio_type\x18\x03 \x01(\tR\tradioType\x12\x1b\n" + + "\tstart_end\x18\x04 \x01(\tR\bstartEnd\x12\x18\n" + + "\achannel\x18\x05 \x01(\x05R\achannel\x12#\n" + + "\ris_additional\x18\x06 \x01(\bR\fisAdditional\x12\x1c\n" + + "\tfrequency\x18\a \x01(\x02R\tfrequency\"\xa6\x01\n" + + "\x0eAce3DeathEvent\x12\x1d\n" + + "\n" + + "soldier_id\x18\x01 \x01(\rR\tsoldierId\x12\x16\n" + + "\x06reason\x18\x02 \x01(\tR\x06reason\x121\n" + + "\x15last_damage_source_id\x18\x03 \x01(\rR\x12lastDamageSourceId\x12*\n" + + "\x11has_damage_source\x18\x04 \x01(\bR\x0fhasDamageSource\"\\\n" + + "\x14Ace3UnconsciousEvent\x12\x1d\n" + + "\n" + + "soldier_id\x18\x01 \x01(\rR\tsoldierId\x12%\n" + + "\x0eis_unconscious\x18\x02 \x01(\bR\risUnconscious\"\xeb\x02\n" + + "\x0eTelemetryEvent\x12\x1f\n" + + "\vfps_average\x18\x01 \x01(\x02R\n" + + "fpsAverage\x12\x17\n" + + "\afps_min\x18\x02 \x01(\x02R\x06fpsMin\x12G\n" + + "\x12side_entity_counts\x18\x03 \x01(\v2\x19.ocap.v2.SideEntityCountsR\x10sideEntityCounts\x12?\n" + + "\rglobal_counts\x18\x04 \x01(\v2\x1a.ocap.v2.GlobalEntityCountR\fglobalCounts\x12/\n" + + "\ascripts\x18\x05 \x01(\v2\x15.ocap.v2.ScriptCountsR\ascripts\x12.\n" + + "\aweather\x18\x06 \x01(\v2\x14.ocap.v2.WeatherDataR\aweather\x124\n" + + "\aplayers\x18\a \x03(\v2\x1a.ocap.v2.PlayerNetworkDataR\aplayers\"\xe0\x01\n" + + "\x10SideEntityCounts\x12,\n" + + "\x04east\x18\x01 \x01(\v2\x18.ocap.v2.SideEntityCountR\x04east\x12,\n" + + "\x04west\x18\x02 \x01(\v2\x18.ocap.v2.SideEntityCountR\x04west\x12:\n" + + "\vindependent\x18\x03 \x01(\v2\x18.ocap.v2.SideEntityCountR\vindependent\x124\n" + + "\bcivilian\x18\x04 \x01(\v2\x18.ocap.v2.SideEntityCountR\bcivilian\"q\n" + + "\x0fSideEntityCount\x12-\n" + + "\x05local\x18\x01 \x01(\v2\x17.ocap.v2.EntityLocalityR\x05local\x12/\n" + + "\x06remote\x18\x02 \x01(\v2\x17.ocap.v2.EntityLocalityR\x06remote\"\xcc\x01\n" + + "\x0eEntityLocality\x12\x1f\n" + + "\vunits_total\x18\x01 \x01(\rR\n" + + "unitsTotal\x12\x1f\n" + + "\vunits_alive\x18\x02 \x01(\rR\n" + + "unitsAlive\x12\x1d\n" + + "\n" + + "units_dead\x18\x03 \x01(\rR\tunitsDead\x12\x16\n" + + "\x06groups\x18\x04 \x01(\rR\x06groups\x12\x1a\n" + + "\bvehicles\x18\x05 \x01(\rR\bvehicles\x12%\n" + + "\x0eweapon_holders\x18\x06 \x01(\rR\rweaponHolders\"\xa3\x02\n" + + "\x11GlobalEntityCount\x12\x1f\n" + + "\vunits_alive\x18\x01 \x01(\rR\n" + + "unitsAlive\x12\x1d\n" + + "\n" + + "units_dead\x18\x02 \x01(\rR\tunitsDead\x12\x16\n" + + "\x06groups\x18\x03 \x01(\rR\x06groups\x12\x1a\n" + + "\bvehicles\x18\x04 \x01(\rR\bvehicles\x12%\n" + + "\x0eweapon_holders\x18\x05 \x01(\rR\rweaponHolders\x12#\n" + + "\rplayers_alive\x18\x06 \x01(\rR\fplayersAlive\x12!\n" + + "\fplayers_dead\x18\a \x01(\rR\vplayersDead\x12+\n" + + "\x11players_connected\x18\b \x01(\rR\x10playersConnected\"~\n" + + "\fScriptCounts\x12\x14\n" + + "\x05spawn\x18\x01 \x01(\rR\x05spawn\x12\x17\n" + + "\aexec_vm\x18\x02 \x01(\rR\x06execVm\x12\x12\n" + + "\x04exec\x18\x03 \x01(\rR\x04exec\x12\x19\n" + + "\bexec_fsm\x18\x04 \x01(\rR\aexecFsm\x12\x10\n" + + "\x03pfh\x18\x05 \x01(\rR\x03pfh\"\xd3\x02\n" + + "\vWeatherData\x12\x10\n" + + "\x03fog\x18\x01 \x01(\x02R\x03fog\x12\x1a\n" + + "\bovercast\x18\x02 \x01(\x02R\bovercast\x12\x12\n" + + "\x04rain\x18\x03 \x01(\x02R\x04rain\x12\x1a\n" + + "\bhumidity\x18\x04 \x01(\x02R\bhumidity\x12\x14\n" + + "\x05waves\x18\x05 \x01(\x02R\x05waves\x12\x19\n" + + "\bwind_dir\x18\x06 \x01(\x02R\awindDir\x12\x19\n" + + "\bwind_str\x18\a \x01(\x02R\awindStr\x12\x14\n" + + "\x05gusts\x18\b \x01(\x02R\x05gusts\x12\x1e\n" + + "\n" + + "lightnings\x18\t \x01(\x02R\n" + + "lightnings\x12%\n" + + "\x0emoon_intensity\x18\n" + + " \x01(\x02R\rmoonIntensity\x12\x1d\n" + + "\n" + + "moon_phase\x18\v \x01(\x02R\tmoonPhase\x12\x1e\n" + + "\vsun_or_moon\x18\f \x01(\x02R\tsunOrMoon\"u\n" + + "\x11PlayerNetworkData\x12\x10\n" + + "\x03uid\x18\x01 \x01(\tR\x03uid\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" + + "\x04ping\x18\x03 \x01(\x02R\x04ping\x12\x0e\n" + + "\x02bw\x18\x04 \x01(\x02R\x02bw\x12\x16\n" + + "\x06desync\x18\x05 \x01(\x02R\x06desync\"<\n" + + "\fGeneralEvent\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"J\n" + + "\fConnectEvent\x12\x1b\n" + + "\tunit_name\x18\x01 \x01(\tR\bunitName\x12\x1d\n" + + "\n" + + "is_connect\x18\x02 \x01(\bR\tisConnect\"?\n" + + "\x0fEndMissionEvent\x12\x12\n" + + "\x04side\x18\x01 \x01(\tR\x04side\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"\x87\x01\n" + + "\x05Chunk\x12\x14\n" + + "\x05index\x18\x01 \x01(\rR\x05index\x12\x1f\n" + + "\vstart_frame\x18\x02 \x01(\rR\n" + + "startFrame\x12\x1f\n" + + "\vframe_count\x18\x03 \x01(\rR\n" + + "frameCount\x12&\n" + + "\x06frames\x18\x04 \x03(\v2\x0e.ocap.v2.FrameR\x06frames\"\x8a\x01\n" + + "\x05Frame\x12\x1b\n" + + "\tframe_num\x18\x01 \x01(\rR\bframeNum\x121\n" + + "\bsoldiers\x18\x02 \x03(\v2\x15.ocap.v2.SoldierStateR\bsoldiers\x121\n" + + "\bvehicles\x18\x03 \x03(\v2\x15.ocap.v2.VehicleStateR\bvehicles\"\x82\x04\n" + + "\fSoldierState\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12/\n" + + "\bposition\x18\x02 \x01(\v2\x13.ocap.v2.Position3DR\bposition\x12\x18\n" + + "\abearing\x18\x03 \x01(\rR\abearing\x12\x1c\n" + + "\tlifestate\x18\x04 \x01(\rR\tlifestate\x12\x1d\n" + + "\n" + + "vehicle_id\x18\x05 \x01(\rR\tvehicleId\x12\x1d\n" + + "\n" + + "in_vehicle\x18\x06 \x01(\bR\tinVehicle\x12\x12\n" + + "\x04name\x18\a \x01(\tR\x04name\x12\x1b\n" + + "\tis_player\x18\b \x01(\bR\bisPlayer\x12\x1d\n" + + "\n" + + "group_name\x18\t \x01(\tR\tgroupName\x12\x12\n" + + "\x04side\x18\n" + + " \x01(\tR\x04side\x12\x12\n" + + "\x04role\x18\v \x01(\tR\x04role\x12!\n" + + "\fvehicle_role\x18\f \x01(\tR\vvehicleRole\x12\x16\n" + + "\x06stance\x18\r \x01(\tR\x06stance\x12*\n" + + "\x11has_stable_vitals\x18\x0e \x01(\bR\x0fhasStableVitals\x12,\n" + + "\x12is_dragged_carried\x18\x0f \x01(\bR\x10isDraggedCarried\x12.\n" + + "\x06scores\x18\x10 \x01(\v2\x16.ocap.v2.SoldierScoresR\x06scores\"\xd2\x01\n" + + "\rSoldierScores\x12%\n" + + "\x0einfantry_kills\x18\x01 \x01(\rR\rinfantryKills\x12#\n" + + "\rvehicle_kills\x18\x02 \x01(\rR\fvehicleKills\x12\x1f\n" + + "\varmor_kills\x18\x03 \x01(\rR\n" + + "armorKills\x12\x1b\n" + + "\tair_kills\x18\x04 \x01(\rR\bairKills\x12\x16\n" + + "\x06deaths\x18\x05 \x01(\rR\x06deaths\x12\x1f\n" + + "\vtotal_score\x18\x06 \x01(\rR\n" + + "totalScore\"\xe1\x02\n" + + "\fVehicleState\x12\x0e\n" + + "\x02id\x18\x01 \x01(\rR\x02id\x12/\n" + + "\bposition\x18\x02 \x01(\v2\x13.ocap.v2.Position3DR\bposition\x12\x18\n" + + "\abearing\x18\x03 \x01(\rR\abearing\x12\x14\n" + + "\x05alive\x18\x04 \x01(\bR\x05alive\x12\x19\n" + + "\bcrew_ids\x18\x05 \x03(\rR\acrewIds\x12\x12\n" + + "\x04fuel\x18\x06 \x01(\x02R\x04fuel\x12\x16\n" + + "\x06damage\x18\a \x01(\x02R\x06damage\x12\x16\n" + + "\x06locked\x18\b \x01(\bR\x06locked\x12\x1b\n" + + "\tengine_on\x18\t \x01(\bR\bengineOn\x12\x12\n" + + "\x04side\x18\n" + + " \x01(\tR\x04side\x12%\n" + + "\x0eturret_azimuth\x18\v \x01(\x02R\rturretAzimuth\x12)\n" + + "\x10turret_elevation\x18\f \x01(\x02R\x0fturretElevation\"\xbe\x02\n" + + "\tMarkerDef\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x12\x12\n" + + "\x04text\x18\x02 \x01(\tR\x04text\x12\x1f\n" + + "\vstart_frame\x18\x03 \x01(\rR\n" + + "startFrame\x12\x1b\n" + + "\tend_frame\x18\x04 \x01(\rR\bendFrame\x12\x1b\n" + + "\tplayer_id\x18\x05 \x01(\x05R\bplayerId\x12\x14\n" + + "\x05color\x18\x06 \x01(\tR\x05color\x12!\n" + + "\x04side\x18\a \x01(\x0e2\r.ocap.v2.SideR\x04side\x125\n" + + "\tpositions\x18\b \x03(\v2\x17.ocap.v2.MarkerPositionR\tpositions\x12\x12\n" + + "\x04size\x18\t \x03(\x02R\x04size\x12\x14\n" + + "\x05shape\x18\n" + + " \x01(\tR\x05shape\x12\x14\n" + + "\x05brush\x18\v \x01(\tR\x05brush\"\xb3\x01\n" + + "\x0eMarkerPosition\x12\x1b\n" + + "\tframe_num\x18\x01 \x01(\rR\bframeNum\x12/\n" + + "\bposition\x18\x02 \x01(\v2\x13.ocap.v2.Position3DR\bposition\x12\x1c\n" + + "\tdirection\x18\x03 \x01(\x02R\tdirection\x12\x14\n" + + "\x05alpha\x18\x04 \x01(\x02R\x05alpha\x12\x1f\n" + + "\vline_coords\x18\x05 \x03(\x02R\n" + + "lineCoords\"\xa2\x01\n" + + "\n" + + "TimeSample\x12\x1b\n" + + "\tframe_num\x18\x01 \x01(\rR\bframeNum\x12&\n" + + "\x0fsystem_time_utc\x18\x02 \x01(\tR\rsystemTimeUtc\x12\x12\n" + + "\x04date\x18\x03 \x01(\tR\x04date\x12'\n" + + "\x0ftime_multiplier\x18\x04 \x01(\x02R\x0etimeMultiplier\x12\x12\n" + + "\x04time\x18\x05 \x01(\x02R\x04time*d\n" + + "\x04Side\x12\x10\n" + + "\fSIDE_UNKNOWN\x10\x00\x12\r\n" + + "\tSIDE_WEST\x10\x01\x12\r\n" + + "\tSIDE_EAST\x10\x02\x12\r\n" + + "\tSIDE_GUER\x10\x03\x12\f\n" + + "\bSIDE_CIV\x10\x04\x12\x0f\n" + + "\vSIDE_GLOBAL\x10\x05B.Z,github.com/OCAP2/web/pkg/schemas/protobuf/v2b\x06proto3" + +var ( + file_ocap_v2_proto_rawDescOnce sync.Once + file_ocap_v2_proto_rawDescData []byte +) + +func file_ocap_v2_proto_rawDescGZIP() []byte { + file_ocap_v2_proto_rawDescOnce.Do(func() { + file_ocap_v2_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_ocap_v2_proto_rawDesc), len(file_ocap_v2_proto_rawDesc))) + }) + return file_ocap_v2_proto_rawDescData +} + +var file_ocap_v2_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_ocap_v2_proto_msgTypes = make([]protoimpl.MessageInfo, 39) +var file_ocap_v2_proto_goTypes = []any{ + (Side)(0), // 0: ocap.v2.Side + (*Position3D)(nil), // 1: ocap.v2.Position3D + (*Manifest)(nil), // 2: ocap.v2.Manifest + (*WorldMeta)(nil), // 3: ocap.v2.WorldMeta + (*MissionMeta)(nil), // 4: ocap.v2.MissionMeta + (*PlayableSlots)(nil), // 5: ocap.v2.PlayableSlots + (*SideFriendly)(nil), // 6: ocap.v2.SideFriendly + (*Addon)(nil), // 7: ocap.v2.Addon + (*SoldierDef)(nil), // 8: ocap.v2.SoldierDef + (*VehicleDef)(nil), // 9: ocap.v2.VehicleDef + (*FiredFrame)(nil), // 10: ocap.v2.FiredFrame + (*Event)(nil), // 11: ocap.v2.Event + (*KillEvent)(nil), // 12: ocap.v2.KillEvent + (*HitEvent)(nil), // 13: ocap.v2.HitEvent + (*ProjectileEvent)(nil), // 14: ocap.v2.ProjectileEvent + (*TrajectoryPoint)(nil), // 15: ocap.v2.TrajectoryPoint + (*ProjectileHit)(nil), // 16: ocap.v2.ProjectileHit + (*ChatEvent)(nil), // 17: ocap.v2.ChatEvent + (*RadioEvent)(nil), // 18: ocap.v2.RadioEvent + (*Ace3DeathEvent)(nil), // 19: ocap.v2.Ace3DeathEvent + (*Ace3UnconsciousEvent)(nil), // 20: ocap.v2.Ace3UnconsciousEvent + (*TelemetryEvent)(nil), // 21: ocap.v2.TelemetryEvent + (*SideEntityCounts)(nil), // 22: ocap.v2.SideEntityCounts + (*SideEntityCount)(nil), // 23: ocap.v2.SideEntityCount + (*EntityLocality)(nil), // 24: ocap.v2.EntityLocality + (*GlobalEntityCount)(nil), // 25: ocap.v2.GlobalEntityCount + (*ScriptCounts)(nil), // 26: ocap.v2.ScriptCounts + (*WeatherData)(nil), // 27: ocap.v2.WeatherData + (*PlayerNetworkData)(nil), // 28: ocap.v2.PlayerNetworkData + (*GeneralEvent)(nil), // 29: ocap.v2.GeneralEvent + (*ConnectEvent)(nil), // 30: ocap.v2.ConnectEvent + (*EndMissionEvent)(nil), // 31: ocap.v2.EndMissionEvent + (*Chunk)(nil), // 32: ocap.v2.Chunk + (*Frame)(nil), // 33: ocap.v2.Frame + (*SoldierState)(nil), // 34: ocap.v2.SoldierState + (*SoldierScores)(nil), // 35: ocap.v2.SoldierScores + (*VehicleState)(nil), // 36: ocap.v2.VehicleState + (*MarkerDef)(nil), // 37: ocap.v2.MarkerDef + (*MarkerPosition)(nil), // 38: ocap.v2.MarkerPosition + (*TimeSample)(nil), // 39: ocap.v2.TimeSample +} +var file_ocap_v2_proto_depIdxs = []int32{ + 3, // 0: ocap.v2.Manifest.world:type_name -> ocap.v2.WorldMeta + 4, // 1: ocap.v2.Manifest.mission:type_name -> ocap.v2.MissionMeta + 8, // 2: ocap.v2.Manifest.soldiers:type_name -> ocap.v2.SoldierDef + 9, // 3: ocap.v2.Manifest.vehicles:type_name -> ocap.v2.VehicleDef + 11, // 4: ocap.v2.Manifest.events:type_name -> ocap.v2.Event + 37, // 5: ocap.v2.Manifest.markers:type_name -> ocap.v2.MarkerDef + 39, // 6: ocap.v2.Manifest.times:type_name -> ocap.v2.TimeSample + 5, // 7: ocap.v2.MissionMeta.playable_slots:type_name -> ocap.v2.PlayableSlots + 6, // 8: ocap.v2.MissionMeta.side_friendly:type_name -> ocap.v2.SideFriendly + 7, // 9: ocap.v2.MissionMeta.addons:type_name -> ocap.v2.Addon + 0, // 10: ocap.v2.SoldierDef.side:type_name -> ocap.v2.Side + 10, // 11: ocap.v2.SoldierDef.frames_fired:type_name -> ocap.v2.FiredFrame + 1, // 12: ocap.v2.FiredFrame.start_pos:type_name -> ocap.v2.Position3D + 1, // 13: ocap.v2.FiredFrame.end_pos:type_name -> ocap.v2.Position3D + 12, // 14: ocap.v2.Event.kill:type_name -> ocap.v2.KillEvent + 13, // 15: ocap.v2.Event.hit:type_name -> ocap.v2.HitEvent + 14, // 16: ocap.v2.Event.projectile:type_name -> ocap.v2.ProjectileEvent + 17, // 17: ocap.v2.Event.chat:type_name -> ocap.v2.ChatEvent + 18, // 18: ocap.v2.Event.radio:type_name -> ocap.v2.RadioEvent + 19, // 19: ocap.v2.Event.ace3_death:type_name -> ocap.v2.Ace3DeathEvent + 20, // 20: ocap.v2.Event.ace3_unconscious:type_name -> ocap.v2.Ace3UnconsciousEvent + 29, // 21: ocap.v2.Event.general:type_name -> ocap.v2.GeneralEvent + 30, // 22: ocap.v2.Event.connect:type_name -> ocap.v2.ConnectEvent + 31, // 23: ocap.v2.Event.end_mission:type_name -> ocap.v2.EndMissionEvent + 21, // 24: ocap.v2.Event.telemetry:type_name -> ocap.v2.TelemetryEvent + 15, // 25: ocap.v2.ProjectileEvent.trajectory:type_name -> ocap.v2.TrajectoryPoint + 16, // 26: ocap.v2.ProjectileEvent.hits:type_name -> ocap.v2.ProjectileHit + 1, // 27: ocap.v2.TrajectoryPoint.position:type_name -> ocap.v2.Position3D + 1, // 28: ocap.v2.ProjectileHit.position:type_name -> ocap.v2.Position3D + 22, // 29: ocap.v2.TelemetryEvent.side_entity_counts:type_name -> ocap.v2.SideEntityCounts + 25, // 30: ocap.v2.TelemetryEvent.global_counts:type_name -> ocap.v2.GlobalEntityCount + 26, // 31: ocap.v2.TelemetryEvent.scripts:type_name -> ocap.v2.ScriptCounts + 27, // 32: ocap.v2.TelemetryEvent.weather:type_name -> ocap.v2.WeatherData + 28, // 33: ocap.v2.TelemetryEvent.players:type_name -> ocap.v2.PlayerNetworkData + 23, // 34: ocap.v2.SideEntityCounts.east:type_name -> ocap.v2.SideEntityCount + 23, // 35: ocap.v2.SideEntityCounts.west:type_name -> ocap.v2.SideEntityCount + 23, // 36: ocap.v2.SideEntityCounts.independent:type_name -> ocap.v2.SideEntityCount + 23, // 37: ocap.v2.SideEntityCounts.civilian:type_name -> ocap.v2.SideEntityCount + 24, // 38: ocap.v2.SideEntityCount.local:type_name -> ocap.v2.EntityLocality + 24, // 39: ocap.v2.SideEntityCount.remote:type_name -> ocap.v2.EntityLocality + 33, // 40: ocap.v2.Chunk.frames:type_name -> ocap.v2.Frame + 34, // 41: ocap.v2.Frame.soldiers:type_name -> ocap.v2.SoldierState + 36, // 42: ocap.v2.Frame.vehicles:type_name -> ocap.v2.VehicleState + 1, // 43: ocap.v2.SoldierState.position:type_name -> ocap.v2.Position3D + 35, // 44: ocap.v2.SoldierState.scores:type_name -> ocap.v2.SoldierScores + 1, // 45: ocap.v2.VehicleState.position:type_name -> ocap.v2.Position3D + 0, // 46: ocap.v2.MarkerDef.side:type_name -> ocap.v2.Side + 38, // 47: ocap.v2.MarkerDef.positions:type_name -> ocap.v2.MarkerPosition + 1, // 48: ocap.v2.MarkerPosition.position:type_name -> ocap.v2.Position3D + 49, // [49:49] is the sub-list for method output_type + 49, // [49:49] is the sub-list for method input_type + 49, // [49:49] is the sub-list for extension type_name + 49, // [49:49] is the sub-list for extension extendee + 0, // [0:49] is the sub-list for field type_name +} + +func init() { file_ocap_v2_proto_init() } +func file_ocap_v2_proto_init() { + if File_ocap_v2_proto != nil { + return + } + file_ocap_v2_proto_msgTypes[10].OneofWrappers = []any{ + (*Event_Kill)(nil), + (*Event_Hit)(nil), + (*Event_Projectile)(nil), + (*Event_Chat)(nil), + (*Event_Radio)(nil), + (*Event_Ace3Death)(nil), + (*Event_Ace3Unconscious)(nil), + (*Event_General)(nil), + (*Event_Connect)(nil), + (*Event_EndMission)(nil), + (*Event_Telemetry)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_ocap_v2_proto_rawDesc), len(file_ocap_v2_proto_rawDesc)), + NumEnums: 1, + NumMessages: 39, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_ocap_v2_proto_goTypes, + DependencyIndexes: file_ocap_v2_proto_depIdxs, + EnumInfos: file_ocap_v2_proto_enumTypes, + MessageInfos: file_ocap_v2_proto_msgTypes, + }.Build() + File_ocap_v2_proto = out.File + file_ocap_v2_proto_goTypes = nil + file_ocap_v2_proto_depIdxs = nil +} diff --git a/pkg/schemas/protobuf/v2/ocap_v2.proto b/pkg/schemas/protobuf/v2/ocap_v2.proto new file mode 100644 index 000000000..d8d136a72 --- /dev/null +++ b/pkg/schemas/protobuf/v2/ocap_v2.proto @@ -0,0 +1,394 @@ +syntax = "proto3"; +package ocap.v2; +option go_package = "github.com/OCAP2/web/pkg/schemas/protobuf/v2"; + +// ─── Shared ─── + +message Position3D { + float x = 1; + float y = 2; + float z = 3; +} + +enum Side { + SIDE_UNKNOWN = 0; + SIDE_WEST = 1; + SIDE_EAST = 2; + SIDE_GUER = 3; + SIDE_CIV = 4; + SIDE_GLOBAL = 5; +} + +// ─── Manifest (loaded once) ─── + +message Manifest { + uint32 version = 1; // = 2 + WorldMeta world = 2; + MissionMeta mission = 3; + uint32 frame_count = 4; + uint32 chunk_size = 5; + uint32 capture_delay_ms = 6; + uint32 chunk_count = 7; + + repeated SoldierDef soldiers = 10; + repeated VehicleDef vehicles = 11; + repeated Event events = 12; + repeated MarkerDef markers = 13; + repeated TimeSample times = 14; +} + +message WorldMeta { + string world_name = 1; + float world_size = 2; + float latitude = 3; + float longitude = 4; + string author = 5; + string display_name = 6; +} + +message MissionMeta { + string mission_name = 1; + string briefing_name = 2; + string author = 3; + string server_name = 4; + string extension_version = 5; + string addon_version = 6; + string extension_build = 7; + string tag = 8; + PlayableSlots playable_slots = 9; + SideFriendly side_friendly = 10; + repeated Addon addons = 11; +} + +message PlayableSlots { + uint32 west = 1; + uint32 east = 2; + uint32 independent = 3; + uint32 civilian = 4; +} + +message SideFriendly { + bool east_west = 1; + bool east_independent = 2; + bool west_independent = 3; +} + +message Addon { + string name = 1; + string workshop_id = 2; +} + +// ─── Entity definitions ─── + +message SoldierDef { + uint32 id = 1; + string name = 2; + Side side = 3; + string group_name = 4; + string role = 5; + uint32 start_frame = 6; + uint32 end_frame = 7; + bool is_player = 8; + string class_name = 9; + string player_uid = 10; + repeated FiredFrame frames_fired = 11; +} + +message VehicleDef { + uint32 id = 1; + string name = 2; + string vehicle_class = 3; // ocapType: car, tank, heli, etc. + string class_name = 4; + uint32 start_frame = 5; + uint32 end_frame = 6; + string customization = 7; +} + +message FiredFrame { + uint32 frame_num = 1; + Position3D start_pos = 2; + Position3D end_pos = 3; + string weapon = 4; + string magazine = 5; + string firing_mode = 6; +} + +// ─── Events (typed) ─── + +message Event { + uint32 frame_num = 1; + oneof event { + KillEvent kill = 10; + HitEvent hit = 11; + ProjectileEvent projectile = 12; + ChatEvent chat = 13; + RadioEvent radio = 14; + Ace3DeathEvent ace3_death = 15; + Ace3UnconsciousEvent ace3_unconscious = 16; + GeneralEvent general = 17; + ConnectEvent connect = 18; + EndMissionEvent end_mission = 19; + TelemetryEvent telemetry = 20; + } +} + +message KillEvent { + uint32 victim_soldier_id = 1; + uint32 victim_vehicle_id = 2; + uint32 killer_soldier_id = 3; + uint32 killer_vehicle_id = 4; + string weapon_name = 5; + string weapon_magazine = 6; + string event_text = 7; + float distance = 8; + bool victim_is_vehicle = 9; + bool killer_is_vehicle = 10; +} + +message HitEvent { + uint32 victim_soldier_id = 1; + uint32 victim_vehicle_id = 2; + uint32 shooter_soldier_id = 3; + uint32 shooter_vehicle_id = 4; + string weapon_name = 5; + string weapon_magazine = 6; + string event_text = 7; + float distance = 8; + bool victim_is_vehicle = 9; + bool shooter_is_vehicle = 10; +} + +message ProjectileEvent { + uint32 firer_id = 1; + uint32 vehicle_id = 2; + string weapon = 3; // WeaponDisplay + string magazine = 4; // MagazineDisplay + string simulation_type = 5; + repeated TrajectoryPoint trajectory = 6; + repeated ProjectileHit hits = 7; + string muzzle = 8; // MuzzleDisplay + string magazine_icon = 9; // MagazineIcon (Arma path for ammo icon) +} + +message TrajectoryPoint { + Position3D position = 1; + uint32 frame_num = 2; +} + +message ProjectileHit { + uint32 frame_num = 1; + Position3D position = 2; + uint32 soldier_id = 3; + uint32 vehicle_id = 4; + bool hit_soldier = 5; + bool hit_vehicle = 6; + repeated string components_hit = 7; +} + +message ChatEvent { + uint32 soldier_id = 1; + string channel = 2; + string from_name = 3; + string message = 4; + string player_uid = 5; +} + +message RadioEvent { + uint32 soldier_id = 1; + string radio = 2; + string radio_type = 3; + string start_end = 4; + int32 channel = 5; + bool is_additional = 6; + float frequency = 7; +} + +message Ace3DeathEvent { + uint32 soldier_id = 1; + string reason = 2; + uint32 last_damage_source_id = 3; + bool has_damage_source = 4; +} + +message Ace3UnconsciousEvent { + uint32 soldier_id = 1; + bool is_unconscious = 2; +} + +message TelemetryEvent { + float fps_average = 1; + float fps_min = 2; + SideEntityCounts side_entity_counts = 3; + GlobalEntityCount global_counts = 4; + ScriptCounts scripts = 5; + WeatherData weather = 6; + repeated PlayerNetworkData players = 7; +} + +message SideEntityCounts { + SideEntityCount east = 1; + SideEntityCount west = 2; + SideEntityCount independent = 3; + SideEntityCount civilian = 4; +} + +message SideEntityCount { + EntityLocality local = 1; + EntityLocality remote = 2; +} + +message EntityLocality { + uint32 units_total = 1; + uint32 units_alive = 2; + uint32 units_dead = 3; + uint32 groups = 4; + uint32 vehicles = 5; + uint32 weapon_holders = 6; +} + +message GlobalEntityCount { + uint32 units_alive = 1; + uint32 units_dead = 2; + uint32 groups = 3; + uint32 vehicles = 4; + uint32 weapon_holders = 5; + uint32 players_alive = 6; + uint32 players_dead = 7; + uint32 players_connected = 8; +} + +message ScriptCounts { + uint32 spawn = 1; + uint32 exec_vm = 2; + uint32 exec = 3; + uint32 exec_fsm = 4; + uint32 pfh = 5; +} + +message WeatherData { + float fog = 1; + float overcast = 2; + float rain = 3; + float humidity = 4; + float waves = 5; + float wind_dir = 6; + float wind_str = 7; + float gusts = 8; + float lightnings = 9; + float moon_intensity = 10; + float moon_phase = 11; + float sun_or_moon = 12; +} + +message PlayerNetworkData { + string uid = 1; + string name = 2; + float ping = 3; + float bw = 4; + float desync = 5; +} + +message GeneralEvent { + string name = 1; + string message = 2; +} + +message ConnectEvent { + string unit_name = 1; + bool is_connect = 2; // true = connected, false = disconnected +} + +message EndMissionEvent { + string side = 1; + string message = 2; +} + +// ─── Chunks (loaded on demand) ─── + +message Chunk { + uint32 index = 1; + uint32 start_frame = 2; + uint32 frame_count = 3; + repeated Frame frames = 4; +} + +message Frame { + uint32 frame_num = 1; + repeated SoldierState soldiers = 2; + repeated VehicleState vehicles = 3; +} + +message SoldierState { + uint32 id = 1; + Position3D position = 2; + uint32 bearing = 3; + uint32 lifestate = 4; // 0=dead, 1=alive, 2=unconscious + uint32 vehicle_id = 5; + bool in_vehicle = 6; + string name = 7; + bool is_player = 8; + string group_name = 9; + string side = 10; + string role = 11; + string vehicle_role = 12; + string stance = 13; + bool has_stable_vitals = 14; + bool is_dragged_carried = 15; + SoldierScores scores = 16; +} + +message SoldierScores { + uint32 infantry_kills = 1; + uint32 vehicle_kills = 2; + uint32 armor_kills = 3; + uint32 air_kills = 4; + uint32 deaths = 5; + uint32 total_score = 6; +} + +message VehicleState { + uint32 id = 1; + Position3D position = 2; + uint32 bearing = 3; + bool alive = 4; + repeated uint32 crew_ids = 5; + float fuel = 6; + float damage = 7; + bool locked = 8; + bool engine_on = 9; + string side = 10; + float turret_azimuth = 11; + float turret_elevation = 12; +} + +// ─── Markers ─── + +message MarkerDef { + string type = 1; + string text = 2; + uint32 start_frame = 3; + uint32 end_frame = 4; + int32 player_id = 5; + string color = 6; + Side side = 7; + repeated MarkerPosition positions = 8; + repeated float size = 9; + string shape = 10; + string brush = 11; +} + +message MarkerPosition { + uint32 frame_num = 1; + Position3D position = 2; + float direction = 3; + float alpha = 4; + repeated float line_coords = 5; +} + +message TimeSample { + uint32 frame_num = 1; + string system_time_utc = 2; + string date = 3; + float time_multiplier = 4; + float time = 5; +} diff --git a/ui/src/data/decoders/__tests__/protobuf-decoder-v2.test.ts b/ui/src/data/decoders/__tests__/protobuf-decoder-v2.test.ts new file mode 100644 index 000000000..6657d5039 --- /dev/null +++ b/ui/src/data/decoders/__tests__/protobuf-decoder-v2.test.ts @@ -0,0 +1,401 @@ +import { describe, expect, it } from "vitest"; +import { ProtobufDecoderV2 } from "../protobuf-decoder-v2"; + +import { + Manifest as PbManifest, + Chunk as PbChunk, + Side as PbSide, +} from "../generated/v2/ocap_v2"; + +// ─── Helper: encode a protobuf message to ArrayBuffer ─── + +function encodePb(msg: { encode: (m: T) => { finish: () => Uint8Array }; fromPartial: (o: any) => T }, data: any): ArrayBuffer { + const bytes = msg.encode(msg.fromPartial(data)).finish(); + return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); +} + +// ─── Manifest decoding tests ─── + +describe("ProtobufDecoderV2.decodeManifest", () => { + const decoder = new ProtobufDecoderV2(); + + it("decodes a minimal v2 manifest", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { worldName: "Altis", worldSize: 30720 }, + mission: { missionName: "Test Op", extensionVersion: "5.0.0" }, + frameCount: 1000, + chunkSize: 300, + captureDelayMs: 1000, + chunkCount: 4, + }); + + const manifest = decoder.decodeManifest(buffer); + + expect(manifest.version).toBe(2); + expect(manifest.worldName).toBe("Altis"); + expect(manifest.missionName).toBe("Test Op"); + expect(manifest.frameCount).toBe(1000); + expect(manifest.chunkSize).toBe(300); + expect(manifest.captureDelayMs).toBe(1000); + expect(manifest.chunkCount).toBe(4); + expect(manifest.entities).toEqual([]); + expect(manifest.events).toEqual([]); + expect(manifest.markers).toEqual([]); + expect(manifest.times).toEqual([]); + expect(manifest.extensionVersion).toBe("5.0.0"); + }); + + it("decodes soldier definitions", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { worldName: "Altis" }, + mission: { missionName: "Op" }, + frameCount: 1000, + chunkSize: 300, + captureDelayMs: 1000, + chunkCount: 4, + soldiers: [{ + id: 42, + name: "Player1", + side: PbSide.SIDE_WEST, + groupName: "Alpha 1", + role: "Rifleman", + startFrame: 0, + endFrame: 999, + isPlayer: true, + className: "B_Soldier_F", + playerUid: "76561198000000000", + }], + }); + + const manifest = decoder.decodeManifest(buffer); + expect(manifest.entities).toHaveLength(1); + + const entity = manifest.entities[0]; + expect(entity.id).toBe(42); + expect(entity.type).toBe("man"); + expect(entity.name).toBe("Player1"); + expect(entity.side).toBe("WEST"); + expect(entity.groupName).toBe("Alpha 1"); + expect(entity.role).toBe("Rifleman"); + expect(entity.isPlayer).toBe(true); + }); + + it("decodes vehicle definitions", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { worldName: "Altis" }, + mission: { missionName: "Op" }, + frameCount: 100, + chunkSize: 100, + captureDelayMs: 1000, + chunkCount: 1, + vehicles: [{ + id: 5, + name: "Hunter", + vehicleClass: "car", + startFrame: 0, + endFrame: 100, + }], + }); + + const manifest = decoder.decodeManifest(buffer); + expect(manifest.entities).toHaveLength(1); + expect(manifest.entities[0].type).toBe("car"); + expect(manifest.entities[0].name).toBe("Hunter"); + }); + + it("decodes v2 world metadata", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { + worldName: "Altis", + worldSize: 30720, + latitude: -35.09, + longitude: 16.81, + author: "BIS", + displayName: "Altis", + }, + mission: { missionName: "Op" }, + frameCount: 100, + chunkSize: 100, + captureDelayMs: 1000, + chunkCount: 1, + }); + + const manifest = decoder.decodeManifest(buffer); + expect(manifest.world).toBeDefined(); + expect(manifest.world!.worldSize).toBeCloseTo(30720); + expect(manifest.world!.latitude).toBeCloseTo(-35.09, 1); + expect(manifest.world!.longitude).toBeCloseTo(16.81, 1); + }); + + it("decodes v2 mission metadata", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { worldName: "Altis" }, + mission: { + missionName: "Op", + serverName: "TestServer", + briefingName: "Briefing", + playableSlots: { west: 10, east: 8, independent: 4, civilian: 2 }, + sideFriendly: { eastWest: false, eastIndependent: true, westIndependent: false }, + addons: [{ name: "CBA_A3", workshopId: "450814997" }], + }, + frameCount: 100, + chunkSize: 100, + captureDelayMs: 1000, + chunkCount: 1, + }); + + const manifest = decoder.decodeManifest(buffer); + expect(manifest.mission).toBeDefined(); + expect(manifest.mission!.serverName).toBe("TestServer"); + expect(manifest.mission!.briefingName).toBe("Briefing"); + expect(manifest.mission!.playableSlots?.west).toBe(10); + expect(manifest.mission!.sideFriendly?.eastIndependent).toBe(true); + expect(manifest.mission!.addons).toHaveLength(1); + expect(manifest.mission!.addons![0].name).toBe("CBA_A3"); + }); + + it("decodes kill events", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { worldName: "Altis" }, + mission: { missionName: "Op" }, + frameCount: 500, + chunkSize: 300, + captureDelayMs: 1000, + chunkCount: 2, + events: [{ + frameNum: 100, + kill: { + victimSoldierId: 2, + killerSoldierId: 1, + weaponName: "arifle_MX_F", + eventText: "Player1 killed Player2", + distance: 150.0, + }, + }], + }); + + const manifest = decoder.decodeManifest(buffer); + expect(manifest.events).toHaveLength(1); + const event = manifest.events[0]; + expect(event.frameNum).toBe(100); + expect(event.type).toBe("killed"); + if (event.type === "killed") { + expect(event.victimId).toBe(2); + expect(event.causedById).toBe(1); + expect(event.weapon).toBe("Player1 killed Player2"); + expect(event.distance).toBeCloseTo(150.0); + } + }); + + it("decodes connect/disconnect events", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { worldName: "Altis" }, + mission: { missionName: "Op" }, + frameCount: 500, + chunkSize: 300, + captureDelayMs: 1000, + chunkCount: 2, + events: [ + { frameNum: 50, connect: { unitName: "Player1", isConnect: true } }, + { frameNum: 200, connect: { unitName: "Player2", isConnect: false } }, + ], + }); + + const manifest = decoder.decodeManifest(buffer); + expect(manifest.events).toHaveLength(2); + + expect(manifest.events[0].type).toBe("connected"); + if (manifest.events[0].type === "connected") { + expect(manifest.events[0].unitName).toBe("Player1"); + } + + expect(manifest.events[1].type).toBe("disconnected"); + if (manifest.events[1].type === "disconnected") { + expect(manifest.events[1].unitName).toBe("Player2"); + } + }); + + it("decodes time samples", () => { + const buffer = encodePb(PbManifest, { + version: 2, + world: { worldName: "Altis" }, + mission: { missionName: "Op" }, + frameCount: 500, + chunkSize: 300, + captureDelayMs: 1000, + chunkCount: 2, + times: [{ frameNum: 0, systemTimeUtc: "2025-01-15T12:00:00Z" }], + }); + + const manifest = decoder.decodeManifest(buffer); + expect(manifest.times).toHaveLength(1); + expect(manifest.times[0].systemTimeUtc).toBe("2025-01-15T12:00:00Z"); + }); +}); + +// ─── Chunk decoding tests ─── + +describe("ProtobufDecoderV2.decodeChunk", () => { + const decoder = new ProtobufDecoderV2(); + + it("decodes a chunk with soldier and vehicle states", () => { + const buffer = encodePb(PbChunk, { + index: 0, + startFrame: 0, + frameCount: 2, + frames: [ + { + frameNum: 0, + soldiers: [{ + id: 1, + position: { x: 100.0, y: 200.0, z: 10.0 }, + bearing: 90, + lifestate: 1, + name: "Player1", + isPlayer: true, + groupName: "Alpha", + side: "WEST", + }], + vehicles: [{ + id: 10, + position: { x: 300.0, y: 400.0, z: 5.0 }, + bearing: 180, + alive: true, + crewIds: [1], + fuel: 0.75, + side: "WEST", + }], + }, + { + frameNum: 1, + soldiers: [{ + id: 1, + position: { x: 105.0, y: 205.0, z: 10.0 }, + bearing: 95, + lifestate: 1, + }], + }, + ], + }); + + const chunk = decoder.decodeChunk(buffer); + + // Soldier states + const soldierStates = chunk.entities.get(1); + expect(soldierStates).toBeDefined(); + expect(soldierStates).toHaveLength(2); + expect(soldierStates![0].position).toEqual([100.0, 200.0, 10.0]); + expect(soldierStates![0].direction).toBe(90); + expect(soldierStates![0].alive).toBe(1); + expect(soldierStates![0].groupName).toBe("Alpha"); + expect(soldierStates![0].side).toBe("WEST"); + + // Vehicle states + const vehicleStates = chunk.entities.get(10); + expect(vehicleStates).toBeDefined(); + expect(vehicleStates).toHaveLength(2); + expect(vehicleStates![0].position).toEqual([300.0, 400.0, 5.0]); + expect(vehicleStates![0].alive).toBe(1); + expect(vehicleStates![0].crewIds).toEqual([1]); + expect(vehicleStates![0].fuel).toBeCloseTo(0.75); + expect(vehicleStates![1]).toBeUndefined(); // vehicle absent from frame 1 + }); + + it("decodes v2 soldier extensions", () => { + const buffer = encodePb(PbChunk, { + index: 0, + startFrame: 0, + frameCount: 1, + frames: [{ + frameNum: 0, + soldiers: [{ + id: 1, + position: { x: 100.0, y: 200.0, z: 10.0 }, + bearing: 90, + lifestate: 1, + vehicleRole: "driver", + stance: "CROUCH", + hasStableVitals: true, + isDraggedCarried: false, + scores: { + infantryKills: 2, + vehicleKills: 1, + armorKills: 0, + airKills: 0, + deaths: 0, + totalScore: 5, + }, + }], + }], + }); + + const chunk = decoder.decodeChunk(buffer); + const state = chunk.entities.get(1)![0]; + expect(state.vehicleRole).toBe("driver"); + expect(state.stance).toBe("CROUCH"); + expect(state.hasStableVitals).toBe(true); + expect(state.scores).toBeDefined(); + expect(state.scores!.infantryKills).toBe(2); + expect(state.scores!.totalScore).toBe(5); + }); + + it("decodes v2 vehicle extensions", () => { + const buffer = encodePb(PbChunk, { + index: 0, + startFrame: 0, + frameCount: 1, + frames: [{ + frameNum: 0, + vehicles: [{ + id: 10, + position: { x: 300.0, y: 400.0, z: 5.0 }, + bearing: 180, + alive: true, + fuel: 0.6, + damage: 0.2, + locked: true, + engineOn: true, + turretAzimuth: 45.0, + turretElevation: -5.0, + }], + }], + }); + + const chunk = decoder.decodeChunk(buffer); + const state = chunk.entities.get(10)![0]; + expect(state.fuel).toBeCloseTo(0.6); + expect(state.damage).toBeCloseTo(0.2); + expect(state.locked).toBe(true); + expect(state.engineOn).toBe(true); + expect(state.turretAzimuth).toBeCloseTo(45.0); + expect(state.turretElevation).toBeCloseTo(-5.0); + }); + + it("returns empty map for empty chunk", () => { + const buffer = encodePb(PbChunk, { + index: 0, + startFrame: 0, + frameCount: 0, + }); + + const chunk = decoder.decodeChunk(buffer); + expect(chunk.entities.size).toBe(0); + }); +}); + +// ─── DecoderStrategy interface compliance ─── + +describe("DecoderStrategy interface", () => { + it("ProtobufDecoderV2 has both required methods", () => { + const decoder = new ProtobufDecoderV2(); + expect(typeof decoder.decodeManifest).toBe("function"); + expect(typeof decoder.decodeChunk).toBe("function"); + }); +}); diff --git a/ui/src/data/decoders/generated/v2/ocap_v2.ts b/ui/src/data/decoders/generated/v2/ocap_v2.ts new file mode 100644 index 000000000..d4b62a52c --- /dev/null +++ b/ui/src/data/decoders/generated/v2/ocap_v2.ts @@ -0,0 +1,6977 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v2.11.2 +// protoc v4.25.1 +// source: ocap_v2.proto + +/* eslint-disable */ +import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; + +export const protobufPackage = "ocap.v2"; + +export enum Side { + SIDE_UNKNOWN = 0, + SIDE_WEST = 1, + SIDE_EAST = 2, + SIDE_GUER = 3, + SIDE_CIV = 4, + SIDE_GLOBAL = 5, + UNRECOGNIZED = -1, +} + +export function sideFromJSON(object: any): Side { + switch (object) { + case 0: + case "SIDE_UNKNOWN": + return Side.SIDE_UNKNOWN; + case 1: + case "SIDE_WEST": + return Side.SIDE_WEST; + case 2: + case "SIDE_EAST": + return Side.SIDE_EAST; + case 3: + case "SIDE_GUER": + return Side.SIDE_GUER; + case 4: + case "SIDE_CIV": + return Side.SIDE_CIV; + case 5: + case "SIDE_GLOBAL": + return Side.SIDE_GLOBAL; + case -1: + case "UNRECOGNIZED": + default: + return Side.UNRECOGNIZED; + } +} + +export function sideToJSON(object: Side): string { + switch (object) { + case Side.SIDE_UNKNOWN: + return "SIDE_UNKNOWN"; + case Side.SIDE_WEST: + return "SIDE_WEST"; + case Side.SIDE_EAST: + return "SIDE_EAST"; + case Side.SIDE_GUER: + return "SIDE_GUER"; + case Side.SIDE_CIV: + return "SIDE_CIV"; + case Side.SIDE_GLOBAL: + return "SIDE_GLOBAL"; + case Side.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + +export interface Position3D { + x: number; + y: number; + z: number; +} + +export interface Manifest { + /** = 2 */ + version: number; + world: WorldMeta | undefined; + mission: MissionMeta | undefined; + frameCount: number; + chunkSize: number; + captureDelayMs: number; + chunkCount: number; + soldiers: SoldierDef[]; + vehicles: VehicleDef[]; + events: Event[]; + markers: MarkerDef[]; + times: TimeSample[]; +} + +export interface WorldMeta { + worldName: string; + worldSize: number; + latitude: number; + longitude: number; + author: string; + displayName: string; +} + +export interface MissionMeta { + missionName: string; + briefingName: string; + author: string; + serverName: string; + extensionVersion: string; + addonVersion: string; + extensionBuild: string; + tag: string; + playableSlots: PlayableSlots | undefined; + sideFriendly: SideFriendly | undefined; + addons: Addon[]; +} + +export interface PlayableSlots { + west: number; + east: number; + independent: number; + civilian: number; +} + +export interface SideFriendly { + eastWest: boolean; + eastIndependent: boolean; + westIndependent: boolean; +} + +export interface Addon { + name: string; + workshopId: string; +} + +export interface SoldierDef { + id: number; + name: string; + side: Side; + groupName: string; + role: string; + startFrame: number; + endFrame: number; + isPlayer: boolean; + className: string; + playerUid: string; + framesFired: FiredFrame[]; +} + +export interface VehicleDef { + id: number; + name: string; + /** ocapType: car, tank, heli, etc. */ + vehicleClass: string; + className: string; + startFrame: number; + endFrame: number; + customization: string; +} + +export interface FiredFrame { + frameNum: number; + startPos: Position3D | undefined; + endPos: Position3D | undefined; + weapon: string; + magazine: string; + firingMode: string; +} + +export interface Event { + frameNum: number; + kill?: KillEvent | undefined; + hit?: HitEvent | undefined; + projectile?: ProjectileEvent | undefined; + chat?: ChatEvent | undefined; + radio?: RadioEvent | undefined; + ace3Death?: Ace3DeathEvent | undefined; + ace3Unconscious?: Ace3UnconsciousEvent | undefined; + general?: GeneralEvent | undefined; + connect?: ConnectEvent | undefined; + endMission?: EndMissionEvent | undefined; + telemetry?: TelemetryEvent | undefined; +} + +export interface KillEvent { + victimSoldierId: number; + victimVehicleId: number; + killerSoldierId: number; + killerVehicleId: number; + weaponName: string; + weaponMagazine: string; + eventText: string; + distance: number; + victimIsVehicle: boolean; + killerIsVehicle: boolean; +} + +export interface HitEvent { + victimSoldierId: number; + victimVehicleId: number; + shooterSoldierId: number; + shooterVehicleId: number; + weaponName: string; + weaponMagazine: string; + eventText: string; + distance: number; + victimIsVehicle: boolean; + shooterIsVehicle: boolean; +} + +export interface ProjectileEvent { + firerId: number; + vehicleId: number; + /** WeaponDisplay */ + weapon: string; + /** MagazineDisplay */ + magazine: string; + simulationType: string; + trajectory: TrajectoryPoint[]; + hits: ProjectileHit[]; + /** MuzzleDisplay */ + muzzle: string; + /** MagazineIcon (Arma path for ammo icon) */ + magazineIcon: string; +} + +export interface TrajectoryPoint { + position: Position3D | undefined; + frameNum: number; +} + +export interface ProjectileHit { + frameNum: number; + position: Position3D | undefined; + soldierId: number; + vehicleId: number; + hitSoldier: boolean; + hitVehicle: boolean; + componentsHit: string[]; +} + +export interface ChatEvent { + soldierId: number; + channel: string; + fromName: string; + message: string; + playerUid: string; +} + +export interface RadioEvent { + soldierId: number; + radio: string; + radioType: string; + startEnd: string; + channel: number; + isAdditional: boolean; + frequency: number; +} + +export interface Ace3DeathEvent { + soldierId: number; + reason: string; + lastDamageSourceId: number; + hasDamageSource: boolean; +} + +export interface Ace3UnconsciousEvent { + soldierId: number; + isUnconscious: boolean; +} + +export interface TelemetryEvent { + fpsAverage: number; + fpsMin: number; + sideEntityCounts: SideEntityCounts | undefined; + globalCounts: GlobalEntityCount | undefined; + scripts: ScriptCounts | undefined; + weather: WeatherData | undefined; + players: PlayerNetworkData[]; +} + +export interface SideEntityCounts { + east: SideEntityCount | undefined; + west: SideEntityCount | undefined; + independent: SideEntityCount | undefined; + civilian: SideEntityCount | undefined; +} + +export interface SideEntityCount { + local: EntityLocality | undefined; + remote: EntityLocality | undefined; +} + +export interface EntityLocality { + unitsTotal: number; + unitsAlive: number; + unitsDead: number; + groups: number; + vehicles: number; + weaponHolders: number; +} + +export interface GlobalEntityCount { + unitsAlive: number; + unitsDead: number; + groups: number; + vehicles: number; + weaponHolders: number; + playersAlive: number; + playersDead: number; + playersConnected: number; +} + +export interface ScriptCounts { + spawn: number; + execVm: number; + exec: number; + execFsm: number; + pfh: number; +} + +export interface WeatherData { + fog: number; + overcast: number; + rain: number; + humidity: number; + waves: number; + windDir: number; + windStr: number; + gusts: number; + lightnings: number; + moonIntensity: number; + moonPhase: number; + sunOrMoon: number; +} + +export interface PlayerNetworkData { + uid: string; + name: string; + ping: number; + bw: number; + desync: number; +} + +export interface GeneralEvent { + name: string; + message: string; +} + +export interface ConnectEvent { + unitName: string; + /** true = connected, false = disconnected */ + isConnect: boolean; +} + +export interface EndMissionEvent { + side: string; + message: string; +} + +export interface Chunk { + index: number; + startFrame: number; + frameCount: number; + frames: Frame[]; +} + +export interface Frame { + frameNum: number; + soldiers: SoldierState[]; + vehicles: VehicleState[]; +} + +export interface SoldierState { + id: number; + position: Position3D | undefined; + bearing: number; + /** 0=dead, 1=alive, 2=unconscious */ + lifestate: number; + vehicleId: number; + inVehicle: boolean; + name: string; + isPlayer: boolean; + groupName: string; + side: string; + role: string; + vehicleRole: string; + stance: string; + hasStableVitals: boolean; + isDraggedCarried: boolean; + scores: SoldierScores | undefined; +} + +export interface SoldierScores { + infantryKills: number; + vehicleKills: number; + armorKills: number; + airKills: number; + deaths: number; + totalScore: number; +} + +export interface VehicleState { + id: number; + position: Position3D | undefined; + bearing: number; + alive: boolean; + crewIds: number[]; + fuel: number; + damage: number; + locked: boolean; + engineOn: boolean; + side: string; + turretAzimuth: number; + turretElevation: number; +} + +export interface MarkerDef { + type: string; + text: string; + startFrame: number; + endFrame: number; + playerId: number; + color: string; + side: Side; + positions: MarkerPosition[]; + size: number[]; + shape: string; + brush: string; +} + +export interface MarkerPosition { + frameNum: number; + position: Position3D | undefined; + direction: number; + alpha: number; + lineCoords: number[]; +} + +export interface TimeSample { + frameNum: number; + systemTimeUtc: string; + date: string; + timeMultiplier: number; + time: number; +} + +function createBasePosition3D(): Position3D { + return { x: 0, y: 0, z: 0 }; +} + +export const Position3D: MessageFns = { + encode(message: Position3D, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.x !== 0) { + writer.uint32(13).float(message.x); + } + if (message.y !== 0) { + writer.uint32(21).float(message.y); + } + if (message.z !== 0) { + writer.uint32(29).float(message.z); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Position3D { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePosition3D(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 13) { + break; + } + + message.x = reader.float(); + continue; + } + case 2: { + if (tag !== 21) { + break; + } + + message.y = reader.float(); + continue; + } + case 3: { + if (tag !== 29) { + break; + } + + message.z = reader.float(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Position3D { + return { + x: isSet(object.x) ? globalThis.Number(object.x) : 0, + y: isSet(object.y) ? globalThis.Number(object.y) : 0, + z: isSet(object.z) ? globalThis.Number(object.z) : 0, + }; + }, + + toJSON(message: Position3D): unknown { + const obj: any = {}; + if (message.x !== 0) { + obj.x = message.x; + } + if (message.y !== 0) { + obj.y = message.y; + } + if (message.z !== 0) { + obj.z = message.z; + } + return obj; + }, + + create, I>>(base?: I): Position3D { + return Position3D.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Position3D { + const message = createBasePosition3D(); + message.x = object.x ?? 0; + message.y = object.y ?? 0; + message.z = object.z ?? 0; + return message; + }, +}; + +function createBaseManifest(): Manifest { + return { + version: 0, + world: undefined, + mission: undefined, + frameCount: 0, + chunkSize: 0, + captureDelayMs: 0, + chunkCount: 0, + soldiers: [], + vehicles: [], + events: [], + markers: [], + times: [], + }; +} + +export const Manifest: MessageFns = { + encode(message: Manifest, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.version !== 0) { + writer.uint32(8).uint32(message.version); + } + if (message.world !== undefined) { + WorldMeta.encode(message.world, writer.uint32(18).fork()).join(); + } + if (message.mission !== undefined) { + MissionMeta.encode(message.mission, writer.uint32(26).fork()).join(); + } + if (message.frameCount !== 0) { + writer.uint32(32).uint32(message.frameCount); + } + if (message.chunkSize !== 0) { + writer.uint32(40).uint32(message.chunkSize); + } + if (message.captureDelayMs !== 0) { + writer.uint32(48).uint32(message.captureDelayMs); + } + if (message.chunkCount !== 0) { + writer.uint32(56).uint32(message.chunkCount); + } + for (const v of message.soldiers) { + SoldierDef.encode(v!, writer.uint32(82).fork()).join(); + } + for (const v of message.vehicles) { + VehicleDef.encode(v!, writer.uint32(90).fork()).join(); + } + for (const v of message.events) { + Event.encode(v!, writer.uint32(98).fork()).join(); + } + for (const v of message.markers) { + MarkerDef.encode(v!, writer.uint32(106).fork()).join(); + } + for (const v of message.times) { + TimeSample.encode(v!, writer.uint32(114).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Manifest { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseManifest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.version = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.world = WorldMeta.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.mission = MissionMeta.decode(reader, reader.uint32()); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.frameCount = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.chunkSize = reader.uint32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.captureDelayMs = reader.uint32(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.chunkCount = reader.uint32(); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.soldiers.push(SoldierDef.decode(reader, reader.uint32())); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.vehicles.push(VehicleDef.decode(reader, reader.uint32())); + continue; + } + case 12: { + if (tag !== 98) { + break; + } + + message.events.push(Event.decode(reader, reader.uint32())); + continue; + } + case 13: { + if (tag !== 106) { + break; + } + + message.markers.push(MarkerDef.decode(reader, reader.uint32())); + continue; + } + case 14: { + if (tag !== 114) { + break; + } + + message.times.push(TimeSample.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Manifest { + return { + version: isSet(object.version) ? globalThis.Number(object.version) : 0, + world: isSet(object.world) ? WorldMeta.fromJSON(object.world) : undefined, + mission: isSet(object.mission) ? MissionMeta.fromJSON(object.mission) : undefined, + frameCount: isSet(object.frameCount) + ? globalThis.Number(object.frameCount) + : isSet(object.frame_count) + ? globalThis.Number(object.frame_count) + : 0, + chunkSize: isSet(object.chunkSize) + ? globalThis.Number(object.chunkSize) + : isSet(object.chunk_size) + ? globalThis.Number(object.chunk_size) + : 0, + captureDelayMs: isSet(object.captureDelayMs) + ? globalThis.Number(object.captureDelayMs) + : isSet(object.capture_delay_ms) + ? globalThis.Number(object.capture_delay_ms) + : 0, + chunkCount: isSet(object.chunkCount) + ? globalThis.Number(object.chunkCount) + : isSet(object.chunk_count) + ? globalThis.Number(object.chunk_count) + : 0, + soldiers: globalThis.Array.isArray(object?.soldiers) + ? object.soldiers.map((e: any) => SoldierDef.fromJSON(e)) + : [], + vehicles: globalThis.Array.isArray(object?.vehicles) + ? object.vehicles.map((e: any) => VehicleDef.fromJSON(e)) + : [], + events: globalThis.Array.isArray(object?.events) ? object.events.map((e: any) => Event.fromJSON(e)) : [], + markers: globalThis.Array.isArray(object?.markers) ? object.markers.map((e: any) => MarkerDef.fromJSON(e)) : [], + times: globalThis.Array.isArray(object?.times) ? object.times.map((e: any) => TimeSample.fromJSON(e)) : [], + }; + }, + + toJSON(message: Manifest): unknown { + const obj: any = {}; + if (message.version !== 0) { + obj.version = Math.round(message.version); + } + if (message.world !== undefined) { + obj.world = WorldMeta.toJSON(message.world); + } + if (message.mission !== undefined) { + obj.mission = MissionMeta.toJSON(message.mission); + } + if (message.frameCount !== 0) { + obj.frameCount = Math.round(message.frameCount); + } + if (message.chunkSize !== 0) { + obj.chunkSize = Math.round(message.chunkSize); + } + if (message.captureDelayMs !== 0) { + obj.captureDelayMs = Math.round(message.captureDelayMs); + } + if (message.chunkCount !== 0) { + obj.chunkCount = Math.round(message.chunkCount); + } + if (message.soldiers?.length) { + obj.soldiers = message.soldiers.map((e) => SoldierDef.toJSON(e)); + } + if (message.vehicles?.length) { + obj.vehicles = message.vehicles.map((e) => VehicleDef.toJSON(e)); + } + if (message.events?.length) { + obj.events = message.events.map((e) => Event.toJSON(e)); + } + if (message.markers?.length) { + obj.markers = message.markers.map((e) => MarkerDef.toJSON(e)); + } + if (message.times?.length) { + obj.times = message.times.map((e) => TimeSample.toJSON(e)); + } + return obj; + }, + + create, I>>(base?: I): Manifest { + return Manifest.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Manifest { + const message = createBaseManifest(); + message.version = object.version ?? 0; + message.world = (object.world !== undefined && object.world !== null) + ? WorldMeta.fromPartial(object.world) + : undefined; + message.mission = (object.mission !== undefined && object.mission !== null) + ? MissionMeta.fromPartial(object.mission) + : undefined; + message.frameCount = object.frameCount ?? 0; + message.chunkSize = object.chunkSize ?? 0; + message.captureDelayMs = object.captureDelayMs ?? 0; + message.chunkCount = object.chunkCount ?? 0; + message.soldiers = object.soldiers?.map((e) => SoldierDef.fromPartial(e)) || []; + message.vehicles = object.vehicles?.map((e) => VehicleDef.fromPartial(e)) || []; + message.events = object.events?.map((e) => Event.fromPartial(e)) || []; + message.markers = object.markers?.map((e) => MarkerDef.fromPartial(e)) || []; + message.times = object.times?.map((e) => TimeSample.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseWorldMeta(): WorldMeta { + return { worldName: "", worldSize: 0, latitude: 0, longitude: 0, author: "", displayName: "" }; +} + +export const WorldMeta: MessageFns = { + encode(message: WorldMeta, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.worldName !== "") { + writer.uint32(10).string(message.worldName); + } + if (message.worldSize !== 0) { + writer.uint32(21).float(message.worldSize); + } + if (message.latitude !== 0) { + writer.uint32(29).float(message.latitude); + } + if (message.longitude !== 0) { + writer.uint32(37).float(message.longitude); + } + if (message.author !== "") { + writer.uint32(42).string(message.author); + } + if (message.displayName !== "") { + writer.uint32(50).string(message.displayName); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): WorldMeta { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseWorldMeta(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.worldName = reader.string(); + continue; + } + case 2: { + if (tag !== 21) { + break; + } + + message.worldSize = reader.float(); + continue; + } + case 3: { + if (tag !== 29) { + break; + } + + message.latitude = reader.float(); + continue; + } + case 4: { + if (tag !== 37) { + break; + } + + message.longitude = reader.float(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.author = reader.string(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.displayName = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): WorldMeta { + return { + worldName: isSet(object.worldName) + ? globalThis.String(object.worldName) + : isSet(object.world_name) + ? globalThis.String(object.world_name) + : "", + worldSize: isSet(object.worldSize) + ? globalThis.Number(object.worldSize) + : isSet(object.world_size) + ? globalThis.Number(object.world_size) + : 0, + latitude: isSet(object.latitude) ? globalThis.Number(object.latitude) : 0, + longitude: isSet(object.longitude) ? globalThis.Number(object.longitude) : 0, + author: isSet(object.author) ? globalThis.String(object.author) : "", + displayName: isSet(object.displayName) + ? globalThis.String(object.displayName) + : isSet(object.display_name) + ? globalThis.String(object.display_name) + : "", + }; + }, + + toJSON(message: WorldMeta): unknown { + const obj: any = {}; + if (message.worldName !== "") { + obj.worldName = message.worldName; + } + if (message.worldSize !== 0) { + obj.worldSize = message.worldSize; + } + if (message.latitude !== 0) { + obj.latitude = message.latitude; + } + if (message.longitude !== 0) { + obj.longitude = message.longitude; + } + if (message.author !== "") { + obj.author = message.author; + } + if (message.displayName !== "") { + obj.displayName = message.displayName; + } + return obj; + }, + + create, I>>(base?: I): WorldMeta { + return WorldMeta.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): WorldMeta { + const message = createBaseWorldMeta(); + message.worldName = object.worldName ?? ""; + message.worldSize = object.worldSize ?? 0; + message.latitude = object.latitude ?? 0; + message.longitude = object.longitude ?? 0; + message.author = object.author ?? ""; + message.displayName = object.displayName ?? ""; + return message; + }, +}; + +function createBaseMissionMeta(): MissionMeta { + return { + missionName: "", + briefingName: "", + author: "", + serverName: "", + extensionVersion: "", + addonVersion: "", + extensionBuild: "", + tag: "", + playableSlots: undefined, + sideFriendly: undefined, + addons: [], + }; +} + +export const MissionMeta: MessageFns = { + encode(message: MissionMeta, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.missionName !== "") { + writer.uint32(10).string(message.missionName); + } + if (message.briefingName !== "") { + writer.uint32(18).string(message.briefingName); + } + if (message.author !== "") { + writer.uint32(26).string(message.author); + } + if (message.serverName !== "") { + writer.uint32(34).string(message.serverName); + } + if (message.extensionVersion !== "") { + writer.uint32(42).string(message.extensionVersion); + } + if (message.addonVersion !== "") { + writer.uint32(50).string(message.addonVersion); + } + if (message.extensionBuild !== "") { + writer.uint32(58).string(message.extensionBuild); + } + if (message.tag !== "") { + writer.uint32(66).string(message.tag); + } + if (message.playableSlots !== undefined) { + PlayableSlots.encode(message.playableSlots, writer.uint32(74).fork()).join(); + } + if (message.sideFriendly !== undefined) { + SideFriendly.encode(message.sideFriendly, writer.uint32(82).fork()).join(); + } + for (const v of message.addons) { + Addon.encode(v!, writer.uint32(90).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): MissionMeta { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMissionMeta(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.missionName = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.briefingName = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.author = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.serverName = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.extensionVersion = reader.string(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.addonVersion = reader.string(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.extensionBuild = reader.string(); + continue; + } + case 8: { + if (tag !== 66) { + break; + } + + message.tag = reader.string(); + continue; + } + case 9: { + if (tag !== 74) { + break; + } + + message.playableSlots = PlayableSlots.decode(reader, reader.uint32()); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.sideFriendly = SideFriendly.decode(reader, reader.uint32()); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.addons.push(Addon.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): MissionMeta { + return { + missionName: isSet(object.missionName) + ? globalThis.String(object.missionName) + : isSet(object.mission_name) + ? globalThis.String(object.mission_name) + : "", + briefingName: isSet(object.briefingName) + ? globalThis.String(object.briefingName) + : isSet(object.briefing_name) + ? globalThis.String(object.briefing_name) + : "", + author: isSet(object.author) ? globalThis.String(object.author) : "", + serverName: isSet(object.serverName) + ? globalThis.String(object.serverName) + : isSet(object.server_name) + ? globalThis.String(object.server_name) + : "", + extensionVersion: isSet(object.extensionVersion) + ? globalThis.String(object.extensionVersion) + : isSet(object.extension_version) + ? globalThis.String(object.extension_version) + : "", + addonVersion: isSet(object.addonVersion) + ? globalThis.String(object.addonVersion) + : isSet(object.addon_version) + ? globalThis.String(object.addon_version) + : "", + extensionBuild: isSet(object.extensionBuild) + ? globalThis.String(object.extensionBuild) + : isSet(object.extension_build) + ? globalThis.String(object.extension_build) + : "", + tag: isSet(object.tag) ? globalThis.String(object.tag) : "", + playableSlots: isSet(object.playableSlots) + ? PlayableSlots.fromJSON(object.playableSlots) + : isSet(object.playable_slots) + ? PlayableSlots.fromJSON(object.playable_slots) + : undefined, + sideFriendly: isSet(object.sideFriendly) + ? SideFriendly.fromJSON(object.sideFriendly) + : isSet(object.side_friendly) + ? SideFriendly.fromJSON(object.side_friendly) + : undefined, + addons: globalThis.Array.isArray(object?.addons) + ? object.addons.map((e: any) => Addon.fromJSON(e)) + : [], + }; + }, + + toJSON(message: MissionMeta): unknown { + const obj: any = {}; + if (message.missionName !== "") { + obj.missionName = message.missionName; + } + if (message.briefingName !== "") { + obj.briefingName = message.briefingName; + } + if (message.author !== "") { + obj.author = message.author; + } + if (message.serverName !== "") { + obj.serverName = message.serverName; + } + if (message.extensionVersion !== "") { + obj.extensionVersion = message.extensionVersion; + } + if (message.addonVersion !== "") { + obj.addonVersion = message.addonVersion; + } + if (message.extensionBuild !== "") { + obj.extensionBuild = message.extensionBuild; + } + if (message.tag !== "") { + obj.tag = message.tag; + } + if (message.playableSlots !== undefined) { + obj.playableSlots = PlayableSlots.toJSON(message.playableSlots); + } + if (message.sideFriendly !== undefined) { + obj.sideFriendly = SideFriendly.toJSON(message.sideFriendly); + } + if (message.addons?.length) { + obj.addons = message.addons.map((e) => Addon.toJSON(e)); + } + return obj; + }, + + create, I>>(base?: I): MissionMeta { + return MissionMeta.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): MissionMeta { + const message = createBaseMissionMeta(); + message.missionName = object.missionName ?? ""; + message.briefingName = object.briefingName ?? ""; + message.author = object.author ?? ""; + message.serverName = object.serverName ?? ""; + message.extensionVersion = object.extensionVersion ?? ""; + message.addonVersion = object.addonVersion ?? ""; + message.extensionBuild = object.extensionBuild ?? ""; + message.tag = object.tag ?? ""; + message.playableSlots = (object.playableSlots !== undefined && object.playableSlots !== null) + ? PlayableSlots.fromPartial(object.playableSlots) + : undefined; + message.sideFriendly = (object.sideFriendly !== undefined && object.sideFriendly !== null) + ? SideFriendly.fromPartial(object.sideFriendly) + : undefined; + message.addons = object.addons?.map((e) => Addon.fromPartial(e)) || []; + return message; + }, +}; + +function createBasePlayableSlots(): PlayableSlots { + return { west: 0, east: 0, independent: 0, civilian: 0 }; +} + +export const PlayableSlots: MessageFns = { + encode(message: PlayableSlots, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.west !== 0) { + writer.uint32(8).uint32(message.west); + } + if (message.east !== 0) { + writer.uint32(16).uint32(message.east); + } + if (message.independent !== 0) { + writer.uint32(24).uint32(message.independent); + } + if (message.civilian !== 0) { + writer.uint32(32).uint32(message.civilian); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): PlayableSlots { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePlayableSlots(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.west = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.east = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.independent = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.civilian = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): PlayableSlots { + return { + west: isSet(object.west) ? globalThis.Number(object.west) : 0, + east: isSet(object.east) ? globalThis.Number(object.east) : 0, + independent: isSet(object.independent) ? globalThis.Number(object.independent) : 0, + civilian: isSet(object.civilian) ? globalThis.Number(object.civilian) : 0, + }; + }, + + toJSON(message: PlayableSlots): unknown { + const obj: any = {}; + if (message.west !== 0) { + obj.west = Math.round(message.west); + } + if (message.east !== 0) { + obj.east = Math.round(message.east); + } + if (message.independent !== 0) { + obj.independent = Math.round(message.independent); + } + if (message.civilian !== 0) { + obj.civilian = Math.round(message.civilian); + } + return obj; + }, + + create, I>>(base?: I): PlayableSlots { + return PlayableSlots.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): PlayableSlots { + const message = createBasePlayableSlots(); + message.west = object.west ?? 0; + message.east = object.east ?? 0; + message.independent = object.independent ?? 0; + message.civilian = object.civilian ?? 0; + return message; + }, +}; + +function createBaseSideFriendly(): SideFriendly { + return { eastWest: false, eastIndependent: false, westIndependent: false }; +} + +export const SideFriendly: MessageFns = { + encode(message: SideFriendly, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.eastWest !== false) { + writer.uint32(8).bool(message.eastWest); + } + if (message.eastIndependent !== false) { + writer.uint32(16).bool(message.eastIndependent); + } + if (message.westIndependent !== false) { + writer.uint32(24).bool(message.westIndependent); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SideFriendly { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSideFriendly(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.eastWest = reader.bool(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.eastIndependent = reader.bool(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.westIndependent = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SideFriendly { + return { + eastWest: isSet(object.eastWest) + ? globalThis.Boolean(object.eastWest) + : isSet(object.east_west) + ? globalThis.Boolean(object.east_west) + : false, + eastIndependent: isSet(object.eastIndependent) + ? globalThis.Boolean(object.eastIndependent) + : isSet(object.east_independent) + ? globalThis.Boolean(object.east_independent) + : false, + westIndependent: isSet(object.westIndependent) + ? globalThis.Boolean(object.westIndependent) + : isSet(object.west_independent) + ? globalThis.Boolean(object.west_independent) + : false, + }; + }, + + toJSON(message: SideFriendly): unknown { + const obj: any = {}; + if (message.eastWest !== false) { + obj.eastWest = message.eastWest; + } + if (message.eastIndependent !== false) { + obj.eastIndependent = message.eastIndependent; + } + if (message.westIndependent !== false) { + obj.westIndependent = message.westIndependent; + } + return obj; + }, + + create, I>>(base?: I): SideFriendly { + return SideFriendly.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SideFriendly { + const message = createBaseSideFriendly(); + message.eastWest = object.eastWest ?? false; + message.eastIndependent = object.eastIndependent ?? false; + message.westIndependent = object.westIndependent ?? false; + return message; + }, +}; + +function createBaseAddon(): Addon { + return { name: "", workshopId: "" }; +} + +export const Addon: MessageFns = { + encode(message: Addon, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + if (message.workshopId !== "") { + writer.uint32(18).string(message.workshopId); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Addon { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseAddon(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.workshopId = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Addon { + return { + name: isSet(object.name) ? globalThis.String(object.name) : "", + workshopId: isSet(object.workshopId) + ? globalThis.String(object.workshopId) + : isSet(object.workshop_id) + ? globalThis.String(object.workshop_id) + : "", + }; + }, + + toJSON(message: Addon): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + if (message.workshopId !== "") { + obj.workshopId = message.workshopId; + } + return obj; + }, + + create, I>>(base?: I): Addon { + return Addon.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Addon { + const message = createBaseAddon(); + message.name = object.name ?? ""; + message.workshopId = object.workshopId ?? ""; + return message; + }, +}; + +function createBaseSoldierDef(): SoldierDef { + return { + id: 0, + name: "", + side: 0, + groupName: "", + role: "", + startFrame: 0, + endFrame: 0, + isPlayer: false, + className: "", + playerUid: "", + framesFired: [], + }; +} + +export const SoldierDef: MessageFns = { + encode(message: SoldierDef, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== 0) { + writer.uint32(8).uint32(message.id); + } + if (message.name !== "") { + writer.uint32(18).string(message.name); + } + if (message.side !== 0) { + writer.uint32(24).int32(message.side); + } + if (message.groupName !== "") { + writer.uint32(34).string(message.groupName); + } + if (message.role !== "") { + writer.uint32(42).string(message.role); + } + if (message.startFrame !== 0) { + writer.uint32(48).uint32(message.startFrame); + } + if (message.endFrame !== 0) { + writer.uint32(56).uint32(message.endFrame); + } + if (message.isPlayer !== false) { + writer.uint32(64).bool(message.isPlayer); + } + if (message.className !== "") { + writer.uint32(74).string(message.className); + } + if (message.playerUid !== "") { + writer.uint32(82).string(message.playerUid); + } + for (const v of message.framesFired) { + FiredFrame.encode(v!, writer.uint32(90).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SoldierDef { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSoldierDef(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.id = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.name = reader.string(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.side = reader.int32() as any; + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.groupName = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.role = reader.string(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.startFrame = reader.uint32(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.endFrame = reader.uint32(); + continue; + } + case 8: { + if (tag !== 64) { + break; + } + + message.isPlayer = reader.bool(); + continue; + } + case 9: { + if (tag !== 74) { + break; + } + + message.className = reader.string(); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.playerUid = reader.string(); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.framesFired.push(FiredFrame.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SoldierDef { + return { + id: isSet(object.id) ? globalThis.Number(object.id) : 0, + name: isSet(object.name) ? globalThis.String(object.name) : "", + side: isSet(object.side) ? sideFromJSON(object.side) : 0, + groupName: isSet(object.groupName) + ? globalThis.String(object.groupName) + : isSet(object.group_name) + ? globalThis.String(object.group_name) + : "", + role: isSet(object.role) ? globalThis.String(object.role) : "", + startFrame: isSet(object.startFrame) + ? globalThis.Number(object.startFrame) + : isSet(object.start_frame) + ? globalThis.Number(object.start_frame) + : 0, + endFrame: isSet(object.endFrame) + ? globalThis.Number(object.endFrame) + : isSet(object.end_frame) + ? globalThis.Number(object.end_frame) + : 0, + isPlayer: isSet(object.isPlayer) + ? globalThis.Boolean(object.isPlayer) + : isSet(object.is_player) + ? globalThis.Boolean(object.is_player) + : false, + className: isSet(object.className) + ? globalThis.String(object.className) + : isSet(object.class_name) + ? globalThis.String(object.class_name) + : "", + playerUid: isSet(object.playerUid) + ? globalThis.String(object.playerUid) + : isSet(object.player_uid) + ? globalThis.String(object.player_uid) + : "", + framesFired: globalThis.Array.isArray(object?.framesFired) + ? object.framesFired.map((e: any) => FiredFrame.fromJSON(e)) + : globalThis.Array.isArray(object?.frames_fired) + ? object.frames_fired.map((e: any) => FiredFrame.fromJSON(e)) + : [], + }; + }, + + toJSON(message: SoldierDef): unknown { + const obj: any = {}; + if (message.id !== 0) { + obj.id = Math.round(message.id); + } + if (message.name !== "") { + obj.name = message.name; + } + if (message.side !== 0) { + obj.side = sideToJSON(message.side); + } + if (message.groupName !== "") { + obj.groupName = message.groupName; + } + if (message.role !== "") { + obj.role = message.role; + } + if (message.startFrame !== 0) { + obj.startFrame = Math.round(message.startFrame); + } + if (message.endFrame !== 0) { + obj.endFrame = Math.round(message.endFrame); + } + if (message.isPlayer !== false) { + obj.isPlayer = message.isPlayer; + } + if (message.className !== "") { + obj.className = message.className; + } + if (message.playerUid !== "") { + obj.playerUid = message.playerUid; + } + if (message.framesFired?.length) { + obj.framesFired = message.framesFired.map((e) => FiredFrame.toJSON(e)); + } + return obj; + }, + + create, I>>(base?: I): SoldierDef { + return SoldierDef.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SoldierDef { + const message = createBaseSoldierDef(); + message.id = object.id ?? 0; + message.name = object.name ?? ""; + message.side = object.side ?? 0; + message.groupName = object.groupName ?? ""; + message.role = object.role ?? ""; + message.startFrame = object.startFrame ?? 0; + message.endFrame = object.endFrame ?? 0; + message.isPlayer = object.isPlayer ?? false; + message.className = object.className ?? ""; + message.playerUid = object.playerUid ?? ""; + message.framesFired = object.framesFired?.map((e) => FiredFrame.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseVehicleDef(): VehicleDef { + return { id: 0, name: "", vehicleClass: "", className: "", startFrame: 0, endFrame: 0, customization: "" }; +} + +export const VehicleDef: MessageFns = { + encode(message: VehicleDef, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== 0) { + writer.uint32(8).uint32(message.id); + } + if (message.name !== "") { + writer.uint32(18).string(message.name); + } + if (message.vehicleClass !== "") { + writer.uint32(26).string(message.vehicleClass); + } + if (message.className !== "") { + writer.uint32(34).string(message.className); + } + if (message.startFrame !== 0) { + writer.uint32(40).uint32(message.startFrame); + } + if (message.endFrame !== 0) { + writer.uint32(48).uint32(message.endFrame); + } + if (message.customization !== "") { + writer.uint32(58).string(message.customization); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): VehicleDef { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVehicleDef(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.id = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.name = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.vehicleClass = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.className = reader.string(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.startFrame = reader.uint32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.endFrame = reader.uint32(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.customization = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): VehicleDef { + return { + id: isSet(object.id) ? globalThis.Number(object.id) : 0, + name: isSet(object.name) ? globalThis.String(object.name) : "", + vehicleClass: isSet(object.vehicleClass) + ? globalThis.String(object.vehicleClass) + : isSet(object.vehicle_class) + ? globalThis.String(object.vehicle_class) + : "", + className: isSet(object.className) + ? globalThis.String(object.className) + : isSet(object.class_name) + ? globalThis.String(object.class_name) + : "", + startFrame: isSet(object.startFrame) + ? globalThis.Number(object.startFrame) + : isSet(object.start_frame) + ? globalThis.Number(object.start_frame) + : 0, + endFrame: isSet(object.endFrame) + ? globalThis.Number(object.endFrame) + : isSet(object.end_frame) + ? globalThis.Number(object.end_frame) + : 0, + customization: isSet(object.customization) ? globalThis.String(object.customization) : "", + }; + }, + + toJSON(message: VehicleDef): unknown { + const obj: any = {}; + if (message.id !== 0) { + obj.id = Math.round(message.id); + } + if (message.name !== "") { + obj.name = message.name; + } + if (message.vehicleClass !== "") { + obj.vehicleClass = message.vehicleClass; + } + if (message.className !== "") { + obj.className = message.className; + } + if (message.startFrame !== 0) { + obj.startFrame = Math.round(message.startFrame); + } + if (message.endFrame !== 0) { + obj.endFrame = Math.round(message.endFrame); + } + if (message.customization !== "") { + obj.customization = message.customization; + } + return obj; + }, + + create, I>>(base?: I): VehicleDef { + return VehicleDef.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): VehicleDef { + const message = createBaseVehicleDef(); + message.id = object.id ?? 0; + message.name = object.name ?? ""; + message.vehicleClass = object.vehicleClass ?? ""; + message.className = object.className ?? ""; + message.startFrame = object.startFrame ?? 0; + message.endFrame = object.endFrame ?? 0; + message.customization = object.customization ?? ""; + return message; + }, +}; + +function createBaseFiredFrame(): FiredFrame { + return { frameNum: 0, startPos: undefined, endPos: undefined, weapon: "", magazine: "", firingMode: "" }; +} + +export const FiredFrame: MessageFns = { + encode(message: FiredFrame, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.frameNum !== 0) { + writer.uint32(8).uint32(message.frameNum); + } + if (message.startPos !== undefined) { + Position3D.encode(message.startPos, writer.uint32(18).fork()).join(); + } + if (message.endPos !== undefined) { + Position3D.encode(message.endPos, writer.uint32(26).fork()).join(); + } + if (message.weapon !== "") { + writer.uint32(34).string(message.weapon); + } + if (message.magazine !== "") { + writer.uint32(42).string(message.magazine); + } + if (message.firingMode !== "") { + writer.uint32(50).string(message.firingMode); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): FiredFrame { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseFiredFrame(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.frameNum = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.startPos = Position3D.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.endPos = Position3D.decode(reader, reader.uint32()); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.weapon = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.magazine = reader.string(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.firingMode = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): FiredFrame { + return { + frameNum: isSet(object.frameNum) + ? globalThis.Number(object.frameNum) + : isSet(object.frame_num) + ? globalThis.Number(object.frame_num) + : 0, + startPos: isSet(object.startPos) + ? Position3D.fromJSON(object.startPos) + : isSet(object.start_pos) + ? Position3D.fromJSON(object.start_pos) + : undefined, + endPos: isSet(object.endPos) + ? Position3D.fromJSON(object.endPos) + : isSet(object.end_pos) + ? Position3D.fromJSON(object.end_pos) + : undefined, + weapon: isSet(object.weapon) ? globalThis.String(object.weapon) : "", + magazine: isSet(object.magazine) ? globalThis.String(object.magazine) : "", + firingMode: isSet(object.firingMode) + ? globalThis.String(object.firingMode) + : isSet(object.firing_mode) + ? globalThis.String(object.firing_mode) + : "", + }; + }, + + toJSON(message: FiredFrame): unknown { + const obj: any = {}; + if (message.frameNum !== 0) { + obj.frameNum = Math.round(message.frameNum); + } + if (message.startPos !== undefined) { + obj.startPos = Position3D.toJSON(message.startPos); + } + if (message.endPos !== undefined) { + obj.endPos = Position3D.toJSON(message.endPos); + } + if (message.weapon !== "") { + obj.weapon = message.weapon; + } + if (message.magazine !== "") { + obj.magazine = message.magazine; + } + if (message.firingMode !== "") { + obj.firingMode = message.firingMode; + } + return obj; + }, + + create, I>>(base?: I): FiredFrame { + return FiredFrame.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): FiredFrame { + const message = createBaseFiredFrame(); + message.frameNum = object.frameNum ?? 0; + message.startPos = (object.startPos !== undefined && object.startPos !== null) + ? Position3D.fromPartial(object.startPos) + : undefined; + message.endPos = (object.endPos !== undefined && object.endPos !== null) + ? Position3D.fromPartial(object.endPos) + : undefined; + message.weapon = object.weapon ?? ""; + message.magazine = object.magazine ?? ""; + message.firingMode = object.firingMode ?? ""; + return message; + }, +}; + +function createBaseEvent(): Event { + return { + frameNum: 0, + kill: undefined, + hit: undefined, + projectile: undefined, + chat: undefined, + radio: undefined, + ace3Death: undefined, + ace3Unconscious: undefined, + general: undefined, + connect: undefined, + endMission: undefined, + telemetry: undefined, + }; +} + +export const Event: MessageFns = { + encode(message: Event, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.frameNum !== 0) { + writer.uint32(8).uint32(message.frameNum); + } + if (message.kill !== undefined) { + KillEvent.encode(message.kill, writer.uint32(82).fork()).join(); + } + if (message.hit !== undefined) { + HitEvent.encode(message.hit, writer.uint32(90).fork()).join(); + } + if (message.projectile !== undefined) { + ProjectileEvent.encode(message.projectile, writer.uint32(98).fork()).join(); + } + if (message.chat !== undefined) { + ChatEvent.encode(message.chat, writer.uint32(106).fork()).join(); + } + if (message.radio !== undefined) { + RadioEvent.encode(message.radio, writer.uint32(114).fork()).join(); + } + if (message.ace3Death !== undefined) { + Ace3DeathEvent.encode(message.ace3Death, writer.uint32(122).fork()).join(); + } + if (message.ace3Unconscious !== undefined) { + Ace3UnconsciousEvent.encode(message.ace3Unconscious, writer.uint32(130).fork()).join(); + } + if (message.general !== undefined) { + GeneralEvent.encode(message.general, writer.uint32(138).fork()).join(); + } + if (message.connect !== undefined) { + ConnectEvent.encode(message.connect, writer.uint32(146).fork()).join(); + } + if (message.endMission !== undefined) { + EndMissionEvent.encode(message.endMission, writer.uint32(154).fork()).join(); + } + if (message.telemetry !== undefined) { + TelemetryEvent.encode(message.telemetry, writer.uint32(162).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Event { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.frameNum = reader.uint32(); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.kill = KillEvent.decode(reader, reader.uint32()); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.hit = HitEvent.decode(reader, reader.uint32()); + continue; + } + case 12: { + if (tag !== 98) { + break; + } + + message.projectile = ProjectileEvent.decode(reader, reader.uint32()); + continue; + } + case 13: { + if (tag !== 106) { + break; + } + + message.chat = ChatEvent.decode(reader, reader.uint32()); + continue; + } + case 14: { + if (tag !== 114) { + break; + } + + message.radio = RadioEvent.decode(reader, reader.uint32()); + continue; + } + case 15: { + if (tag !== 122) { + break; + } + + message.ace3Death = Ace3DeathEvent.decode(reader, reader.uint32()); + continue; + } + case 16: { + if (tag !== 130) { + break; + } + + message.ace3Unconscious = Ace3UnconsciousEvent.decode(reader, reader.uint32()); + continue; + } + case 17: { + if (tag !== 138) { + break; + } + + message.general = GeneralEvent.decode(reader, reader.uint32()); + continue; + } + case 18: { + if (tag !== 146) { + break; + } + + message.connect = ConnectEvent.decode(reader, reader.uint32()); + continue; + } + case 19: { + if (tag !== 154) { + break; + } + + message.endMission = EndMissionEvent.decode(reader, reader.uint32()); + continue; + } + case 20: { + if (tag !== 162) { + break; + } + + message.telemetry = TelemetryEvent.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Event { + return { + frameNum: isSet(object.frameNum) + ? globalThis.Number(object.frameNum) + : isSet(object.frame_num) + ? globalThis.Number(object.frame_num) + : 0, + kill: isSet(object.kill) ? KillEvent.fromJSON(object.kill) : undefined, + hit: isSet(object.hit) ? HitEvent.fromJSON(object.hit) : undefined, + projectile: isSet(object.projectile) ? ProjectileEvent.fromJSON(object.projectile) : undefined, + chat: isSet(object.chat) ? ChatEvent.fromJSON(object.chat) : undefined, + radio: isSet(object.radio) ? RadioEvent.fromJSON(object.radio) : undefined, + ace3Death: isSet(object.ace3Death) + ? Ace3DeathEvent.fromJSON(object.ace3Death) + : isSet(object.ace3_death) + ? Ace3DeathEvent.fromJSON(object.ace3_death) + : undefined, + ace3Unconscious: isSet(object.ace3Unconscious) + ? Ace3UnconsciousEvent.fromJSON(object.ace3Unconscious) + : isSet(object.ace3_unconscious) + ? Ace3UnconsciousEvent.fromJSON(object.ace3_unconscious) + : undefined, + general: isSet(object.general) ? GeneralEvent.fromJSON(object.general) : undefined, + connect: isSet(object.connect) ? ConnectEvent.fromJSON(object.connect) : undefined, + endMission: isSet(object.endMission) + ? EndMissionEvent.fromJSON(object.endMission) + : isSet(object.end_mission) + ? EndMissionEvent.fromJSON(object.end_mission) + : undefined, + telemetry: isSet(object.telemetry) ? TelemetryEvent.fromJSON(object.telemetry) : undefined, + }; + }, + + toJSON(message: Event): unknown { + const obj: any = {}; + if (message.frameNum !== 0) { + obj.frameNum = Math.round(message.frameNum); + } + if (message.kill !== undefined) { + obj.kill = KillEvent.toJSON(message.kill); + } + if (message.hit !== undefined) { + obj.hit = HitEvent.toJSON(message.hit); + } + if (message.projectile !== undefined) { + obj.projectile = ProjectileEvent.toJSON(message.projectile); + } + if (message.chat !== undefined) { + obj.chat = ChatEvent.toJSON(message.chat); + } + if (message.radio !== undefined) { + obj.radio = RadioEvent.toJSON(message.radio); + } + if (message.ace3Death !== undefined) { + obj.ace3Death = Ace3DeathEvent.toJSON(message.ace3Death); + } + if (message.ace3Unconscious !== undefined) { + obj.ace3Unconscious = Ace3UnconsciousEvent.toJSON(message.ace3Unconscious); + } + if (message.general !== undefined) { + obj.general = GeneralEvent.toJSON(message.general); + } + if (message.connect !== undefined) { + obj.connect = ConnectEvent.toJSON(message.connect); + } + if (message.endMission !== undefined) { + obj.endMission = EndMissionEvent.toJSON(message.endMission); + } + if (message.telemetry !== undefined) { + obj.telemetry = TelemetryEvent.toJSON(message.telemetry); + } + return obj; + }, + + create, I>>(base?: I): Event { + return Event.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Event { + const message = createBaseEvent(); + message.frameNum = object.frameNum ?? 0; + message.kill = (object.kill !== undefined && object.kill !== null) ? KillEvent.fromPartial(object.kill) : undefined; + message.hit = (object.hit !== undefined && object.hit !== null) ? HitEvent.fromPartial(object.hit) : undefined; + message.projectile = (object.projectile !== undefined && object.projectile !== null) + ? ProjectileEvent.fromPartial(object.projectile) + : undefined; + message.chat = (object.chat !== undefined && object.chat !== null) ? ChatEvent.fromPartial(object.chat) : undefined; + message.radio = (object.radio !== undefined && object.radio !== null) + ? RadioEvent.fromPartial(object.radio) + : undefined; + message.ace3Death = (object.ace3Death !== undefined && object.ace3Death !== null) + ? Ace3DeathEvent.fromPartial(object.ace3Death) + : undefined; + message.ace3Unconscious = (object.ace3Unconscious !== undefined && object.ace3Unconscious !== null) + ? Ace3UnconsciousEvent.fromPartial(object.ace3Unconscious) + : undefined; + message.general = (object.general !== undefined && object.general !== null) + ? GeneralEvent.fromPartial(object.general) + : undefined; + message.connect = (object.connect !== undefined && object.connect !== null) + ? ConnectEvent.fromPartial(object.connect) + : undefined; + message.endMission = (object.endMission !== undefined && object.endMission !== null) + ? EndMissionEvent.fromPartial(object.endMission) + : undefined; + message.telemetry = (object.telemetry !== undefined && object.telemetry !== null) + ? TelemetryEvent.fromPartial(object.telemetry) + : undefined; + return message; + }, +}; + +function createBaseKillEvent(): KillEvent { + return { + victimSoldierId: 0, + victimVehicleId: 0, + killerSoldierId: 0, + killerVehicleId: 0, + weaponName: "", + weaponMagazine: "", + eventText: "", + distance: 0, + victimIsVehicle: false, + killerIsVehicle: false, + }; +} + +export const KillEvent: MessageFns = { + encode(message: KillEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.victimSoldierId !== 0) { + writer.uint32(8).uint32(message.victimSoldierId); + } + if (message.victimVehicleId !== 0) { + writer.uint32(16).uint32(message.victimVehicleId); + } + if (message.killerSoldierId !== 0) { + writer.uint32(24).uint32(message.killerSoldierId); + } + if (message.killerVehicleId !== 0) { + writer.uint32(32).uint32(message.killerVehicleId); + } + if (message.weaponName !== "") { + writer.uint32(42).string(message.weaponName); + } + if (message.weaponMagazine !== "") { + writer.uint32(50).string(message.weaponMagazine); + } + if (message.eventText !== "") { + writer.uint32(58).string(message.eventText); + } + if (message.distance !== 0) { + writer.uint32(69).float(message.distance); + } + if (message.victimIsVehicle !== false) { + writer.uint32(72).bool(message.victimIsVehicle); + } + if (message.killerIsVehicle !== false) { + writer.uint32(80).bool(message.killerIsVehicle); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): KillEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseKillEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.victimSoldierId = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.victimVehicleId = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.killerSoldierId = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.killerVehicleId = reader.uint32(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.weaponName = reader.string(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.weaponMagazine = reader.string(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.eventText = reader.string(); + continue; + } + case 8: { + if (tag !== 69) { + break; + } + + message.distance = reader.float(); + continue; + } + case 9: { + if (tag !== 72) { + break; + } + + message.victimIsVehicle = reader.bool(); + continue; + } + case 10: { + if (tag !== 80) { + break; + } + + message.killerIsVehicle = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): KillEvent { + return { + victimSoldierId: isSet(object.victimSoldierId) + ? globalThis.Number(object.victimSoldierId) + : isSet(object.victim_soldier_id) + ? globalThis.Number(object.victim_soldier_id) + : 0, + victimVehicleId: isSet(object.victimVehicleId) + ? globalThis.Number(object.victimVehicleId) + : isSet(object.victim_vehicle_id) + ? globalThis.Number(object.victim_vehicle_id) + : 0, + killerSoldierId: isSet(object.killerSoldierId) + ? globalThis.Number(object.killerSoldierId) + : isSet(object.killer_soldier_id) + ? globalThis.Number(object.killer_soldier_id) + : 0, + killerVehicleId: isSet(object.killerVehicleId) + ? globalThis.Number(object.killerVehicleId) + : isSet(object.killer_vehicle_id) + ? globalThis.Number(object.killer_vehicle_id) + : 0, + weaponName: isSet(object.weaponName) + ? globalThis.String(object.weaponName) + : isSet(object.weapon_name) + ? globalThis.String(object.weapon_name) + : "", + weaponMagazine: isSet(object.weaponMagazine) + ? globalThis.String(object.weaponMagazine) + : isSet(object.weapon_magazine) + ? globalThis.String(object.weapon_magazine) + : "", + eventText: isSet(object.eventText) + ? globalThis.String(object.eventText) + : isSet(object.event_text) + ? globalThis.String(object.event_text) + : "", + distance: isSet(object.distance) ? globalThis.Number(object.distance) : 0, + victimIsVehicle: isSet(object.victimIsVehicle) + ? globalThis.Boolean(object.victimIsVehicle) + : isSet(object.victim_is_vehicle) + ? globalThis.Boolean(object.victim_is_vehicle) + : false, + killerIsVehicle: isSet(object.killerIsVehicle) + ? globalThis.Boolean(object.killerIsVehicle) + : isSet(object.killer_is_vehicle) + ? globalThis.Boolean(object.killer_is_vehicle) + : false, + }; + }, + + toJSON(message: KillEvent): unknown { + const obj: any = {}; + if (message.victimSoldierId !== 0) { + obj.victimSoldierId = Math.round(message.victimSoldierId); + } + if (message.victimVehicleId !== 0) { + obj.victimVehicleId = Math.round(message.victimVehicleId); + } + if (message.killerSoldierId !== 0) { + obj.killerSoldierId = Math.round(message.killerSoldierId); + } + if (message.killerVehicleId !== 0) { + obj.killerVehicleId = Math.round(message.killerVehicleId); + } + if (message.weaponName !== "") { + obj.weaponName = message.weaponName; + } + if (message.weaponMagazine !== "") { + obj.weaponMagazine = message.weaponMagazine; + } + if (message.eventText !== "") { + obj.eventText = message.eventText; + } + if (message.distance !== 0) { + obj.distance = message.distance; + } + if (message.victimIsVehicle !== false) { + obj.victimIsVehicle = message.victimIsVehicle; + } + if (message.killerIsVehicle !== false) { + obj.killerIsVehicle = message.killerIsVehicle; + } + return obj; + }, + + create, I>>(base?: I): KillEvent { + return KillEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): KillEvent { + const message = createBaseKillEvent(); + message.victimSoldierId = object.victimSoldierId ?? 0; + message.victimVehicleId = object.victimVehicleId ?? 0; + message.killerSoldierId = object.killerSoldierId ?? 0; + message.killerVehicleId = object.killerVehicleId ?? 0; + message.weaponName = object.weaponName ?? ""; + message.weaponMagazine = object.weaponMagazine ?? ""; + message.eventText = object.eventText ?? ""; + message.distance = object.distance ?? 0; + message.victimIsVehicle = object.victimIsVehicle ?? false; + message.killerIsVehicle = object.killerIsVehicle ?? false; + return message; + }, +}; + +function createBaseHitEvent(): HitEvent { + return { + victimSoldierId: 0, + victimVehicleId: 0, + shooterSoldierId: 0, + shooterVehicleId: 0, + weaponName: "", + weaponMagazine: "", + eventText: "", + distance: 0, + victimIsVehicle: false, + shooterIsVehicle: false, + }; +} + +export const HitEvent: MessageFns = { + encode(message: HitEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.victimSoldierId !== 0) { + writer.uint32(8).uint32(message.victimSoldierId); + } + if (message.victimVehicleId !== 0) { + writer.uint32(16).uint32(message.victimVehicleId); + } + if (message.shooterSoldierId !== 0) { + writer.uint32(24).uint32(message.shooterSoldierId); + } + if (message.shooterVehicleId !== 0) { + writer.uint32(32).uint32(message.shooterVehicleId); + } + if (message.weaponName !== "") { + writer.uint32(42).string(message.weaponName); + } + if (message.weaponMagazine !== "") { + writer.uint32(50).string(message.weaponMagazine); + } + if (message.eventText !== "") { + writer.uint32(58).string(message.eventText); + } + if (message.distance !== 0) { + writer.uint32(69).float(message.distance); + } + if (message.victimIsVehicle !== false) { + writer.uint32(72).bool(message.victimIsVehicle); + } + if (message.shooterIsVehicle !== false) { + writer.uint32(80).bool(message.shooterIsVehicle); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): HitEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseHitEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.victimSoldierId = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.victimVehicleId = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.shooterSoldierId = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.shooterVehicleId = reader.uint32(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.weaponName = reader.string(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.weaponMagazine = reader.string(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.eventText = reader.string(); + continue; + } + case 8: { + if (tag !== 69) { + break; + } + + message.distance = reader.float(); + continue; + } + case 9: { + if (tag !== 72) { + break; + } + + message.victimIsVehicle = reader.bool(); + continue; + } + case 10: { + if (tag !== 80) { + break; + } + + message.shooterIsVehicle = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): HitEvent { + return { + victimSoldierId: isSet(object.victimSoldierId) + ? globalThis.Number(object.victimSoldierId) + : isSet(object.victim_soldier_id) + ? globalThis.Number(object.victim_soldier_id) + : 0, + victimVehicleId: isSet(object.victimVehicleId) + ? globalThis.Number(object.victimVehicleId) + : isSet(object.victim_vehicle_id) + ? globalThis.Number(object.victim_vehicle_id) + : 0, + shooterSoldierId: isSet(object.shooterSoldierId) + ? globalThis.Number(object.shooterSoldierId) + : isSet(object.shooter_soldier_id) + ? globalThis.Number(object.shooter_soldier_id) + : 0, + shooterVehicleId: isSet(object.shooterVehicleId) + ? globalThis.Number(object.shooterVehicleId) + : isSet(object.shooter_vehicle_id) + ? globalThis.Number(object.shooter_vehicle_id) + : 0, + weaponName: isSet(object.weaponName) + ? globalThis.String(object.weaponName) + : isSet(object.weapon_name) + ? globalThis.String(object.weapon_name) + : "", + weaponMagazine: isSet(object.weaponMagazine) + ? globalThis.String(object.weaponMagazine) + : isSet(object.weapon_magazine) + ? globalThis.String(object.weapon_magazine) + : "", + eventText: isSet(object.eventText) + ? globalThis.String(object.eventText) + : isSet(object.event_text) + ? globalThis.String(object.event_text) + : "", + distance: isSet(object.distance) ? globalThis.Number(object.distance) : 0, + victimIsVehicle: isSet(object.victimIsVehicle) + ? globalThis.Boolean(object.victimIsVehicle) + : isSet(object.victim_is_vehicle) + ? globalThis.Boolean(object.victim_is_vehicle) + : false, + shooterIsVehicle: isSet(object.shooterIsVehicle) + ? globalThis.Boolean(object.shooterIsVehicle) + : isSet(object.shooter_is_vehicle) + ? globalThis.Boolean(object.shooter_is_vehicle) + : false, + }; + }, + + toJSON(message: HitEvent): unknown { + const obj: any = {}; + if (message.victimSoldierId !== 0) { + obj.victimSoldierId = Math.round(message.victimSoldierId); + } + if (message.victimVehicleId !== 0) { + obj.victimVehicleId = Math.round(message.victimVehicleId); + } + if (message.shooterSoldierId !== 0) { + obj.shooterSoldierId = Math.round(message.shooterSoldierId); + } + if (message.shooterVehicleId !== 0) { + obj.shooterVehicleId = Math.round(message.shooterVehicleId); + } + if (message.weaponName !== "") { + obj.weaponName = message.weaponName; + } + if (message.weaponMagazine !== "") { + obj.weaponMagazine = message.weaponMagazine; + } + if (message.eventText !== "") { + obj.eventText = message.eventText; + } + if (message.distance !== 0) { + obj.distance = message.distance; + } + if (message.victimIsVehicle !== false) { + obj.victimIsVehicle = message.victimIsVehicle; + } + if (message.shooterIsVehicle !== false) { + obj.shooterIsVehicle = message.shooterIsVehicle; + } + return obj; + }, + + create, I>>(base?: I): HitEvent { + return HitEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): HitEvent { + const message = createBaseHitEvent(); + message.victimSoldierId = object.victimSoldierId ?? 0; + message.victimVehicleId = object.victimVehicleId ?? 0; + message.shooterSoldierId = object.shooterSoldierId ?? 0; + message.shooterVehicleId = object.shooterVehicleId ?? 0; + message.weaponName = object.weaponName ?? ""; + message.weaponMagazine = object.weaponMagazine ?? ""; + message.eventText = object.eventText ?? ""; + message.distance = object.distance ?? 0; + message.victimIsVehicle = object.victimIsVehicle ?? false; + message.shooterIsVehicle = object.shooterIsVehicle ?? false; + return message; + }, +}; + +function createBaseProjectileEvent(): ProjectileEvent { + return { + firerId: 0, + vehicleId: 0, + weapon: "", + magazine: "", + simulationType: "", + trajectory: [], + hits: [], + muzzle: "", + magazineIcon: "", + }; +} + +export const ProjectileEvent: MessageFns = { + encode(message: ProjectileEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.firerId !== 0) { + writer.uint32(8).uint32(message.firerId); + } + if (message.vehicleId !== 0) { + writer.uint32(16).uint32(message.vehicleId); + } + if (message.weapon !== "") { + writer.uint32(26).string(message.weapon); + } + if (message.magazine !== "") { + writer.uint32(34).string(message.magazine); + } + if (message.simulationType !== "") { + writer.uint32(42).string(message.simulationType); + } + for (const v of message.trajectory) { + TrajectoryPoint.encode(v!, writer.uint32(50).fork()).join(); + } + for (const v of message.hits) { + ProjectileHit.encode(v!, writer.uint32(58).fork()).join(); + } + if (message.muzzle !== "") { + writer.uint32(66).string(message.muzzle); + } + if (message.magazineIcon !== "") { + writer.uint32(74).string(message.magazineIcon); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ProjectileEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseProjectileEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.firerId = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.vehicleId = reader.uint32(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.weapon = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.magazine = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.simulationType = reader.string(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.trajectory.push(TrajectoryPoint.decode(reader, reader.uint32())); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.hits.push(ProjectileHit.decode(reader, reader.uint32())); + continue; + } + case 8: { + if (tag !== 66) { + break; + } + + message.muzzle = reader.string(); + continue; + } + case 9: { + if (tag !== 74) { + break; + } + + message.magazineIcon = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ProjectileEvent { + return { + firerId: isSet(object.firerId) + ? globalThis.Number(object.firerId) + : isSet(object.firer_id) + ? globalThis.Number(object.firer_id) + : 0, + vehicleId: isSet(object.vehicleId) + ? globalThis.Number(object.vehicleId) + : isSet(object.vehicle_id) + ? globalThis.Number(object.vehicle_id) + : 0, + weapon: isSet(object.weapon) ? globalThis.String(object.weapon) : "", + magazine: isSet(object.magazine) ? globalThis.String(object.magazine) : "", + simulationType: isSet(object.simulationType) + ? globalThis.String(object.simulationType) + : isSet(object.simulation_type) + ? globalThis.String(object.simulation_type) + : "", + trajectory: globalThis.Array.isArray(object?.trajectory) + ? object.trajectory.map((e: any) => TrajectoryPoint.fromJSON(e)) + : [], + hits: globalThis.Array.isArray(object?.hits) + ? object.hits.map((e: any) => ProjectileHit.fromJSON(e)) + : [], + muzzle: isSet(object.muzzle) ? globalThis.String(object.muzzle) : "", + magazineIcon: isSet(object.magazineIcon) + ? globalThis.String(object.magazineIcon) + : isSet(object.magazine_icon) + ? globalThis.String(object.magazine_icon) + : "", + }; + }, + + toJSON(message: ProjectileEvent): unknown { + const obj: any = {}; + if (message.firerId !== 0) { + obj.firerId = Math.round(message.firerId); + } + if (message.vehicleId !== 0) { + obj.vehicleId = Math.round(message.vehicleId); + } + if (message.weapon !== "") { + obj.weapon = message.weapon; + } + if (message.magazine !== "") { + obj.magazine = message.magazine; + } + if (message.simulationType !== "") { + obj.simulationType = message.simulationType; + } + if (message.trajectory?.length) { + obj.trajectory = message.trajectory.map((e) => TrajectoryPoint.toJSON(e)); + } + if (message.hits?.length) { + obj.hits = message.hits.map((e) => ProjectileHit.toJSON(e)); + } + if (message.muzzle !== "") { + obj.muzzle = message.muzzle; + } + if (message.magazineIcon !== "") { + obj.magazineIcon = message.magazineIcon; + } + return obj; + }, + + create, I>>(base?: I): ProjectileEvent { + return ProjectileEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ProjectileEvent { + const message = createBaseProjectileEvent(); + message.firerId = object.firerId ?? 0; + message.vehicleId = object.vehicleId ?? 0; + message.weapon = object.weapon ?? ""; + message.magazine = object.magazine ?? ""; + message.simulationType = object.simulationType ?? ""; + message.trajectory = object.trajectory?.map((e) => TrajectoryPoint.fromPartial(e)) || []; + message.hits = object.hits?.map((e) => ProjectileHit.fromPartial(e)) || []; + message.muzzle = object.muzzle ?? ""; + message.magazineIcon = object.magazineIcon ?? ""; + return message; + }, +}; + +function createBaseTrajectoryPoint(): TrajectoryPoint { + return { position: undefined, frameNum: 0 }; +} + +export const TrajectoryPoint: MessageFns = { + encode(message: TrajectoryPoint, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.position !== undefined) { + Position3D.encode(message.position, writer.uint32(10).fork()).join(); + } + if (message.frameNum !== 0) { + writer.uint32(16).uint32(message.frameNum); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): TrajectoryPoint { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTrajectoryPoint(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.position = Position3D.decode(reader, reader.uint32()); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.frameNum = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): TrajectoryPoint { + return { + position: isSet(object.position) ? Position3D.fromJSON(object.position) : undefined, + frameNum: isSet(object.frameNum) + ? globalThis.Number(object.frameNum) + : isSet(object.frame_num) + ? globalThis.Number(object.frame_num) + : 0, + }; + }, + + toJSON(message: TrajectoryPoint): unknown { + const obj: any = {}; + if (message.position !== undefined) { + obj.position = Position3D.toJSON(message.position); + } + if (message.frameNum !== 0) { + obj.frameNum = Math.round(message.frameNum); + } + return obj; + }, + + create, I>>(base?: I): TrajectoryPoint { + return TrajectoryPoint.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): TrajectoryPoint { + const message = createBaseTrajectoryPoint(); + message.position = (object.position !== undefined && object.position !== null) + ? Position3D.fromPartial(object.position) + : undefined; + message.frameNum = object.frameNum ?? 0; + return message; + }, +}; + +function createBaseProjectileHit(): ProjectileHit { + return { + frameNum: 0, + position: undefined, + soldierId: 0, + vehicleId: 0, + hitSoldier: false, + hitVehicle: false, + componentsHit: [], + }; +} + +export const ProjectileHit: MessageFns = { + encode(message: ProjectileHit, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.frameNum !== 0) { + writer.uint32(8).uint32(message.frameNum); + } + if (message.position !== undefined) { + Position3D.encode(message.position, writer.uint32(18).fork()).join(); + } + if (message.soldierId !== 0) { + writer.uint32(24).uint32(message.soldierId); + } + if (message.vehicleId !== 0) { + writer.uint32(32).uint32(message.vehicleId); + } + if (message.hitSoldier !== false) { + writer.uint32(40).bool(message.hitSoldier); + } + if (message.hitVehicle !== false) { + writer.uint32(48).bool(message.hitVehicle); + } + for (const v of message.componentsHit) { + writer.uint32(58).string(v!); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ProjectileHit { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseProjectileHit(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.frameNum = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.position = Position3D.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.soldierId = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.vehicleId = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.hitSoldier = reader.bool(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.hitVehicle = reader.bool(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.componentsHit.push(reader.string()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ProjectileHit { + return { + frameNum: isSet(object.frameNum) + ? globalThis.Number(object.frameNum) + : isSet(object.frame_num) + ? globalThis.Number(object.frame_num) + : 0, + position: isSet(object.position) ? Position3D.fromJSON(object.position) : undefined, + soldierId: isSet(object.soldierId) + ? globalThis.Number(object.soldierId) + : isSet(object.soldier_id) + ? globalThis.Number(object.soldier_id) + : 0, + vehicleId: isSet(object.vehicleId) + ? globalThis.Number(object.vehicleId) + : isSet(object.vehicle_id) + ? globalThis.Number(object.vehicle_id) + : 0, + hitSoldier: isSet(object.hitSoldier) + ? globalThis.Boolean(object.hitSoldier) + : isSet(object.hit_soldier) + ? globalThis.Boolean(object.hit_soldier) + : false, + hitVehicle: isSet(object.hitVehicle) + ? globalThis.Boolean(object.hitVehicle) + : isSet(object.hit_vehicle) + ? globalThis.Boolean(object.hit_vehicle) + : false, + componentsHit: globalThis.Array.isArray(object?.componentsHit) + ? object.componentsHit.map((e: any) => globalThis.String(e)) + : globalThis.Array.isArray(object?.components_hit) + ? object.components_hit.map((e: any) => globalThis.String(e)) + : [], + }; + }, + + toJSON(message: ProjectileHit): unknown { + const obj: any = {}; + if (message.frameNum !== 0) { + obj.frameNum = Math.round(message.frameNum); + } + if (message.position !== undefined) { + obj.position = Position3D.toJSON(message.position); + } + if (message.soldierId !== 0) { + obj.soldierId = Math.round(message.soldierId); + } + if (message.vehicleId !== 0) { + obj.vehicleId = Math.round(message.vehicleId); + } + if (message.hitSoldier !== false) { + obj.hitSoldier = message.hitSoldier; + } + if (message.hitVehicle !== false) { + obj.hitVehicle = message.hitVehicle; + } + if (message.componentsHit?.length) { + obj.componentsHit = message.componentsHit; + } + return obj; + }, + + create, I>>(base?: I): ProjectileHit { + return ProjectileHit.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ProjectileHit { + const message = createBaseProjectileHit(); + message.frameNum = object.frameNum ?? 0; + message.position = (object.position !== undefined && object.position !== null) + ? Position3D.fromPartial(object.position) + : undefined; + message.soldierId = object.soldierId ?? 0; + message.vehicleId = object.vehicleId ?? 0; + message.hitSoldier = object.hitSoldier ?? false; + message.hitVehicle = object.hitVehicle ?? false; + message.componentsHit = object.componentsHit?.map((e) => e) || []; + return message; + }, +}; + +function createBaseChatEvent(): ChatEvent { + return { soldierId: 0, channel: "", fromName: "", message: "", playerUid: "" }; +} + +export const ChatEvent: MessageFns = { + encode(message: ChatEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.soldierId !== 0) { + writer.uint32(8).uint32(message.soldierId); + } + if (message.channel !== "") { + writer.uint32(18).string(message.channel); + } + if (message.fromName !== "") { + writer.uint32(26).string(message.fromName); + } + if (message.message !== "") { + writer.uint32(34).string(message.message); + } + if (message.playerUid !== "") { + writer.uint32(42).string(message.playerUid); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ChatEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseChatEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.soldierId = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.channel = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.fromName = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.message = reader.string(); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.playerUid = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ChatEvent { + return { + soldierId: isSet(object.soldierId) + ? globalThis.Number(object.soldierId) + : isSet(object.soldier_id) + ? globalThis.Number(object.soldier_id) + : 0, + channel: isSet(object.channel) ? globalThis.String(object.channel) : "", + fromName: isSet(object.fromName) + ? globalThis.String(object.fromName) + : isSet(object.from_name) + ? globalThis.String(object.from_name) + : "", + message: isSet(object.message) ? globalThis.String(object.message) : "", + playerUid: isSet(object.playerUid) + ? globalThis.String(object.playerUid) + : isSet(object.player_uid) + ? globalThis.String(object.player_uid) + : "", + }; + }, + + toJSON(message: ChatEvent): unknown { + const obj: any = {}; + if (message.soldierId !== 0) { + obj.soldierId = Math.round(message.soldierId); + } + if (message.channel !== "") { + obj.channel = message.channel; + } + if (message.fromName !== "") { + obj.fromName = message.fromName; + } + if (message.message !== "") { + obj.message = message.message; + } + if (message.playerUid !== "") { + obj.playerUid = message.playerUid; + } + return obj; + }, + + create, I>>(base?: I): ChatEvent { + return ChatEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ChatEvent { + const message = createBaseChatEvent(); + message.soldierId = object.soldierId ?? 0; + message.channel = object.channel ?? ""; + message.fromName = object.fromName ?? ""; + message.message = object.message ?? ""; + message.playerUid = object.playerUid ?? ""; + return message; + }, +}; + +function createBaseRadioEvent(): RadioEvent { + return { soldierId: 0, radio: "", radioType: "", startEnd: "", channel: 0, isAdditional: false, frequency: 0 }; +} + +export const RadioEvent: MessageFns = { + encode(message: RadioEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.soldierId !== 0) { + writer.uint32(8).uint32(message.soldierId); + } + if (message.radio !== "") { + writer.uint32(18).string(message.radio); + } + if (message.radioType !== "") { + writer.uint32(26).string(message.radioType); + } + if (message.startEnd !== "") { + writer.uint32(34).string(message.startEnd); + } + if (message.channel !== 0) { + writer.uint32(40).int32(message.channel); + } + if (message.isAdditional !== false) { + writer.uint32(48).bool(message.isAdditional); + } + if (message.frequency !== 0) { + writer.uint32(61).float(message.frequency); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): RadioEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseRadioEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.soldierId = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.radio = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.radioType = reader.string(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.startEnd = reader.string(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.channel = reader.int32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.isAdditional = reader.bool(); + continue; + } + case 7: { + if (tag !== 61) { + break; + } + + message.frequency = reader.float(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): RadioEvent { + return { + soldierId: isSet(object.soldierId) + ? globalThis.Number(object.soldierId) + : isSet(object.soldier_id) + ? globalThis.Number(object.soldier_id) + : 0, + radio: isSet(object.radio) ? globalThis.String(object.radio) : "", + radioType: isSet(object.radioType) + ? globalThis.String(object.radioType) + : isSet(object.radio_type) + ? globalThis.String(object.radio_type) + : "", + startEnd: isSet(object.startEnd) + ? globalThis.String(object.startEnd) + : isSet(object.start_end) + ? globalThis.String(object.start_end) + : "", + channel: isSet(object.channel) ? globalThis.Number(object.channel) : 0, + isAdditional: isSet(object.isAdditional) + ? globalThis.Boolean(object.isAdditional) + : isSet(object.is_additional) + ? globalThis.Boolean(object.is_additional) + : false, + frequency: isSet(object.frequency) ? globalThis.Number(object.frequency) : 0, + }; + }, + + toJSON(message: RadioEvent): unknown { + const obj: any = {}; + if (message.soldierId !== 0) { + obj.soldierId = Math.round(message.soldierId); + } + if (message.radio !== "") { + obj.radio = message.radio; + } + if (message.radioType !== "") { + obj.radioType = message.radioType; + } + if (message.startEnd !== "") { + obj.startEnd = message.startEnd; + } + if (message.channel !== 0) { + obj.channel = Math.round(message.channel); + } + if (message.isAdditional !== false) { + obj.isAdditional = message.isAdditional; + } + if (message.frequency !== 0) { + obj.frequency = message.frequency; + } + return obj; + }, + + create, I>>(base?: I): RadioEvent { + return RadioEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): RadioEvent { + const message = createBaseRadioEvent(); + message.soldierId = object.soldierId ?? 0; + message.radio = object.radio ?? ""; + message.radioType = object.radioType ?? ""; + message.startEnd = object.startEnd ?? ""; + message.channel = object.channel ?? 0; + message.isAdditional = object.isAdditional ?? false; + message.frequency = object.frequency ?? 0; + return message; + }, +}; + +function createBaseAce3DeathEvent(): Ace3DeathEvent { + return { soldierId: 0, reason: "", lastDamageSourceId: 0, hasDamageSource: false }; +} + +export const Ace3DeathEvent: MessageFns = { + encode(message: Ace3DeathEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.soldierId !== 0) { + writer.uint32(8).uint32(message.soldierId); + } + if (message.reason !== "") { + writer.uint32(18).string(message.reason); + } + if (message.lastDamageSourceId !== 0) { + writer.uint32(24).uint32(message.lastDamageSourceId); + } + if (message.hasDamageSource !== false) { + writer.uint32(32).bool(message.hasDamageSource); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Ace3DeathEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseAce3DeathEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.soldierId = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.reason = reader.string(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.lastDamageSourceId = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.hasDamageSource = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Ace3DeathEvent { + return { + soldierId: isSet(object.soldierId) + ? globalThis.Number(object.soldierId) + : isSet(object.soldier_id) + ? globalThis.Number(object.soldier_id) + : 0, + reason: isSet(object.reason) ? globalThis.String(object.reason) : "", + lastDamageSourceId: isSet(object.lastDamageSourceId) + ? globalThis.Number(object.lastDamageSourceId) + : isSet(object.last_damage_source_id) + ? globalThis.Number(object.last_damage_source_id) + : 0, + hasDamageSource: isSet(object.hasDamageSource) + ? globalThis.Boolean(object.hasDamageSource) + : isSet(object.has_damage_source) + ? globalThis.Boolean(object.has_damage_source) + : false, + }; + }, + + toJSON(message: Ace3DeathEvent): unknown { + const obj: any = {}; + if (message.soldierId !== 0) { + obj.soldierId = Math.round(message.soldierId); + } + if (message.reason !== "") { + obj.reason = message.reason; + } + if (message.lastDamageSourceId !== 0) { + obj.lastDamageSourceId = Math.round(message.lastDamageSourceId); + } + if (message.hasDamageSource !== false) { + obj.hasDamageSource = message.hasDamageSource; + } + return obj; + }, + + create, I>>(base?: I): Ace3DeathEvent { + return Ace3DeathEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Ace3DeathEvent { + const message = createBaseAce3DeathEvent(); + message.soldierId = object.soldierId ?? 0; + message.reason = object.reason ?? ""; + message.lastDamageSourceId = object.lastDamageSourceId ?? 0; + message.hasDamageSource = object.hasDamageSource ?? false; + return message; + }, +}; + +function createBaseAce3UnconsciousEvent(): Ace3UnconsciousEvent { + return { soldierId: 0, isUnconscious: false }; +} + +export const Ace3UnconsciousEvent: MessageFns = { + encode(message: Ace3UnconsciousEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.soldierId !== 0) { + writer.uint32(8).uint32(message.soldierId); + } + if (message.isUnconscious !== false) { + writer.uint32(16).bool(message.isUnconscious); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Ace3UnconsciousEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseAce3UnconsciousEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.soldierId = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.isUnconscious = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Ace3UnconsciousEvent { + return { + soldierId: isSet(object.soldierId) + ? globalThis.Number(object.soldierId) + : isSet(object.soldier_id) + ? globalThis.Number(object.soldier_id) + : 0, + isUnconscious: isSet(object.isUnconscious) + ? globalThis.Boolean(object.isUnconscious) + : isSet(object.is_unconscious) + ? globalThis.Boolean(object.is_unconscious) + : false, + }; + }, + + toJSON(message: Ace3UnconsciousEvent): unknown { + const obj: any = {}; + if (message.soldierId !== 0) { + obj.soldierId = Math.round(message.soldierId); + } + if (message.isUnconscious !== false) { + obj.isUnconscious = message.isUnconscious; + } + return obj; + }, + + create, I>>(base?: I): Ace3UnconsciousEvent { + return Ace3UnconsciousEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Ace3UnconsciousEvent { + const message = createBaseAce3UnconsciousEvent(); + message.soldierId = object.soldierId ?? 0; + message.isUnconscious = object.isUnconscious ?? false; + return message; + }, +}; + +function createBaseTelemetryEvent(): TelemetryEvent { + return { + fpsAverage: 0, + fpsMin: 0, + sideEntityCounts: undefined, + globalCounts: undefined, + scripts: undefined, + weather: undefined, + players: [], + }; +} + +export const TelemetryEvent: MessageFns = { + encode(message: TelemetryEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.fpsAverage !== 0) { + writer.uint32(13).float(message.fpsAverage); + } + if (message.fpsMin !== 0) { + writer.uint32(21).float(message.fpsMin); + } + if (message.sideEntityCounts !== undefined) { + SideEntityCounts.encode(message.sideEntityCounts, writer.uint32(26).fork()).join(); + } + if (message.globalCounts !== undefined) { + GlobalEntityCount.encode(message.globalCounts, writer.uint32(34).fork()).join(); + } + if (message.scripts !== undefined) { + ScriptCounts.encode(message.scripts, writer.uint32(42).fork()).join(); + } + if (message.weather !== undefined) { + WeatherData.encode(message.weather, writer.uint32(50).fork()).join(); + } + for (const v of message.players) { + PlayerNetworkData.encode(v!, writer.uint32(58).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): TelemetryEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTelemetryEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 13) { + break; + } + + message.fpsAverage = reader.float(); + continue; + } + case 2: { + if (tag !== 21) { + break; + } + + message.fpsMin = reader.float(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.sideEntityCounts = SideEntityCounts.decode(reader, reader.uint32()); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.globalCounts = GlobalEntityCount.decode(reader, reader.uint32()); + continue; + } + case 5: { + if (tag !== 42) { + break; + } + + message.scripts = ScriptCounts.decode(reader, reader.uint32()); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.weather = WeatherData.decode(reader, reader.uint32()); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.players.push(PlayerNetworkData.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): TelemetryEvent { + return { + fpsAverage: isSet(object.fpsAverage) + ? globalThis.Number(object.fpsAverage) + : isSet(object.fps_average) + ? globalThis.Number(object.fps_average) + : 0, + fpsMin: isSet(object.fpsMin) + ? globalThis.Number(object.fpsMin) + : isSet(object.fps_min) + ? globalThis.Number(object.fps_min) + : 0, + sideEntityCounts: isSet(object.sideEntityCounts) + ? SideEntityCounts.fromJSON(object.sideEntityCounts) + : isSet(object.side_entity_counts) + ? SideEntityCounts.fromJSON(object.side_entity_counts) + : undefined, + globalCounts: isSet(object.globalCounts) + ? GlobalEntityCount.fromJSON(object.globalCounts) + : isSet(object.global_counts) + ? GlobalEntityCount.fromJSON(object.global_counts) + : undefined, + scripts: isSet(object.scripts) ? ScriptCounts.fromJSON(object.scripts) : undefined, + weather: isSet(object.weather) ? WeatherData.fromJSON(object.weather) : undefined, + players: globalThis.Array.isArray(object?.players) + ? object.players.map((e: any) => PlayerNetworkData.fromJSON(e)) + : [], + }; + }, + + toJSON(message: TelemetryEvent): unknown { + const obj: any = {}; + if (message.fpsAverage !== 0) { + obj.fpsAverage = message.fpsAverage; + } + if (message.fpsMin !== 0) { + obj.fpsMin = message.fpsMin; + } + if (message.sideEntityCounts !== undefined) { + obj.sideEntityCounts = SideEntityCounts.toJSON(message.sideEntityCounts); + } + if (message.globalCounts !== undefined) { + obj.globalCounts = GlobalEntityCount.toJSON(message.globalCounts); + } + if (message.scripts !== undefined) { + obj.scripts = ScriptCounts.toJSON(message.scripts); + } + if (message.weather !== undefined) { + obj.weather = WeatherData.toJSON(message.weather); + } + if (message.players?.length) { + obj.players = message.players.map((e) => PlayerNetworkData.toJSON(e)); + } + return obj; + }, + + create, I>>(base?: I): TelemetryEvent { + return TelemetryEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): TelemetryEvent { + const message = createBaseTelemetryEvent(); + message.fpsAverage = object.fpsAverage ?? 0; + message.fpsMin = object.fpsMin ?? 0; + message.sideEntityCounts = (object.sideEntityCounts !== undefined && object.sideEntityCounts !== null) + ? SideEntityCounts.fromPartial(object.sideEntityCounts) + : undefined; + message.globalCounts = (object.globalCounts !== undefined && object.globalCounts !== null) + ? GlobalEntityCount.fromPartial(object.globalCounts) + : undefined; + message.scripts = (object.scripts !== undefined && object.scripts !== null) + ? ScriptCounts.fromPartial(object.scripts) + : undefined; + message.weather = (object.weather !== undefined && object.weather !== null) + ? WeatherData.fromPartial(object.weather) + : undefined; + message.players = object.players?.map((e) => PlayerNetworkData.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseSideEntityCounts(): SideEntityCounts { + return { east: undefined, west: undefined, independent: undefined, civilian: undefined }; +} + +export const SideEntityCounts: MessageFns = { + encode(message: SideEntityCounts, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.east !== undefined) { + SideEntityCount.encode(message.east, writer.uint32(10).fork()).join(); + } + if (message.west !== undefined) { + SideEntityCount.encode(message.west, writer.uint32(18).fork()).join(); + } + if (message.independent !== undefined) { + SideEntityCount.encode(message.independent, writer.uint32(26).fork()).join(); + } + if (message.civilian !== undefined) { + SideEntityCount.encode(message.civilian, writer.uint32(34).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SideEntityCounts { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSideEntityCounts(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.east = SideEntityCount.decode(reader, reader.uint32()); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.west = SideEntityCount.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.independent = SideEntityCount.decode(reader, reader.uint32()); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.civilian = SideEntityCount.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SideEntityCounts { + return { + east: isSet(object.east) ? SideEntityCount.fromJSON(object.east) : undefined, + west: isSet(object.west) ? SideEntityCount.fromJSON(object.west) : undefined, + independent: isSet(object.independent) ? SideEntityCount.fromJSON(object.independent) : undefined, + civilian: isSet(object.civilian) ? SideEntityCount.fromJSON(object.civilian) : undefined, + }; + }, + + toJSON(message: SideEntityCounts): unknown { + const obj: any = {}; + if (message.east !== undefined) { + obj.east = SideEntityCount.toJSON(message.east); + } + if (message.west !== undefined) { + obj.west = SideEntityCount.toJSON(message.west); + } + if (message.independent !== undefined) { + obj.independent = SideEntityCount.toJSON(message.independent); + } + if (message.civilian !== undefined) { + obj.civilian = SideEntityCount.toJSON(message.civilian); + } + return obj; + }, + + create, I>>(base?: I): SideEntityCounts { + return SideEntityCounts.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SideEntityCounts { + const message = createBaseSideEntityCounts(); + message.east = (object.east !== undefined && object.east !== null) + ? SideEntityCount.fromPartial(object.east) + : undefined; + message.west = (object.west !== undefined && object.west !== null) + ? SideEntityCount.fromPartial(object.west) + : undefined; + message.independent = (object.independent !== undefined && object.independent !== null) + ? SideEntityCount.fromPartial(object.independent) + : undefined; + message.civilian = (object.civilian !== undefined && object.civilian !== null) + ? SideEntityCount.fromPartial(object.civilian) + : undefined; + return message; + }, +}; + +function createBaseSideEntityCount(): SideEntityCount { + return { local: undefined, remote: undefined }; +} + +export const SideEntityCount: MessageFns = { + encode(message: SideEntityCount, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.local !== undefined) { + EntityLocality.encode(message.local, writer.uint32(10).fork()).join(); + } + if (message.remote !== undefined) { + EntityLocality.encode(message.remote, writer.uint32(18).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SideEntityCount { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSideEntityCount(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.local = EntityLocality.decode(reader, reader.uint32()); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.remote = EntityLocality.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SideEntityCount { + return { + local: isSet(object.local) ? EntityLocality.fromJSON(object.local) : undefined, + remote: isSet(object.remote) ? EntityLocality.fromJSON(object.remote) : undefined, + }; + }, + + toJSON(message: SideEntityCount): unknown { + const obj: any = {}; + if (message.local !== undefined) { + obj.local = EntityLocality.toJSON(message.local); + } + if (message.remote !== undefined) { + obj.remote = EntityLocality.toJSON(message.remote); + } + return obj; + }, + + create, I>>(base?: I): SideEntityCount { + return SideEntityCount.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SideEntityCount { + const message = createBaseSideEntityCount(); + message.local = (object.local !== undefined && object.local !== null) + ? EntityLocality.fromPartial(object.local) + : undefined; + message.remote = (object.remote !== undefined && object.remote !== null) + ? EntityLocality.fromPartial(object.remote) + : undefined; + return message; + }, +}; + +function createBaseEntityLocality(): EntityLocality { + return { unitsTotal: 0, unitsAlive: 0, unitsDead: 0, groups: 0, vehicles: 0, weaponHolders: 0 }; +} + +export const EntityLocality: MessageFns = { + encode(message: EntityLocality, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.unitsTotal !== 0) { + writer.uint32(8).uint32(message.unitsTotal); + } + if (message.unitsAlive !== 0) { + writer.uint32(16).uint32(message.unitsAlive); + } + if (message.unitsDead !== 0) { + writer.uint32(24).uint32(message.unitsDead); + } + if (message.groups !== 0) { + writer.uint32(32).uint32(message.groups); + } + if (message.vehicles !== 0) { + writer.uint32(40).uint32(message.vehicles); + } + if (message.weaponHolders !== 0) { + writer.uint32(48).uint32(message.weaponHolders); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): EntityLocality { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEntityLocality(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.unitsTotal = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.unitsAlive = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.unitsDead = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.groups = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.vehicles = reader.uint32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.weaponHolders = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): EntityLocality { + return { + unitsTotal: isSet(object.unitsTotal) + ? globalThis.Number(object.unitsTotal) + : isSet(object.units_total) + ? globalThis.Number(object.units_total) + : 0, + unitsAlive: isSet(object.unitsAlive) + ? globalThis.Number(object.unitsAlive) + : isSet(object.units_alive) + ? globalThis.Number(object.units_alive) + : 0, + unitsDead: isSet(object.unitsDead) + ? globalThis.Number(object.unitsDead) + : isSet(object.units_dead) + ? globalThis.Number(object.units_dead) + : 0, + groups: isSet(object.groups) ? globalThis.Number(object.groups) : 0, + vehicles: isSet(object.vehicles) ? globalThis.Number(object.vehicles) : 0, + weaponHolders: isSet(object.weaponHolders) + ? globalThis.Number(object.weaponHolders) + : isSet(object.weapon_holders) + ? globalThis.Number(object.weapon_holders) + : 0, + }; + }, + + toJSON(message: EntityLocality): unknown { + const obj: any = {}; + if (message.unitsTotal !== 0) { + obj.unitsTotal = Math.round(message.unitsTotal); + } + if (message.unitsAlive !== 0) { + obj.unitsAlive = Math.round(message.unitsAlive); + } + if (message.unitsDead !== 0) { + obj.unitsDead = Math.round(message.unitsDead); + } + if (message.groups !== 0) { + obj.groups = Math.round(message.groups); + } + if (message.vehicles !== 0) { + obj.vehicles = Math.round(message.vehicles); + } + if (message.weaponHolders !== 0) { + obj.weaponHolders = Math.round(message.weaponHolders); + } + return obj; + }, + + create, I>>(base?: I): EntityLocality { + return EntityLocality.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): EntityLocality { + const message = createBaseEntityLocality(); + message.unitsTotal = object.unitsTotal ?? 0; + message.unitsAlive = object.unitsAlive ?? 0; + message.unitsDead = object.unitsDead ?? 0; + message.groups = object.groups ?? 0; + message.vehicles = object.vehicles ?? 0; + message.weaponHolders = object.weaponHolders ?? 0; + return message; + }, +}; + +function createBaseGlobalEntityCount(): GlobalEntityCount { + return { + unitsAlive: 0, + unitsDead: 0, + groups: 0, + vehicles: 0, + weaponHolders: 0, + playersAlive: 0, + playersDead: 0, + playersConnected: 0, + }; +} + +export const GlobalEntityCount: MessageFns = { + encode(message: GlobalEntityCount, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.unitsAlive !== 0) { + writer.uint32(8).uint32(message.unitsAlive); + } + if (message.unitsDead !== 0) { + writer.uint32(16).uint32(message.unitsDead); + } + if (message.groups !== 0) { + writer.uint32(24).uint32(message.groups); + } + if (message.vehicles !== 0) { + writer.uint32(32).uint32(message.vehicles); + } + if (message.weaponHolders !== 0) { + writer.uint32(40).uint32(message.weaponHolders); + } + if (message.playersAlive !== 0) { + writer.uint32(48).uint32(message.playersAlive); + } + if (message.playersDead !== 0) { + writer.uint32(56).uint32(message.playersDead); + } + if (message.playersConnected !== 0) { + writer.uint32(64).uint32(message.playersConnected); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GlobalEntityCount { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGlobalEntityCount(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.unitsAlive = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.unitsDead = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.groups = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.vehicles = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.weaponHolders = reader.uint32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.playersAlive = reader.uint32(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.playersDead = reader.uint32(); + continue; + } + case 8: { + if (tag !== 64) { + break; + } + + message.playersConnected = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GlobalEntityCount { + return { + unitsAlive: isSet(object.unitsAlive) + ? globalThis.Number(object.unitsAlive) + : isSet(object.units_alive) + ? globalThis.Number(object.units_alive) + : 0, + unitsDead: isSet(object.unitsDead) + ? globalThis.Number(object.unitsDead) + : isSet(object.units_dead) + ? globalThis.Number(object.units_dead) + : 0, + groups: isSet(object.groups) ? globalThis.Number(object.groups) : 0, + vehicles: isSet(object.vehicles) ? globalThis.Number(object.vehicles) : 0, + weaponHolders: isSet(object.weaponHolders) + ? globalThis.Number(object.weaponHolders) + : isSet(object.weapon_holders) + ? globalThis.Number(object.weapon_holders) + : 0, + playersAlive: isSet(object.playersAlive) + ? globalThis.Number(object.playersAlive) + : isSet(object.players_alive) + ? globalThis.Number(object.players_alive) + : 0, + playersDead: isSet(object.playersDead) + ? globalThis.Number(object.playersDead) + : isSet(object.players_dead) + ? globalThis.Number(object.players_dead) + : 0, + playersConnected: isSet(object.playersConnected) + ? globalThis.Number(object.playersConnected) + : isSet(object.players_connected) + ? globalThis.Number(object.players_connected) + : 0, + }; + }, + + toJSON(message: GlobalEntityCount): unknown { + const obj: any = {}; + if (message.unitsAlive !== 0) { + obj.unitsAlive = Math.round(message.unitsAlive); + } + if (message.unitsDead !== 0) { + obj.unitsDead = Math.round(message.unitsDead); + } + if (message.groups !== 0) { + obj.groups = Math.round(message.groups); + } + if (message.vehicles !== 0) { + obj.vehicles = Math.round(message.vehicles); + } + if (message.weaponHolders !== 0) { + obj.weaponHolders = Math.round(message.weaponHolders); + } + if (message.playersAlive !== 0) { + obj.playersAlive = Math.round(message.playersAlive); + } + if (message.playersDead !== 0) { + obj.playersDead = Math.round(message.playersDead); + } + if (message.playersConnected !== 0) { + obj.playersConnected = Math.round(message.playersConnected); + } + return obj; + }, + + create, I>>(base?: I): GlobalEntityCount { + return GlobalEntityCount.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GlobalEntityCount { + const message = createBaseGlobalEntityCount(); + message.unitsAlive = object.unitsAlive ?? 0; + message.unitsDead = object.unitsDead ?? 0; + message.groups = object.groups ?? 0; + message.vehicles = object.vehicles ?? 0; + message.weaponHolders = object.weaponHolders ?? 0; + message.playersAlive = object.playersAlive ?? 0; + message.playersDead = object.playersDead ?? 0; + message.playersConnected = object.playersConnected ?? 0; + return message; + }, +}; + +function createBaseScriptCounts(): ScriptCounts { + return { spawn: 0, execVm: 0, exec: 0, execFsm: 0, pfh: 0 }; +} + +export const ScriptCounts: MessageFns = { + encode(message: ScriptCounts, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.spawn !== 0) { + writer.uint32(8).uint32(message.spawn); + } + if (message.execVm !== 0) { + writer.uint32(16).uint32(message.execVm); + } + if (message.exec !== 0) { + writer.uint32(24).uint32(message.exec); + } + if (message.execFsm !== 0) { + writer.uint32(32).uint32(message.execFsm); + } + if (message.pfh !== 0) { + writer.uint32(40).uint32(message.pfh); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ScriptCounts { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseScriptCounts(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.spawn = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.execVm = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.exec = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.execFsm = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.pfh = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ScriptCounts { + return { + spawn: isSet(object.spawn) ? globalThis.Number(object.spawn) : 0, + execVm: isSet(object.execVm) + ? globalThis.Number(object.execVm) + : isSet(object.exec_vm) + ? globalThis.Number(object.exec_vm) + : 0, + exec: isSet(object.exec) ? globalThis.Number(object.exec) : 0, + execFsm: isSet(object.execFsm) + ? globalThis.Number(object.execFsm) + : isSet(object.exec_fsm) + ? globalThis.Number(object.exec_fsm) + : 0, + pfh: isSet(object.pfh) ? globalThis.Number(object.pfh) : 0, + }; + }, + + toJSON(message: ScriptCounts): unknown { + const obj: any = {}; + if (message.spawn !== 0) { + obj.spawn = Math.round(message.spawn); + } + if (message.execVm !== 0) { + obj.execVm = Math.round(message.execVm); + } + if (message.exec !== 0) { + obj.exec = Math.round(message.exec); + } + if (message.execFsm !== 0) { + obj.execFsm = Math.round(message.execFsm); + } + if (message.pfh !== 0) { + obj.pfh = Math.round(message.pfh); + } + return obj; + }, + + create, I>>(base?: I): ScriptCounts { + return ScriptCounts.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ScriptCounts { + const message = createBaseScriptCounts(); + message.spawn = object.spawn ?? 0; + message.execVm = object.execVm ?? 0; + message.exec = object.exec ?? 0; + message.execFsm = object.execFsm ?? 0; + message.pfh = object.pfh ?? 0; + return message; + }, +}; + +function createBaseWeatherData(): WeatherData { + return { + fog: 0, + overcast: 0, + rain: 0, + humidity: 0, + waves: 0, + windDir: 0, + windStr: 0, + gusts: 0, + lightnings: 0, + moonIntensity: 0, + moonPhase: 0, + sunOrMoon: 0, + }; +} + +export const WeatherData: MessageFns = { + encode(message: WeatherData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.fog !== 0) { + writer.uint32(13).float(message.fog); + } + if (message.overcast !== 0) { + writer.uint32(21).float(message.overcast); + } + if (message.rain !== 0) { + writer.uint32(29).float(message.rain); + } + if (message.humidity !== 0) { + writer.uint32(37).float(message.humidity); + } + if (message.waves !== 0) { + writer.uint32(45).float(message.waves); + } + if (message.windDir !== 0) { + writer.uint32(53).float(message.windDir); + } + if (message.windStr !== 0) { + writer.uint32(61).float(message.windStr); + } + if (message.gusts !== 0) { + writer.uint32(69).float(message.gusts); + } + if (message.lightnings !== 0) { + writer.uint32(77).float(message.lightnings); + } + if (message.moonIntensity !== 0) { + writer.uint32(85).float(message.moonIntensity); + } + if (message.moonPhase !== 0) { + writer.uint32(93).float(message.moonPhase); + } + if (message.sunOrMoon !== 0) { + writer.uint32(101).float(message.sunOrMoon); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): WeatherData { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseWeatherData(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 13) { + break; + } + + message.fog = reader.float(); + continue; + } + case 2: { + if (tag !== 21) { + break; + } + + message.overcast = reader.float(); + continue; + } + case 3: { + if (tag !== 29) { + break; + } + + message.rain = reader.float(); + continue; + } + case 4: { + if (tag !== 37) { + break; + } + + message.humidity = reader.float(); + continue; + } + case 5: { + if (tag !== 45) { + break; + } + + message.waves = reader.float(); + continue; + } + case 6: { + if (tag !== 53) { + break; + } + + message.windDir = reader.float(); + continue; + } + case 7: { + if (tag !== 61) { + break; + } + + message.windStr = reader.float(); + continue; + } + case 8: { + if (tag !== 69) { + break; + } + + message.gusts = reader.float(); + continue; + } + case 9: { + if (tag !== 77) { + break; + } + + message.lightnings = reader.float(); + continue; + } + case 10: { + if (tag !== 85) { + break; + } + + message.moonIntensity = reader.float(); + continue; + } + case 11: { + if (tag !== 93) { + break; + } + + message.moonPhase = reader.float(); + continue; + } + case 12: { + if (tag !== 101) { + break; + } + + message.sunOrMoon = reader.float(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): WeatherData { + return { + fog: isSet(object.fog) ? globalThis.Number(object.fog) : 0, + overcast: isSet(object.overcast) ? globalThis.Number(object.overcast) : 0, + rain: isSet(object.rain) ? globalThis.Number(object.rain) : 0, + humidity: isSet(object.humidity) ? globalThis.Number(object.humidity) : 0, + waves: isSet(object.waves) ? globalThis.Number(object.waves) : 0, + windDir: isSet(object.windDir) + ? globalThis.Number(object.windDir) + : isSet(object.wind_dir) + ? globalThis.Number(object.wind_dir) + : 0, + windStr: isSet(object.windStr) + ? globalThis.Number(object.windStr) + : isSet(object.wind_str) + ? globalThis.Number(object.wind_str) + : 0, + gusts: isSet(object.gusts) ? globalThis.Number(object.gusts) : 0, + lightnings: isSet(object.lightnings) ? globalThis.Number(object.lightnings) : 0, + moonIntensity: isSet(object.moonIntensity) + ? globalThis.Number(object.moonIntensity) + : isSet(object.moon_intensity) + ? globalThis.Number(object.moon_intensity) + : 0, + moonPhase: isSet(object.moonPhase) + ? globalThis.Number(object.moonPhase) + : isSet(object.moon_phase) + ? globalThis.Number(object.moon_phase) + : 0, + sunOrMoon: isSet(object.sunOrMoon) + ? globalThis.Number(object.sunOrMoon) + : isSet(object.sun_or_moon) + ? globalThis.Number(object.sun_or_moon) + : 0, + }; + }, + + toJSON(message: WeatherData): unknown { + const obj: any = {}; + if (message.fog !== 0) { + obj.fog = message.fog; + } + if (message.overcast !== 0) { + obj.overcast = message.overcast; + } + if (message.rain !== 0) { + obj.rain = message.rain; + } + if (message.humidity !== 0) { + obj.humidity = message.humidity; + } + if (message.waves !== 0) { + obj.waves = message.waves; + } + if (message.windDir !== 0) { + obj.windDir = message.windDir; + } + if (message.windStr !== 0) { + obj.windStr = message.windStr; + } + if (message.gusts !== 0) { + obj.gusts = message.gusts; + } + if (message.lightnings !== 0) { + obj.lightnings = message.lightnings; + } + if (message.moonIntensity !== 0) { + obj.moonIntensity = message.moonIntensity; + } + if (message.moonPhase !== 0) { + obj.moonPhase = message.moonPhase; + } + if (message.sunOrMoon !== 0) { + obj.sunOrMoon = message.sunOrMoon; + } + return obj; + }, + + create, I>>(base?: I): WeatherData { + return WeatherData.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): WeatherData { + const message = createBaseWeatherData(); + message.fog = object.fog ?? 0; + message.overcast = object.overcast ?? 0; + message.rain = object.rain ?? 0; + message.humidity = object.humidity ?? 0; + message.waves = object.waves ?? 0; + message.windDir = object.windDir ?? 0; + message.windStr = object.windStr ?? 0; + message.gusts = object.gusts ?? 0; + message.lightnings = object.lightnings ?? 0; + message.moonIntensity = object.moonIntensity ?? 0; + message.moonPhase = object.moonPhase ?? 0; + message.sunOrMoon = object.sunOrMoon ?? 0; + return message; + }, +}; + +function createBasePlayerNetworkData(): PlayerNetworkData { + return { uid: "", name: "", ping: 0, bw: 0, desync: 0 }; +} + +export const PlayerNetworkData: MessageFns = { + encode(message: PlayerNetworkData, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.uid !== "") { + writer.uint32(10).string(message.uid); + } + if (message.name !== "") { + writer.uint32(18).string(message.name); + } + if (message.ping !== 0) { + writer.uint32(29).float(message.ping); + } + if (message.bw !== 0) { + writer.uint32(37).float(message.bw); + } + if (message.desync !== 0) { + writer.uint32(45).float(message.desync); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): PlayerNetworkData { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePlayerNetworkData(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.uid = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.name = reader.string(); + continue; + } + case 3: { + if (tag !== 29) { + break; + } + + message.ping = reader.float(); + continue; + } + case 4: { + if (tag !== 37) { + break; + } + + message.bw = reader.float(); + continue; + } + case 5: { + if (tag !== 45) { + break; + } + + message.desync = reader.float(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): PlayerNetworkData { + return { + uid: isSet(object.uid) ? globalThis.String(object.uid) : "", + name: isSet(object.name) ? globalThis.String(object.name) : "", + ping: isSet(object.ping) ? globalThis.Number(object.ping) : 0, + bw: isSet(object.bw) ? globalThis.Number(object.bw) : 0, + desync: isSet(object.desync) ? globalThis.Number(object.desync) : 0, + }; + }, + + toJSON(message: PlayerNetworkData): unknown { + const obj: any = {}; + if (message.uid !== "") { + obj.uid = message.uid; + } + if (message.name !== "") { + obj.name = message.name; + } + if (message.ping !== 0) { + obj.ping = message.ping; + } + if (message.bw !== 0) { + obj.bw = message.bw; + } + if (message.desync !== 0) { + obj.desync = message.desync; + } + return obj; + }, + + create, I>>(base?: I): PlayerNetworkData { + return PlayerNetworkData.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): PlayerNetworkData { + const message = createBasePlayerNetworkData(); + message.uid = object.uid ?? ""; + message.name = object.name ?? ""; + message.ping = object.ping ?? 0; + message.bw = object.bw ?? 0; + message.desync = object.desync ?? 0; + return message; + }, +}; + +function createBaseGeneralEvent(): GeneralEvent { + return { name: "", message: "" }; +} + +export const GeneralEvent: MessageFns = { + encode(message: GeneralEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.name !== "") { + writer.uint32(10).string(message.name); + } + if (message.message !== "") { + writer.uint32(18).string(message.message); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): GeneralEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGeneralEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.name = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.message = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): GeneralEvent { + return { + name: isSet(object.name) ? globalThis.String(object.name) : "", + message: isSet(object.message) ? globalThis.String(object.message) : "", + }; + }, + + toJSON(message: GeneralEvent): unknown { + const obj: any = {}; + if (message.name !== "") { + obj.name = message.name; + } + if (message.message !== "") { + obj.message = message.message; + } + return obj; + }, + + create, I>>(base?: I): GeneralEvent { + return GeneralEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): GeneralEvent { + const message = createBaseGeneralEvent(); + message.name = object.name ?? ""; + message.message = object.message ?? ""; + return message; + }, +}; + +function createBaseConnectEvent(): ConnectEvent { + return { unitName: "", isConnect: false }; +} + +export const ConnectEvent: MessageFns = { + encode(message: ConnectEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.unitName !== "") { + writer.uint32(10).string(message.unitName); + } + if (message.isConnect !== false) { + writer.uint32(16).bool(message.isConnect); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): ConnectEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseConnectEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.unitName = reader.string(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.isConnect = reader.bool(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): ConnectEvent { + return { + unitName: isSet(object.unitName) + ? globalThis.String(object.unitName) + : isSet(object.unit_name) + ? globalThis.String(object.unit_name) + : "", + isConnect: isSet(object.isConnect) + ? globalThis.Boolean(object.isConnect) + : isSet(object.is_connect) + ? globalThis.Boolean(object.is_connect) + : false, + }; + }, + + toJSON(message: ConnectEvent): unknown { + const obj: any = {}; + if (message.unitName !== "") { + obj.unitName = message.unitName; + } + if (message.isConnect !== false) { + obj.isConnect = message.isConnect; + } + return obj; + }, + + create, I>>(base?: I): ConnectEvent { + return ConnectEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): ConnectEvent { + const message = createBaseConnectEvent(); + message.unitName = object.unitName ?? ""; + message.isConnect = object.isConnect ?? false; + return message; + }, +}; + +function createBaseEndMissionEvent(): EndMissionEvent { + return { side: "", message: "" }; +} + +export const EndMissionEvent: MessageFns = { + encode(message: EndMissionEvent, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.side !== "") { + writer.uint32(10).string(message.side); + } + if (message.message !== "") { + writer.uint32(18).string(message.message); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): EndMissionEvent { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEndMissionEvent(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.side = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.message = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): EndMissionEvent { + return { + side: isSet(object.side) ? globalThis.String(object.side) : "", + message: isSet(object.message) ? globalThis.String(object.message) : "", + }; + }, + + toJSON(message: EndMissionEvent): unknown { + const obj: any = {}; + if (message.side !== "") { + obj.side = message.side; + } + if (message.message !== "") { + obj.message = message.message; + } + return obj; + }, + + create, I>>(base?: I): EndMissionEvent { + return EndMissionEvent.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): EndMissionEvent { + const message = createBaseEndMissionEvent(); + message.side = object.side ?? ""; + message.message = object.message ?? ""; + return message; + }, +}; + +function createBaseChunk(): Chunk { + return { index: 0, startFrame: 0, frameCount: 0, frames: [] }; +} + +export const Chunk: MessageFns = { + encode(message: Chunk, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.index !== 0) { + writer.uint32(8).uint32(message.index); + } + if (message.startFrame !== 0) { + writer.uint32(16).uint32(message.startFrame); + } + if (message.frameCount !== 0) { + writer.uint32(24).uint32(message.frameCount); + } + for (const v of message.frames) { + Frame.encode(v!, writer.uint32(34).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Chunk { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseChunk(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.index = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.startFrame = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.frameCount = reader.uint32(); + continue; + } + case 4: { + if (tag !== 34) { + break; + } + + message.frames.push(Frame.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Chunk { + return { + index: isSet(object.index) ? globalThis.Number(object.index) : 0, + startFrame: isSet(object.startFrame) + ? globalThis.Number(object.startFrame) + : isSet(object.start_frame) + ? globalThis.Number(object.start_frame) + : 0, + frameCount: isSet(object.frameCount) + ? globalThis.Number(object.frameCount) + : isSet(object.frame_count) + ? globalThis.Number(object.frame_count) + : 0, + frames: globalThis.Array.isArray(object?.frames) ? object.frames.map((e: any) => Frame.fromJSON(e)) : [], + }; + }, + + toJSON(message: Chunk): unknown { + const obj: any = {}; + if (message.index !== 0) { + obj.index = Math.round(message.index); + } + if (message.startFrame !== 0) { + obj.startFrame = Math.round(message.startFrame); + } + if (message.frameCount !== 0) { + obj.frameCount = Math.round(message.frameCount); + } + if (message.frames?.length) { + obj.frames = message.frames.map((e) => Frame.toJSON(e)); + } + return obj; + }, + + create, I>>(base?: I): Chunk { + return Chunk.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Chunk { + const message = createBaseChunk(); + message.index = object.index ?? 0; + message.startFrame = object.startFrame ?? 0; + message.frameCount = object.frameCount ?? 0; + message.frames = object.frames?.map((e) => Frame.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseFrame(): Frame { + return { frameNum: 0, soldiers: [], vehicles: [] }; +} + +export const Frame: MessageFns = { + encode(message: Frame, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.frameNum !== 0) { + writer.uint32(8).uint32(message.frameNum); + } + for (const v of message.soldiers) { + SoldierState.encode(v!, writer.uint32(18).fork()).join(); + } + for (const v of message.vehicles) { + VehicleState.encode(v!, writer.uint32(26).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): Frame { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseFrame(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.frameNum = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.soldiers.push(SoldierState.decode(reader, reader.uint32())); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.vehicles.push(VehicleState.decode(reader, reader.uint32())); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): Frame { + return { + frameNum: isSet(object.frameNum) + ? globalThis.Number(object.frameNum) + : isSet(object.frame_num) + ? globalThis.Number(object.frame_num) + : 0, + soldiers: globalThis.Array.isArray(object?.soldiers) + ? object.soldiers.map((e: any) => SoldierState.fromJSON(e)) + : [], + vehicles: globalThis.Array.isArray(object?.vehicles) + ? object.vehicles.map((e: any) => VehicleState.fromJSON(e)) + : [], + }; + }, + + toJSON(message: Frame): unknown { + const obj: any = {}; + if (message.frameNum !== 0) { + obj.frameNum = Math.round(message.frameNum); + } + if (message.soldiers?.length) { + obj.soldiers = message.soldiers.map((e) => SoldierState.toJSON(e)); + } + if (message.vehicles?.length) { + obj.vehicles = message.vehicles.map((e) => VehicleState.toJSON(e)); + } + return obj; + }, + + create, I>>(base?: I): Frame { + return Frame.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): Frame { + const message = createBaseFrame(); + message.frameNum = object.frameNum ?? 0; + message.soldiers = object.soldiers?.map((e) => SoldierState.fromPartial(e)) || []; + message.vehicles = object.vehicles?.map((e) => VehicleState.fromPartial(e)) || []; + return message; + }, +}; + +function createBaseSoldierState(): SoldierState { + return { + id: 0, + position: undefined, + bearing: 0, + lifestate: 0, + vehicleId: 0, + inVehicle: false, + name: "", + isPlayer: false, + groupName: "", + side: "", + role: "", + vehicleRole: "", + stance: "", + hasStableVitals: false, + isDraggedCarried: false, + scores: undefined, + }; +} + +export const SoldierState: MessageFns = { + encode(message: SoldierState, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== 0) { + writer.uint32(8).uint32(message.id); + } + if (message.position !== undefined) { + Position3D.encode(message.position, writer.uint32(18).fork()).join(); + } + if (message.bearing !== 0) { + writer.uint32(24).uint32(message.bearing); + } + if (message.lifestate !== 0) { + writer.uint32(32).uint32(message.lifestate); + } + if (message.vehicleId !== 0) { + writer.uint32(40).uint32(message.vehicleId); + } + if (message.inVehicle !== false) { + writer.uint32(48).bool(message.inVehicle); + } + if (message.name !== "") { + writer.uint32(58).string(message.name); + } + if (message.isPlayer !== false) { + writer.uint32(64).bool(message.isPlayer); + } + if (message.groupName !== "") { + writer.uint32(74).string(message.groupName); + } + if (message.side !== "") { + writer.uint32(82).string(message.side); + } + if (message.role !== "") { + writer.uint32(90).string(message.role); + } + if (message.vehicleRole !== "") { + writer.uint32(98).string(message.vehicleRole); + } + if (message.stance !== "") { + writer.uint32(106).string(message.stance); + } + if (message.hasStableVitals !== false) { + writer.uint32(112).bool(message.hasStableVitals); + } + if (message.isDraggedCarried !== false) { + writer.uint32(120).bool(message.isDraggedCarried); + } + if (message.scores !== undefined) { + SoldierScores.encode(message.scores, writer.uint32(130).fork()).join(); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SoldierState { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSoldierState(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.id = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.position = Position3D.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.bearing = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.lifestate = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.vehicleId = reader.uint32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.inVehicle = reader.bool(); + continue; + } + case 7: { + if (tag !== 58) { + break; + } + + message.name = reader.string(); + continue; + } + case 8: { + if (tag !== 64) { + break; + } + + message.isPlayer = reader.bool(); + continue; + } + case 9: { + if (tag !== 74) { + break; + } + + message.groupName = reader.string(); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.side = reader.string(); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.role = reader.string(); + continue; + } + case 12: { + if (tag !== 98) { + break; + } + + message.vehicleRole = reader.string(); + continue; + } + case 13: { + if (tag !== 106) { + break; + } + + message.stance = reader.string(); + continue; + } + case 14: { + if (tag !== 112) { + break; + } + + message.hasStableVitals = reader.bool(); + continue; + } + case 15: { + if (tag !== 120) { + break; + } + + message.isDraggedCarried = reader.bool(); + continue; + } + case 16: { + if (tag !== 130) { + break; + } + + message.scores = SoldierScores.decode(reader, reader.uint32()); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SoldierState { + return { + id: isSet(object.id) ? globalThis.Number(object.id) : 0, + position: isSet(object.position) ? Position3D.fromJSON(object.position) : undefined, + bearing: isSet(object.bearing) ? globalThis.Number(object.bearing) : 0, + lifestate: isSet(object.lifestate) ? globalThis.Number(object.lifestate) : 0, + vehicleId: isSet(object.vehicleId) + ? globalThis.Number(object.vehicleId) + : isSet(object.vehicle_id) + ? globalThis.Number(object.vehicle_id) + : 0, + inVehicle: isSet(object.inVehicle) + ? globalThis.Boolean(object.inVehicle) + : isSet(object.in_vehicle) + ? globalThis.Boolean(object.in_vehicle) + : false, + name: isSet(object.name) ? globalThis.String(object.name) : "", + isPlayer: isSet(object.isPlayer) + ? globalThis.Boolean(object.isPlayer) + : isSet(object.is_player) + ? globalThis.Boolean(object.is_player) + : false, + groupName: isSet(object.groupName) + ? globalThis.String(object.groupName) + : isSet(object.group_name) + ? globalThis.String(object.group_name) + : "", + side: isSet(object.side) ? globalThis.String(object.side) : "", + role: isSet(object.role) ? globalThis.String(object.role) : "", + vehicleRole: isSet(object.vehicleRole) + ? globalThis.String(object.vehicleRole) + : isSet(object.vehicle_role) + ? globalThis.String(object.vehicle_role) + : "", + stance: isSet(object.stance) ? globalThis.String(object.stance) : "", + hasStableVitals: isSet(object.hasStableVitals) + ? globalThis.Boolean(object.hasStableVitals) + : isSet(object.has_stable_vitals) + ? globalThis.Boolean(object.has_stable_vitals) + : false, + isDraggedCarried: isSet(object.isDraggedCarried) + ? globalThis.Boolean(object.isDraggedCarried) + : isSet(object.is_dragged_carried) + ? globalThis.Boolean(object.is_dragged_carried) + : false, + scores: isSet(object.scores) ? SoldierScores.fromJSON(object.scores) : undefined, + }; + }, + + toJSON(message: SoldierState): unknown { + const obj: any = {}; + if (message.id !== 0) { + obj.id = Math.round(message.id); + } + if (message.position !== undefined) { + obj.position = Position3D.toJSON(message.position); + } + if (message.bearing !== 0) { + obj.bearing = Math.round(message.bearing); + } + if (message.lifestate !== 0) { + obj.lifestate = Math.round(message.lifestate); + } + if (message.vehicleId !== 0) { + obj.vehicleId = Math.round(message.vehicleId); + } + if (message.inVehicle !== false) { + obj.inVehicle = message.inVehicle; + } + if (message.name !== "") { + obj.name = message.name; + } + if (message.isPlayer !== false) { + obj.isPlayer = message.isPlayer; + } + if (message.groupName !== "") { + obj.groupName = message.groupName; + } + if (message.side !== "") { + obj.side = message.side; + } + if (message.role !== "") { + obj.role = message.role; + } + if (message.vehicleRole !== "") { + obj.vehicleRole = message.vehicleRole; + } + if (message.stance !== "") { + obj.stance = message.stance; + } + if (message.hasStableVitals !== false) { + obj.hasStableVitals = message.hasStableVitals; + } + if (message.isDraggedCarried !== false) { + obj.isDraggedCarried = message.isDraggedCarried; + } + if (message.scores !== undefined) { + obj.scores = SoldierScores.toJSON(message.scores); + } + return obj; + }, + + create, I>>(base?: I): SoldierState { + return SoldierState.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SoldierState { + const message = createBaseSoldierState(); + message.id = object.id ?? 0; + message.position = (object.position !== undefined && object.position !== null) + ? Position3D.fromPartial(object.position) + : undefined; + message.bearing = object.bearing ?? 0; + message.lifestate = object.lifestate ?? 0; + message.vehicleId = object.vehicleId ?? 0; + message.inVehicle = object.inVehicle ?? false; + message.name = object.name ?? ""; + message.isPlayer = object.isPlayer ?? false; + message.groupName = object.groupName ?? ""; + message.side = object.side ?? ""; + message.role = object.role ?? ""; + message.vehicleRole = object.vehicleRole ?? ""; + message.stance = object.stance ?? ""; + message.hasStableVitals = object.hasStableVitals ?? false; + message.isDraggedCarried = object.isDraggedCarried ?? false; + message.scores = (object.scores !== undefined && object.scores !== null) + ? SoldierScores.fromPartial(object.scores) + : undefined; + return message; + }, +}; + +function createBaseSoldierScores(): SoldierScores { + return { infantryKills: 0, vehicleKills: 0, armorKills: 0, airKills: 0, deaths: 0, totalScore: 0 }; +} + +export const SoldierScores: MessageFns = { + encode(message: SoldierScores, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.infantryKills !== 0) { + writer.uint32(8).uint32(message.infantryKills); + } + if (message.vehicleKills !== 0) { + writer.uint32(16).uint32(message.vehicleKills); + } + if (message.armorKills !== 0) { + writer.uint32(24).uint32(message.armorKills); + } + if (message.airKills !== 0) { + writer.uint32(32).uint32(message.airKills); + } + if (message.deaths !== 0) { + writer.uint32(40).uint32(message.deaths); + } + if (message.totalScore !== 0) { + writer.uint32(48).uint32(message.totalScore); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): SoldierScores { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseSoldierScores(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.infantryKills = reader.uint32(); + continue; + } + case 2: { + if (tag !== 16) { + break; + } + + message.vehicleKills = reader.uint32(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.armorKills = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.airKills = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.deaths = reader.uint32(); + continue; + } + case 6: { + if (tag !== 48) { + break; + } + + message.totalScore = reader.uint32(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): SoldierScores { + return { + infantryKills: isSet(object.infantryKills) + ? globalThis.Number(object.infantryKills) + : isSet(object.infantry_kills) + ? globalThis.Number(object.infantry_kills) + : 0, + vehicleKills: isSet(object.vehicleKills) + ? globalThis.Number(object.vehicleKills) + : isSet(object.vehicle_kills) + ? globalThis.Number(object.vehicle_kills) + : 0, + armorKills: isSet(object.armorKills) + ? globalThis.Number(object.armorKills) + : isSet(object.armor_kills) + ? globalThis.Number(object.armor_kills) + : 0, + airKills: isSet(object.airKills) + ? globalThis.Number(object.airKills) + : isSet(object.air_kills) + ? globalThis.Number(object.air_kills) + : 0, + deaths: isSet(object.deaths) ? globalThis.Number(object.deaths) : 0, + totalScore: isSet(object.totalScore) + ? globalThis.Number(object.totalScore) + : isSet(object.total_score) + ? globalThis.Number(object.total_score) + : 0, + }; + }, + + toJSON(message: SoldierScores): unknown { + const obj: any = {}; + if (message.infantryKills !== 0) { + obj.infantryKills = Math.round(message.infantryKills); + } + if (message.vehicleKills !== 0) { + obj.vehicleKills = Math.round(message.vehicleKills); + } + if (message.armorKills !== 0) { + obj.armorKills = Math.round(message.armorKills); + } + if (message.airKills !== 0) { + obj.airKills = Math.round(message.airKills); + } + if (message.deaths !== 0) { + obj.deaths = Math.round(message.deaths); + } + if (message.totalScore !== 0) { + obj.totalScore = Math.round(message.totalScore); + } + return obj; + }, + + create, I>>(base?: I): SoldierScores { + return SoldierScores.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): SoldierScores { + const message = createBaseSoldierScores(); + message.infantryKills = object.infantryKills ?? 0; + message.vehicleKills = object.vehicleKills ?? 0; + message.armorKills = object.armorKills ?? 0; + message.airKills = object.airKills ?? 0; + message.deaths = object.deaths ?? 0; + message.totalScore = object.totalScore ?? 0; + return message; + }, +}; + +function createBaseVehicleState(): VehicleState { + return { + id: 0, + position: undefined, + bearing: 0, + alive: false, + crewIds: [], + fuel: 0, + damage: 0, + locked: false, + engineOn: false, + side: "", + turretAzimuth: 0, + turretElevation: 0, + }; +} + +export const VehicleState: MessageFns = { + encode(message: VehicleState, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.id !== 0) { + writer.uint32(8).uint32(message.id); + } + if (message.position !== undefined) { + Position3D.encode(message.position, writer.uint32(18).fork()).join(); + } + if (message.bearing !== 0) { + writer.uint32(24).uint32(message.bearing); + } + if (message.alive !== false) { + writer.uint32(32).bool(message.alive); + } + writer.uint32(42).fork(); + for (const v of message.crewIds) { + writer.uint32(v); + } + writer.join(); + if (message.fuel !== 0) { + writer.uint32(53).float(message.fuel); + } + if (message.damage !== 0) { + writer.uint32(61).float(message.damage); + } + if (message.locked !== false) { + writer.uint32(64).bool(message.locked); + } + if (message.engineOn !== false) { + writer.uint32(72).bool(message.engineOn); + } + if (message.side !== "") { + writer.uint32(82).string(message.side); + } + if (message.turretAzimuth !== 0) { + writer.uint32(93).float(message.turretAzimuth); + } + if (message.turretElevation !== 0) { + writer.uint32(101).float(message.turretElevation); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): VehicleState { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseVehicleState(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.id = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.position = Position3D.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.bearing = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.alive = reader.bool(); + continue; + } + case 5: { + if (tag === 40) { + message.crewIds.push(reader.uint32()); + + continue; + } + + if (tag === 42) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.crewIds.push(reader.uint32()); + } + + continue; + } + + break; + } + case 6: { + if (tag !== 53) { + break; + } + + message.fuel = reader.float(); + continue; + } + case 7: { + if (tag !== 61) { + break; + } + + message.damage = reader.float(); + continue; + } + case 8: { + if (tag !== 64) { + break; + } + + message.locked = reader.bool(); + continue; + } + case 9: { + if (tag !== 72) { + break; + } + + message.engineOn = reader.bool(); + continue; + } + case 10: { + if (tag !== 82) { + break; + } + + message.side = reader.string(); + continue; + } + case 11: { + if (tag !== 93) { + break; + } + + message.turretAzimuth = reader.float(); + continue; + } + case 12: { + if (tag !== 101) { + break; + } + + message.turretElevation = reader.float(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): VehicleState { + return { + id: isSet(object.id) ? globalThis.Number(object.id) : 0, + position: isSet(object.position) ? Position3D.fromJSON(object.position) : undefined, + bearing: isSet(object.bearing) ? globalThis.Number(object.bearing) : 0, + alive: isSet(object.alive) ? globalThis.Boolean(object.alive) : false, + crewIds: globalThis.Array.isArray(object?.crewIds) + ? object.crewIds.map((e: any) => globalThis.Number(e)) + : globalThis.Array.isArray(object?.crew_ids) + ? object.crew_ids.map((e: any) => globalThis.Number(e)) + : [], + fuel: isSet(object.fuel) ? globalThis.Number(object.fuel) : 0, + damage: isSet(object.damage) ? globalThis.Number(object.damage) : 0, + locked: isSet(object.locked) ? globalThis.Boolean(object.locked) : false, + engineOn: isSet(object.engineOn) + ? globalThis.Boolean(object.engineOn) + : isSet(object.engine_on) + ? globalThis.Boolean(object.engine_on) + : false, + side: isSet(object.side) ? globalThis.String(object.side) : "", + turretAzimuth: isSet(object.turretAzimuth) + ? globalThis.Number(object.turretAzimuth) + : isSet(object.turret_azimuth) + ? globalThis.Number(object.turret_azimuth) + : 0, + turretElevation: isSet(object.turretElevation) + ? globalThis.Number(object.turretElevation) + : isSet(object.turret_elevation) + ? globalThis.Number(object.turret_elevation) + : 0, + }; + }, + + toJSON(message: VehicleState): unknown { + const obj: any = {}; + if (message.id !== 0) { + obj.id = Math.round(message.id); + } + if (message.position !== undefined) { + obj.position = Position3D.toJSON(message.position); + } + if (message.bearing !== 0) { + obj.bearing = Math.round(message.bearing); + } + if (message.alive !== false) { + obj.alive = message.alive; + } + if (message.crewIds?.length) { + obj.crewIds = message.crewIds.map((e) => Math.round(e)); + } + if (message.fuel !== 0) { + obj.fuel = message.fuel; + } + if (message.damage !== 0) { + obj.damage = message.damage; + } + if (message.locked !== false) { + obj.locked = message.locked; + } + if (message.engineOn !== false) { + obj.engineOn = message.engineOn; + } + if (message.side !== "") { + obj.side = message.side; + } + if (message.turretAzimuth !== 0) { + obj.turretAzimuth = message.turretAzimuth; + } + if (message.turretElevation !== 0) { + obj.turretElevation = message.turretElevation; + } + return obj; + }, + + create, I>>(base?: I): VehicleState { + return VehicleState.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): VehicleState { + const message = createBaseVehicleState(); + message.id = object.id ?? 0; + message.position = (object.position !== undefined && object.position !== null) + ? Position3D.fromPartial(object.position) + : undefined; + message.bearing = object.bearing ?? 0; + message.alive = object.alive ?? false; + message.crewIds = object.crewIds?.map((e) => e) || []; + message.fuel = object.fuel ?? 0; + message.damage = object.damage ?? 0; + message.locked = object.locked ?? false; + message.engineOn = object.engineOn ?? false; + message.side = object.side ?? ""; + message.turretAzimuth = object.turretAzimuth ?? 0; + message.turretElevation = object.turretElevation ?? 0; + return message; + }, +}; + +function createBaseMarkerDef(): MarkerDef { + return { + type: "", + text: "", + startFrame: 0, + endFrame: 0, + playerId: 0, + color: "", + side: 0, + positions: [], + size: [], + shape: "", + brush: "", + }; +} + +export const MarkerDef: MessageFns = { + encode(message: MarkerDef, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.type !== "") { + writer.uint32(10).string(message.type); + } + if (message.text !== "") { + writer.uint32(18).string(message.text); + } + if (message.startFrame !== 0) { + writer.uint32(24).uint32(message.startFrame); + } + if (message.endFrame !== 0) { + writer.uint32(32).uint32(message.endFrame); + } + if (message.playerId !== 0) { + writer.uint32(40).int32(message.playerId); + } + if (message.color !== "") { + writer.uint32(50).string(message.color); + } + if (message.side !== 0) { + writer.uint32(56).int32(message.side); + } + for (const v of message.positions) { + MarkerPosition.encode(v!, writer.uint32(66).fork()).join(); + } + writer.uint32(74).fork(); + for (const v of message.size) { + writer.float(v); + } + writer.join(); + if (message.shape !== "") { + writer.uint32(82).string(message.shape); + } + if (message.brush !== "") { + writer.uint32(90).string(message.brush); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): MarkerDef { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMarkerDef(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 10) { + break; + } + + message.type = reader.string(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.text = reader.string(); + continue; + } + case 3: { + if (tag !== 24) { + break; + } + + message.startFrame = reader.uint32(); + continue; + } + case 4: { + if (tag !== 32) { + break; + } + + message.endFrame = reader.uint32(); + continue; + } + case 5: { + if (tag !== 40) { + break; + } + + message.playerId = reader.int32(); + continue; + } + case 6: { + if (tag !== 50) { + break; + } + + message.color = reader.string(); + continue; + } + case 7: { + if (tag !== 56) { + break; + } + + message.side = reader.int32() as any; + continue; + } + case 8: { + if (tag !== 66) { + break; + } + + message.positions.push(MarkerPosition.decode(reader, reader.uint32())); + continue; + } + case 9: { + if (tag === 77) { + message.size.push(reader.float()); + + continue; + } + + if (tag === 74) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.size.push(reader.float()); + } + + continue; + } + + break; + } + case 10: { + if (tag !== 82) { + break; + } + + message.shape = reader.string(); + continue; + } + case 11: { + if (tag !== 90) { + break; + } + + message.brush = reader.string(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): MarkerDef { + return { + type: isSet(object.type) ? globalThis.String(object.type) : "", + text: isSet(object.text) ? globalThis.String(object.text) : "", + startFrame: isSet(object.startFrame) + ? globalThis.Number(object.startFrame) + : isSet(object.start_frame) + ? globalThis.Number(object.start_frame) + : 0, + endFrame: isSet(object.endFrame) + ? globalThis.Number(object.endFrame) + : isSet(object.end_frame) + ? globalThis.Number(object.end_frame) + : 0, + playerId: isSet(object.playerId) + ? globalThis.Number(object.playerId) + : isSet(object.player_id) + ? globalThis.Number(object.player_id) + : 0, + color: isSet(object.color) ? globalThis.String(object.color) : "", + side: isSet(object.side) ? sideFromJSON(object.side) : 0, + positions: globalThis.Array.isArray(object?.positions) + ? object.positions.map((e: any) => MarkerPosition.fromJSON(e)) + : [], + size: globalThis.Array.isArray(object?.size) + ? object.size.map((e: any) => globalThis.Number(e)) + : [], + shape: isSet(object.shape) ? globalThis.String(object.shape) : "", + brush: isSet(object.brush) ? globalThis.String(object.brush) : "", + }; + }, + + toJSON(message: MarkerDef): unknown { + const obj: any = {}; + if (message.type !== "") { + obj.type = message.type; + } + if (message.text !== "") { + obj.text = message.text; + } + if (message.startFrame !== 0) { + obj.startFrame = Math.round(message.startFrame); + } + if (message.endFrame !== 0) { + obj.endFrame = Math.round(message.endFrame); + } + if (message.playerId !== 0) { + obj.playerId = Math.round(message.playerId); + } + if (message.color !== "") { + obj.color = message.color; + } + if (message.side !== 0) { + obj.side = sideToJSON(message.side); + } + if (message.positions?.length) { + obj.positions = message.positions.map((e) => MarkerPosition.toJSON(e)); + } + if (message.size?.length) { + obj.size = message.size; + } + if (message.shape !== "") { + obj.shape = message.shape; + } + if (message.brush !== "") { + obj.brush = message.brush; + } + return obj; + }, + + create, I>>(base?: I): MarkerDef { + return MarkerDef.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): MarkerDef { + const message = createBaseMarkerDef(); + message.type = object.type ?? ""; + message.text = object.text ?? ""; + message.startFrame = object.startFrame ?? 0; + message.endFrame = object.endFrame ?? 0; + message.playerId = object.playerId ?? 0; + message.color = object.color ?? ""; + message.side = object.side ?? 0; + message.positions = object.positions?.map((e) => MarkerPosition.fromPartial(e)) || []; + message.size = object.size?.map((e) => e) || []; + message.shape = object.shape ?? ""; + message.brush = object.brush ?? ""; + return message; + }, +}; + +function createBaseMarkerPosition(): MarkerPosition { + return { frameNum: 0, position: undefined, direction: 0, alpha: 0, lineCoords: [] }; +} + +export const MarkerPosition: MessageFns = { + encode(message: MarkerPosition, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.frameNum !== 0) { + writer.uint32(8).uint32(message.frameNum); + } + if (message.position !== undefined) { + Position3D.encode(message.position, writer.uint32(18).fork()).join(); + } + if (message.direction !== 0) { + writer.uint32(29).float(message.direction); + } + if (message.alpha !== 0) { + writer.uint32(37).float(message.alpha); + } + writer.uint32(42).fork(); + for (const v of message.lineCoords) { + writer.float(v); + } + writer.join(); + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): MarkerPosition { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMarkerPosition(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.frameNum = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.position = Position3D.decode(reader, reader.uint32()); + continue; + } + case 3: { + if (tag !== 29) { + break; + } + + message.direction = reader.float(); + continue; + } + case 4: { + if (tag !== 37) { + break; + } + + message.alpha = reader.float(); + continue; + } + case 5: { + if (tag === 45) { + message.lineCoords.push(reader.float()); + + continue; + } + + if (tag === 42) { + const end2 = reader.uint32() + reader.pos; + while (reader.pos < end2) { + message.lineCoords.push(reader.float()); + } + + continue; + } + + break; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): MarkerPosition { + return { + frameNum: isSet(object.frameNum) + ? globalThis.Number(object.frameNum) + : isSet(object.frame_num) + ? globalThis.Number(object.frame_num) + : 0, + position: isSet(object.position) ? Position3D.fromJSON(object.position) : undefined, + direction: isSet(object.direction) ? globalThis.Number(object.direction) : 0, + alpha: isSet(object.alpha) ? globalThis.Number(object.alpha) : 0, + lineCoords: globalThis.Array.isArray(object?.lineCoords) + ? object.lineCoords.map((e: any) => globalThis.Number(e)) + : globalThis.Array.isArray(object?.line_coords) + ? object.line_coords.map((e: any) => globalThis.Number(e)) + : [], + }; + }, + + toJSON(message: MarkerPosition): unknown { + const obj: any = {}; + if (message.frameNum !== 0) { + obj.frameNum = Math.round(message.frameNum); + } + if (message.position !== undefined) { + obj.position = Position3D.toJSON(message.position); + } + if (message.direction !== 0) { + obj.direction = message.direction; + } + if (message.alpha !== 0) { + obj.alpha = message.alpha; + } + if (message.lineCoords?.length) { + obj.lineCoords = message.lineCoords; + } + return obj; + }, + + create, I>>(base?: I): MarkerPosition { + return MarkerPosition.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): MarkerPosition { + const message = createBaseMarkerPosition(); + message.frameNum = object.frameNum ?? 0; + message.position = (object.position !== undefined && object.position !== null) + ? Position3D.fromPartial(object.position) + : undefined; + message.direction = object.direction ?? 0; + message.alpha = object.alpha ?? 0; + message.lineCoords = object.lineCoords?.map((e) => e) || []; + return message; + }, +}; + +function createBaseTimeSample(): TimeSample { + return { frameNum: 0, systemTimeUtc: "", date: "", timeMultiplier: 0, time: 0 }; +} + +export const TimeSample: MessageFns = { + encode(message: TimeSample, writer: BinaryWriter = new BinaryWriter()): BinaryWriter { + if (message.frameNum !== 0) { + writer.uint32(8).uint32(message.frameNum); + } + if (message.systemTimeUtc !== "") { + writer.uint32(18).string(message.systemTimeUtc); + } + if (message.date !== "") { + writer.uint32(26).string(message.date); + } + if (message.timeMultiplier !== 0) { + writer.uint32(37).float(message.timeMultiplier); + } + if (message.time !== 0) { + writer.uint32(45).float(message.time); + } + return writer; + }, + + decode(input: BinaryReader | Uint8Array, length?: number): TimeSample { + const reader = input instanceof BinaryReader ? input : new BinaryReader(input); + const end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTimeSample(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (tag !== 8) { + break; + } + + message.frameNum = reader.uint32(); + continue; + } + case 2: { + if (tag !== 18) { + break; + } + + message.systemTimeUtc = reader.string(); + continue; + } + case 3: { + if (tag !== 26) { + break; + } + + message.date = reader.string(); + continue; + } + case 4: { + if (tag !== 37) { + break; + } + + message.timeMultiplier = reader.float(); + continue; + } + case 5: { + if (tag !== 45) { + break; + } + + message.time = reader.float(); + continue; + } + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skip(tag & 7); + } + return message; + }, + + fromJSON(object: any): TimeSample { + return { + frameNum: isSet(object.frameNum) + ? globalThis.Number(object.frameNum) + : isSet(object.frame_num) + ? globalThis.Number(object.frame_num) + : 0, + systemTimeUtc: isSet(object.systemTimeUtc) + ? globalThis.String(object.systemTimeUtc) + : isSet(object.system_time_utc) + ? globalThis.String(object.system_time_utc) + : "", + date: isSet(object.date) ? globalThis.String(object.date) : "", + timeMultiplier: isSet(object.timeMultiplier) + ? globalThis.Number(object.timeMultiplier) + : isSet(object.time_multiplier) + ? globalThis.Number(object.time_multiplier) + : 0, + time: isSet(object.time) ? globalThis.Number(object.time) : 0, + }; + }, + + toJSON(message: TimeSample): unknown { + const obj: any = {}; + if (message.frameNum !== 0) { + obj.frameNum = Math.round(message.frameNum); + } + if (message.systemTimeUtc !== "") { + obj.systemTimeUtc = message.systemTimeUtc; + } + if (message.date !== "") { + obj.date = message.date; + } + if (message.timeMultiplier !== 0) { + obj.timeMultiplier = message.timeMultiplier; + } + if (message.time !== 0) { + obj.time = message.time; + } + return obj; + }, + + create, I>>(base?: I): TimeSample { + return TimeSample.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): TimeSample { + const message = createBaseTimeSample(); + message.frameNum = object.frameNum ?? 0; + message.systemTimeUtc = object.systemTimeUtc ?? ""; + message.date = object.date ?? ""; + message.timeMultiplier = object.timeMultiplier ?? 0; + message.time = object.time ?? 0; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin ? T + : T extends globalThis.Array ? globalThis.Array> + : T extends ReadonlyArray ? ReadonlyArray> + : T extends {} ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} + +export interface MessageFns { + encode(message: T, writer?: BinaryWriter): BinaryWriter; + decode(input: BinaryReader | Uint8Array, length?: number): T; + fromJSON(object: any): T; + toJSON(message: T): unknown; + create, I>>(base?: I): T; + fromPartial, I>>(object: I): T; +} diff --git a/ui/src/data/decoders/protobuf-decoder-v2.ts b/ui/src/data/decoders/protobuf-decoder-v2.ts new file mode 100644 index 000000000..009330b0c --- /dev/null +++ b/ui/src/data/decoders/protobuf-decoder-v2.ts @@ -0,0 +1,408 @@ +import type { ArmaCoord } from "../../utils/coordinates"; +import type { + AliveState, + ChunkData, + EntityDef as AppEntityDef, + EntityState as AppEntityState, + EntityType, + EventDef, + Manifest as AppManifest, + MarkerDef as AppMarkerDef, + Side, + SoldierScores, + TelemetrySample, +} from "../types"; +import type { DecoderStrategy } from "./decoder.interface"; +import { + Manifest as PbManifest, + Chunk as PbChunk, + Side as PbSide, + type SoldierDef as PbSoldierDef, + type VehicleDef as PbVehicleDef, + type SoldierState as PbSoldierState, + type VehicleState as PbVehicleState, + type Event as PbEvent, + type MarkerDef as PbMarkerDef, + type MarkerPosition as PbMarkerPosition, + type Position3D as PbPosition3D, +} from "./generated/v2/ocap_v2"; + +// ───────── Enum mapping ───────── + +const SIDE_MAP: Record = { + [PbSide.SIDE_WEST]: "WEST", + [PbSide.SIDE_EAST]: "EAST", + [PbSide.SIDE_GUER]: "GUER", + [PbSide.SIDE_CIV]: "CIV", +}; + +const MARKER_SIDE_MAP: Record = { + [PbSide.SIDE_UNKNOWN]: "GLOBAL", // markers without a side are visible to all + ...SIDE_MAP, + [PbSide.SIDE_GLOBAL]: "GLOBAL", +}; + +function mapVehicleClass(vehicleClass: string): EntityType { + switch (vehicleClass) { + case "car": return "car"; + case "tank": return "tank"; + case "apc": return "apc"; + case "truck": return "truck"; + case "sea": return "ship"; + case "heli": return "heli"; + case "plane": return "plane"; + case "parachute": return "parachute"; + case "static-weapon": return "staticWeapon"; + case "static-mortar": return "staticMortar"; + default: return "unknown"; + } +} + +function mapSideString(raw: string): Side { + switch (raw) { + case "WEST": return "WEST"; + case "EAST": return "EAST"; + case "GUER": + case "INDEPENDENT": return "GUER"; + case "CIV": + case "CIVILIAN": return "CIV"; + default: return "CIV"; + } +} + +function posToArma(p: PbPosition3D | undefined): ArmaCoord { + if (!p) return [0, 0, 0]; + return [p.x, p.y, p.z]; +} + +// ───────── Entity conversions ───────── + +function convertSoldierDef(pb: PbSoldierDef): AppEntityDef { + const def: AppEntityDef = { + id: pb.id, + type: "man", + name: pb.name, + side: SIDE_MAP[pb.side] ?? "CIV", + groupName: pb.groupName, + isPlayer: pb.isPlayer, + startFrame: pb.startFrame, + endFrame: pb.endFrame, + }; + if (pb.role) def.role = pb.role; + if (pb.framesFired.length > 0) { + def.framesFired = pb.framesFired.map((ff) => [ + ff.frameNum, + posToArma(ff.endPos), + ]); + } + return def; +} + +function convertVehicleDef(pb: PbVehicleDef): AppEntityDef { + return { + id: pb.id, + type: mapVehicleClass(pb.vehicleClass), + name: pb.name, + side: "CIV", // Vehicles don't have a side in their definition + groupName: "", + isPlayer: false, + startFrame: pb.startFrame, + endFrame: pb.endFrame, + }; +} + +function convertSoldierState(pb: PbSoldierState): AppEntityState { + const state: AppEntityState = { + position: posToArma(pb.position), + direction: pb.bearing, + alive: (pb.lifestate & 0x3) as AliveState, + }; + if (pb.name) state.name = pb.name; + if (pb.vehicleId) state.vehicleId = pb.vehicleId; + if (pb.inVehicle) state.isInVehicle = pb.inVehicle; + if (pb.isPlayer) state.isPlayer = pb.isPlayer; + if (pb.groupName) state.groupName = pb.groupName; + if (pb.side) state.side = mapSideString(pb.side); + // v2 extensions + if (pb.vehicleRole) state.vehicleRole = pb.vehicleRole; + if (pb.stance) state.stance = pb.stance; + if (pb.hasStableVitals) state.hasStableVitals = pb.hasStableVitals; + if (pb.isDraggedCarried) state.isDraggedCarried = pb.isDraggedCarried; + if (pb.scores) { + state.scores = { + infantryKills: pb.scores.infantryKills, + vehicleKills: pb.scores.vehicleKills, + armorKills: pb.scores.armorKills, + airKills: pb.scores.airKills, + deaths: pb.scores.deaths, + totalScore: pb.scores.totalScore, + } satisfies SoldierScores; + } + return state; +} + +function convertVehicleState(pb: PbVehicleState): AppEntityState { + const state: AppEntityState = { + position: posToArma(pb.position), + direction: pb.bearing, + alive: (pb.alive ? 1 : 0) as AliveState, + }; + if (pb.crewIds.length > 0) state.crewIds = [...pb.crewIds]; + if (pb.side) state.side = mapSideString(pb.side); + // v2 extensions + if (pb.fuel !== 0) state.fuel = pb.fuel; + if (pb.damage !== 0) state.damage = pb.damage; + if (pb.locked) state.locked = pb.locked; + if (pb.engineOn) state.engineOn = pb.engineOn; + if (pb.turretAzimuth !== 0) state.turretAzimuth = pb.turretAzimuth; + if (pb.turretElevation !== 0) state.turretElevation = pb.turretElevation; + return state; +} + +// ───────── Event conversions ───────── + +function convertEvent(pb: PbEvent): EventDef | null { + const { frameNum } = pb; + + if (pb.kill) { + const k = pb.kill; + return { + frameNum, + type: "killed", + victimId: k.victimIsVehicle ? k.victimVehicleId : k.victimSoldierId, + causedById: k.killerIsVehicle ? k.killerVehicleId : k.killerSoldierId, + distance: k.distance, + weapon: k.eventText || k.weaponName, + }; + } + if (pb.hit) { + const h = pb.hit; + return { + frameNum, + type: "hit", + victimId: h.victimIsVehicle ? h.victimVehicleId : h.victimSoldierId, + causedById: h.shooterIsVehicle ? h.shooterVehicleId : h.shooterSoldierId, + distance: h.distance, + weapon: h.eventText || h.weaponName, + }; + } + if (pb.connect) { + return { + frameNum, + type: pb.connect.isConnect ? "connected" : "disconnected", + unitName: pb.connect.unitName, + }; + } + if (pb.endMission) { + return { + frameNum, + type: "endMission", + side: pb.endMission.side, + message: pb.endMission.message, + }; + } + if (pb.telemetry) { + // Telemetry is performance/metrics data, not a gameplay event. + // Extracted separately into manifest.telemetry for metrics display. + return null; + } + if (pb.general) { + return { + frameNum, + type: "generalEvent", + message: pb.general.message, + }; + } + if (pb.chat) { + return { + frameNum, + type: "generalEvent", + message: `[${pb.chat.channel}] ${pb.chat.fromName}: ${pb.chat.message}`, + }; + } + return null; +} + +// ───────── Marker conversions ───────── + +function convertMarkerPosition(pb: PbMarkerPosition): [number, ...any] { + if (pb.lineCoords.length > 0) { + return [pb.frameNum, pb.lineCoords, pb.direction, pb.alpha]; + } + const pos = pb.position; + return [ + pb.frameNum, + pos ? pos.x : 0, + pos ? pos.y : 0, + pos ? pos.z : 0, + pb.direction, + pb.alpha, + ]; +} + +function convertMarkerDef(pb: PbMarkerDef): AppMarkerDef { + const positions = pb.positions.map(convertMarkerPosition); + const alpha = pb.positions.length > 0 ? pb.positions[0].alpha : 1; + const side = MARKER_SIDE_MAP[pb.side] ?? String(pb.side); + + const marker: AppMarkerDef = { + shape: (pb.shape || "ICON") as AppMarkerDef["shape"], + type: pb.type, + side, + color: pb.color, + positions, + player: pb.playerId, + alpha, + startFrame: pb.startFrame, + endFrame: pb.endFrame || -1, // 0 (proto default) means "show forever" + }; + if (pb.text) marker.text = pb.text; + if (pb.size.length >= 2) marker.size = [pb.size[0], pb.size[1]]; + if (pb.brush) marker.brush = pb.brush; + return marker; +} + +// ───────── Public decoder ───────── + +export class ProtobufDecoderV2 implements DecoderStrategy { + decodeManifest(buffer: ArrayBuffer): AppManifest { + const pb = PbManifest.decode(new Uint8Array(buffer)); + + // Merge soldier + vehicle defs into a single entities array. + const entities: AppEntityDef[] = [ + ...pb.soldiers.map(convertSoldierDef), + ...pb.vehicles.map(convertVehicleDef), + ]; + + const manifest: AppManifest = { + version: pb.version, + worldName: pb.world?.worldName ?? "", + missionName: pb.mission?.missionName ?? "", + frameCount: pb.frameCount, + chunkSize: pb.chunkSize, + captureDelayMs: pb.captureDelayMs, + chunkCount: pb.chunkCount, + entities, + events: pb.events.map(convertEvent).filter((e): e is EventDef => e !== null), + markers: pb.markers.map(convertMarkerDef), + times: pb.times.map((t) => ({ + frameNum: t.frameNum, + systemTimeUtc: t.systemTimeUtc, + date: t.date || undefined, + timeMultiplier: t.timeMultiplier || undefined, + })), + extensionVersion: pb.mission?.extensionVersion || undefined, + addonVersion: pb.mission?.addonVersion || undefined, + }; + + // Extract telemetry data (separate from gameplay events). + const telemetrySamples: TelemetrySample[] = []; + for (const e of pb.events) { + if (e.telemetry) { + const t = e.telemetry; + telemetrySamples.push({ + frameNum: e.frameNum, + fpsAverage: t.fpsAverage, + fpsMin: t.fpsMin, + globalCounts: t.globalCounts ? { + unitsAlive: t.globalCounts.unitsAlive, + unitsDead: t.globalCounts.unitsDead, + groups: t.globalCounts.groups, + vehicles: t.globalCounts.vehicles, + weaponHolders: t.globalCounts.weaponHolders, + playersAlive: t.globalCounts.playersAlive, + playersDead: t.globalCounts.playersDead, + playersConnected: t.globalCounts.playersConnected, + } : undefined, + weather: t.weather ? { + fog: t.weather.fog, + overcast: t.weather.overcast, + rain: t.weather.rain, + } : undefined, + playerCount: t.players.length, + }); + } + } + if (telemetrySamples.length > 0) { + manifest.telemetry = telemetrySamples; + } + + // v2 world metadata. + if (pb.world) { + manifest.world = { + worldSize: pb.world.worldSize || undefined, + latitude: pb.world.latitude || undefined, + longitude: pb.world.longitude || undefined, + author: pb.world.author || undefined, + displayName: pb.world.displayName || undefined, + }; + } + + // v2 mission metadata. + if (pb.mission) { + manifest.mission = { + serverName: pb.mission.serverName || undefined, + briefingName: pb.mission.briefingName || undefined, + extensionBuild: pb.mission.extensionBuild || undefined, + }; + if (pb.mission.playableSlots) { + manifest.mission.playableSlots = { + west: pb.mission.playableSlots.west, + east: pb.mission.playableSlots.east, + independent: pb.mission.playableSlots.independent, + civilian: pb.mission.playableSlots.civilian, + }; + } + if (pb.mission.sideFriendly) { + manifest.mission.sideFriendly = { + eastWest: pb.mission.sideFriendly.eastWest, + eastIndependent: pb.mission.sideFriendly.eastIndependent, + westIndependent: pb.mission.sideFriendly.westIndependent, + }; + } + if (pb.mission.addons.length > 0) { + manifest.mission.addons = pb.mission.addons.map((a) => ({ + name: a.name, + workshopId: a.workshopId, + })); + } + } + + if (pb.mission?.author) { + manifest.missionAuthor = pb.mission.author; + } + + return manifest; + } + + decodeChunk(buffer: ArrayBuffer): ChunkData { + const pb = PbChunk.decode(new Uint8Array(buffer)); + + const entities = new Map(); + for (const frame of pb.frames) { + const idx = frame.frameNum - pb.startFrame; + + // Process soldier states. + for (const raw of frame.soldiers) { + let arr = entities.get(raw.id); + if (!arr) { + arr = new Array(pb.frameCount); + entities.set(raw.id, arr); + } + arr[idx] = convertSoldierState(raw); + } + + // Process vehicle states. + for (const raw of frame.vehicles) { + let arr = entities.get(raw.id); + if (!arr) { + arr = new Array(pb.frameCount); + entities.set(raw.id, arr); + } + arr[idx] = convertVehicleState(raw); + } + } + + return { entities }; + } +} diff --git a/ui/src/data/types.ts b/ui/src/data/types.ts index b0dcbf462..6e1940a2f 100644 --- a/ui/src/data/types.ts +++ b/ui/src/data/types.ts @@ -55,6 +55,40 @@ export interface EntityState { groupName?: string; /** Per-frame side (may change mid-mission). */ side?: Side; + // v2 soldier extensions + /** Role within vehicle (driver, gunner, commander, cargo). */ + vehicleRole?: string; + /** Stance (STAND, CROUCH, PRONE). */ + stance?: string; + /** Whether soldier has stable vitals (ACE3 medical). */ + hasStableVitals?: boolean; + /** Whether soldier is being dragged/carried. */ + isDraggedCarried?: boolean; + /** Combat scores. */ + scores?: SoldierScores; + // v2 vehicle extensions + /** Vehicle fuel level (0-1). */ + fuel?: number; + /** Vehicle damage level (0-1). */ + damage?: number; + /** Vehicle lock state. */ + locked?: boolean; + /** Vehicle engine state. */ + engineOn?: boolean; + /** Turret horizontal angle. */ + turretAzimuth?: number; + /** Turret vertical angle. */ + turretElevation?: number; +} + +/** Soldier combat scores (v2). */ +export interface SoldierScores { + infantryKills: number; + vehicleKills: number; + armorKills: number; + airKills: number; + deaths: number; + totalScore: number; } // --------------- Event discriminated union --------------- @@ -143,6 +177,46 @@ export interface Manifest { times: Array<{ frameNum: number; systemTimeUtc: string; date?: string; timeMultiplier?: number }>; extensionVersion?: string; addonVersion?: string; + // v2 extensions + telemetry?: TelemetrySample[]; + world?: { + worldSize?: number; + latitude?: number; + longitude?: number; + author?: string; + displayName?: string; + }; + mission?: { + serverName?: string; + briefingName?: string; + extensionBuild?: string; + playableSlots?: { west: number; east: number; independent: number; civilian: number }; + sideFriendly?: { eastWest: boolean; eastIndependent: boolean; westIndependent: boolean }; + addons?: Array<{ name: string; workshopId: string }>; + }; +} + +/** Telemetry sample: FPS + optional entity counts, weather, and player network data. */ +export interface TelemetrySample { + frameNum: number; + fpsAverage: number; + fpsMin: number; + globalCounts?: { + unitsAlive: number; + unitsDead: number; + groups: number; + vehicles: number; + weaponHolders: number; + playersAlive: number; + playersDead: number; + playersConnected: number; + }; + weather?: { + fog: number; + overcast: number; + rain: number; + }; + playerCount?: number; } /** A decoded chunk: entity ID -> array of states for this chunk's frames. */ diff --git a/ui/src/pages/recording-playback/load-operation.ts b/ui/src/pages/recording-playback/load-operation.ts index 735bd59c5..fb74b6fda 100644 --- a/ui/src/pages/recording-playback/load-operation.ts +++ b/ui/src/pages/recording-playback/load-operation.ts @@ -2,6 +2,7 @@ import type { Operation, WorldConfig } from "../../data/types"; import type { ApiClient } from "../../data/api-client"; import { JsonDecoder } from "../../data/decoders/json-decoder"; import { ProtobufDecoder } from "../../data/decoders/protobuf-decoder"; +import { ProtobufDecoderV2 } from "../../data/decoders/protobuf-decoder-v2"; import type { DecoderStrategy } from "../../data/decoders/decoder.interface"; import { ChunkManager } from "../../data/chunk-manager"; import type { PlaybackEngine } from "../../playback/engine"; @@ -34,7 +35,8 @@ export async function loadOperation( let manifest; if (op.storageFormat === "protobuf") { - decoder = new ProtobufDecoder(); + const version = op.schemaVersion ?? 1; + decoder = version >= 2 ? new ProtobufDecoderV2() : new ProtobufDecoder(); const chunkMgr = new ChunkManager(decoder, api); manifest = await chunkMgr.loadManifest(filename); await chunkMgr.loadChunk(0);