Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement entity riding #330

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e3f5270
Send available entities to player on session start
NeutronicMC Nov 7, 2021
d4d9dda
Remove debug message
NeutronicMC Nov 7, 2021
a75b16b
Move actorIdentifier struct inside sendAvailableEntities method
NeutronicMC Nov 7, 2021
78129ef
world.Entities() returns slice of entities
NeutronicMC Nov 7, 2021
ac88571
Implement entity riding
NeutronicMC Nov 9, 2021
95b15ef
Change GetSeat to Seat
NeutronicMC Nov 10, 2021
e1376ed
Fix calling GetSeat instead of CheckSeats on Dismount
NeutronicMC Nov 10, 2021
04a058f
Add docs for Rider and Rideable interfaces
NeutronicMC Nov 10, 2021
d0fd0cb
Use world.Entity instead of runtime ids and remove redundant seatMu
NeutronicMC Nov 10, 2021
65e8524
Merge branch 'df-mc:master' into master
NeutronicMC Nov 10, 2021
ca3c57b
Remove redundant break statements
NeutronicMC Nov 10, 2021
30928f4
Merge remote-tracking branch 'origin/master'
NeutronicMC Nov 10, 2021
d5a5070
Only export necessary methods
NeutronicMC Nov 11, 2021
9455629
Remove redundant code
NeutronicMC Nov 11, 2021
170ece5
Changes from review
NeutronicMC Nov 17, 2021
27535aa
Mount/Dismount event handlers
NeutronicMC Nov 22, 2021
412cd37
Mount/Dismount handlers
NeutronicMC Nov 23, 2021
2308e41
entity.Rideable implements world.Entity
NeutronicMC Nov 23, 2021
40393e8
use entity.Rideable instead of world.Entity
NeutronicMC Nov 25, 2021
2b75cd8
remove redundant type assertion
NeutronicMC Nov 25, 2021
1f92826
Dismount entity on player death or disconnect
NeutronicMC Nov 25, 2021
932e56f
Merge branch 'master' into NeutronicMC_master
DaPigGuy Jan 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions server/entity/rideable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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.
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 mgl64.Vec2, yaw, pitch float32)
}
16 changes: 16 additions & 0 deletions server/entity/rider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package entity

import (
"github.com/df-mc/dragonfly/server/world"
"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
// 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)
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions server/player/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) {}

Expand Down
116 changes: 116 additions & 0 deletions server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ 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"
"go.uber.org/atomic"
Expand Down Expand Up @@ -70,6 +71,10 @@ type Player struct {
armour *inventory.Armour
heldSlot *atomic.Uint32

seatPosition atomic.Value
ridingMu sync.Mutex
riding world.Entity
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved

sneaking, sprinting, swimming, flying,
invisible, immobile, onGround, usingItem atomic.Bool
usingSince atomic.Int64
Expand Down Expand Up @@ -117,6 +122,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,
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved
locale: language.BritishEnglish,
scale: *atomic.NewFloat64(1),
}
Expand All @@ -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
}

Expand Down Expand Up @@ -2050,6 +2057,115 @@ 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) {
ctx := event.C()
p.handler().HandleMount(ctx, r)
ctx.Continue(func() {
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)
}
}
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved
} else {
// Check and update seat position
p.checkSeats(r)
}
})
}

// DismountEntity dismounts the player from an entity.
func (p *Player) DismountEntity() {
ctx := event.C()
e, seat := p.RidingEntity()
if e != nil {
if rideable, ok := e.(entity.Rideable); ok {
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved
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)
}
})
}
}
}

// checkSeats moves a player to the seat corresponding to their current index within the slice of riders.
func (p *Player) checkSeats(e world.Entity) {
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
p.updateState()
}
}
}
}

// SeatPosition returns the position of the player's seat.
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 {
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved
if rideable, ok := e.(entity.Rideable); ok {
riders := rideable.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) {
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) {
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved
p.ridingMu.Lock()
defer p.ridingMu.Unlock()
if p.riding != nil {
riders := p.riding.(entity.Rideable).Riders()
for i, r := range riders {
if r == p {
return p.riding, i
}
}
return p.riding, -1
}
return nil, -1
}

// EncodeEntity ...
func (p *Player) EncodeEntity() string {
return "minecraft:player"
Expand Down
7 changes: 7 additions & 0 deletions server/session/controllable.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ 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"
"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"
)
Expand Down Expand Up @@ -58,6 +60,11 @@ type Controllable interface {
Flying() bool
StopFlying()

SeatPosition() mgl32.Vec3
MountEntity(e entity.Rideable)
DismountEntity()
RidingEntity() (world.Entity, int)
NeutronicMC marked this conversation as resolved.
Show resolved Hide resolved

StartBreaking(pos cube.Pos, face cube.Face)
ContinueBreaking(face cube.Face)
FinishBreaking()
Expand Down
9 changes: 9 additions & 0 deletions server/session/entity_metadata.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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 rd, _ := r.RidingEntity(); rd != nil {
m.setFlag(dataKeyFlags, dataFlagRiding)
}
}
if n, ok := e.(named); ok {
m[dataKeyNameTag] = n.NameTag()
m[dataKeyAlwaysShowNameTag] = uint8(1)
Expand Down Expand Up @@ -98,6 +106,7 @@ const (
dataKeyScale = 38
dataKeyBoundingBoxWidth = 53
dataKeyBoundingBoxHeight = 54
dataKeyRiderSeatPosition = 56
dataKeyAlwaysShowNameTag = 81
)

Expand Down
4 changes: 4 additions & 0 deletions server/session/handler_interact.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func (h *InteractHandler) Handle(p packet.Packet, s *Session) error {
WindowID: 0,
ContainerType: 0xff,
})
case packet.InteractActionLeaveVehicle:
if e, _ := s.c.RidingEntity(); e != nil {
s.c.DismountEntity()
}
default:
return fmt.Errorf("unexpected interact packet action %v", pk.ActionType)
}
Expand Down
5 changes: 5 additions & 0 deletions server/session/handler_inventory_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -111,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)
Expand Down
11 changes: 11 additions & 0 deletions server/session/handler_player_auth_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ 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 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.

newPos := vec32To64(pk.Position)
Expand Down
25 changes: 25 additions & 0 deletions server/session/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,31 @@ func (s *Session) ViewEntityTeleport(e world.Entity, position mgl64.Vec3) {
}
}

// 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: byte(protocol.EntityLinkRemove),
RiderInitiated: true,
Immediate: true,
}})
}

// ViewEntityItems ...
func (s *Session) ViewEntityItems(e world.Entity) {
runtimeID := s.entityRuntimeID(e)
Expand Down
5 changes: 5 additions & 0 deletions server/world/viewer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +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)
// 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)
Expand Down