This repository has been archived on 2026-10-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Ananta-ps/internal/gameserver/traffic.go
T

255 lines
6.8 KiB
Go

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)
}