Files
minego/pkg/game/player/player.go

332 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package player
import (
"context"
"fmt"
"math"
"time"
"git.konjactw.dev/patyhank/minego/pkg/bot"
"git.konjactw.dev/patyhank/minego/pkg/game/world"
"git.konjactw.dev/patyhank/minego/pkg/protocol"
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/client"
"git.konjactw.dev/patyhank/minego/pkg/protocol/packet/game/server"
pk "github.com/Tnze/go-mc/net/packet"
"github.com/go-gl/mathgl/mgl64"
)
type Player struct {
c bot.Client
entity *world.Entity
stateID int32
lastReceivedPacketTime time.Time
}
// New 創建新的 Player 實例
func New(c bot.Client) *Player {
pl := &Player{
c: c,
entity: &world.Entity{},
}
c.PacketHandler().AddGenericPacketHandler(func(ctx context.Context, pk client.ClientboundPacket) {
pl.lastReceivedPacketTime = time.Now()
})
bot.AddHandler(c, func(ctx context.Context, p *client.KeepAlive) {
c.WritePacket(ctx, &server.KeepAlive{
ID: p.ID,
})
})
bot.AddHandler(c, func(ctx context.Context, p *client.Disconnect) {
fmt.Println(p.Reason.String())
})
bot.AddHandler(c, func(ctx context.Context, p *client.SystemChatMessage) {
if !p.Overlay {
bot.PublishEvent(c, MessageEvent{Message: p.Content})
}
})
bot.AddHandler(c, func(ctx context.Context, p *client.PlayerPosition) {
pl.entity.SetPosition(mgl64.Vec3{p.X, p.Y, p.Z})
pl.entity.SetRotation(mgl64.Vec2{float64(p.XRot), float64(p.YRot)})
c.WritePacket(context.Background(), &server.AcceptTeleportation{TeleportID: p.ID})
})
bot.AddHandler(c, func(ctx context.Context, p *client.PlayerRotation) {
pl.entity.SetRotation(mgl64.Vec2{float64(p.Yaw), float64(p.Pitch)})
})
return pl
}
func (p *Player) CheckServer() {
for time.Since(p.lastReceivedPacketTime) > 50*time.Millisecond && p.c.IsConnected() {
time.Sleep(50 * time.Millisecond)
}
}
// StateID 返回當前狀態 ID
func (p *Player) StateID() int32 {
return p.stateID
}
// UpdateStateID 更新狀態 ID
func (p *Player) UpdateStateID(id int32) {
p.stateID = id
}
// Entity 返回玩家實體
func (p *Player) Entity() bot.Entity {
return p.entity
}
// FlyTo 直線飛行到指定位置每5格飛行一段
func (p *Player) FlyTo(pos mgl64.Vec3) error {
if p.c == nil {
return fmt.Errorf("client is not initialized")
}
if p.entity == nil {
return fmt.Errorf("player entity is not initialized")
}
currentPos := p.entity.Position()
direction := pos.Sub(currentPos)
distance := direction.Len()
if distance == 0 {
return nil // 已經在目標位置
}
segmentLength := 8.0
for {
currentPos = p.entity.Position()
direction = pos.Sub(currentPos)
distance = direction.Len()
if distance == 0 {
return nil
}
// 正規化方向向量
direction = direction.Normalize()
moveDistance := math.Min(segmentLength, distance)
target := currentPos.Add(direction.Mul(moveDistance))
if err := p.c.WritePacket(context.Background(), &server.MovePlayerPos{
X: target.X(),
FeetY: target.Y(),
Z: target.Z(),
Flags: 0x00,
}); err != nil {
return fmt.Errorf("failed to move player: %w", err)
}
time.Sleep(50 * time.Millisecond)
}
return nil
}
// WalkTo 使用 A* 演算法步行到指定位置
func (p *Player) WalkTo(pos mgl64.Vec3) error {
if p.c == nil {
return fmt.Errorf("client is not initialized")
}
if p.entity == nil {
return fmt.Errorf("player entity is not initialized")
}
currentPos := p.entity.Position()
// 使用 A* 演算法尋找路徑
path, err := AStar(p.c.World(), currentPos, pos)
if err != nil {
return fmt.Errorf("failed to find path: %w", err)
}
if len(path) == 0 {
return fmt.Errorf("no path found to target position")
}
// 沿著路徑移動
for _, waypoint := range path {
if err := p.c.WritePacket(context.Background(), &server.MovePlayerPos{
X: waypoint.X(),
FeetY: waypoint.Y(),
Z: waypoint.Z(),
Flags: 0x0,
}); err != nil {
return fmt.Errorf("failed to move to waypoint: %w", err)
}
// 短暫延遲以模擬真實移動
time.Sleep(100 * time.Millisecond)
}
return nil
}
// LookAt 看向指定位置
func (p *Player) LookAt(target mgl64.Vec3) error {
if p.c == nil {
return fmt.Errorf("client is not initialized")
}
if p.entity == nil {
return fmt.Errorf("player entity is not initialized")
}
// 計算視角
playerPos := p.entity.Position()
direction := target.Sub(playerPos).Normalize()
// 計算 yaw 和 pitch
yaw := float32(math.Atan2(-direction.X(), direction.Z()) * 180 / math.Pi)
pitch := float32(math.Asin(-direction.Y()) * 180 / math.Pi)
return p.c.WritePacket(context.Background(), &server.MovePlayerRot{
Yaw: yaw,
Pitch: pitch,
Flags: 0x00,
})
}
// BreakBlock 破壞指定位置的方塊
func (p *Player) BreakBlock(pos protocol.Position) error {
if p.c == nil {
return fmt.Errorf("client is not initialized")
}
// 發送開始挖掘封包
startPacket := &server.PlayerAction{
Status: 0,
Sequence: p.stateID,
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
Face: 1,
}
if err := p.c.WritePacket(context.Background(), startPacket); err != nil {
return fmt.Errorf("failed to send start destroy packet: %w", err)
}
// 發送完成挖掘封包
finishPacket := &server.PlayerAction{
Status: 2,
Sequence: p.stateID,
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
Face: 1,
}
return p.c.WritePacket(context.Background(), finishPacket)
}
// PlaceBlock 在指定位置放置方塊
func (p *Player) PlaceBlock(pos protocol.Position) error {
if p.c == nil {
return fmt.Errorf("client is not initialized")
}
packet := &server.UseItemOn{
Hand: 0,
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
Face: 1,
CursorX: 0.5,
CursorY: 0.5,
CursorZ: 0.5,
InsideBlock: false,
Sequence: p.stateID,
}
return p.c.WritePacket(context.Background(), packet)
}
// PlaceBlock 在指定位置放置方塊
func (p *Player) PlaceBlockWithArgs(pos protocol.Position, face int32, cursor mgl64.Vec3) error {
if p.c == nil {
return fmt.Errorf("client is not initialized")
}
packet := &server.UseItemOn{
Hand: 0,
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
Face: face,
CursorX: float32(cursor[0]),
CursorY: float32(cursor[1]),
CursorZ: float32(cursor[2]),
InsideBlock: false,
Sequence: p.stateID,
}
return p.c.WritePacket(context.Background(), packet)
}
// OpenContainer 打開指定位置的容器
func (p *Player) OpenContainer(pos protocol.Position) (bot.Container, error) {
if p.c == nil {
return nil, fmt.Errorf("client is not initialized")
}
// 發送使用物品封包來打開容器
packet := &server.UseItemOn{
Hand: 1,
Location: pk.Position{X: int(pos[0]), Y: int(pos[1]), Z: int(pos[2])},
Face: 1,
CursorX: 0.5,
CursorY: 0.5,
CursorZ: 0.5,
InsideBlock: false,
WorldBorderHit: false,
Sequence: p.stateID,
}
if err := p.c.WritePacket(context.Background(), packet); err != nil {
return nil, fmt.Errorf("failed to open container: %w", err)
}
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*10)
defer cancelFunc()
for p.c.Inventory().Container() == nil && ctx.Err() == nil {
time.Sleep(time.Millisecond * 50)
}
return p.c.Inventory().Container(), nil
}
// UseItem 使用指定手中的物品
func (p *Player) UseItem(hand int8) error {
if p.c == nil {
return fmt.Errorf("client is not initialized")
}
return p.c.WritePacket(context.Background(), &server.UseItem{
Hand: int32(hand),
Sequence: p.stateID,
Yaw: 0,
Pitch: 0,
})
}
// OpenMenu 打開指定命令的選單
func (p *Player) OpenMenu(command string) (bot.Container, error) {
if p.c == nil {
return nil, fmt.Errorf("client is not initialized")
}
if err := p.c.WritePacket(context.Background(), &server.ChatCommand{
Command: command,
}); err != nil {
return nil, fmt.Errorf("failed to open menu with command '%s': %w", command, err)
}
// 返回客戶端的容器處理器
return p.c.Inventory().Container(), nil
}