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

Nether portals and mechanics #426

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
646907e
Initial implementation of nether portals (WIP)
JustTalDevelops Feb 19, 2022
78f1ade
block/portal.go: Simply portal deactivation on neighbour change.
JustTalDevelops Feb 19, 2022
f777865
player/player.go: Make Travel exported.
JustTalDevelops Feb 19, 2022
694f4c2
world/world.go: Moved Place checks to the PlaceBlock function.
JustTalDevelops Feb 19, 2022
5b72308
block/portal.go: Fix destroying a frame not destroying all frames.
JustTalDevelops Feb 19, 2022
d6e58bd
player/player.go: Default the portal timeout to true.
JustTalDevelops Feb 19, 2022
50b9b73
player/player.go: Don't request a new transfer during a transfer if t…
JustTalDevelops Feb 19, 2022
bed9456
block/portal.go: Destroy portals when water flows into a frame.
JustTalDevelops Feb 19, 2022
5577268
portal/scan.go: Allow portals that don't specify requirements to be m…
JustTalDevelops Feb 19, 2022
656a992
portal/scan.go: Fix completion check.
JustTalDevelops Feb 19, 2022
69c1c07
player/player.go: Ensure the position is only translated if the sourc…
JustTalDevelops Feb 19, 2022
8711c67
portal/scan.go: Simplify logic.
JustTalDevelops Feb 20, 2022
38c3928
Revert "portal/scan.go: Simplify logic."
JustTalDevelops Feb 20, 2022
fc2cf59
Fix multi-axis scan logic
JustTalDevelops Feb 20, 2022
ade7b4d
Detect portal collision using their block models
JustTalDevelops Feb 22, 2022
cdc901c
player/player.go: Fix typo.
JustTalDevelops Feb 22, 2022
95bc01e
portal/nether.go: Fixed intersecting portals.
JustTalDevelops Feb 22, 2022
7a76d60
model/portal.go: Fix missing zero before decimal point.
JustTalDevelops Feb 22, 2022
eedec39
Allow fire blocks to be overridden by portal ignition
JustTalDevelops Feb 23, 2022
fca683d
Implement TravelComputers for other entities that can travel through …
JustTalDevelops Feb 23, 2022
8538faf
entity/travel.go: Slightly increase AABB growth.
JustTalDevelops Feb 23, 2022
51f7e36
Use the *world.World provided in Tick.
JustTalDevelops Feb 23, 2022
4431fa3
entity/travel.go: Only grow AABB after the BlocksAround call
JustTalDevelops Feb 23, 2022
479d6b6
Merge remote-tracking branch 'origin/master' into feature/nether-portals
JustTalDevelops Feb 24, 2022
d8d8f3a
block/portal.go: Fix typo.
JustTalDevelops Feb 26, 2022
b45deae
Remove world.Entity as an Instantaneous parameter
JustTalDevelops Feb 27, 2022
b431bd7
Undo unnecessary ordering
JustTalDevelops Feb 27, 2022
3bb09b0
entity/travel.go: Update Traveller doc.
JustTalDevelops Feb 27, 2022
611f4aa
Use an interface to detect a portal collision
JustTalDevelops Feb 27, 2022
8223c9c
Merge branch 'master' into feature/nether-portals
JustTalDevelops Jul 31, 2022
5e3b773
portal/nether.go: Various fixes.
JustTalDevelops Jul 31, 2022
0295a6f
Merge branch 'master' into feature/nether-portals
JustTalDevelops Jul 31, 2022
cfc74d7
entity/travel.go: Various improvements.
JustTalDevelops Jul 31, 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
15 changes: 15 additions & 0 deletions server/block/fire.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/df-mc/dragonfly/server/entity/damage"
"github.com/df-mc/dragonfly/server/event"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/portal"
"math/rand"
"time"
)
Expand Down Expand Up @@ -207,6 +208,20 @@ func (f Fire) EntityInside(_ cube.Pos, _ *world.World, e world.Entity) {
}
}

// Place ...
func (Fire) Place(pos cube.Pos, w *world.World) bool {
for _, f := range cube.Faces() {
if o, ok := w.Block(pos.Side(f)).(Obsidian); ok && !o.Crying {
if p, ok := portal.NetherPortalFromPos(w, pos); ok && p.Framed() && !p.Activated() {
p.Activate()
return false
}
return true
}
}
return true
}

// ScheduledTick ...
func (f Fire) ScheduledTick(pos cube.Pos, w *world.World, r *rand.Rand) {
f.tick(pos, w, r)
Expand Down
5 changes: 5 additions & 0 deletions server/block/hash.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions server/block/model/portal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package model

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/go-gl/mathgl/mgl64"
)

// Portal is a model used by portal blocks.
type Portal struct {
// Axis is the axis which the portal faces.
Axis cube.Axis
}

// BBox ...
func (p Portal) BBox(cube.Pos, *world.World) []cube.BBox {
min, max := mgl64.Vec3{0, 0, 0.375}, mgl64.Vec3{1, 1, 0.25}
if p.Axis == cube.Z {
min[0], min[2], max[0], max[2] = 0.375, 0, 0.25, 1
}
return []cube.BBox{cube.Box(min[0], min[1], min[2], max[0], max[1], max[2])}
}

// FaceSolid ...
func (Portal) FaceSolid(cube.Pos, cube.Face, *world.World) bool {
return false
}
6 changes: 6 additions & 0 deletions server/block/obsidian.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package block

import (
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
)

// Obsidian is a dark purple block known for its high blast resistance and strength, most commonly found when
Expand Down Expand Up @@ -37,6 +38,11 @@ func (o Obsidian) EncodeBlock() (string, map[string]any) {
return "minecraft:obsidian", nil
}

// Frame ...
func (o Obsidian) Frame(dimension world.Dimension) bool {
return dimension == world.Nether
}

// BreakInfo ...
func (o Obsidian) BreakInfo() BreakInfo {
return newBreakInfo(35, func(t item.Tool) bool {
Expand Down
43 changes: 43 additions & 0 deletions server/block/portal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package block

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/block/model"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/portal"
)

// Portal is the translucent part of the nether portal that teleports the player to and from the Nether.
type Portal struct {
transparent

// Axis is the axis which the portal faces.
Axis cube.Axis
}

// Model ...
func (p Portal) Model() world.BlockModel {
return model.Portal{Axis: p.Axis}
}

// Portal ...
func (Portal) Portal() world.Dimension {
return world.Nether
}

// HasLiquidDrops ...
func (p Portal) HasLiquidDrops() bool {
return false
}

// EncodeBlock ...
func (p Portal) EncodeBlock() (string, map[string]interface{}) {
return "minecraft:portal", map[string]interface{}{"portal_axis": p.Axis.String()}
}

// NeighbourUpdateTick ...
func (p Portal) NeighbourUpdateTick(pos, _ cube.Pos, w *world.World) {
if n, ok := portal.NetherPortalFromPos(w, pos); ok && (!n.Framed() || !n.Activated()) {
n.Deactivate()
}
}
3 changes: 3 additions & 0 deletions server/block/register.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package block

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/item"
"github.com/df-mc/dragonfly/server/world"
)
Expand Down Expand Up @@ -74,6 +75,8 @@ func init() {
world.RegisterBlock(PackedIce{})
world.RegisterBlock(PackedMud{})
world.RegisterBlock(Podzol{})
world.RegisterBlock(Portal{Axis: cube.X})
world.RegisterBlock(Portal{Axis: cube.Z})
world.RegisterBlock(QuartzBricks{})
world.RegisterBlock(RawCopper{})
world.RegisterBlock(RawGold{})
Expand Down
30 changes: 25 additions & 5 deletions server/entity/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Item struct {
age, pickupDelay int
i item.Stack

t *TravelComputer
c *MovementComputer
}

Expand All @@ -31,11 +32,17 @@ func NewItem(i item.Stack, pos mgl64.Vec3) *Item {
}
i = nbtconv.ReadItem(nbtconv.WriteItem(i, true), nil)

it := &Item{i: i, pickupDelay: 10, c: &MovementComputer{
Gravity: 0.04,
DragBeforeGravity: true,
Drag: 0.02,
}}
it := &Item{
i: i,
pickupDelay: 10,

t: &TravelComputer{Instantaneous: func() bool { return true }},
c: &MovementComputer{
Gravity: 0.04,
DragBeforeGravity: true,
Drag: 0.02,
},
}
it.transform = newTransform(it, pos)
return it
}
Expand Down Expand Up @@ -77,6 +84,8 @@ func (it *Item) Tick(w *world.World, current int64) {
it.pos, it.vel = m.pos, m.vel
it.mu.Unlock()

it.t.TickTravelling(it)

m.Send()

if m.pos[1] < float64(w.Range()[0]) && current%10 == 0 {
Expand All @@ -95,6 +104,17 @@ func (it *Item) Tick(w *world.World, current int64) {
}
}

// Teleport teleports the item to the given position.
func (it *Item) Teleport(pos mgl64.Vec3) {
it.mu.Lock()
defer it.mu.Unlock()

for _, v := range it.World().Viewers(pos) {
v.ViewEntityTeleport(it, pos)
}
it.pos = pos
}

// checkNearby checks the entities of the chunks around for item collectors and other item stacks. If a
// collector is found in range, the item will be picked up. If another item stack with the same item type is
// found in range, the item stacks will merge.
Expand Down
122 changes: 122 additions & 0 deletions server/entity/travel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package entity

import (
"github.com/df-mc/dragonfly/server/block/cube"
"github.com/df-mc/dragonfly/server/world"
"github.com/df-mc/dragonfly/server/world/portal"
"github.com/go-gl/mathgl/mgl64"
"math"
"sync"
"time"
)

// TravelComputer handles the interdimensional travelling of an entity.
type TravelComputer struct {
// Instantaneous is a function that returns true if the entity given can travel instantly.
Instantaneous func() bool

mu sync.RWMutex
start time.Time
awaitingTravel bool
travelling bool
timedOut bool
}

// Traveller represents a world.Entity that can travel between dimensions.
type Traveller interface {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I'm a massive fan of the term Travel and Traveller, it really doesn't make it clear that this is about portals. Do you reckon we could name this anything else?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am honestly not sure, I'm pretty awful at naming things lmao

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any suggestions?

world.Entity
// Teleport teleports the entity to the position given.
Teleport(pos mgl64.Vec3)
}

// portalBlock represents a block that can be used as a portal to travel between dimensions.
type portalBlock interface {
world.Block
// Portal returns the dimension that the portal leads to.
Portal() world.Dimension
}

// TickTravelling checks if the player is colliding with a nether portal block. If so, it teleports the player
// to the other dimension after four seconds or instantly if instantaneous is true.
func (t *TravelComputer) TickTravelling(e Traveller) {
w := e.World()
box := e.BBox().Translate(e.Position()).Grow(0.25)

min, max := box.Min(), box.Max()
minX, minY, minZ := int(math.Floor(min[0])), int(math.Floor(min[1])), int(math.Floor(min[2]))
maxX, maxY, maxZ := int(math.Ceil(max[0])), int(math.Ceil(max[1])), int(math.Ceil(max[2]))
found, target := false, world.Dimension(nil)
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
for z := minZ; z <= maxZ; z++ {
pos := cube.Pos{x, y, z}
p, ok := w.Block(pos).(portalBlock)
if !ok {
continue
}
for _, blockBox := range p.Model().BBox(pos, w) {
if blockBox.Translate(pos.Vec3()).IntersectsWith(box) {
found, target = true, p.Portal()
break
}
}
}
}
}

t.mu.Lock()
defer t.mu.Unlock()
if !found {
if t.travelling {
// Don't reset if we're travelling.
return
}
t.timedOut, t.awaitingTravel = false, false
return
}

switch target {
case world.Nether:
if t.timedOut {
// Timed out, we can't travel through Nether portals.
return
}
if t.Instantaneous() || (t.awaitingTravel && time.Since(t.start) >= time.Second*4) {
t.mu.Unlock()
t.Travel(e, w, w.PortalDestination(world.Nether))
t.mu.Lock()
} else if !t.awaitingTravel {
t.start, t.awaitingTravel = time.Now(), true
}
}
}

// Travel moves the player to the given Nether or Overworld world, and translates the player's current position based
// on the source world.
func (t *TravelComputer) Travel(e Traveller, source *world.World, destination *world.World) {
sourceDimension, targetDimension := source.Dimension(), destination.Dimension()
pos := cube.PosFromVec3(e.Position())
if sourceDimension == world.Overworld {
pos = cube.Pos{pos.X() / 8, pos.Y() + sourceDimension.Range().Min(), pos.Z() / 8}
} else if sourceDimension == world.Nether {
pos = cube.Pos{pos.X() * 8, pos.Y() - targetDimension.Range().Min(), pos.Z() * 8}
}

t.mu.Lock()
defer t.mu.Unlock()
t.travelling, t.timedOut, t.awaitingTravel = true, true, false

go func() {
spawn := pos.Vec3Middle()
if netherPortal, ok := portal.FindOrCreateNetherPortal(destination, pos, 128); ok {
spawn = netherPortal.Spawn().Vec3Middle()
}

destination.AddEntity(e)
e.Teleport(spawn)

t.mu.Lock()
defer t.mu.Unlock()
t.travelling = false
}()
}
6 changes: 5 additions & 1 deletion server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type Player struct {

enchantSeed atomic.Int64

tc *entity.TravelComputer
mc *entity.MovementComputer

breaking atomic.Bool
Expand Down Expand Up @@ -136,7 +137,8 @@ func New(name string, skin skin.Skin, pos mgl64.Vec3) *Player {
immunity: *atomic.NewValue(time.Now()),
pos: *atomic.NewValue(pos),
cooldowns: make(map[string]time.Time),
mc: &entity.MovementComputer{Gravity: 0.06, Drag: 0.02, DragBeforeGravity: true},
mc: &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true},
tc: &entity.TravelComputer{Instantaneous: func() bool { return p.GameMode().CreativeInventory() }},
}
return p
}
Expand Down Expand Up @@ -2259,6 +2261,8 @@ func (p *Player) Tick(w *world.World, current int64) {
} else {
p.vel.Store(mgl64.Vec3{})
}

p.tc.TickTravelling(p)
}

// tickAirSupply tick's the player's air supply, consuming it when underwater, and replenishing it when out of water.
Expand Down
2 changes: 2 additions & 0 deletions server/world/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type TickerEntity interface {
Entity
// Tick ticks the entity with the current World and tick passed.
Tick(w *World, current int64)

Entity
JustTalDevelops marked this conversation as resolved.
Show resolved Hide resolved
}

// SaveableEntity is an Entity that can be saved and loaded with the World it was added to. These entities can be
Expand Down
Loading