package gameserver import ( "context" "encoding/json" "log" "os" "sync" "time" "ananta-ps/pkg/protocol" ) type TrafficVehicle struct { ID uint64 `json:"id"` TemplateID uint32 `json:"templateId"` LaneID uint32 `json:"laneId"` Distance float32 `json:"distance"` Speed float32 `json:"speed"` // meters / sec LaneLength float32 `json:"laneLength"` } type RoadLane struct { ID uint32 `json:"id"` Length float32 `json:"length"` } type RoadNetworkData struct { Lanes []RoadLane `json:"lanes"` } type CityTrafficEngine struct { mu sync.RWMutex vehicles map[uint64]*TrafficVehicle lanes []RoadLane isRunning bool cancel context.CancelFunc server *Server idCounter uint64 maxVehicles int } func NewCityTrafficEngine(srv *Server) *CityTrafficEngine { return &CityTrafficEngine{ vehicles: make(map[uint64]*TrafficVehicle), server: srv, idCounter: 0x7000000000000001, maxVehicles: 20, } } func (te *CityTrafficEngine) LoadRoadNetwork(path string) error { data, err := os.ReadFile(path) if err != nil { // Provide default mock lanes if file is not found te.lanes = []RoadLane{ {ID: 101, Length: 500.0}, {ID: 102, Length: 650.0}, {ID: 103, Length: 800.0}, {ID: 104, Length: 450.0}, } log.Printf("[TRAFFIC] Road network file %s not found, initialized %d fallback road lanes", path, len(te.lanes)) return nil } var netData RoadNetworkData if err := json.Unmarshal(data, &netData); err != nil { return err } te.lanes = netData.Lanes log.Printf("[TRAFFIC] Loaded %d road lanes from %s", len(te.lanes), path) return nil } func (te *CityTrafficEngine) Start(ctx context.Context) { te.mu.Lock() if te.isRunning { te.mu.Unlock() return } ctx, cancel := context.WithCancel(ctx) te.cancel = cancel te.isRunning = true // Seed initial traffic vehicles templatePool := []uint32{1001, 1002, 1003, 1004, 1005} for i := 0; i < te.maxVehicles && i < len(te.lanes)*5; i++ { lane := te.lanes[i%len(te.lanes)] vID := te.idCounter te.idCounter++ te.vehicles[vID] = &TrafficVehicle{ ID: vID, TemplateID: templatePool[i%len(templatePool)], LaneID: lane.ID, Distance: float32(i*40) + 10.0, Speed: 8.0 + float32(i%5)*1.5, LaneLength: lane.Length, } } te.mu.Unlock() log.Printf("[TRAFFIC] CityTrafficEngine started with %d simulated AI vehicles", len(te.vehicles)) go te.runLoop(ctx) } func (te *CityTrafficEngine) Stop() { te.mu.Lock() defer te.mu.Unlock() if te.cancel != nil { te.cancel() } te.isRunning = false te.vehicles = make(map[uint64]*TrafficVehicle) } func (te *CityTrafficEngine) runLoop(ctx context.Context) { ticker := time.NewTicker(150 * time.Millisecond) // ~6.7 Hz downsampled traffic lane updates defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: te.tick(0.150) } } } func (te *CityTrafficEngine) tick(dt float32) { te.mu.Lock() if len(te.vehicles) == 0 { te.mu.Unlock() return } w := protocol.NewUxWriter() // Encode SyncAetherAIVehicleLaneDatas payload w.U8(0xFF).Int7(len(te.vehicles)) for _, v := range te.vehicles { v.Distance += v.Speed * dt if v.Distance > v.LaneLength { v.Distance = 0 } w.U64(v.ID).U32(v.LaneID).F32(v.Distance).F32(v.Speed) } payload := w.Bytes() te.mu.Unlock() // Broadcast to all active sessions methodID := protocol.MethodGetMethodIDDefault("SyncAetherAIVehicleLaneDatas", 68582850) sessions := te.server.GetActiveSessions() for _, sess := range sessions { _ = sess.NotifyData(methodID, payload) } } // Vehicle RPC Handlers func (s *Server) handleAskSummonVehicle(sess *Session, pkt *protocol.RpcPacket) error { r := protocol.NewUxReader(pkt.Body) vehTemplateId, _ := r.U32() if vehTemplateId == 0 { vehTemplateId = 1001 } vehUnitId := uint64(0x6000000000000000) | uint64(time.Now().UnixNano()&0xFFFFFFFF) sess.activeVehicleId = vehUnitId log.Printf("[VEHICLE] AskSummonVehicle template=%d unitId=%d for player=%d", vehTemplateId, vehUnitId, sess.pid) if err := sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID); err != nil { return err } // SyncSpawnVehicle w := protocol.NewUxWriter() w.U64(vehUnitId).U32(vehTemplateId).F32(sess.posX + 3.0).F32(sess.posY).F32(sess.posZ + 3.0).F32(sess.facing) return sess.NotifyData(protocol.MethodGetMethodIDDefault("SyncSpawnVehicle", 68582851), w.Bytes()) } func (s *Server) handleAskDriveVehicle(sess *Session, pkt *protocol.RpcPacket) error { r := protocol.NewUxReader(pkt.Body) vehUnitId, _ := r.U64() log.Printf("[VEHICLE] AskDriveVehicle vehUnitId=%d player=%d", vehUnitId, sess.pid) if err := sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID); err != nil { return err } sess.isDriving = true sess.activeVehicleId = vehUnitId // SyncDriverEnterVehicle w := protocol.NewUxWriter() w.U64(vehUnitId).U64(sess.pid).U8(0) // SeatIndex 0 = Driver return sess.NotifyData(protocol.MethodGetMethodIDDefault("SyncDriverEnterVehicle", 68582852), w.Bytes()) } func (s *Server) handleAskExitVehicle(sess *Session, pkt *protocol.RpcPacket) error { log.Printf("[VEHICLE] AskExitVehicle player=%d vehUnitId=%d", sess.pid, sess.activeVehicleId) if err := sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID); err != nil { return err } sess.isDriving = false // SyncDriverExitVehicle w := protocol.NewUxWriter() w.U64(sess.activeVehicleId).U64(sess.pid) return sess.NotifyData(protocol.MethodGetMethodIDDefault("SyncDriverExitVehicle", 68582853), w.Bytes()) } func (s *Server) handleAskVehicleMove(sess *Session, pkt *protocol.RpcPacket) error { r := protocol.NewUxReader(pkt.Body) vehID, _ := r.U64() x, _ := r.F32() y, _ := r.F32() z, _ := r.F32() facing, _ := r.F32() sess.posX = x sess.posY = y sess.posZ = z sess.facing = facing // Echo SyncVehicleTransform to other peers if needed w := protocol.NewUxWriter() w.U64(vehID).F32(x).F32(y).F32(z).F32(facing) return sess.NotifyData(protocol.MethodGetMethodIDDefault("SyncVehicleTransform", 68582854), w.Bytes()) } func (s *Server) handleChangeVehicleDoorState(sess *Session, pkt *protocol.RpcPacket) error { r := protocol.NewUxReader(pkt.Body) vehID, _ := r.U64() doorIdx, _ := r.U8() isOpen, _ := r.Bool() log.Printf("[VEHICLE] ChangeVehicleDoorState veh=%d door=%d open=%v", vehID, doorIdx, isOpen) if err := sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID); err != nil { return err } w := protocol.NewUxWriter() w.U64(vehID).U8(doorIdx).Bool(isOpen) return sess.NotifyData(protocol.MethodGetMethodIDDefault("SyncVehicleDoorState", 68582855), w.Bytes()) } func (s *Server) handleAskVehicleSendGamePlaySignal(sess *Session, pkt *protocol.RpcPacket) error { r := protocol.NewUxReader(pkt.Body) vehID, _ := r.U64() sigType, _ := r.U32() log.Printf("[VEHICLE] GamePlaySignal veh=%d sigType=%d", vehID, sigType) return sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID) }