223 lines
6.1 KiB
Go
223 lines
6.1 KiB
Go
package gameserver
|
|
|
|
import (
|
|
"log"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"ananta-ps/pkg/protocol"
|
|
)
|
|
|
|
const (
|
|
CarShopTeleportConfig uint32 = 8
|
|
JiguanTeleportConfig uint32 = 2
|
|
GarageExitTeleportConfig uint32 = 7
|
|
)
|
|
|
|
type Vec3 struct {
|
|
X float32
|
|
Y float32
|
|
Z float32
|
|
}
|
|
|
|
type ShopAnchor struct {
|
|
Entrance Vec3
|
|
Interior Vec3
|
|
Facing float32
|
|
Leave Vec3
|
|
LeaveYaw float32
|
|
}
|
|
|
|
var defaultShopInteriors = []ShopAnchor{
|
|
{
|
|
Entrance: Vec3{X: 2260.5, Y: 0, Z: 1374.3},
|
|
Interior: Vec3{X: 2271.5, Y: 0, Z: 1374.3},
|
|
Facing: 90.0,
|
|
Leave: Vec3{X: 2240.0, Y: 0, Z: 1374.3},
|
|
LeaveYaw: 270.0,
|
|
},
|
|
{
|
|
Entrance: Vec3{X: 2800.0, Y: 0, Z: 1296.0},
|
|
Interior: Vec3{X: 2817.3, Y: 0, Z: 1296.7},
|
|
Facing: 270.0,
|
|
Leave: Vec3{X: 2780.0, Y: 0, Z: 1296.0},
|
|
LeaveYaw: 90.0,
|
|
},
|
|
}
|
|
|
|
type PreTeleportOption struct {
|
|
ConfigID uint32
|
|
Position Vec3
|
|
Facing float32
|
|
BeforeResName string
|
|
AfterResName string
|
|
BeforePosition Vec3
|
|
BeforeRot Vec3
|
|
AfterPosition Vec3
|
|
AfterRot Vec3
|
|
CustomBefore bool
|
|
CustomAfter bool
|
|
}
|
|
|
|
func (s *Server) handleAskTeleport(sess *Session, pkt *protocol.RpcPacket) error {
|
|
r := protocol.NewUxReader(pkt.Body)
|
|
opt := decodePreTeleportOption(r)
|
|
|
|
log.Printf("[TELEPORT] AskTeleport config=%d before='%s' after='%s' pos=(%.1f,%.1f,%.1f)",
|
|
opt.ConfigID, opt.BeforeResName, opt.AfterResName, opt.Position.X, opt.Position.Y, opt.Position.Z)
|
|
|
|
now := time.Now().UnixMilli()
|
|
switch opt.ConfigID {
|
|
case CarShopTeleportConfig:
|
|
isExit := strings.Contains(strings.ToLower(opt.BeforeResName), "out")
|
|
if !isExit && sess.lastShopExitMs != 0 && (now-sess.lastShopExitMs < 4000) {
|
|
log.Printf("[TELEPORT] Suppressed car shop enter debounce (within 4s of exit)")
|
|
return sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID)
|
|
}
|
|
|
|
shop := findNearestShop(Vec3{X: sess.posX, Y: sess.posY, Z: sess.posZ})
|
|
if opt.Position.X == 0 && opt.Position.Y == 0 && opt.Position.Z == 0 {
|
|
if isExit {
|
|
opt.Position = shop.Leave
|
|
opt.Facing = shop.LeaveYaw
|
|
opt.AfterPosition = shop.Leave
|
|
opt.AfterRot = Vec3{X: 0, Y: shop.LeaveYaw, Z: 0}
|
|
} else {
|
|
opt.Position = shop.Interior
|
|
opt.Facing = shop.Facing
|
|
opt.AfterPosition = shop.Interior
|
|
opt.AfterRot = Vec3{X: 0, Y: shop.Facing, Z: 0}
|
|
}
|
|
}
|
|
|
|
if isExit {
|
|
sess.lastShopExitMs = now
|
|
}
|
|
|
|
case JiguanTeleportConfig:
|
|
// Elevator landing
|
|
if opt.Position.X == 0 && opt.Position.Y == 0 && opt.Position.Z == 0 {
|
|
opt.Position = Vec3{X: sess.posX + 5.0, Y: sess.posY, Z: sess.posZ}
|
|
opt.AfterPosition = opt.Position
|
|
opt.CustomAfter = true
|
|
}
|
|
|
|
case GarageExitTeleportConfig:
|
|
// Garage exit
|
|
if opt.Position.X == 0 && opt.Position.Y == 0 && opt.Position.Z == 0 {
|
|
opt.Position = Vec3{X: 2280.0, Y: 10.0, Z: 1400.0}
|
|
opt.AfterPosition = opt.Position
|
|
}
|
|
}
|
|
|
|
sess.pendingTeleport = opt
|
|
|
|
if err := sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Echo SyncPreTeleportOption
|
|
w := protocol.NewUxWriter()
|
|
encodePreTeleportOption(w, opt)
|
|
return sess.NotifyData(protocol.MethodGetMethodIDDefault("SyncPreTeleportOption", 68582739), w.Bytes())
|
|
}
|
|
|
|
func (s *Server) handleReportPreTeleportFinish(sess *Session, pkt *protocol.RpcPacket) error {
|
|
log.Printf("[TELEPORT] ReportPreTeleportFinish received, applying teleport destination")
|
|
if err := sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID); err != nil {
|
|
return err
|
|
}
|
|
|
|
opt := sess.pendingTeleport
|
|
if opt != nil {
|
|
dest := opt.Position
|
|
if dest.X != 0 || dest.Y != 0 || dest.Z != 0 {
|
|
_ = sess.teleport(dest.X, dest.Y, dest.Z, opt.Facing)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) handleReportPostTeleportFinish(sess *Session, pkt *protocol.RpcPacket) error {
|
|
log.Printf("[TELEPORT] ReportPostTeleportFinish completed")
|
|
sess.pendingTeleport = nil
|
|
return sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID)
|
|
}
|
|
|
|
func (s *Server) handleAskEnterRaidByMapEntrance(sess *Session, pkt *protocol.RpcPacket) error {
|
|
r := protocol.NewUxReader(pkt.Body)
|
|
entranceID, _ := r.U32()
|
|
log.Printf("[TELEPORT] AskEnterRaidByMapEntrance entranceID=%d", entranceID)
|
|
|
|
if err := sess.ReturnEmptyOk(pkt.InvokeID, pkt.MethodID); err != nil {
|
|
return err
|
|
}
|
|
|
|
// SyncTeleport to entrance target
|
|
w := protocol.NewUxWriter()
|
|
w.U64(sess.unitId).U32(entranceID).F32(sess.posX).F32(sess.posY).F32(sess.posZ).F32(sess.facing)
|
|
return sess.NotifyData(protocol.MethodGetMethodIDDefault("SyncTeleport", 68582740), w.Bytes())
|
|
}
|
|
|
|
func findNearestShop(pos Vec3) ShopAnchor {
|
|
best := defaultShopInteriors[0]
|
|
bestDist := float64(math.MaxFloat64)
|
|
for _, shop := range defaultShopInteriors {
|
|
dx := float64(shop.Entrance.X - pos.X)
|
|
dz := float64(shop.Entrance.Z - pos.Z)
|
|
d := dx*dx + dz*dz
|
|
if d < bestDist {
|
|
bestDist = d
|
|
best = shop
|
|
}
|
|
}
|
|
return best
|
|
}
|
|
|
|
func decodePreTeleportOption(r *protocol.UxReader) *PreTeleportOption {
|
|
opt := &PreTeleportOption{}
|
|
if r == nil || r.Remaining() == 0 {
|
|
return opt
|
|
}
|
|
opt.ConfigID, _ = r.U32()
|
|
opt.Position.X, _ = r.F32()
|
|
opt.Position.Y, _ = r.F32()
|
|
opt.Position.Z, _ = r.F32()
|
|
opt.Facing, _ = r.F32()
|
|
opt.BeforeResName, _ = r.UxString()
|
|
opt.AfterResName, _ = r.UxString()
|
|
opt.BeforePosition.X, _ = r.F32()
|
|
opt.BeforePosition.Y, _ = r.F32()
|
|
opt.BeforePosition.Z, _ = r.F32()
|
|
opt.BeforeRot.X, _ = r.F32()
|
|
opt.BeforeRot.Y, _ = r.F32()
|
|
opt.BeforeRot.Z, _ = r.F32()
|
|
opt.AfterPosition.X, _ = r.F32()
|
|
opt.AfterPosition.Y, _ = r.F32()
|
|
opt.AfterPosition.Z, _ = r.F32()
|
|
opt.AfterRot.X, _ = r.F32()
|
|
opt.AfterRot.Y, _ = r.F32()
|
|
opt.AfterRot.Z, _ = r.F32()
|
|
opt.CustomBefore, _ = r.Bool()
|
|
opt.CustomAfter, _ = r.Bool()
|
|
return opt
|
|
}
|
|
|
|
func encodePreTeleportOption(w *protocol.UxWriter, opt *PreTeleportOption) {
|
|
if opt == nil {
|
|
return
|
|
}
|
|
w.U32(opt.ConfigID)
|
|
w.F32(opt.Position.X).F32(opt.Position.Y).F32(opt.Position.Z)
|
|
w.F32(opt.Facing)
|
|
w.UxString(opt.BeforeResName)
|
|
w.UxString(opt.AfterResName)
|
|
w.F32(opt.BeforePosition.X).F32(opt.BeforePosition.Y).F32(opt.BeforePosition.Z)
|
|
w.F32(opt.BeforeRot.X).F32(opt.BeforeRot.Y).F32(opt.BeforeRot.Z)
|
|
w.F32(opt.AfterPosition.X).F32(opt.AfterPosition.Y).F32(opt.AfterPosition.Z)
|
|
w.F32(opt.AfterRot.X).F32(opt.AfterRot.Y).F32(opt.AfterRot.Z)
|
|
w.Bool(opt.CustomBefore)
|
|
w.Bool(opt.CustomAfter)
|
|
}
|