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

Implemented shields #643

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions server/item/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func init() {
world.RegisterItem(Salmon{})
world.RegisterItem(Scute{})
world.RegisterItem(Shears{})
world.RegisterItem(Shield{})
world.RegisterItem(ShulkerShell{})
world.RegisterItem(Slimeball{})
world.RegisterItem(Snowball{})
Expand Down
30 changes: 30 additions & 0 deletions server/item/shield.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package item

// Shield is a tool used for protecting the player against attacks.
type Shield struct{}

// MaxCount ...
func (Shield) MaxCount() int {
return 1
}

// RepairableBy ...
func (Shield) RepairableBy(i Stack) bool {
if planks, ok := i.Item().(interface{ RepairsWoodTools() bool }); ok {
return planks.RepairsWoodTools()
}
return false
}

// DurabilityInfo ...
func (s Shield) DurabilityInfo() DurabilityInfo {
return DurabilityInfo{
MaxDurability: 336,
BrokenItem: simpleItem(Stack{}),
}
}

// EncodeItem ...
func (Shield) EncodeItem() (name string, meta int16) {
return "minecraft:shield", 0
}
104 changes: 97 additions & 7 deletions server/player/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ type Player struct {
invisible, immobile, onGround, usingItem atomic.Bool
usingSince atomic.Int64

glideTicks atomic.Int64
fireTicks atomic.Int64
blockingDelayTicks atomic.Int64
glideTicks atomic.Int64
fireTicks atomic.Int64

fallDistance atomic.Float64

breathing bool
Expand Down Expand Up @@ -545,9 +547,54 @@ func (p *Player) Hurt(dmg float64, source damage.Source) (float64, bool) {
return 0, true
}

w, pos := p.World(), p.Position()
totalDamage := p.FinalDamageFrom(dmg, source)
damageLeft := totalDamage
if ok, _ := p.Blocking(); ok && source.ReducedByArmour() {
affected := true
if src, ok := source.(damage.SourceEntityAttack); ok {
diff := p.Position().Sub(src.Attacker.Position()).Normalize()
JustTalDevelops marked this conversation as resolved.
Show resolved Hide resolved
diff[1] = 0
if diff.Dot(entity.DirectionVector(p)) >= 0.0 {
affected = false
}
}
if affected {
w.PlaySound(pos, sound.ShieldBlock{})
p.SetBlockingDelay(time.Millisecond * 250)

if src, ok := source.(damage.SourceEntityAttack); ok {
if l, ok := src.Attacker.(entity.Living); ok {
l.KnockBack(pos, 0.5, 0.4)
}
if a, ok := src.Attacker.(*Player); ok {
held, _ := a.HeldItems()
if _, ok := held.Item().(item.Axe); ok {
p.SetBlockingDelay(time.Second * 5)
}
}
}
if src, ok := source.(damage.SourceProjectile); ok {
if p, ok := src.Projectile.(interface {
Velocity() mgl64.Vec3
SetVelocity(mgl64.Vec3)
}); ok {
p.SetVelocity(p.Velocity().Mul(-1))
}
}
if dmg >= 3.0 {
i := int(math.Ceil(totalDamage))
held, other := p.HeldItems()
if _, ok := held.Item().(item.Shield); ok {
p.SetHeldItems(p.damageItem(held, i), other)
} else {
p.SetHeldItems(held, p.damageItem(other, i))
}
}
return 0, false
}
}

damageLeft := totalDamage
if a := p.Absorption(); a > 0 {
if damageLeft > a {
damageLeft -= a
Expand All @@ -565,7 +612,7 @@ func (p *Player) Hurt(dmg float64, source damage.Source) (float64, bool) {

var damageToAttacker int
damageToArmour := int(math.Max(math.Floor(dmg/4), 1))
thornsArmour := map[int]item.Stack{}
thornsArmour := make(map[int]item.Stack)
for slot, it := range p.armour.Slots() {
if _, ok := it.Item().(item.Durable); ok {
if e, ok := it.Enchantment(enchantment.Thorns{}); ok && rand.Float64() < float64(e.Level())*0.15 {
Expand Down Expand Up @@ -600,7 +647,6 @@ func (p *Player) Hurt(dmg float64, source damage.Source) (float64, bool) {
}
}

w, pos := p.World(), p.Position()
for _, viewer := range p.viewers() {
viewer.ViewEntityAction(p, entity.HurtAction{})
}
Expand Down Expand Up @@ -968,8 +1014,8 @@ func (p *Player) StopSprinting() {
if !p.sprinting.CAS(true, false) {
return
}
p.SetSpeed(p.Speed() / 1.3)

p.SetSpeed(p.Speed() / 1.3)
p.updateState()
}

Expand Down Expand Up @@ -1079,6 +1125,18 @@ func (p *Player) StopFlying() {
p.session().SendGameMode(p.GameMode())
}

// Blocking returns true if the player is currently blocking with a shield. The first boolean is true if the player was
// holding a shield. The second boolean is true if the player was performing the necessary actions in order to block.
TwistedAsylumMC marked this conversation as resolved.
Show resolved Hide resolved
func (p *Player) Blocking() (bool, bool) {
if p.BlockingDelay() > 0 || !p.sneaking.Load() || p.usingItem.Load() {
return false, false
}
held, other := p.HeldItems()
_, heldShield := held.Item().(item.Shield)
_, otherShield := other.Item().(item.Shield)
return heldShield || otherShield, true
}

// Jump makes the player jump if they are on ground. It exhausts the player by 0.05 food points, an additional 0.15
// is exhausted if the player is sprint jumping.
func (p *Player) Jump() {
Expand Down Expand Up @@ -1164,6 +1222,11 @@ func (p *Player) OnFireDuration() time.Duration {
return time.Duration(p.fireTicks.Load()) * time.Second / 20
}

// BlockingDelay ...
TwistedAsylumMC marked this conversation as resolved.
Show resolved Hide resolved
func (p *Player) BlockingDelay() time.Duration {
return time.Duration(p.blockingDelayTicks.Load()) * time.Second / 20
}

// SetOnFire ...
func (p *Player) SetOnFire(duration time.Duration) {
ticks := int64(duration.Seconds() * 20)
Expand All @@ -1174,6 +1237,15 @@ func (p *Player) SetOnFire(duration time.Duration) {
p.updateState()
}

// SetBlockingDelay ...
TwistedAsylumMC marked this conversation as resolved.
Show resolved Hide resolved
func (p *Player) SetBlockingDelay(duration time.Duration) {
blocking, _ := p.Blocking()
p.blockingDelayTicks.Store(int64(duration.Seconds() * 20))
if blocking {
p.updateState()
}
}

// Extinguish ...
func (p *Player) Extinguish() {
p.SetOnFire(0)
Expand Down Expand Up @@ -1345,11 +1417,11 @@ func (p *Player) UseItem() {
return
}
p.SetHeldItems(p.subtractItem(i, 1), left)
w.PlaySound(p.Position().Add(mgl64.Vec3{0, 1.5}), sound.Burp{})

useCtx := p.useContext()
useCtx.NewItem = usable.Consume(w, p)
p.addNewItem(useCtx)
w.PlaySound(p.Position().Add(mgl64.Vec3{0, 1.5}), sound.Burp{})
}
}

Expand All @@ -1360,6 +1432,7 @@ func (p *Player) UseItem() {
// the item started being used.
func (p *Player) ReleaseItem() {
if !p.usingItem.CAS(true, false) || !p.canRelease() || !p.GameMode().AllowsInteraction() {
p.updateState()
return
}
ctx := p.useContext()
Expand Down Expand Up @@ -2290,6 +2363,12 @@ func (p *Player) Tick(w *world.World, current int64) {
p.Hurt(1, damage.SourceFireTick{})
}
}
if p.BlockingDelay() > 0 {
p.blockingDelayTicks.Sub(1)
if p.BlockingDelay() <= 0 {
p.SetBlockingDelay(0)
}
}

if current%4 == 0 && p.usingItem.Load() {
held, _ := p.HeldItems()
Expand Down Expand Up @@ -2657,6 +2736,17 @@ func (p *Player) SwingArm() {
if p.Dead() {
return
}

duration := time.Millisecond * 300
if e, ok := p.Effect(effect.Haste{}); ok {
duration -= time.Duration(e.Level()) * time.Millisecond * 50
} else if e, ok = p.Effect(effect.MiningFatigue{}); ok {
duration += time.Duration(e.Level()*2) * time.Millisecond * 50
}
if duration > 0 {
p.SetBlockingDelay(duration)
}

for _, v := range p.viewers() {
v.ViewEntityAction(p, entity.SwingArmAction{})
}
Expand Down
47 changes: 30 additions & 17 deletions server/session/entity_metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,19 @@ func (s *Session) parseEntityMetadata(e world.Entity) entityMetadata {
dataKeyPotionColour: int32(0),
dataKeyPotionAmbient: byte(0),
dataKeyColour: byte(0),
dataKeyFlags: int64(0),
dataKeyFlagsExtended: int64(0),
}

m.setFlag(dataKeyFlags, dataFlagAffectedByGravity)
m.setFlag(dataKeyFlags, dataFlagCanClimb)
if sn, ok := e.(sneaker); ok && sn.Sneaking() {
m.setFlag(dataKeyFlags, dataFlagSneaking)
if b, ok := e.(blocker); ok {
if _, ok = b.Blocking(); ok {
m.setFlag(dataKeyFlagsExtended, dataFlagBlocking)
}
}
}
if sp, ok := e.(sprinter); ok && sp.Sprinting() {
m.setFlag(dataKeyFlags, dataFlagSprinting)
Expand Down Expand Up @@ -142,10 +149,9 @@ func (s *Session) parseEntityMetadata(e world.Entity) entityMetadata {
// setFlag sets a flag with a specific index in the int64 stored in the entity metadata map to the value
// passed. It is typically used for entity metadata flags.
func (m entityMetadata) setFlag(key uint32, index uint8) {
if v, ok := m[key]; !ok {
m[key] = int64(0) ^ (1 << uint64(index))
} else {
m[key] = v.(int64) ^ (1 << uint64(index))
actualIndex := index % 64
if v, ok := m[key]; ok {
m[key] = v.(int64) ^ (1 << uint64(actualIndex))
}
}

Expand Down Expand Up @@ -174,6 +180,7 @@ const (
dataKeyAreaEffectCloudParticleID = 63
dataKeyAlwaysShowNameTag = 81
dataKeyScoreTag = 84
dataKeyFlagsExtended = 92
dataKeyAreaEffectCloudDuration = 95
dataKeyAreaEffectCloudSpawnTime = 96
dataKeyAreaEffectCloudRadiusPerTick = 97
Expand All @@ -189,19 +196,21 @@ const (
dataFlagSprinting
dataFlagUsingItem
dataFlagInvisible
dataFlagIgnited = 10
dataFlagCritical = 13
dataFlagCanShowNameTag = 14
dataFlagAlwaysShowNameTag = 15
dataFlagNoAI = 16
dataFlagCanClimb = 19
dataFlagGliding = 32
dataFlagBreathing = 35
dataFlagLinger = 46
dataFlagHasCollision = 47
dataFlagAffectedByGravity = 48
dataFlagEnchanted = 51
dataFlagSwimming = 56
dataFlagIgnited = 10
dataFlagCritical = 13
dataFlagCanShowNameTag = 14
dataFlagAlwaysShowNameTag = 15
dataFlagNoAI = 16
dataFlagCanClimb = 19
dataFlagGliding = 32
dataFlagBreathing = 35
dataFlagLinger = 46
dataFlagHasCollision = 47
dataFlagAffectedByGravity = 48
dataFlagEnchanted = 51
dataFlagSwimming = 56
dataFlagBlocking = 71
dataFlagTransitionBlocking = 72
)

type sneaker interface {
Expand All @@ -220,6 +229,10 @@ type glider interface {
Gliding() bool
}

type blocker interface {
Blocking() (bool, bool)
}

type breather interface {
Breathing() bool
AirSupply() time.Duration
Expand Down
2 changes: 2 additions & 0 deletions server/session/world.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,8 @@ func (s *Session) playSound(pos mgl64.Vec3, t world.Sound, disableRelative bool)
}
case sound.MusicDiscEnd:
pk.SoundType = packet.SoundEventRecordNull
case sound.ShieldBlock:
pk.SoundType = packet.SoundEventShieldBlock
case sound.FireCharge:
s.writePacket(&packet.LevelEvent{
EventType: packet.LevelEventSoundBlazeFireball,
Expand Down
3 changes: 3 additions & 0 deletions server/world/sound/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,6 @@ type GoatHorn struct {
// FireCharge is a sound played when a player lights a block on fire with a fire charge, or when a dispenser or a
// blaze shoots a fireball.
type FireCharge struct{ sound }

// ShieldBlock is a sound played when a player blocks an attack using a shield.
type ShieldBlock struct{ sound }