func (p *Player) Hurt(dmg float64, source damage.Source) (float64, bool) {
	if p.Dead() || !p.GameMode().AllowsTakingDamage() {
		return 0, false
	}
	for _, e := range p.Effects() {
		if _, ok := e.Type().(effect.FireResistance); ok && (source == damage.SourceFire{} || source == damage.SourceFireTick{} || source == damage.SourceLava{}) {
			return 0, false
		}
	}
	var (
		ctx        = event.C()
		vulnerable = false
		n          = 0.0
	)
	p.handler().HandleHurt(ctx, &dmg, source)
	ctx.Continue(func() {
		vulnerable = true
		if dmg < 0 {
			return
		}
		if source.ReducedByArmour() {
			p.Exhaust(0.1)
		}
		finalDamage := p.FinalDamageFrom(dmg, source)
		n = finalDamage
		a := p.absorption()
		if a > 0 && (effect.Absorption{}).Absorbs(source) {
			if finalDamage > a {
				finalDamage -= a
				p.SetAbsorption(0)
				p.effects.Remove(effect.Absorption{}, p)
			} else {
				p.SetAbsorption(a - finalDamage)
				finalDamage = 0
			}
		}
		if src, ok := source.(damage.SourceEntityAttack); ok {
			var d int
			for i, it := range p.armour.Slots() {
				if t, ok := it.Enchantment(enchantment.Thorns{}); ok {
					if rand.Float64() < float64(t.Level())*0.15 {
						_ = p.armour.Inv().SetItem(i, p.damageItem(it, 3))
						if t.Level() > 10 {
							d += t.Level() - 10
							continue
						}
						d += 1 + rand.Intn(4)
					} else {
						_ = p.armour.Inv().SetItem(i, p.damageItem(it, 1))
					}
				}
			}
			if l, ok := src.Attacker.(entity.Living); ok && d > 0 {
				l.Hurt(float64(d), damage.SourceCustom{})
			}
		}
		p.addHealth(-finalDamage)
		for _, viewer := range p.viewers() {
			viewer.ViewEntityAction(p, action.Hurt{})
		}
		p.immunity.Store(time.Now().Add(time.Second / 2))
		if p.Dead() {
			p.kill(source)
		}
	})
	return n, vulnerable
}
Hurt hurts the player for a given amount of damage. The source passed represents the cause of the damage,
for example damage.SourceEntityAttack if the player is attacked by another entity.
If the final damage exceeds the health that the player currently has, the player is killed and will have to
respawn.
If the damage passed is negative, Hurt will not do anything.
Hurt returns the final damage dealt to the Player and if the Player was vulnerable to this kind of damage.
package player
import (
	"fmt"
	"github.com/df-mc/dragonfly/server/block"
	blockAction "github.com/df-mc/dragonfly/server/block/action"
	"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/action"
	"github.com/df-mc/dragonfly/server/entity/damage"
	"github.com/df-mc/dragonfly/server/entity/effect"
	"github.com/df-mc/dragonfly/server/entity/healing"
	"github.com/df-mc/dragonfly/server/entity/physics"
	"github.com/df-mc/dragonfly/server/event"
	"github.com/df-mc/dragonfly/server/item"
	"github.com/df-mc/dragonfly/server/item/armour"
	"github.com/df-mc/dragonfly/server/item/enchantment"
	"github.com/df-mc/dragonfly/server/item/inventory"
	"github.com/df-mc/dragonfly/server/item/tool"
	"github.com/df-mc/dragonfly/server/player/bossbar"
	"github.com/df-mc/dragonfly/server/player/chat"
	"github.com/df-mc/dragonfly/server/player/form"
	"github.com/df-mc/dragonfly/server/player/scoreboard"
	"github.com/df-mc/dragonfly/server/player/skin"
	"github.com/df-mc/dragonfly/server/player/title"
	"github.com/df-mc/dragonfly/server/session"
	"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/mgl64"
	"github.com/google/uuid"
	"go.uber.org/atomic"
	"golang.org/x/text/language"
	"math"
	"math/rand"
	"net"
	"strings"
	"sync"
	"time"
)
type Player struct {
	name                                string
	uuid                                uuid.UUID
	xuid                                string
	locale                              language.Tag
	pos, vel                            atomic.Value
	nameTag                             atomic.String
	yaw, pitch, absorptionHealth, scale atomic.Float64
	gameModeMu sync.RWMutex
	gameMode   world.GameMode
	skinMu sync.RWMutex
	skin   skin.Skin
	sMutex sync.RWMutex
	s *session.Session
	hMutex sync.RWMutex
	h Handler
	inv, offHand *inventory.Inventory
	armour       *inventory.Armour
	heldSlot     *atomic.Uint32
	sneaking, sprinting, swimming, flying,
	invisible, immobile, onGround, usingItem atomic.Bool
	usingSince atomic.Int64
	fireTicks    atomic.Int64
	fallDistance atomic.Float64
	speed    atomic.Float64
	health   *entity.HealthManager
	effects  *entity.EffectManager
	immunity atomic.Value
	mc *entity.MovementComputer
	breaking          atomic.Bool
	breakingPos       atomic.Value
	lastBreakDuration time.Duration
	breakParticleCounter atomic.Uint32
	hunger *hungerManager
}
func New(name string, skin skin.Skin, pos mgl64.Vec3) *Player {
	p := &Player{}
	*p = Player{
		inv: inventory.New(36, func(slot int, item item.Stack) {
			if slot == int(p.heldSlot.Load()) {
				p.broadcastItems(slot, item)
			}
		}),
		uuid:     uuid.New(),
		offHand:  inventory.New(1, p.broadcastItems),
		armour:   inventory.NewArmour(p.broadcastArmour),
		hunger:   newHungerManager(),
		health:   entity.NewHealthManager(),
		effects:  entity.NewEffectManager(),
		gameMode: world.GameModeAdventure{},
		h:        NopHandler{},
		name:     name,
		skin:     skin,
		speed:    *atomic.NewFloat64(0.1),
		nameTag:  *atomic.NewString(name),
		heldSlot: atomic.NewUint32(0),
		locale:   language.BritishEnglish,
		scale:    *atomic.NewFloat64(1),
	}
	p.mc = &entity.MovementComputer{Gravity: 0.08, Drag: 0.02, DragBeforeGravity: true}
	p.pos.Store(pos)
	p.vel.Store(mgl64.Vec3{})
	p.immunity.Store(time.Now())
	p.breakingPos.Store(cube.Pos{})
	return p
}
func NewWithSession(name, xuid string, uuid uuid.UUID, skin skin.Skin, s *session.Session, pos mgl64.Vec3, data *Data) *Player {
	p := New(name, skin, pos)
	p.s, p.uuid, p.xuid, p.skin = s, uuid, xuid, skin
	p.inv, p.offHand, p.armour, p.heldSlot = s.HandleInventories()
	p.locale, _ = language.Parse(strings.Replace(s.ClientData().LanguageCode, "_", "-", 1))
	chat.Global.Subscribe(p)
	if data != nil {
		p.load(*data)
	}
	return p
}
func (p *Player) Name() string {
	return p.name
}
func (p *Player) UUID() uuid.UUID {
	return p.uuid
}
func (p *Player) XUID() string {
	return p.xuid
}
func (p *Player) Addr() net.Addr {
	if p.session() == session.Nop {
		return nil
	}
	return p.session().Addr()
}
func (p *Player) Skin() skin.Skin {
	p.skinMu.RLock()
	defer p.skinMu.RUnlock()
	return p.skin
}
func (p *Player) SetSkin(skin skin.Skin) {
	if p.Dead() {
		return
	}
	ctx := event.C()
	p.handler().HandleSkinChange(ctx, skin)
	ctx.Continue(func() {
		p.skinMu.Lock()
		p.skin = skin
		p.skinMu.Unlock()
		for _, v := range p.viewers() {
			v.ViewSkin(p)
		}
	})
	ctx.Stop(func() {
		p.session().ViewSkin(p)
	})
}
func (p *Player) Locale() language.Tag {
	return p.locale
}
func (p *Player) Handle(h Handler) {
	p.hMutex.Lock()
	defer p.hMutex.Unlock()
	if h == nil {
		h = NopHandler{}
	}
	p.h = h
}
func (p *Player) Message(a ...interface{}) {
	p.session().SendMessage(format(a))
}
func (p *Player) Messagef(f string, a ...interface{}) {
	msg := fmt.Sprintf(f, a...)
	p.session().SendMessage(msg)
}
func (p *Player) SendPopup(a ...interface{}) {
	p.session().SendPopup(format(a))
}
func (p *Player) SendTip(a ...interface{}) {
	p.session().SendTip(format(a))
}
func (p *Player) ResetFallDistance() {
	p.fallDistance.Store(0)
}
func (p *Player) FallDistance() float64 {
	return p.fallDistance.Load()
}
func (p *Player) SendTitle(t title.Title) {
	p.session().SetTitleDurations(t.FadeInDuration(), t.Duration(), t.FadeOutDuration())
	p.session().SendTitle(t.Text())
	if t.Subtitle() != "" {
		p.session().SendSubtitle(t.Subtitle())
	}
	if t.ActionText() != "" {
		p.session().SendActionBarMessage(t.ActionText())
	}
}
func (p *Player) SendScoreboard(scoreboard *scoreboard.Scoreboard) {
	p.session().SendScoreboard(scoreboard)
}
func (p *Player) RemoveScoreboard() {
	p.session().RemoveScoreboard()
}
func (p *Player) SendBossBar(bar bossbar.BossBar) {
	p.session().SendBossBar(bar.Text(), bar.HealthPercentage())
}
func (p *Player) RemoveBossBar() {
	p.session().RemoveBossBar()
}
func (p *Player) Chat(msg ...interface{}) {
	message := format(msg)
	ctx := event.C()
	p.handler().HandleChat(ctx, &message)
	ctx.Continue(func() {
		_, _ = fmt.Fprintf(chat.Global, "<%v> %v\n", p.name, message)
	})
}
func (p *Player) ExecuteCommand(commandLine string) {
	if p.Dead() {
		return
	}
	args := strings.Split(commandLine, " ")
	commandName := strings.TrimPrefix(args[0], "/")
	command, ok := cmd.ByAlias(commandName)
	if !ok {
		output := &cmd.Output{}
		output.Errorf("Unknown command '%v'", commandName)
		p.SendCommandOutput(output)
		return
	}
	ctx := event.C()
	p.handler().HandleCommandExecution(ctx, command, args[1:])
	ctx.Continue(func() {
		command.Execute(strings.TrimPrefix(strings.TrimPrefix(commandLine, "/"+commandName), " "), p)
	})
}
func (p *Player) Disconnect(msg ...interface{}) {
	p.session().Disconnect(format(msg))
	p.close()
}
func (p *Player) Transfer(address string) (err error) {
	addr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		return err
	}
	ctx := event.C()
	p.handler().HandleTransfer(ctx, addr)
	ctx.Continue(func() {
		p.session().Transfer(addr.IP, addr.Port)
	})
	return
}
func (p *Player) SendCommandOutput(output *cmd.Output) {
	p.session().SendCommandOutput(output)
}
func (p *Player) SendForm(f form.Form) {
	p.session().SendForm(f)
}
func (p *Player) ShowCoordinates() {
	p.session().EnableCoordinates(true)
}
func (p *Player) HideCoordinates() {
	p.session().EnableCoordinates(false)
}
func (p *Player) EnableInstantRespawn() {
	p.session().EnableInstantRespawn(true)
}
func (p *Player) DisableInstantRespawn() {
	p.session().EnableInstantRespawn(false)
}
func (p *Player) SetNameTag(name string) {
	p.nameTag.Store(name)
	p.updateState()
}
func (p *Player) NameTag() string {
	return p.nameTag.Load()
}
func (p *Player) SetSpeed(speed float64) {
	p.speed.Store(speed)
	p.session().SendSpeed(speed)
}
func (p *Player) Speed() float64 {
	return p.speed.Load()
}
func (p *Player) Health() float64 {
	return p.health.Health()
}
func (p *Player) MaxHealth() float64 {
	return p.health.MaxHealth()
}
func (p *Player) SetMaxHealth(health float64) {
	p.health.SetMaxHealth(health)
	p.session().SendHealth(p.health)
}
func (p *Player) addHealth(health float64) {
	p.health.AddHealth(health)
	p.session().SendHealth(p.health)
}
func (p *Player) Heal(health float64, source healing.Source) {
	if p.Dead() || health < 0 || !p.GameMode().AllowsTakingDamage() {
		return
	}
	ctx := event.C()
	p.handler().HandleHeal(ctx, &health, source)
	ctx.Continue(func() {
		p.addHealth(health)
	})
}
func (p *Player) updateFallState(distanceThisTick float64) {
	fallDistance := p.fallDistance.Load()
	if p.OnGround() {
		if fallDistance > 0 {
			p.fall(fallDistance)
			p.ResetFallDistance()
		}
	} else if distanceThisTick < fallDistance {
		p.fallDistance.Sub(distanceThisTick)
	} else {
		p.ResetFallDistance()
	}
}
func (p *Player) fall(fallDistance float64) {
	w := p.World()
	pos := cube.PosFromVec3(p.Position())
	b := w.Block(pos)
	if len(b.Model().AABB(pos, w)) == 0 {
		pos = pos.Side(cube.FaceDown)
		b = w.Block(pos)
	}
	if h, ok := b.(block.EntityLander); ok {
		h.EntityLand(pos, w, p)
	}
	fallDamage := fallDistance - 3
	for _, e := range p.Effects() {
		if _, ok := e.Type().(effect.JumpBoost); ok {
			fallDamage -= float64(e.Level())
		}
	}
	if fallDamage < 0.5 {
		return
	}
	p.Hurt(math.Ceil(fallDamage), damage.SourceFall{})
}