From e3f5270012798e4515f02a943d3b40cefac2c7b4 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Sun, 7 Nov 2021 10:22:39 -0800 Subject: [PATCH 01/19] Send available entities to player on session start --- server/session/session.go | 25 +++++++++++++++++++++++++ server/world/entity.go | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/server/session/session.go b/server/session/session.go index 7baa2eb66..a4149e397 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -12,6 +12,7 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl64" "github.com/sandertv/gophertunnel/minecraft" + "github.com/sandertv/gophertunnel/minecraft/nbt" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/login" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" @@ -104,6 +105,12 @@ type Conn interface { StartGame(data minecraft.GameData) error } +// actorIdentifier represents the structure of an actor identifier sent over the network. +type actorIdentifier struct { + // Unique namespaced identifier for an entity. + ID string `nbt:"id"` +} + // Nop represents a no-operation session. It does not do anything when sending a packet to it. var Nop = &Session{} @@ -167,6 +174,8 @@ func (s *Session) Start(c Controllable, w *world.World, gm world.GameMode, onSto s.chunkLoader = world.NewLoader(int(s.chunkRadius), w, s) s.chunkLoader.Move(w.Spawn().Vec3Middle()) + s.sendAvailableEntities() + s.initPlayerList() w.AddEntity(s.c) @@ -406,3 +415,19 @@ func (s *Session) closePlayerList() { sessions = n sessionMu.Unlock() } + +// sendAvailableEntities sends all registered entities to the player. +func (s *Session) sendAvailableEntities() { + entities := world.Entities() + var entityData []actorIdentifier + for _, entity := range entities { + id := entity.EncodeEntity() + s.log.Debugf("entity %v\n", id) + entityData = append(entityData, actorIdentifier{ID: id}) + } + serializedEntityData, err := nbt.Marshal(map[string]interface{}{"idlist": entityData}) + if err != nil { + panic(fmt.Errorf("failed to serialize entity data: %v", err)) + } + s.writePacket(&packet.AvailableActorIdentifiers{SerialisedEntityIdentifiers: serializedEntityData}) +} diff --git a/server/world/entity.go b/server/world/entity.go index 3517e8be0..87929e049 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -67,3 +67,8 @@ func EntityByName(name string) (SaveableEntity, bool) { e, ok := entities[name] return e, ok } + +// Entities returns all registered entities. +func Entities() map[string]SaveableEntity { + return entities +} From d4d9ddae606f59e1b06ac48687987116f7347bf6 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Sun, 7 Nov 2021 10:32:13 -0800 Subject: [PATCH 02/19] Remove debug message --- server/session/session.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/session/session.go b/server/session/session.go index a4149e397..724d7c8a8 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -422,7 +422,6 @@ func (s *Session) sendAvailableEntities() { var entityData []actorIdentifier for _, entity := range entities { id := entity.EncodeEntity() - s.log.Debugf("entity %v\n", id) entityData = append(entityData, actorIdentifier{ID: id}) } serializedEntityData, err := nbt.Marshal(map[string]interface{}{"idlist": entityData}) From a75b16be41af2c777785fbd9948e5004e6620665 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Sun, 7 Nov 2021 10:44:01 -0800 Subject: [PATCH 03/19] Move actorIdentifier struct inside sendAvailableEntities method --- server/session/session.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/session/session.go b/server/session/session.go index 724d7c8a8..a809ec5c0 100644 --- a/server/session/session.go +++ b/server/session/session.go @@ -105,12 +105,6 @@ type Conn interface { StartGame(data minecraft.GameData) error } -// actorIdentifier represents the structure of an actor identifier sent over the network. -type actorIdentifier struct { - // Unique namespaced identifier for an entity. - ID string `nbt:"id"` -} - // Nop represents a no-operation session. It does not do anything when sending a packet to it. var Nop = &Session{} @@ -418,6 +412,12 @@ func (s *Session) closePlayerList() { // sendAvailableEntities sends all registered entities to the player. func (s *Session) sendAvailableEntities() { + // actorIdentifier represents the structure of an actor identifier sent over the network. + type actorIdentifier struct { + // Unique namespaced identifier for an entity. + ID string `nbt:"id"` + } + entities := world.Entities() var entityData []actorIdentifier for _, entity := range entities { From 78129ef179417aec4150e29248073ff091719fc3 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Sun, 7 Nov 2021 10:55:23 -0800 Subject: [PATCH 04/19] world.Entities() returns slice of entities --- server/world/entity.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/world/entity.go b/server/world/entity.go index 87929e049..903403ad6 100644 --- a/server/world/entity.go +++ b/server/world/entity.go @@ -69,6 +69,10 @@ func EntityByName(name string) (SaveableEntity, bool) { } // Entities returns all registered entities. -func Entities() map[string]SaveableEntity { - return entities +func Entities() []SaveableEntity { + es := make([]SaveableEntity, 0, len(entities)) + for _, e := range entities { + es = append(es, e) + } + return es } From ac8857107c9fee851a0a6b55c99b0eb94b39cc3e Mon Sep 17 00:00:00 2001 From: Neutronic Date: Tue, 9 Nov 2021 11:59:38 -0800 Subject: [PATCH 05/19] Implement entity riding --- server/entity/rideable.go | 18 ++++ server/entity/rider.go | 24 +++++ server/player/player.go | 92 +++++++++++++++++++ server/session/controllable.go | 9 ++ server/session/entity_metadata.go | 9 ++ server/session/handler_interact.go | 9 ++ .../session/handler_inventory_transaction.go | 10 ++ server/session/handler_player_auth_input.go | 8 ++ server/session/world.go | 12 +++ server/world/viewer.go | 3 + 10 files changed, 194 insertions(+) create mode 100644 server/entity/rideable.go create mode 100644 server/entity/rider.go diff --git a/server/entity/rideable.go b/server/entity/rideable.go new file mode 100644 index 000000000..6df3047bc --- /dev/null +++ b/server/entity/rideable.go @@ -0,0 +1,18 @@ +package entity + +import ( + "github.com/go-gl/mathgl/mgl32" +) + +type Rideable interface { + // SeatPositions returns the possible seat positions for an entity in the order that they will be filled. + SeatPositions() []mgl32.Vec3 + // Riders returns a slice entities that are currently riding an entity in the order that they were added. + Riders() []Rider + // AddRider adds a rider to the entity. + AddRider(e Rider) + // RemoveRider removes a rider from the entity. + RemoveRider(e Rider) + // Move moves the entity using the given vector, yaw, and pitch. + Move(vector mgl32.Vec2, yaw, pitch float32) +} diff --git a/server/entity/rider.go b/server/entity/rider.go new file mode 100644 index 000000000..05667df03 --- /dev/null +++ b/server/entity/rider.go @@ -0,0 +1,24 @@ +package entity + +import ( + "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl32" +) + +type Rider interface { + // SeatPosition returns the Rider's current seat position. + SeatPosition() mgl32.Vec3 + // RideEntity links the Rider to an entity if the entity is Rideable and if there is a seat available. + RideEntity(e world.Entity) + // DismountEntity unlinks the Rider from an entity. + DismountEntity(e world.Entity) + // CheckSeats moves a Rider to the seat corresponding to their current index within the slice of riders. + // It is called whenever a Rider dismounts an entity. + CheckSeats(e world.Entity) + // GetSeat returns the index of a Rider within the slice of riders. + GetSeat(e world.Entity) int + // Riding returns the runtime ID of the entity the Rider is riding. + Riding() uint64 + // SetRiding saves the runtime ID of the entity the Rider is riding. + SetRiding(id uint64) +} diff --git a/server/player/player.go b/server/player/player.go index 854895d72..47dd0f71f 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -28,8 +28,10 @@ import ( "github.com/df-mc/dragonfly/server/world" "github.com/df-mc/dragonfly/server/world/particle" "github.com/df-mc/dragonfly/server/world/sound" + "github.com/go-gl/mathgl/mgl32" "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" + "github.com/sandertv/gophertunnel/minecraft/protocol" "go.uber.org/atomic" "golang.org/x/text/language" "math" @@ -70,6 +72,10 @@ type Player struct { armour *inventory.Armour heldSlot *atomic.Uint32 + seatMu sync.RWMutex + seatPosition atomic.Value + riding atomic.Uint64 + sneaking, sprinting, swimming, flying, invisible, immobile, onGround, usingItem atomic.Bool usingSince atomic.Int64 @@ -125,6 +131,7 @@ func New(name string, skin skin.Skin, pos mgl64.Vec3) *Player { p.vel.Store(mgl64.Vec3{}) p.immunity.Store(time.Now()) p.breakingPos.Store(cube.Pos{}) + p.seatPosition.Store(mgl32.Vec3{0, 0, 0}) return p } @@ -2050,6 +2057,91 @@ func (p *Player) PunchAir() { }) } +// RideEntity links the player to an entity if the entity is rideable and if there is a seat available. +func (p *Player) RideEntity(e world.Entity) { + if rideable, ok := e.(entity.Rideable); ok { + p.seatMu.Lock() + if p.GetSeat(e) == -1 { + rideable.AddRider(p) + riders := rideable.Riders() + seat := len(riders) + positions := rideable.SeatPositions() + if len(positions) >= seat { + p.seatPosition.Store(positions[seat-1]) + p.updateState() + linkType := protocol.EntityLinkPassenger + if seat-1 == 0 { + linkType = protocol.EntityLinkRider + } + for _, v := range p.viewers() { + v.ViewEntityLink(p, e, byte(linkType)) + } + } + } + p.seatMu.Unlock() + } +} + +// DismountEntity unlinks the player from an entity. +func (p *Player) DismountEntity(e world.Entity) { + if rideable, ok := e.(entity.Rideable); ok { + rideable.RemoveRider(p) + for _, v := range p.viewers() { + v.ViewEntityLink(p, e, protocol.EntityLinkRemove) + } + for _, r := range rideable.Riders() { + r.GetSeat(e) + } + } +} + +// CheckSeats moves a player to the seat corresponding to their current index within the slice of riders. +func (p *Player) CheckSeats(e world.Entity) { + if rideable, ok := e.(entity.Rideable); ok { + seat := p.GetSeat(e) + if seat != -1 { + positions := rideable.SeatPositions() + if positions[seat] != p.seatPosition.Load() { + p.seatPosition.Store(positions[seat]) + if seat == 0 { + for _, v := range p.viewers() { + v.ViewEntityLink(p, e, protocol.EntityLinkRider) + } + } + p.updateState() + } + } + } +} + +// SeatPosition returns the position of the player's seat. +func (p *Player) SeatPosition() mgl32.Vec3 { + return p.seatPosition.Load().(mgl32.Vec3) +} + +// GetSeat returns the index of a player within the slice of riders. +func (p *Player) GetSeat(e world.Entity) int { + if rideable, ok := e.(entity.Rideable); ok { + riders := rideable.Riders() + for i, r := range riders { + if r == p { + return i + } + } + } + return -1 +} + +// Riding returns the runtime ID of the entity the player is riding. +func (p *Player) Riding() uint64 { + return p.riding.Load() +} + +// SetRiding saves the runtime ID of the entity the player is riding. +func (p *Player) SetRiding(id uint64) { + p.riding.Store(id) +} + // EncodeEntity ... func (p *Player) EncodeEntity() string { return "minecraft:player" diff --git a/server/session/controllable.go b/server/session/controllable.go index 39b292442..8d2e80266 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -8,6 +8,7 @@ import ( "github.com/df-mc/dragonfly/server/player/form" "github.com/df-mc/dragonfly/server/player/skin" "github.com/df-mc/dragonfly/server/world" + "github.com/go-gl/mathgl/mgl32" "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" ) @@ -58,6 +59,14 @@ type Controllable interface { Flying() bool StopFlying() + SeatPosition() mgl32.Vec3 + RideEntity(e world.Entity) + DismountEntity(e world.Entity) + CheckSeats(e world.Entity) + GetSeat(e world.Entity) int + Riding() uint64 + SetRiding(id uint64) + StartBreaking(pos cube.Pos, face cube.Face) ContinueBreaking(face cube.Face) FinishBreaking() diff --git a/server/session/entity_metadata.go b/server/session/entity_metadata.go index bf8e2e042..cbcfb631e 100644 --- a/server/session/entity_metadata.go +++ b/server/session/entity_metadata.go @@ -1,6 +1,7 @@ package session import ( + "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/world" "image/color" @@ -52,6 +53,13 @@ func parseEntityMetadata(e world.Entity) entityMetadata { if s, ok := e.(scaled); ok { m[dataKeyScale] = float32(s.Scale()) } + + if r, ok := e.(entity.Rider); ok { + m[dataKeyRiderSeatPosition] = r.SeatPosition() + if r.Riding() != 0 { + m.setFlag(dataKeyFlags, dataFlagRiding) + } + } if n, ok := e.(named); ok { m[dataKeyNameTag] = n.NameTag() m[dataKeyAlwaysShowNameTag] = uint8(1) @@ -98,6 +106,7 @@ const ( dataKeyScale = 38 dataKeyBoundingBoxWidth = 53 dataKeyBoundingBoxHeight = 54 + dataKeyRiderSeatPosition = 56 dataKeyAlwaysShowNameTag = 81 ) diff --git a/server/session/handler_interact.go b/server/session/handler_interact.go index 673509463..351ca3b19 100644 --- a/server/session/handler_interact.go +++ b/server/session/handler_interact.go @@ -26,6 +26,15 @@ func (h *InteractHandler) Handle(p packet.Packet, s *Session) error { WindowID: 0, ContainerType: 0xff, }) + break + case packet.InteractActionLeaveVehicle: + if s.c.Riding() == 0 { + return nil + } + if e, found := s.entityFromRuntimeID(s.c.Riding()); found { + s.c.DismountEntity(e) + } + break default: return fmt.Errorf("unexpected interact packet action %v", pk.ActionType) } diff --git a/server/session/handler_inventory_transaction.go b/server/session/handler_inventory_transaction.go index a0d81d677..d569860a2 100644 --- a/server/session/handler_inventory_transaction.go +++ b/server/session/handler_inventory_transaction.go @@ -3,6 +3,7 @@ package session import ( "fmt" "github.com/df-mc/dragonfly/server/block/cube" + "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/event" "github.com/sandertv/gophertunnel/minecraft/protocol" "github.com/sandertv/gophertunnel/minecraft/protocol/packet" @@ -34,6 +35,15 @@ func (h *InventoryTransactionHandler) Handle(p packet.Packet, s *Session) error if !held.Equal(stackToItem(data.HeldItem.Stack)) { return nil } + if pk.TransactionData.(*protocol.UseItemOnEntityTransactionData).ActionType == protocol.UseItemOnEntityActionInteract { + // Check if the entity is rideable, and if so ride the entity. + e, found := s.entityFromRuntimeID(data.TargetEntityRuntimeID) + if found { + if _, ok := e.(entity.Rideable); ok { + s.c.RideEntity(e) + } + } + } return h.handleUseItemOnEntityTransaction(data, s) case *protocol.UseItemTransactionData: held, _ := s.c.HeldItems() diff --git a/server/session/handler_player_auth_input.go b/server/session/handler_player_auth_input.go index b1a8dc0a3..84f3b433e 100644 --- a/server/session/handler_player_auth_input.go +++ b/server/session/handler_player_auth_input.go @@ -32,6 +32,14 @@ func (h PlayerAuthInputHandler) handleMovement(pk *packet.PlayerAuthInput, s *Se } } + // Check if player is riding an entity, and move the entity. + if e, found := s.entityFromRuntimeID(s.c.Riding()); found { + if mounted := s.c.GetSeat(e); mounted == 0 { + rideable, _ := e.(entity.Rideable) + rideable.Move(pk.MoveVector, pk.Yaw, pk.Pitch) + } + } + pk.Position = pk.Position.Sub(mgl32.Vec3{0, 1.62}) // Subtract the base offset of players from the pos. newPos := vec32To64(pk.Position) diff --git a/server/session/world.go b/server/session/world.go index 3fa42a950..802a25a4e 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -348,6 +348,18 @@ func (s *Session) ViewEntityTeleport(e world.Entity, position mgl64.Vec3) { } } +// ViewEntityLink ... +func (s *Session) ViewEntityLink(r world.Entity, rd world.Entity, linkType byte) { + riddenID := s.entityRuntimeID(rd) + s.writePacket(&packet.SetActorLink{EntityLink: protocol.EntityLink{ + RiddenEntityUniqueID: int64(riddenID), + RiderEntityUniqueID: int64(s.entityRuntimeID(r)), + Type: linkType, + RiderInitiated: true, + }}) + s.c.SetRiding(riddenID) +} + // ViewEntityItems ... func (s *Session) ViewEntityItems(e world.Entity) { runtimeID := s.entityRuntimeID(e) diff --git a/server/world/viewer.go b/server/world/viewer.go index 3a8625339..7fdf7f1a8 100644 --- a/server/world/viewer.go +++ b/server/world/viewer.go @@ -29,6 +29,9 @@ type Viewer interface { // ViewEntityTeleport views the teleportation of an entity. The entity is immediately moved to a different // target position. ViewEntityTeleport(e Entity, position mgl64.Vec3) + // ViewEntityLink views the linking of one entity to another. It is called when any entity rides another or + // changes its link type. + ViewEntityLink(r Entity, rd Entity, linkType byte) // ViewChunk views the chunk passed at a particular position. It is called for every chunk loaded using // the world.Loader. ViewChunk(pos ChunkPos, c *chunk.Chunk, blockNBT map[cube.Pos]Block) From 95b15ef3afd63fa5f376ac382f14f3579c11535b Mon Sep 17 00:00:00 2001 From: Neutronic Date: Tue, 9 Nov 2021 20:06:15 -0800 Subject: [PATCH 06/19] Change GetSeat to Seat --- server/entity/rider.go | 4 ++-- server/player/player.go | 10 ++++------ server/session/controllable.go | 2 +- server/session/handler_player_auth_input.go | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/server/entity/rider.go b/server/entity/rider.go index 05667df03..b12c6b7cc 100644 --- a/server/entity/rider.go +++ b/server/entity/rider.go @@ -15,8 +15,8 @@ type Rider interface { // CheckSeats moves a Rider to the seat corresponding to their current index within the slice of riders. // It is called whenever a Rider dismounts an entity. CheckSeats(e world.Entity) - // GetSeat returns the index of a Rider within the slice of riders. - GetSeat(e world.Entity) int + // Seat returns the index of a Rider within the slice of riders. + Seat(e world.Entity) int // Riding returns the runtime ID of the entity the Rider is riding. Riding() uint64 // SetRiding saves the runtime ID of the entity the Rider is riding. diff --git a/server/player/player.go b/server/player/player.go index 47dd0f71f..80770d1e8 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2060,8 +2060,7 @@ func (p *Player) PunchAir() { // RideEntity links the player to an entity if the entity is rideable and if there is a seat available. func (p *Player) RideEntity(e world.Entity) { if rideable, ok := e.(entity.Rideable); ok { - p.seatMu.Lock() - if p.GetSeat(e) == -1 { + if p.Seat(e) == -1 { rideable.AddRider(p) riders := rideable.Riders() seat := len(riders) @@ -2078,7 +2077,6 @@ func (p *Player) RideEntity(e world.Entity) { } } } - p.seatMu.Unlock() } } @@ -2098,7 +2096,7 @@ func (p *Player) DismountEntity(e world.Entity) { // CheckSeats moves a player to the seat corresponding to their current index within the slice of riders. func (p *Player) CheckSeats(e world.Entity) { if rideable, ok := e.(entity.Rideable); ok { - seat := p.GetSeat(e) + seat := p.Seat(e) if seat != -1 { positions := rideable.SeatPositions() if positions[seat] != p.seatPosition.Load() { @@ -2119,8 +2117,8 @@ func (p *Player) SeatPosition() mgl32.Vec3 { return p.seatPosition.Load().(mgl32.Vec3) } -// GetSeat returns the index of a player within the slice of riders. -func (p *Player) GetSeat(e world.Entity) int { +// Seat returns the index of a player within the slice of riders. +func (p *Player) Seat(e world.Entity) int { if rideable, ok := e.(entity.Rideable); ok { riders := rideable.Riders() for i, r := range riders { diff --git a/server/session/controllable.go b/server/session/controllable.go index 8d2e80266..bd39bc7d1 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -63,7 +63,7 @@ type Controllable interface { RideEntity(e world.Entity) DismountEntity(e world.Entity) CheckSeats(e world.Entity) - GetSeat(e world.Entity) int + Seat(e world.Entity) int Riding() uint64 SetRiding(id uint64) diff --git a/server/session/handler_player_auth_input.go b/server/session/handler_player_auth_input.go index 84f3b433e..9cf578fcd 100644 --- a/server/session/handler_player_auth_input.go +++ b/server/session/handler_player_auth_input.go @@ -34,7 +34,7 @@ func (h PlayerAuthInputHandler) handleMovement(pk *packet.PlayerAuthInput, s *Se // Check if player is riding an entity, and move the entity. if e, found := s.entityFromRuntimeID(s.c.Riding()); found { - if mounted := s.c.GetSeat(e); mounted == 0 { + if mounted := s.c.Seat(e); mounted == 0 { rideable, _ := e.(entity.Rideable) rideable.Move(pk.MoveVector, pk.Yaw, pk.Pitch) } From e1376eda29c35b3f566b277af24425c229558ea7 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Tue, 9 Nov 2021 20:06:59 -0800 Subject: [PATCH 07/19] Fix calling GetSeat instead of CheckSeats on Dismount --- server/player/player.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/player/player.go b/server/player/player.go index 80770d1e8..c51978df0 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2088,7 +2088,7 @@ func (p *Player) DismountEntity(e world.Entity) { v.ViewEntityLink(p, e, protocol.EntityLinkRemove) } for _, r := range rideable.Riders() { - r.GetSeat(e) + r.CheckSeats(e) } } } From 04a058f9628df946ab381cb8090247508c6a3ff0 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Tue, 9 Nov 2021 20:11:08 -0800 Subject: [PATCH 08/19] Add docs for Rider and Rideable interfaces --- server/entity/rideable.go | 1 + server/entity/rider.go | 1 + 2 files changed, 2 insertions(+) diff --git a/server/entity/rideable.go b/server/entity/rideable.go index 6df3047bc..71cbe4c82 100644 --- a/server/entity/rideable.go +++ b/server/entity/rideable.go @@ -4,6 +4,7 @@ import ( "github.com/go-gl/mathgl/mgl32" ) +// Rideable is an interface for entities that can be ridden. type Rideable interface { // SeatPositions returns the possible seat positions for an entity in the order that they will be filled. SeatPositions() []mgl32.Vec3 diff --git a/server/entity/rider.go b/server/entity/rider.go index b12c6b7cc..b8694d78a 100644 --- a/server/entity/rider.go +++ b/server/entity/rider.go @@ -5,6 +5,7 @@ import ( "github.com/go-gl/mathgl/mgl32" ) +// Rider is an interface for entities that can ride other entities. type Rider interface { // SeatPosition returns the Rider's current seat position. SeatPosition() mgl32.Vec3 From d0fd0cb644f4030324c6afc2cbfde12c0443d2e1 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Tue, 9 Nov 2021 20:27:53 -0800 Subject: [PATCH 09/19] Use world.Entity instead of runtime ids and remove redundant seatMu --- server/entity/rider.go | 8 ++++---- server/player/player.go | 19 +++++++++++-------- server/session/controllable.go | 4 ++-- server/session/entity_metadata.go | 2 +- server/session/handler_interact.go | 6 ++---- server/session/handler_player_auth_input.go | 7 ++++--- server/session/world.go | 5 ++--- 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/server/entity/rider.go b/server/entity/rider.go index b8694d78a..95d4d93a5 100644 --- a/server/entity/rider.go +++ b/server/entity/rider.go @@ -18,8 +18,8 @@ type Rider interface { CheckSeats(e world.Entity) // Seat returns the index of a Rider within the slice of riders. Seat(e world.Entity) int - // Riding returns the runtime ID of the entity the Rider is riding. - Riding() uint64 - // SetRiding saves the runtime ID of the entity the Rider is riding. - SetRiding(id uint64) + // Riding returns the entity that the player is currently riding. + Riding() world.Entity + // SetRiding saves the entity the Rider is currently riding. + SetRiding(e world.Entity) } diff --git a/server/player/player.go b/server/player/player.go index c51978df0..fe071aa8f 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -72,9 +72,8 @@ type Player struct { armour *inventory.Armour heldSlot *atomic.Uint32 - seatMu sync.RWMutex seatPosition atomic.Value - riding atomic.Uint64 + riding atomic.Value sneaking, sprinting, swimming, flying, invisible, immobile, onGround, usingItem atomic.Bool @@ -2084,6 +2083,7 @@ func (p *Player) RideEntity(e world.Entity) { func (p *Player) DismountEntity(e world.Entity) { if rideable, ok := e.(entity.Rideable); ok { rideable.RemoveRider(p) + p.SetRiding(nil) for _, v := range p.viewers() { v.ViewEntityLink(p, e, protocol.EntityLinkRemove) } @@ -2130,14 +2130,17 @@ func (p *Player) Seat(e world.Entity) int { return -1 } -// Riding returns the runtime ID of the entity the player is riding. -func (p *Player) Riding() uint64 { - return p.riding.Load() +// Riding returns the entity that the player is riding. +func (p *Player) Riding() world.Entity { + if e, ok := p.riding.Load().(world.Entity); ok { + return e + } + return nil } -// SetRiding saves the runtime ID of the entity the player is riding. -func (p *Player) SetRiding(id uint64) { - p.riding.Store(id) +// SetRiding saves the entity the Rider is currently riding. +func (p *Player) SetRiding(e world.Entity) { + p.riding.Store(e) } // EncodeEntity ... diff --git a/server/session/controllable.go b/server/session/controllable.go index bd39bc7d1..8a6a7e01b 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -64,8 +64,8 @@ type Controllable interface { DismountEntity(e world.Entity) CheckSeats(e world.Entity) Seat(e world.Entity) int - Riding() uint64 - SetRiding(id uint64) + Riding() world.Entity + SetRiding(e world.Entity) StartBreaking(pos cube.Pos, face cube.Face) ContinueBreaking(face cube.Face) diff --git a/server/session/entity_metadata.go b/server/session/entity_metadata.go index cbcfb631e..8b512a457 100644 --- a/server/session/entity_metadata.go +++ b/server/session/entity_metadata.go @@ -56,7 +56,7 @@ func parseEntityMetadata(e world.Entity) entityMetadata { if r, ok := e.(entity.Rider); ok { m[dataKeyRiderSeatPosition] = r.SeatPosition() - if r.Riding() != 0 { + if r.Riding() != nil { m.setFlag(dataKeyFlags, dataFlagRiding) } } diff --git a/server/session/handler_interact.go b/server/session/handler_interact.go index 351ca3b19..7c41e37ae 100644 --- a/server/session/handler_interact.go +++ b/server/session/handler_interact.go @@ -28,12 +28,10 @@ func (h *InteractHandler) Handle(p packet.Packet, s *Session) error { }) break case packet.InteractActionLeaveVehicle: - if s.c.Riding() == 0 { + if s.c.Riding() == nil { return nil } - if e, found := s.entityFromRuntimeID(s.c.Riding()); found { - s.c.DismountEntity(e) - } + s.c.DismountEntity(s.c.Riding()) break default: return fmt.Errorf("unexpected interact packet action %v", pk.ActionType) diff --git a/server/session/handler_player_auth_input.go b/server/session/handler_player_auth_input.go index 9cf578fcd..4c42918e2 100644 --- a/server/session/handler_player_auth_input.go +++ b/server/session/handler_player_auth_input.go @@ -33,9 +33,10 @@ func (h PlayerAuthInputHandler) handleMovement(pk *packet.PlayerAuthInput, s *Se } // Check if player is riding an entity, and move the entity. - if e, found := s.entityFromRuntimeID(s.c.Riding()); found { - if mounted := s.c.Seat(e); mounted == 0 { - rideable, _ := e.(entity.Rideable) + riding := s.c.Riding() + if riding != nil { + if mounted := s.c.Seat(riding); mounted == 0 { + rideable, _ := riding.(entity.Rideable) rideable.Move(pk.MoveVector, pk.Yaw, pk.Pitch) } } diff --git a/server/session/world.go b/server/session/world.go index 802a25a4e..747216535 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -350,14 +350,13 @@ func (s *Session) ViewEntityTeleport(e world.Entity, position mgl64.Vec3) { // ViewEntityLink ... func (s *Session) ViewEntityLink(r world.Entity, rd world.Entity, linkType byte) { - riddenID := s.entityRuntimeID(rd) s.writePacket(&packet.SetActorLink{EntityLink: protocol.EntityLink{ - RiddenEntityUniqueID: int64(riddenID), + RiddenEntityUniqueID: int64(s.entityRuntimeID(rd)), RiderEntityUniqueID: int64(s.entityRuntimeID(r)), Type: linkType, RiderInitiated: true, }}) - s.c.SetRiding(riddenID) + s.c.SetRiding(rd) } // ViewEntityItems ... From ca3c57b2b45658b25e4f3f2b2680de0972c3364f Mon Sep 17 00:00:00 2001 From: Neutronic Date: Wed, 10 Nov 2021 00:46:42 -0800 Subject: [PATCH 10/19] Remove redundant break statements --- server/session/handler_interact.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/session/handler_interact.go b/server/session/handler_interact.go index 7c41e37ae..e7e21f34e 100644 --- a/server/session/handler_interact.go +++ b/server/session/handler_interact.go @@ -26,13 +26,11 @@ func (h *InteractHandler) Handle(p packet.Packet, s *Session) error { WindowID: 0, ContainerType: 0xff, }) - break case packet.InteractActionLeaveVehicle: if s.c.Riding() == nil { return nil } s.c.DismountEntity(s.c.Riding()) - break default: return fmt.Errorf("unexpected interact packet action %v", pk.ActionType) } From d5a5070d19f1dfba969a2963ced75be25b1b2566 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Thu, 11 Nov 2021 13:20:04 -0800 Subject: [PATCH 11/19] Only export necessary methods --- server/entity/rideable.go | 3 +- server/entity/rider.go | 11 +---- server/player/player.go | 51 ++++++++++++++------- server/session/controllable.go | 5 +- server/session/entity_metadata.go | 2 +- server/session/handler_interact.go | 5 +- server/session/handler_player_auth_input.go | 11 ++--- server/session/world.go | 1 - 8 files changed, 47 insertions(+), 42 deletions(-) diff --git a/server/entity/rideable.go b/server/entity/rideable.go index 71cbe4c82..9d835f1c1 100644 --- a/server/entity/rideable.go +++ b/server/entity/rideable.go @@ -2,6 +2,7 @@ package entity import ( "github.com/go-gl/mathgl/mgl32" + "github.com/go-gl/mathgl/mgl64" ) // Rideable is an interface for entities that can be ridden. @@ -15,5 +16,5 @@ type Rideable interface { // RemoveRider removes a rider from the entity. RemoveRider(e Rider) // Move moves the entity using the given vector, yaw, and pitch. - Move(vector mgl32.Vec2, yaw, pitch float32) + Move(vector mgl64.Vec2, yaw, pitch float32) } diff --git a/server/entity/rider.go b/server/entity/rider.go index 95d4d93a5..5bf543960 100644 --- a/server/entity/rider.go +++ b/server/entity/rider.go @@ -13,13 +13,6 @@ type Rider interface { RideEntity(e world.Entity) // DismountEntity unlinks the Rider from an entity. DismountEntity(e world.Entity) - // CheckSeats moves a Rider to the seat corresponding to their current index within the slice of riders. - // It is called whenever a Rider dismounts an entity. - CheckSeats(e world.Entity) - // Seat returns the index of a Rider within the slice of riders. - Seat(e world.Entity) int - // Riding returns the entity that the player is currently riding. - Riding() world.Entity - // SetRiding saves the entity the Rider is currently riding. - SetRiding(e world.Entity) + // RidingEntity returns the entity the player is currently riding and the player's seat index. + RidingEntity() (world.Entity, int) } diff --git a/server/player/player.go b/server/player/player.go index fe071aa8f..dbf18ce08 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -73,7 +73,8 @@ type Player struct { heldSlot *atomic.Uint32 seatPosition atomic.Value - riding atomic.Value + ridingMu sync.Mutex + riding world.Entity sneaking, sprinting, swimming, flying, invisible, immobile, onGround, usingItem atomic.Bool @@ -122,6 +123,7 @@ func New(name string, skin skin.Skin, pos mgl64.Vec3) *Player { speed: *atomic.NewFloat64(0.1), nameTag: *atomic.NewString(name), heldSlot: atomic.NewUint32(0), + riding: nil, locale: language.BritishEnglish, scale: *atomic.NewFloat64(1), } @@ -2059,8 +2061,9 @@ func (p *Player) PunchAir() { // RideEntity links the player to an entity if the entity is rideable and if there is a seat available. func (p *Player) RideEntity(e world.Entity) { if rideable, ok := e.(entity.Rideable); ok { - if p.Seat(e) == -1 { + if p.seat(e) == -1 { rideable.AddRider(p) + p.setRiding(e) riders := rideable.Riders() seat := len(riders) positions := rideable.SeatPositions() @@ -2075,6 +2078,9 @@ func (p *Player) RideEntity(e world.Entity) { v.ViewEntityLink(p, e, byte(linkType)) } } + } else { + // Check and update seat position + p.checkSeats(e) } } } @@ -2083,20 +2089,20 @@ func (p *Player) RideEntity(e world.Entity) { func (p *Player) DismountEntity(e world.Entity) { if rideable, ok := e.(entity.Rideable); ok { rideable.RemoveRider(p) - p.SetRiding(nil) + p.setRiding(nil) for _, v := range p.viewers() { v.ViewEntityLink(p, e, protocol.EntityLinkRemove) } for _, r := range rideable.Riders() { - r.CheckSeats(e) + r.RideEntity(e) } } } // CheckSeats moves a player to the seat corresponding to their current index within the slice of riders. -func (p *Player) CheckSeats(e world.Entity) { +func (p *Player) checkSeats(e world.Entity) { if rideable, ok := e.(entity.Rideable); ok { - seat := p.Seat(e) + seat := p.seat(e) if seat != -1 { positions := rideable.SeatPositions() if positions[seat] != p.seatPosition.Load() { @@ -2117,8 +2123,8 @@ func (p *Player) SeatPosition() mgl32.Vec3 { return p.seatPosition.Load().(mgl32.Vec3) } -// Seat returns the index of a player within the slice of riders. -func (p *Player) Seat(e world.Entity) int { +// seat returns the index of a player within the slice of riders. +func (p *Player) seat(e world.Entity) int { if rideable, ok := e.(entity.Rideable); ok { riders := rideable.Riders() for i, r := range riders { @@ -2130,17 +2136,28 @@ func (p *Player) Seat(e world.Entity) int { return -1 } -// Riding returns the entity that the player is riding. -func (p *Player) Riding() world.Entity { - if e, ok := p.riding.Load().(world.Entity); ok { - return e - } - return nil +// setRiding saves the entity the Rider is currently riding. +func (p *Player) setRiding(e world.Entity) { + p.ridingMu.Lock() + p.riding = e + p.ridingMu.Unlock() } -// SetRiding saves the entity the Rider is currently riding. -func (p *Player) SetRiding(e world.Entity) { - p.riding.Store(e) +// RidingEntity returns the entity the player is currently riding and the player's seat index. +func (p *Player) RidingEntity() (world.Entity, int) { + p.ridingMu.Lock() + defer p.ridingMu.Unlock() + if p.riding != nil { + rideable := p.riding.(world.Entity) + riders := rideable.(entity.Rideable).Riders() + for i, r := range riders { + if r == p { + return rideable, i + } + } + return rideable, -1 + } + return nil, -1 } // EncodeEntity ... diff --git a/server/session/controllable.go b/server/session/controllable.go index 8a6a7e01b..6900c2c32 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -62,10 +62,7 @@ type Controllable interface { SeatPosition() mgl32.Vec3 RideEntity(e world.Entity) DismountEntity(e world.Entity) - CheckSeats(e world.Entity) - Seat(e world.Entity) int - Riding() world.Entity - SetRiding(e world.Entity) + RidingEntity() (world.Entity, int) StartBreaking(pos cube.Pos, face cube.Face) ContinueBreaking(face cube.Face) diff --git a/server/session/entity_metadata.go b/server/session/entity_metadata.go index 8b512a457..b15be6c0a 100644 --- a/server/session/entity_metadata.go +++ b/server/session/entity_metadata.go @@ -56,7 +56,7 @@ func parseEntityMetadata(e world.Entity) entityMetadata { if r, ok := e.(entity.Rider); ok { m[dataKeyRiderSeatPosition] = r.SeatPosition() - if r.Riding() != nil { + if rd, _ := r.RidingEntity(); rd != nil { m.setFlag(dataKeyFlags, dataFlagRiding) } } diff --git a/server/session/handler_interact.go b/server/session/handler_interact.go index e7e21f34e..293fe3ce4 100644 --- a/server/session/handler_interact.go +++ b/server/session/handler_interact.go @@ -27,10 +27,9 @@ func (h *InteractHandler) Handle(p packet.Packet, s *Session) error { ContainerType: 0xff, }) case packet.InteractActionLeaveVehicle: - if s.c.Riding() == nil { - return nil + if e, _ := s.c.RidingEntity(); e != nil { + s.c.DismountEntity(e) } - s.c.DismountEntity(s.c.Riding()) default: return fmt.Errorf("unexpected interact packet action %v", pk.ActionType) } diff --git a/server/session/handler_player_auth_input.go b/server/session/handler_player_auth_input.go index 4c42918e2..56065f06d 100644 --- a/server/session/handler_player_auth_input.go +++ b/server/session/handler_player_auth_input.go @@ -33,12 +33,11 @@ func (h PlayerAuthInputHandler) handleMovement(pk *packet.PlayerAuthInput, s *Se } // Check if player is riding an entity, and move the entity. - riding := s.c.Riding() - if riding != nil { - if mounted := s.c.Seat(riding); mounted == 0 { - rideable, _ := riding.(entity.Rideable) - rideable.Move(pk.MoveVector, pk.Yaw, pk.Pitch) - } + riding, seat := s.c.RidingEntity() + if seat == 0 { + rideable, _ := riding.(entity.Rideable) + m := pk.MoveVector + rideable.Move(mgl64.Vec2{float64(m[0]), float64(m[1])}, pk.Yaw, pk.Pitch) } pk.Position = pk.Position.Sub(mgl32.Vec3{0, 1.62}) // Subtract the base offset of players from the pos. diff --git a/server/session/world.go b/server/session/world.go index 747216535..b40a65897 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -356,7 +356,6 @@ func (s *Session) ViewEntityLink(r world.Entity, rd world.Entity, linkType byte) Type: linkType, RiderInitiated: true, }}) - s.c.SetRiding(rd) } // ViewEntityItems ... From 9455629bfec2d2db7ef35f4c41583eb5b64897fb Mon Sep 17 00:00:00 2001 From: Neutronic Date: Thu, 11 Nov 2021 13:23:15 -0800 Subject: [PATCH 12/19] Remove redundant code --- server/player/player.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/player/player.go b/server/player/player.go index dbf18ce08..9bd685b45 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2148,14 +2148,13 @@ func (p *Player) RidingEntity() (world.Entity, int) { p.ridingMu.Lock() defer p.ridingMu.Unlock() if p.riding != nil { - rideable := p.riding.(world.Entity) - riders := rideable.(entity.Rideable).Riders() + riders := p.riding.(entity.Rideable).Riders() for i, r := range riders { if r == p { - return rideable, i + return p.riding, i } } - return rideable, -1 + return p.riding, -1 } return nil, -1 } From 170ece5929e5852cad8aa3147020c53e7c16900c Mon Sep 17 00:00:00 2001 From: Neutronic Date: Wed, 17 Nov 2021 13:16:50 -0800 Subject: [PATCH 13/19] Changes from review --- server/entity/rider.go | 6 +-- server/player/player.go | 46 ++++++++++--------- server/session/controllable.go | 5 +- server/session/handler_interact.go | 2 +- .../session/handler_inventory_transaction.go | 13 ++---- server/session/world.go | 21 +++++++-- server/world/viewer.go | 8 ++-- 7 files changed, 57 insertions(+), 44 deletions(-) diff --git a/server/entity/rider.go b/server/entity/rider.go index 5bf543960..13e8c229b 100644 --- a/server/entity/rider.go +++ b/server/entity/rider.go @@ -9,10 +9,8 @@ import ( type Rider interface { // SeatPosition returns the Rider's current seat position. SeatPosition() mgl32.Vec3 - // RideEntity links the Rider to an entity if the entity is Rideable and if there is a seat available. - RideEntity(e world.Entity) - // DismountEntity unlinks the Rider from an entity. - DismountEntity(e world.Entity) + // MountEntity mounts the Rider to an entity if the entity is Rideable and if there is a seat available. + MountEntity(e Rideable) // RidingEntity returns the entity the player is currently riding and the player's seat index. RidingEntity() (world.Entity, int) } diff --git a/server/player/player.go b/server/player/player.go index 9bd685b45..8922d45d9 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -31,7 +31,6 @@ import ( "github.com/go-gl/mathgl/mgl32" "github.com/go-gl/mathgl/mgl64" "github.com/google/uuid" - "github.com/sandertv/gophertunnel/minecraft/protocol" "go.uber.org/atomic" "golang.org/x/text/language" "math" @@ -2058,24 +2057,24 @@ func (p *Player) PunchAir() { }) } -// RideEntity links the player to an entity if the entity is rideable and if there is a seat available. -func (p *Player) RideEntity(e world.Entity) { - if rideable, ok := e.(entity.Rideable); ok { +// MountEntity mounts the player to an entity if the entity is rideable and if there is a seat available. +func (p *Player) MountEntity(r entity.Rideable) { + if e, ok := r.(world.Entity); ok { if p.seat(e) == -1 { - rideable.AddRider(p) + r.AddRider(p) p.setRiding(e) - riders := rideable.Riders() + riders := r.Riders() seat := len(riders) - positions := rideable.SeatPositions() + positions := r.SeatPositions() if len(positions) >= seat { p.seatPosition.Store(positions[seat-1]) p.updateState() - linkType := protocol.EntityLinkPassenger + driver := false if seat-1 == 0 { - linkType = protocol.EntityLinkRider + driver = true } for _, v := range p.viewers() { - v.ViewEntityLink(p, e, byte(linkType)) + v.ViewEntityMount(p, e, driver) } } } else { @@ -2085,21 +2084,24 @@ func (p *Player) RideEntity(e world.Entity) { } } -// DismountEntity unlinks the player from an entity. -func (p *Player) DismountEntity(e world.Entity) { - if rideable, ok := e.(entity.Rideable); ok { - rideable.RemoveRider(p) - p.setRiding(nil) - for _, v := range p.viewers() { - v.ViewEntityLink(p, e, protocol.EntityLinkRemove) - } - for _, r := range rideable.Riders() { - r.RideEntity(e) +// DismountEntity dismounts the player from an entity. +func (p *Player) DismountEntity() { + e, _ := p.RidingEntity() + if e != nil { + if rideable, ok := e.(entity.Rideable); ok { + rideable.RemoveRider(p) + p.setRiding(nil) + for _, v := range p.viewers() { + v.ViewEntityDismount(p, e) + } + for _, r := range rideable.Riders() { + r.MountEntity(rideable) + } } } } -// CheckSeats moves a player to the seat corresponding to their current index within the slice of riders. +// checkSeats moves a player to the seat corresponding to their current index within the slice of riders. func (p *Player) checkSeats(e world.Entity) { if rideable, ok := e.(entity.Rideable); ok { seat := p.seat(e) @@ -2109,7 +2111,7 @@ func (p *Player) checkSeats(e world.Entity) { p.seatPosition.Store(positions[seat]) if seat == 0 { for _, v := range p.viewers() { - v.ViewEntityLink(p, e, protocol.EntityLinkRider) + v.ViewEntityMount(p, e, true) } } p.updateState() diff --git a/server/session/controllable.go b/server/session/controllable.go index 6900c2c32..494b55813 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -3,6 +3,7 @@ package session import ( "github.com/df-mc/dragonfly/server/block/cube" "github.com/df-mc/dragonfly/server/cmd" + "github.com/df-mc/dragonfly/server/entity" "github.com/df-mc/dragonfly/server/entity/effect" "github.com/df-mc/dragonfly/server/item" "github.com/df-mc/dragonfly/server/player/form" @@ -60,8 +61,8 @@ type Controllable interface { StopFlying() SeatPosition() mgl32.Vec3 - RideEntity(e world.Entity) - DismountEntity(e world.Entity) + MountEntity(e entity.Rideable) + DismountEntity() RidingEntity() (world.Entity, int) StartBreaking(pos cube.Pos, face cube.Face) diff --git a/server/session/handler_interact.go b/server/session/handler_interact.go index 293fe3ce4..ba115f808 100644 --- a/server/session/handler_interact.go +++ b/server/session/handler_interact.go @@ -28,7 +28,7 @@ func (h *InteractHandler) Handle(p packet.Packet, s *Session) error { }) case packet.InteractActionLeaveVehicle: if e, _ := s.c.RidingEntity(); e != nil { - s.c.DismountEntity(e) + s.c.DismountEntity() } default: return fmt.Errorf("unexpected interact packet action %v", pk.ActionType) diff --git a/server/session/handler_inventory_transaction.go b/server/session/handler_inventory_transaction.go index d569860a2..6078d442b 100644 --- a/server/session/handler_inventory_transaction.go +++ b/server/session/handler_inventory_transaction.go @@ -35,15 +35,6 @@ func (h *InventoryTransactionHandler) Handle(p packet.Packet, s *Session) error if !held.Equal(stackToItem(data.HeldItem.Stack)) { return nil } - if pk.TransactionData.(*protocol.UseItemOnEntityTransactionData).ActionType == protocol.UseItemOnEntityActionInteract { - // Check if the entity is rideable, and if so ride the entity. - e, found := s.entityFromRuntimeID(data.TargetEntityRuntimeID) - if found { - if _, ok := e.(entity.Rideable); ok { - s.c.RideEntity(e) - } - } - } return h.handleUseItemOnEntityTransaction(data, s) case *protocol.UseItemTransactionData: held, _ := s.c.HeldItems() @@ -121,6 +112,10 @@ func (h *InventoryTransactionHandler) handleUseItemOnEntityTransaction(data *pro } switch data.ActionType { case protocol.UseItemOnEntityActionInteract: + // Check if the entity is rideable, and if so ride the entity. + if r, ok := e.(entity.Rideable); ok { + s.c.MountEntity(r) + } s.c.UseItemOnEntity(e) case protocol.UseItemOnEntityActionAttack: s.c.AttackEntity(e) diff --git a/server/session/world.go b/server/session/world.go index b40a65897..b556a0d00 100644 --- a/server/session/world.go +++ b/server/session/world.go @@ -348,13 +348,28 @@ func (s *Session) ViewEntityTeleport(e world.Entity, position mgl64.Vec3) { } } -// ViewEntityLink ... -func (s *Session) ViewEntityLink(r world.Entity, rd world.Entity, linkType byte) { +// ViewEntityMount ... +func (s *Session) ViewEntityMount(r world.Entity, rd world.Entity, driver bool) { + linkType := protocol.EntityLinkPassenger + if driver { + linkType = protocol.EntityLinkRider + } + s.writePacket(&packet.SetActorLink{EntityLink: protocol.EntityLink{ + RiddenEntityUniqueID: int64(s.entityRuntimeID(rd)), + RiderEntityUniqueID: int64(s.entityRuntimeID(r)), + Type: byte(linkType), + RiderInitiated: true, + }}) +} + +// ViewEntityDismount ... +func (s *Session) ViewEntityDismount(r world.Entity, rd world.Entity) { s.writePacket(&packet.SetActorLink{EntityLink: protocol.EntityLink{ RiddenEntityUniqueID: int64(s.entityRuntimeID(rd)), RiderEntityUniqueID: int64(s.entityRuntimeID(r)), - Type: linkType, + Type: byte(protocol.EntityLinkRemove), RiderInitiated: true, + Immediate: true, }}) } diff --git a/server/world/viewer.go b/server/world/viewer.go index 7fdf7f1a8..6a63b1462 100644 --- a/server/world/viewer.go +++ b/server/world/viewer.go @@ -29,9 +29,11 @@ type Viewer interface { // ViewEntityTeleport views the teleportation of an entity. The entity is immediately moved to a different // target position. ViewEntityTeleport(e Entity, position mgl64.Vec3) - // ViewEntityLink views the linking of one entity to another. It is called when any entity rides another or - // changes its link type. - ViewEntityLink(r Entity, rd Entity, linkType byte) + // ViewEntityMount views one entity mounting another. It is called when any entity mounts another or + // changes its role (passenger or driver). + ViewEntityMount(r Entity, rd Entity, driver bool) + // ViewEntityDismount views one entity dismounting another. It is called when any entity is dismounted. + ViewEntityDismount(r Entity, rd Entity) // ViewChunk views the chunk passed at a particular position. It is called for every chunk loaded using // the world.Loader. ViewChunk(pos ChunkPos, c *chunk.Chunk, blockNBT map[cube.Pos]Block) From 27535aaf70750a93406cb0212a431b299289fb2d Mon Sep 17 00:00:00 2001 From: Neutronic Date: Mon, 22 Nov 2021 01:32:04 -0800 Subject: [PATCH 14/19] Mount/Dismount event handlers --- server/player/handler.go | 12 +++++++ server/player/player.go | 75 ++++++++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/server/player/handler.go b/server/player/handler.go index cf01f796a..53e54e4d0 100644 --- a/server/player/handler.go +++ b/server/player/handler.go @@ -104,6 +104,12 @@ type Handler interface { // ctx.Cancel() may be called to prevent the player from dropping the entity.Item passed on the ground. // e.Item() may be called to obtain the item stack dropped. HandleItemDrop(ctx *event.Context, e *entity.Item) + // HandleMount handles when a player mounts an entity. ctx.Cancel() may be called to cancel the player mounting + // an entity. + HandleMount(ctx *event.Context, r entity.Rideable) + // HandleDismount handles when a player mounts an entity. ctx.Cancel() may be called to force the player + // to re-mount the entity. + HandleDismount(ctx *event.Context) // HandleTransfer handles a player being transferred to another server. ctx.Cancel() may be called to // cancel the transfer. HandleTransfer(ctx *event.Context, addr *net.UDPAddr) @@ -198,6 +204,12 @@ func (NopHandler) HandleFoodLoss(*event.Context, int, int) {} // HandleDeath ... func (NopHandler) HandleDeath(damage.Source) {} +// HandleMount ... +func (NopHandler) HandleMount(*event.Context, entity.Rideable) {} + +// HandleDismount ... +func (NopHandler) HandleDismount(*event.Context) {} + // HandleRespawn ... func (NopHandler) HandleRespawn(*mgl64.Vec3) {} diff --git a/server/player/player.go b/server/player/player.go index 8922d45d9..e943e350a 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2059,44 +2059,61 @@ func (p *Player) PunchAir() { // MountEntity mounts the player to an entity if the entity is rideable and if there is a seat available. func (p *Player) MountEntity(r entity.Rideable) { - if e, ok := r.(world.Entity); ok { - if p.seat(e) == -1 { - r.AddRider(p) - p.setRiding(e) - riders := r.Riders() - seat := len(riders) - positions := r.SeatPositions() - if len(positions) >= seat { - p.seatPosition.Store(positions[seat-1]) - p.updateState() - driver := false - if seat-1 == 0 { - driver = true - } - for _, v := range p.viewers() { - v.ViewEntityMount(p, e, driver) + ctx := event.C() + p.handler().HandleMount(ctx, r) + ctx.Continue(func() { + if e, ok := r.(world.Entity); ok { + if p.seat(e) == -1 { + r.AddRider(p) + p.setRiding(e) + riders := r.Riders() + seat := len(riders) + positions := r.SeatPositions() + if len(positions) >= seat { + p.seatPosition.Store(positions[seat-1]) + p.updateState() + driver := false + if seat-1 == 0 { + driver = true + } + for _, v := range p.viewers() { + v.ViewEntityMount(p, e, driver) + } } + } else { + // Check and update seat position + p.checkSeats(e) } - } else { - // Check and update seat position - p.checkSeats(e) } - } + }) } // DismountEntity dismounts the player from an entity. func (p *Player) DismountEntity() { - e, _ := p.RidingEntity() + ctx := event.C() + e, seat := p.RidingEntity() if e != nil { if rideable, ok := e.(entity.Rideable); ok { - rideable.RemoveRider(p) - p.setRiding(nil) - for _, v := range p.viewers() { - v.ViewEntityDismount(p, e) - } - for _, r := range rideable.Riders() { - r.MountEntity(rideable) - } + p.handler().HandleDismount(ctx) + ctx.Stop(func() { + driver := false + if seat-1 == 0 { + driver = true + } + for _, v := range p.viewers() { + v.ViewEntityMount(p, e, driver) + } + }) + ctx.Continue(func() { + rideable.RemoveRider(p) + p.setRiding(nil) + for _, v := range p.viewers() { + v.ViewEntityDismount(p, e) + } + for _, r := range rideable.Riders() { + r.MountEntity(rideable) + } + }) } } } From 412cd373d4ac8b8bd8ab618d1c5c60b580891508 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Tue, 23 Nov 2021 01:12:00 -0800 Subject: [PATCH 15/19] Mount/Dismount handlers --- server/player/player.go | 14 ++------------ server/session/handler_player_auth_input.go | 11 +++++++---- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/server/player/player.go b/server/player/player.go index e943e350a..3c11e56c3 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2072,12 +2072,8 @@ func (p *Player) MountEntity(r entity.Rideable) { if len(positions) >= seat { p.seatPosition.Store(positions[seat-1]) p.updateState() - driver := false - if seat-1 == 0 { - driver = true - } for _, v := range p.viewers() { - v.ViewEntityMount(p, e, driver) + v.ViewEntityMount(p, e, seat-1 == 0) } } } else { @@ -2096,13 +2092,7 @@ func (p *Player) DismountEntity() { if rideable, ok := e.(entity.Rideable); ok { p.handler().HandleDismount(ctx) ctx.Stop(func() { - driver := false - if seat-1 == 0 { - driver = true - } - for _, v := range p.viewers() { - v.ViewEntityMount(p, e, driver) - } + p.s.ViewEntityMount(p, e, seat-1 == 0) }) ctx.Continue(func() { rideable.RemoveRider(p) diff --git a/server/session/handler_player_auth_input.go b/server/session/handler_player_auth_input.go index 56065f06d..3575d6374 100644 --- a/server/session/handler_player_auth_input.go +++ b/server/session/handler_player_auth_input.go @@ -34,10 +34,13 @@ func (h PlayerAuthInputHandler) handleMovement(pk *packet.PlayerAuthInput, s *Se // Check if player is riding an entity, and move the entity. riding, seat := s.c.RidingEntity() - if seat == 0 { - rideable, _ := riding.(entity.Rideable) - m := pk.MoveVector - rideable.Move(mgl64.Vec2{float64(m[0]), float64(m[1])}, pk.Yaw, pk.Pitch) + if riding != nil { + if seat == 0 { + rideable, _ := riding.(entity.Rideable) + m := pk.MoveVector + rideable.Move(mgl64.Vec2{float64(m[0]), float64(m[1])}, pk.Yaw, pk.Pitch) + } + s.ViewEntityMount(s.c, riding, seat-1 == 0) } pk.Position = pk.Position.Sub(mgl32.Vec3{0, 1.62}) // Subtract the base offset of players from the pos. From 2308e41c08b48a5db924bc668cf2d1fc6cbfd531 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Tue, 23 Nov 2021 01:20:29 -0800 Subject: [PATCH 16/19] entity.Rideable implements world.Entity --- server/entity/rideable.go | 2 ++ server/player/player.go | 30 ++++++++++++++---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/server/entity/rideable.go b/server/entity/rideable.go index 9d835f1c1..8595ad6c8 100644 --- a/server/entity/rideable.go +++ b/server/entity/rideable.go @@ -1,12 +1,14 @@ package entity import ( + "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl32" "github.com/go-gl/mathgl/mgl64" ) // Rideable is an interface for entities that can be ridden. type Rideable interface { + world.Entity // SeatPositions returns the possible seat positions for an entity in the order that they will be filled. SeatPositions() []mgl32.Vec3 // Riders returns a slice entities that are currently riding an entity in the order that they were added. diff --git a/server/player/player.go b/server/player/player.go index 3c11e56c3..e4e7648ff 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -2062,24 +2062,22 @@ func (p *Player) MountEntity(r entity.Rideable) { ctx := event.C() p.handler().HandleMount(ctx, r) ctx.Continue(func() { - if e, ok := r.(world.Entity); ok { - if p.seat(e) == -1 { - r.AddRider(p) - p.setRiding(e) - riders := r.Riders() - seat := len(riders) - positions := r.SeatPositions() - if len(positions) >= seat { - p.seatPosition.Store(positions[seat-1]) - p.updateState() - for _, v := range p.viewers() { - v.ViewEntityMount(p, e, seat-1 == 0) - } + if p.seat(r) == -1 { + r.AddRider(p) + p.setRiding(r) + riders := r.Riders() + seat := len(riders) + positions := r.SeatPositions() + if len(positions) >= seat { + p.seatPosition.Store(positions[seat-1]) + p.updateState() + for _, v := range p.viewers() { + v.ViewEntityMount(p, r, seat-1 == 0) } - } else { - // Check and update seat position - p.checkSeats(e) } + } else { + // Check and update seat position + p.checkSeats(r) } }) } From 40393e8a7730042c045617ca07b967f062274190 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Thu, 25 Nov 2021 12:46:58 -0800 Subject: [PATCH 17/19] use entity.Rideable instead of world.Entity --- server/entity/rider.go | 3 +- server/player/player.go | 79 ++++++++++++++++------------------ server/session/controllable.go | 2 +- 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/server/entity/rider.go b/server/entity/rider.go index 13e8c229b..4d3ddfdb1 100644 --- a/server/entity/rider.go +++ b/server/entity/rider.go @@ -1,7 +1,6 @@ package entity import ( - "github.com/df-mc/dragonfly/server/world" "github.com/go-gl/mathgl/mgl32" ) @@ -12,5 +11,5 @@ type Rider interface { // MountEntity mounts the Rider to an entity if the entity is Rideable and if there is a seat available. MountEntity(e Rideable) // RidingEntity returns the entity the player is currently riding and the player's seat index. - RidingEntity() (world.Entity, int) + RidingEntity() (Rideable, int) } diff --git a/server/player/player.go b/server/player/player.go index e4e7648ff..0226825a4 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -73,7 +73,7 @@ type Player struct { seatPosition atomic.Value ridingMu sync.Mutex - riding world.Entity + riding entity.Rideable sneaking, sprinting, swimming, flying, invisible, immobile, onGround, usingItem atomic.Bool @@ -122,7 +122,6 @@ func New(name string, skin skin.Skin, pos mgl64.Vec3) *Player { speed: *atomic.NewFloat64(0.1), nameTag: *atomic.NewString(name), heldSlot: atomic.NewUint32(0), - riding: nil, locale: language.BritishEnglish, scale: *atomic.NewFloat64(1), } @@ -2075,10 +2074,10 @@ func (p *Player) MountEntity(r entity.Rideable) { v.ViewEntityMount(p, r, seat-1 == 0) } } - } else { - // Check and update seat position - p.checkSeats(r) + return } + // Check and update seat position + p.checkSeats(r) }) } @@ -2087,40 +2086,36 @@ func (p *Player) DismountEntity() { ctx := event.C() e, seat := p.RidingEntity() if e != nil { - if rideable, ok := e.(entity.Rideable); ok { - p.handler().HandleDismount(ctx) - ctx.Stop(func() { - p.s.ViewEntityMount(p, e, seat-1 == 0) - }) - ctx.Continue(func() { - rideable.RemoveRider(p) - p.setRiding(nil) - for _, v := range p.viewers() { - v.ViewEntityDismount(p, e) - } - for _, r := range rideable.Riders() { - r.MountEntity(rideable) - } - }) - } + p.handler().HandleDismount(ctx) + ctx.Stop(func() { + p.s.ViewEntityMount(p, e, seat-1 == 0) + }) + ctx.Continue(func() { + e.RemoveRider(p) + p.setRiding(nil) + for _, v := range p.viewers() { + v.ViewEntityDismount(p, e) + } + for _, r := range e.Riders() { + r.MountEntity(e) + } + }) } } // checkSeats moves a player to the seat corresponding to their current index within the slice of riders. -func (p *Player) checkSeats(e world.Entity) { - if rideable, ok := e.(entity.Rideable); ok { - seat := p.seat(e) - if seat != -1 { - positions := rideable.SeatPositions() - if positions[seat] != p.seatPosition.Load() { - p.seatPosition.Store(positions[seat]) - if seat == 0 { - for _, v := range p.viewers() { - v.ViewEntityMount(p, e, true) - } +func (p *Player) checkSeats(e entity.Rideable) { + seat := p.seat(e) + if seat != -1 { + positions := e.SeatPositions() + if positions[seat] != p.seatPosition.Load() { + p.seatPosition.Store(positions[seat]) + if seat == 0 { + for _, v := range p.viewers() { + v.ViewEntityMount(p, e, true) } - p.updateState() } + p.updateState() } } } @@ -2131,31 +2126,29 @@ func (p *Player) SeatPosition() mgl32.Vec3 { } // seat returns the index of a player within the slice of riders. -func (p *Player) seat(e world.Entity) int { - if rideable, ok := e.(entity.Rideable); ok { - riders := rideable.Riders() - for i, r := range riders { - if r == p { - return i - } +func (p *Player) seat(e entity.Rideable) int { + riders := e.Riders() + for i, r := range riders { + if r == p { + return i } } return -1 } // setRiding saves the entity the Rider is currently riding. -func (p *Player) setRiding(e world.Entity) { +func (p *Player) setRiding(e entity.Rideable) { p.ridingMu.Lock() p.riding = e p.ridingMu.Unlock() } // RidingEntity returns the entity the player is currently riding and the player's seat index. -func (p *Player) RidingEntity() (world.Entity, int) { +func (p *Player) RidingEntity() (entity.Rideable, int) { p.ridingMu.Lock() defer p.ridingMu.Unlock() if p.riding != nil { - riders := p.riding.(entity.Rideable).Riders() + riders := p.riding.Riders() for i, r := range riders { if r == p { return p.riding, i diff --git a/server/session/controllable.go b/server/session/controllable.go index 494b55813..a0a75b54a 100644 --- a/server/session/controllable.go +++ b/server/session/controllable.go @@ -63,7 +63,7 @@ type Controllable interface { SeatPosition() mgl32.Vec3 MountEntity(e entity.Rideable) DismountEntity() - RidingEntity() (world.Entity, int) + RidingEntity() (entity.Rideable, int) StartBreaking(pos cube.Pos, face cube.Face) ContinueBreaking(face cube.Face) From 2b75cd8a87082db2a53de6ed2387fcd944145b37 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Thu, 25 Nov 2021 13:26:59 -0800 Subject: [PATCH 18/19] remove redundant type assertion --- server/session/handler_player_auth_input.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/session/handler_player_auth_input.go b/server/session/handler_player_auth_input.go index 3575d6374..96e480d19 100644 --- a/server/session/handler_player_auth_input.go +++ b/server/session/handler_player_auth_input.go @@ -36,9 +36,8 @@ func (h PlayerAuthInputHandler) handleMovement(pk *packet.PlayerAuthInput, s *Se riding, seat := s.c.RidingEntity() if riding != nil { if seat == 0 { - rideable, _ := riding.(entity.Rideable) m := pk.MoveVector - rideable.Move(mgl64.Vec2{float64(m[0]), float64(m[1])}, pk.Yaw, pk.Pitch) + riding.Move(mgl64.Vec2{float64(m[0]), float64(m[1])}, pk.Yaw, pk.Pitch) } s.ViewEntityMount(s.c, riding, seat-1 == 0) } From 1f92826095498afcf1a630e390164cfababbca08 Mon Sep 17 00:00:00 2001 From: Neutronic Date: Thu, 25 Nov 2021 13:42:17 -0800 Subject: [PATCH 19/19] Dismount entity on player death or disconnect --- server/player/player.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/player/player.go b/server/player/player.go index 0226825a4..124a1ed09 100644 --- a/server/player/player.go +++ b/server/player/player.go @@ -797,6 +797,7 @@ func (p *Player) kill(src damage.Source) { // Wait for a little bit before removing the entity. The client displays a death animation while the // player is dying. time.AfterFunc(time.Millisecond*1100, func() { + p.DismountEntity() if p.session() == session.Nop { _ = p.Close() return @@ -2246,7 +2247,7 @@ func (p *Player) canReach(pos mgl64.Vec3) bool { // disconnecting of players. func (p *Player) close() { p.handler().HandleQuit() - + p.DismountEntity() p.Handle(NopHandler{}) chat.Global.Unsubscribe(p)