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/teleport.go
T

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