255 lines
6.8 KiB
Go
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)
|
|
}
|