github.com/df-mc/dragonfly@v0.9.13/server/session/player.go (about)

     1  package session
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"github.com/df-mc/atomic"
     7  	"github.com/df-mc/dragonfly/server/block"
     8  	"github.com/df-mc/dragonfly/server/entity"
     9  	"github.com/df-mc/dragonfly/server/entity/effect"
    10  	"github.com/df-mc/dragonfly/server/internal/nbtconv"
    11  	"github.com/df-mc/dragonfly/server/item"
    12  	"github.com/df-mc/dragonfly/server/item/creative"
    13  	"github.com/df-mc/dragonfly/server/item/inventory"
    14  	"github.com/df-mc/dragonfly/server/item/recipe"
    15  	"github.com/df-mc/dragonfly/server/player/form"
    16  	"github.com/df-mc/dragonfly/server/player/skin"
    17  	"github.com/df-mc/dragonfly/server/world"
    18  	"github.com/go-gl/mathgl/mgl64"
    19  	"github.com/google/uuid"
    20  	"github.com/sandertv/gophertunnel/minecraft/protocol"
    21  	"github.com/sandertv/gophertunnel/minecraft/protocol/packet"
    22  	"math"
    23  	"net"
    24  	"time"
    25  	_ "unsafe" // Imported for compiler directives.
    26  )
    27  
    28  // StopShowingEntity stops showing a world.Entity to the Session. It will be completely invisible until a call to
    29  // StartShowingEntity is made.
    30  func (s *Session) StopShowingEntity(e world.Entity) {
    31  	s.HideEntity(e)
    32  	s.entityMutex.Lock()
    33  	s.hiddenEntities[e] = struct{}{}
    34  	s.entityMutex.Unlock()
    35  }
    36  
    37  // StartShowingEntity starts showing a world.Entity to the Session that was previously hidden using StopShowingEntity.
    38  func (s *Session) StartShowingEntity(e world.Entity) {
    39  	s.entityMutex.Lock()
    40  	delete(s.hiddenEntities, e)
    41  	s.entityMutex.Unlock()
    42  	s.ViewEntity(e)
    43  	s.ViewEntityState(e)
    44  	s.ViewEntityItems(e)
    45  	s.ViewEntityArmour(e)
    46  }
    47  
    48  // closeCurrentContainer closes the container the player might currently have open.
    49  func (s *Session) closeCurrentContainer() {
    50  	if !s.containerOpened.Load() {
    51  		return
    52  	}
    53  	s.closeWindow()
    54  
    55  	pos := s.openedPos.Load()
    56  	w := s.c.World()
    57  	b := w.Block(pos)
    58  	if container, ok := b.(block.Container); ok {
    59  		container.RemoveViewer(s, w, pos)
    60  	} else if enderChest, ok := b.(block.EnderChest); ok {
    61  		enderChest.RemoveViewer(w, pos)
    62  	}
    63  }
    64  
    65  // EmptyUIInventory attempts to move all items in the UI inventory to the player's main inventory. If the main inventory
    66  // is full, the items are dropped on the ground instead.
    67  func (s *Session) EmptyUIInventory() {
    68  	if s == Nop {
    69  		return
    70  	}
    71  	items := s.ui.Clear()
    72  	leftover := make([]item.Stack, 0, len(items))
    73  	for _, i := range items {
    74  		if n, err := s.inv.AddItem(i); err != nil {
    75  			leftover = append(leftover, i.Grow(i.Count()-n))
    76  		}
    77  	}
    78  	for _, i := range leftover {
    79  		// We can't put this back in the inventory, so the best option here is to simply get rid of the item if
    80  		// dropping was cancelled.
    81  		s.c.Drop(i)
    82  	}
    83  }
    84  
    85  // SendRespawn spawns the Controllable entity of the session client-side in the world, provided it has died.
    86  func (s *Session) SendRespawn(pos mgl64.Vec3) {
    87  	s.writePacket(&packet.Respawn{
    88  		Position:        vec64To32(pos.Add(entityOffset(s.c))),
    89  		State:           packet.RespawnStateReadyToSpawn,
    90  		EntityRuntimeID: selfEntityRuntimeID,
    91  	})
    92  }
    93  
    94  // sendRecipes sends the current crafting recipes to the session.
    95  func (s *Session) sendRecipes() {
    96  	recipes := make([]protocol.Recipe, 0, len(recipe.Recipes()))
    97  	for index, i := range recipe.Recipes() {
    98  		networkID := uint32(index) + 1
    99  		s.recipes[networkID] = i
   100  
   101  		switch i := i.(type) {
   102  		case recipe.Shapeless:
   103  			recipes = append(recipes, &protocol.ShapelessRecipe{
   104  				RecipeID:        uuid.New().String(),
   105  				Priority:        int32(i.Priority()),
   106  				Input:           stacksToIngredientItems(i.Input()),
   107  				Output:          stacksToRecipeStacks(i.Output()),
   108  				Block:           i.Block(),
   109  				RecipeNetworkID: networkID,
   110  			})
   111  		case recipe.Shaped:
   112  			recipes = append(recipes, &protocol.ShapedRecipe{
   113  				RecipeID:        uuid.New().String(),
   114  				Priority:        int32(i.Priority()),
   115  				Width:           int32(i.Shape().Width()),
   116  				Height:          int32(i.Shape().Height()),
   117  				Input:           stacksToIngredientItems(i.Input()),
   118  				Output:          stacksToRecipeStacks(i.Output()),
   119  				Block:           i.Block(),
   120  				RecipeNetworkID: networkID,
   121  			})
   122  		case recipe.Smithing:
   123  			input, output := stacksToIngredientItems(i.Input()), stacksToRecipeStacks(i.Output())
   124  			recipes = append(recipes, &protocol.SmithingTransformRecipe{
   125  				RecipeID:        uuid.New().String(),
   126  				Base:            input[0],
   127  				Addition:        input[1],
   128  				Template:        input[2],
   129  				Result:          output[0],
   130  				Block:           i.Block(),
   131  				RecipeNetworkID: networkID,
   132  			})
   133  		}
   134  	}
   135  	s.writePacket(&packet.CraftingData{Recipes: recipes, ClearRecipes: true})
   136  }
   137  
   138  // sendInv sends the inventory passed to the client with the window ID.
   139  func (s *Session) sendInv(inv *inventory.Inventory, windowID uint32) {
   140  	pk := &packet.InventoryContent{
   141  		WindowID: windowID,
   142  		Content:  make([]protocol.ItemInstance, 0, s.inv.Size()),
   143  	}
   144  	for _, i := range inv.Slots() {
   145  		pk.Content = append(pk.Content, instanceFromItem(i))
   146  	}
   147  	s.writePacket(pk)
   148  }
   149  
   150  // sendItem sends the item stack passed to the client with the window ID and slot passed.
   151  func (s *Session) sendItem(item item.Stack, slot int, windowID uint32) {
   152  	s.writePacket(&packet.InventorySlot{
   153  		WindowID: windowID,
   154  		Slot:     uint32(slot),
   155  		NewItem:  instanceFromItem(item),
   156  	})
   157  }
   158  
   159  const (
   160  	craftingGridSizeSmall   = 4
   161  	craftingGridSizeLarge   = 9
   162  	craftingGridSmallOffset = 28
   163  	craftingGridLargeOffset = 32
   164  	craftingResult          = 50
   165  )
   166  
   167  // smelter is an interface representing a block used to smelt items.
   168  type smelter interface {
   169  	// ResetExperience resets the collected experience of the smelter, and returns the amount of experience that was reset.
   170  	ResetExperience() int
   171  }
   172  
   173  // invByID attempts to return an inventory by the ID passed. If found, the inventory is returned and the bool
   174  // returned is true.
   175  func (s *Session) invByID(id int32) (*inventory.Inventory, bool) {
   176  	switch id {
   177  	case protocol.ContainerCraftingInput, protocol.ContainerCreatedOutput, protocol.ContainerCursor:
   178  		// UI inventory.
   179  		return s.ui, true
   180  	case protocol.ContainerHotBar, protocol.ContainerInventory, protocol.ContainerCombinedHotBarAndInventory:
   181  		// Hotbar 'inventory', rest of inventory, inventory when container is opened.
   182  		return s.inv, true
   183  	case protocol.ContainerOffhand:
   184  		return s.offHand, true
   185  	case protocol.ContainerArmor:
   186  		// Armour inventory.
   187  		return s.armour.Inventory(), true
   188  	case protocol.ContainerLevelEntity:
   189  		if s.containerOpened.Load() {
   190  			b := s.c.World().Block(s.openedPos.Load())
   191  			if _, chest := b.(block.Chest); chest {
   192  				return s.openedWindow.Load(), true
   193  			} else if _, enderChest := b.(block.EnderChest); enderChest {
   194  				return s.openedWindow.Load(), true
   195  			}
   196  		}
   197  	case protocol.ContainerBarrel:
   198  		if s.containerOpened.Load() {
   199  			if _, barrel := s.c.World().Block(s.openedPos.Load()).(block.Barrel); barrel {
   200  				return s.openedWindow.Load(), true
   201  			}
   202  		}
   203  	case protocol.ContainerBeaconPayment:
   204  		if s.containerOpened.Load() {
   205  			if _, beacon := s.c.World().Block(s.openedPos.Load()).(block.Beacon); beacon {
   206  				return s.ui, true
   207  			}
   208  		}
   209  	case protocol.ContainerAnvilInput, protocol.ContainerAnvilMaterial:
   210  		if s.containerOpened.Load() {
   211  			if _, anvil := s.c.World().Block(s.openedPos.Load()).(block.Anvil); anvil {
   212  				return s.ui, true
   213  			}
   214  		}
   215  	case protocol.ContainerSmithingTableInput, protocol.ContainerSmithingTableMaterial:
   216  		if s.containerOpened.Load() {
   217  			if _, smithing := s.c.World().Block(s.openedPos.Load()).(block.SmithingTable); smithing {
   218  				return s.ui, true
   219  			}
   220  		}
   221  	case protocol.ContainerLoomInput, protocol.ContainerLoomDye, protocol.ContainerLoomMaterial:
   222  		if s.containerOpened.Load() {
   223  			if _, loom := s.c.World().Block(s.openedPos.Load()).(block.Loom); loom {
   224  				return s.ui, true
   225  			}
   226  		}
   227  	case protocol.ContainerStonecutterInput:
   228  		if s.containerOpened.Load() {
   229  			if _, ok := s.c.World().Block(s.openedPos.Load()).(block.Stonecutter); ok {
   230  				return s.ui, true
   231  			}
   232  		}
   233  	case protocol.ContainerGrindstoneInput, protocol.ContainerGrindstoneAdditional:
   234  		if s.containerOpened.Load() {
   235  			if _, ok := s.c.World().Block(s.openedPos.Load()).(block.Grindstone); ok {
   236  				return s.ui, true
   237  			}
   238  		}
   239  	case protocol.ContainerEnchantingInput, protocol.ContainerEnchantingMaterial:
   240  		if s.containerOpened.Load() {
   241  			if _, enchanting := s.c.World().Block(s.openedPos.Load()).(block.EnchantingTable); enchanting {
   242  				return s.ui, true
   243  			}
   244  		}
   245  	case protocol.ContainerFurnaceIngredient, protocol.ContainerFurnaceFuel, protocol.ContainerFurnaceResult,
   246  		protocol.ContainerBlastFurnaceIngredient, protocol.ContainerSmokerIngredient:
   247  		if s.containerOpened.Load() {
   248  			if _, ok := s.c.World().Block(s.openedPos.Load()).(smelter); ok {
   249  				return s.openedWindow.Load(), true
   250  			}
   251  		}
   252  	}
   253  	return nil, false
   254  }
   255  
   256  // Disconnect disconnects the client and ultimately closes the session. If the message passed is non-empty,
   257  // it will be shown to the client.
   258  func (s *Session) Disconnect(message string) {
   259  	if s != Nop {
   260  		s.writePacket(&packet.Disconnect{
   261  			HideDisconnectionScreen: message == "",
   262  			Message:                 message,
   263  		})
   264  		_ = s.conn.Flush()
   265  	}
   266  }
   267  
   268  // SendSpeed sends the speed of the player in an UpdateAttributes packet, so that it is updated client-side.
   269  func (s *Session) SendSpeed(speed float64) {
   270  	s.writePacket(&packet.UpdateAttributes{
   271  		EntityRuntimeID: selfEntityRuntimeID,
   272  		Attributes: []protocol.Attribute{{
   273  			AttributeValue: protocol.AttributeValue{
   274  				Name:  "minecraft:movement",
   275  				Value: float32(speed),
   276  				Max:   math.MaxFloat32,
   277  			},
   278  			Default: 0.1,
   279  		}},
   280  	})
   281  }
   282  
   283  // SendFood ...
   284  func (s *Session) SendFood(food int, saturation, exhaustion float64) {
   285  	s.writePacket(&packet.UpdateAttributes{
   286  		EntityRuntimeID: selfEntityRuntimeID,
   287  		Attributes: []protocol.Attribute{
   288  			{
   289  				AttributeValue: protocol.AttributeValue{
   290  					Name:  "minecraft:player.hunger",
   291  					Value: float32(food),
   292  					Max:   20,
   293  				},
   294  				Default: 20,
   295  			},
   296  			{
   297  				AttributeValue: protocol.AttributeValue{
   298  					Name:  "minecraft:player.saturation",
   299  					Value: float32(saturation),
   300  					Max:   20,
   301  				},
   302  				Default: 20,
   303  			},
   304  			{
   305  				AttributeValue: protocol.AttributeValue{
   306  					Name:  "minecraft:player.exhaustion",
   307  					Value: float32(exhaustion),
   308  					Max:   5,
   309  				},
   310  			},
   311  		},
   312  	})
   313  }
   314  
   315  // SendForm sends a form to the client of the connection. The Submit method of the form is called when the
   316  // client submits the form.
   317  func (s *Session) SendForm(f form.Form) {
   318  	b, _ := json.Marshal(f)
   319  
   320  	h := s.handlers[packet.IDModalFormResponse].(*ModalFormResponseHandler)
   321  	id := h.currentID.Add(1)
   322  
   323  	h.mu.Lock()
   324  	if len(h.forms) > 10 {
   325  		s.log.Debugf("SendForm %v: more than 10 active forms: dropping an existing one.", s.c.Name())
   326  		for k := range h.forms {
   327  			delete(h.forms, k)
   328  			break
   329  		}
   330  	}
   331  	h.forms[id] = f
   332  	h.mu.Unlock()
   333  
   334  	s.writePacket(&packet.ModalFormRequest{
   335  		FormID:   id,
   336  		FormData: b,
   337  	})
   338  }
   339  
   340  // Transfer transfers the player to a server with the IP and port passed.
   341  func (s *Session) Transfer(ip net.IP, port int) {
   342  	s.writePacket(&packet.Transfer{
   343  		Address: ip.String(),
   344  		Port:    uint16(port),
   345  	})
   346  }
   347  
   348  // SendGameMode sends the game mode of the Controllable entity of the session to the client. It makes sure the right
   349  // flags are set to create the full game mode.
   350  func (s *Session) SendGameMode(mode world.GameMode) {
   351  	if s == Nop {
   352  		return
   353  	}
   354  	s.writePacket(&packet.SetPlayerGameType{GameType: gameTypeFromMode(mode)})
   355  	s.sendAbilities()
   356  }
   357  
   358  // sendAbilities sends the abilities of the Controllable entity of the session to the client.
   359  func (s *Session) sendAbilities() {
   360  	mode, abilities := s.c.GameMode(), uint32(0)
   361  	if mode.AllowsFlying() {
   362  		abilities |= protocol.AbilityMayFly
   363  		if s.c.Flying() {
   364  			abilities |= protocol.AbilityFlying
   365  		}
   366  	}
   367  	if !mode.HasCollision() {
   368  		abilities |= protocol.AbilityNoClip
   369  		defer s.c.StartFlying()
   370  		// If the client is currently on the ground and turned to spectator mode, it will be unable to sprint during
   371  		// flight. In order to allow this, we force the client to be flying through a MovePlayer packet.
   372  		s.ViewEntityTeleport(s.c, s.c.Position())
   373  	}
   374  	if !mode.AllowsTakingDamage() {
   375  		abilities |= protocol.AbilityInvulnerable
   376  	}
   377  	if mode.CreativeInventory() {
   378  		abilities |= protocol.AbilityInstantBuild
   379  	}
   380  	if mode.AllowsEditing() {
   381  		abilities |= protocol.AbilityBuild | protocol.AbilityMine
   382  	}
   383  	if mode.AllowsInteraction() {
   384  		abilities |= protocol.AbilityDoorsAndSwitches | protocol.AbilityOpenContainers | protocol.AbilityAttackPlayers | protocol.AbilityAttackMobs
   385  	}
   386  	s.writePacket(&packet.UpdateAbilities{AbilityData: protocol.AbilityData{
   387  		EntityUniqueID:     selfEntityRuntimeID,
   388  		PlayerPermissions:  packet.PermissionLevelMember,
   389  		CommandPermissions: packet.CommandPermissionLevelNormal,
   390  		Layers: []protocol.AbilityLayer{ // TODO: Support customization of fly and walk speeds.
   391  			{
   392  				Type:      protocol.AbilityLayerTypeBase,
   393  				Abilities: protocol.AbilityCount - 1,
   394  				Values:    abilities,
   395  				FlySpeed:  protocol.AbilityBaseFlySpeed,
   396  				WalkSpeed: protocol.AbilityBaseWalkSpeed,
   397  			},
   398  		},
   399  	}})
   400  }
   401  
   402  // SendHealth sends the health and max health to the player.
   403  func (s *Session) SendHealth(health *entity.HealthManager) {
   404  	s.writePacket(&packet.UpdateAttributes{
   405  		EntityRuntimeID: selfEntityRuntimeID,
   406  		Attributes: []protocol.Attribute{{
   407  			AttributeValue: protocol.AttributeValue{
   408  				Name:  "minecraft:health",
   409  				Value: float32(math.Ceil(health.Health())),
   410  				Max:   float32(math.Ceil(health.MaxHealth())),
   411  			},
   412  			Default: 20,
   413  		}},
   414  	})
   415  }
   416  
   417  // SendAbsorption sends the absorption value passed to the player.
   418  func (s *Session) SendAbsorption(value float64) {
   419  	s.writePacket(&packet.UpdateAttributes{
   420  		EntityRuntimeID: selfEntityRuntimeID,
   421  		Attributes: []protocol.Attribute{{
   422  			AttributeValue: protocol.AttributeValue{
   423  				Name:  "minecraft:absorption",
   424  				Value: float32(math.Ceil(value)),
   425  				Max:   float32(math.MaxFloat32),
   426  			},
   427  		}},
   428  	})
   429  }
   430  
   431  // SendEffect sends an effects passed to the player.
   432  func (s *Session) SendEffect(e effect.Effect) {
   433  	s.SendEffectRemoval(e.Type())
   434  	id, _ := effect.ID(e.Type())
   435  	s.writePacket(&packet.MobEffect{
   436  		EntityRuntimeID: selfEntityRuntimeID,
   437  		Operation:       packet.MobEffectAdd,
   438  		EffectType:      int32(id),
   439  		Amplifier:       int32(e.Level() - 1),
   440  		Particles:       !e.ParticlesHidden(),
   441  		Duration:        int32(e.Duration() / (time.Second / 20)),
   442  	})
   443  }
   444  
   445  // SendEffectRemoval sends the removal of an effect passed.
   446  func (s *Session) SendEffectRemoval(e effect.Type) {
   447  	id, ok := effect.ID(e)
   448  	if !ok {
   449  		panic(fmt.Sprintf("unregistered effect type %T", e))
   450  	}
   451  	s.writePacket(&packet.MobEffect{
   452  		EntityRuntimeID: selfEntityRuntimeID,
   453  		Operation:       packet.MobEffectRemove,
   454  		EffectType:      int32(id),
   455  	})
   456  }
   457  
   458  // SendGameRules sends all the provided game rules to the player. Once sent, they will be immediately updated
   459  // on the client if they are valid.
   460  func (s *Session) sendGameRules(gameRules []protocol.GameRule) {
   461  	s.writePacket(&packet.GameRulesChanged{GameRules: gameRules})
   462  }
   463  
   464  // EnableCoordinates will either enable or disable coordinates for the player depending on the value given.
   465  func (s *Session) EnableCoordinates(enable bool) {
   466  	//noinspection SpellCheckingInspection
   467  	s.sendGameRules([]protocol.GameRule{{Name: "showcoordinates", Value: enable}})
   468  }
   469  
   470  // EnableInstantRespawn will either enable or disable instant respawn for the player depending on the value given.
   471  func (s *Session) EnableInstantRespawn(enable bool) {
   472  	//noinspection SpellCheckingInspection
   473  	s.sendGameRules([]protocol.GameRule{{Name: "doimmediaterespawn", Value: enable}})
   474  }
   475  
   476  // addToPlayerList adds the player of a session to the player list of this session. It will be shown in the
   477  // in-game pause menu screen.
   478  func (s *Session) addToPlayerList(session *Session) {
   479  	c := session.c
   480  
   481  	runtimeID := uint64(1)
   482  	s.entityMutex.Lock()
   483  	if session != s {
   484  		s.currentEntityRuntimeID += 1
   485  		runtimeID = s.currentEntityRuntimeID
   486  	}
   487  	s.entityRuntimeIDs[c] = runtimeID
   488  	s.entities[runtimeID] = c
   489  	s.entityMutex.Unlock()
   490  
   491  	s.writePacket(&packet.PlayerList{
   492  		ActionType: packet.PlayerListActionAdd,
   493  		Entries: []protocol.PlayerListEntry{{
   494  			UUID:           c.UUID(),
   495  			EntityUniqueID: int64(runtimeID),
   496  			Username:       c.Name(),
   497  			XUID:           c.XUID(),
   498  			Skin:           skinToProtocol(c.Skin()),
   499  		}},
   500  	})
   501  }
   502  
   503  // skinToProtocol converts a skin to its protocol representation.
   504  func skinToProtocol(s skin.Skin) protocol.Skin {
   505  	var animations []protocol.SkinAnimation
   506  	for _, animation := range s.Animations {
   507  		protocolAnim := protocol.SkinAnimation{
   508  			ImageWidth:  uint32(animation.Bounds().Max.X),
   509  			ImageHeight: uint32(animation.Bounds().Max.Y),
   510  			ImageData:   animation.Pix,
   511  			FrameCount:  float32(animation.FrameCount),
   512  		}
   513  		switch animation.Type() {
   514  		case skin.AnimationHead:
   515  			protocolAnim.AnimationType = protocol.SkinAnimationHead
   516  		case skin.AnimationBody32x32:
   517  			protocolAnim.AnimationType = protocol.SkinAnimationBody32x32
   518  		case skin.AnimationBody128x128:
   519  			protocolAnim.AnimationType = protocol.SkinAnimationBody128x128
   520  		}
   521  		protocolAnim.ExpressionType = uint32(animation.AnimationExpression)
   522  		animations = append(animations, protocolAnim)
   523  	}
   524  
   525  	return protocol.Skin{
   526  		PlayFabID:          s.PlayFabID,
   527  		SkinID:             uuid.New().String(),
   528  		SkinResourcePatch:  s.ModelConfig.Encode(),
   529  		SkinImageWidth:     uint32(s.Bounds().Max.X),
   530  		SkinImageHeight:    uint32(s.Bounds().Max.Y),
   531  		SkinData:           s.Pix,
   532  		CapeImageWidth:     uint32(s.Cape.Bounds().Max.X),
   533  		CapeImageHeight:    uint32(s.Cape.Bounds().Max.Y),
   534  		CapeData:           s.Cape.Pix,
   535  		SkinGeometry:       s.Model,
   536  		PersonaSkin:        s.Persona,
   537  		CapeID:             uuid.New().String(),
   538  		FullID:             uuid.New().String(),
   539  		Animations:         animations,
   540  		Trusted:            true,
   541  		OverrideAppearance: true,
   542  	}
   543  }
   544  
   545  // removeFromPlayerList removes the player of a session from the player list of this session. It will no
   546  // longer be shown in the in-game pause menu screen.
   547  func (s *Session) removeFromPlayerList(session *Session) {
   548  	c := session.c
   549  
   550  	s.entityMutex.Lock()
   551  	delete(s.entities, s.entityRuntimeIDs[c])
   552  	delete(s.entityRuntimeIDs, c)
   553  	s.entityMutex.Unlock()
   554  
   555  	s.writePacket(&packet.PlayerList{
   556  		ActionType: packet.PlayerListActionRemove,
   557  		Entries: []protocol.PlayerListEntry{{
   558  			UUID: c.UUID(),
   559  		}},
   560  	})
   561  }
   562  
   563  // HandleInventories starts handling the inventories of the Controllable entity of the session. It sends packets when
   564  // slots in the inventory are changed.
   565  func (s *Session) HandleInventories() (inv, offHand, enderChest *inventory.Inventory, armour *inventory.Armour, heldSlot *atomic.Uint32) {
   566  	s.inv = inventory.New(36, func(slot int, _, item item.Stack) {
   567  		if s.c == nil {
   568  			return
   569  		}
   570  		if slot == int(s.heldSlot.Load()) {
   571  			for _, viewer := range s.c.World().Viewers(s.c.Position()) {
   572  				viewer.ViewEntityItems(s.c)
   573  			}
   574  		}
   575  		if !s.inTransaction.Load() {
   576  			s.sendItem(item, slot, protocol.WindowIDInventory)
   577  		}
   578  	})
   579  	s.offHand = inventory.New(1, func(slot int, _, item item.Stack) {
   580  		if s.c == nil {
   581  			return
   582  		}
   583  		for _, viewer := range s.c.World().Viewers(s.c.Position()) {
   584  			viewer.ViewEntityItems(s.c)
   585  		}
   586  		if !s.inTransaction.Load() {
   587  			i, _ := s.offHand.Item(0)
   588  			s.writePacket(&packet.InventoryContent{
   589  				WindowID: protocol.WindowIDOffHand,
   590  				Content: []protocol.ItemInstance{
   591  					instanceFromItem(i),
   592  				},
   593  			})
   594  		}
   595  	})
   596  	s.enderChest = inventory.New(27, func(slot int, _, item item.Stack) {
   597  		if s.c == nil {
   598  			return
   599  		}
   600  		if !s.inTransaction.Load() {
   601  			if _, ok := s.c.World().Block(s.openedPos.Load()).(block.EnderChest); ok {
   602  				s.ViewSlotChange(slot, item)
   603  			}
   604  		}
   605  	})
   606  	s.armour = inventory.NewArmour(func(slot int, before, after item.Stack) {
   607  		if s.c == nil {
   608  			return
   609  		}
   610  		if !s.inTransaction.Load() {
   611  			s.sendItem(after, slot, protocol.WindowIDArmour)
   612  		}
   613  		if before.Comparable(after) && before.Empty() == after.Empty() {
   614  			// Only send armour if the item type actually changed.
   615  			return
   616  		}
   617  		for _, viewer := range s.c.World().Viewers(s.c.Position()) {
   618  			viewer.ViewEntityArmour(s.c)
   619  		}
   620  	})
   621  	return s.inv, s.offHand, s.enderChest, s.armour, s.heldSlot
   622  }
   623  
   624  // SetHeldSlot sets the currently held hotbar slot.
   625  func (s *Session) SetHeldSlot(slot int) error {
   626  	if slot > 8 {
   627  		return fmt.Errorf("slot exceeds hotbar range 0-8: slot is %v", slot)
   628  	}
   629  
   630  	s.heldSlot.Store(uint32(slot))
   631  
   632  	for _, viewer := range s.c.World().Viewers(s.c.Position()) {
   633  		viewer.ViewEntityItems(s.c)
   634  	}
   635  
   636  	mainHand, _ := s.c.HeldItems()
   637  	s.writePacket(&packet.MobEquipment{
   638  		EntityRuntimeID: selfEntityRuntimeID,
   639  		NewItem:         instanceFromItem(mainHand),
   640  		InventorySlot:   byte(slot),
   641  		HotBarSlot:      byte(slot),
   642  	})
   643  	return nil
   644  }
   645  
   646  // UpdateHeldSlot updates the held slot of the Session to the slot passed. It also verifies that the item in that slot
   647  // matches an expected item stack.
   648  func (s *Session) UpdateHeldSlot(slot int, expected item.Stack) error {
   649  	// The slot that the player might have selected must be within the hotbar: The held item cannot be in a
   650  	// different place in the inventory.
   651  	if slot > 8 {
   652  		return fmt.Errorf("new held slot exceeds hotbar range 0-8: slot is %v", slot)
   653  	}
   654  	if s.heldSlot.Load() == uint32(slot) {
   655  		// Old slot was the same as new slot, so don't do anything.
   656  		return nil
   657  	}
   658  	// The user swapped changed held slots so stop using item right away.
   659  	s.c.ReleaseItem()
   660  
   661  	s.heldSlot.Store(uint32(slot))
   662  
   663  	clientSideItem := expected
   664  	actual, _ := s.inv.Item(slot)
   665  
   666  	// The item the client claims to have must be identical to the one we have registered server-side.
   667  	if !clientSideItem.Equal(actual) {
   668  		// Only ever debug these as they are frequent and expected to happen whenever client and server get
   669  		// out of sync.
   670  		s.log.Debugf("failed processing packet from %v (%v): failed changing held slot: client-side item must be identical to server-side item, but got differences: client: %v vs server: %v", s.conn.RemoteAddr(), s.c.Name(), clientSideItem, actual)
   671  	}
   672  	for _, viewer := range s.c.World().Viewers(s.c.Position()) {
   673  		viewer.ViewEntityItems(s.c)
   674  	}
   675  	return nil
   676  }
   677  
   678  // SendExperience sends the experience level and progress from the given experience manager to the player.
   679  func (s *Session) SendExperience(e *entity.ExperienceManager) {
   680  	level, progress := e.Level(), e.Progress()
   681  	s.writePacket(&packet.UpdateAttributes{
   682  		EntityRuntimeID: selfEntityRuntimeID,
   683  		Attributes: []protocol.Attribute{
   684  			{
   685  				AttributeValue: protocol.AttributeValue{
   686  					Name:  "minecraft:player.level",
   687  					Value: float32(level),
   688  					Max:   float32(math.MaxInt32),
   689  				},
   690  			},
   691  			{
   692  				AttributeValue: protocol.AttributeValue{
   693  					Name:  "minecraft:player.experience",
   694  					Value: float32(progress),
   695  					Max:   1,
   696  				},
   697  			},
   698  		},
   699  	})
   700  }
   701  
   702  // stackFromItem converts an item.Stack to its network ItemStack representation.
   703  func stackFromItem(it item.Stack) protocol.ItemStack {
   704  	if it.Empty() {
   705  		return protocol.ItemStack{}
   706  	}
   707  
   708  	var blockRuntimeID uint32
   709  	if b, ok := it.Item().(world.Block); ok {
   710  		blockRuntimeID = world.BlockRuntimeID(b)
   711  	}
   712  
   713  	rid, meta, _ := world.ItemRuntimeID(it.Item())
   714  
   715  	return protocol.ItemStack{
   716  		ItemType: protocol.ItemType{
   717  			NetworkID:     rid,
   718  			MetadataValue: uint32(meta),
   719  		},
   720  		HasNetworkID:   true,
   721  		Count:          uint16(it.Count()),
   722  		BlockRuntimeID: int32(blockRuntimeID),
   723  		NBTData:        nbtconv.WriteItem(it, false),
   724  	}
   725  }
   726  
   727  // stackToItem converts a network ItemStack representation back to an item.Stack.
   728  func stackToItem(it protocol.ItemStack) item.Stack {
   729  	t, ok := world.ItemByRuntimeID(it.NetworkID, int16(it.MetadataValue))
   730  	if !ok {
   731  		t = block.Air{}
   732  	}
   733  	if it.BlockRuntimeID > 0 {
   734  		// It shouldn't matter if it (for whatever reason) wasn't able to get the block runtime ID,
   735  		// since on the next line, we assert that the block is an item. If it didn't succeed, it'll
   736  		// return air anyway.
   737  		b, _ := world.BlockByRuntimeID(uint32(it.BlockRuntimeID))
   738  		if t, ok = b.(world.Item); !ok {
   739  			t = block.Air{}
   740  		}
   741  	}
   742  	//noinspection SpellCheckingInspection
   743  	if nbter, ok := t.(world.NBTer); ok && len(it.NBTData) != 0 {
   744  		t = nbter.DecodeNBT(it.NBTData).(world.Item)
   745  	}
   746  	s := item.NewStack(t, int(it.Count))
   747  	return nbtconv.Item(it.NBTData, &s)
   748  }
   749  
   750  // instanceFromItem converts an item.Stack to its network ItemInstance representation.
   751  func instanceFromItem(it item.Stack) protocol.ItemInstance {
   752  	return protocol.ItemInstance{
   753  		StackNetworkID: item_id(it),
   754  		Stack:          stackFromItem(it),
   755  	}
   756  }
   757  
   758  // stacksToRecipeStacks converts a list of item.Stacks to their protocol representation with damage stripped for recipes.
   759  func stacksToRecipeStacks(inputs []item.Stack) []protocol.ItemStack {
   760  	items := make([]protocol.ItemStack, 0, len(inputs))
   761  	for _, i := range inputs {
   762  		items = append(items, deleteDamage(stackFromItem(i)))
   763  	}
   764  	return items
   765  }
   766  
   767  // stacksToIngredientItems converts a list of item.Stacks to recipe ingredient items used over the network.
   768  func stacksToIngredientItems(inputs []item.Stack) []protocol.ItemDescriptorCount {
   769  	items := make([]protocol.ItemDescriptorCount, 0, len(inputs))
   770  	for _, i := range inputs {
   771  		if i.Empty() {
   772  			items = append(items, protocol.ItemDescriptorCount{Descriptor: &protocol.InvalidItemDescriptor{}})
   773  			continue
   774  		}
   775  		rid, meta, ok := world.ItemRuntimeID(i.Item())
   776  		if !ok {
   777  			panic("should never happen")
   778  		}
   779  		if _, ok = i.Value("variants"); ok {
   780  			meta = math.MaxInt16 // Used to indicate that the item has multiple selectable variants.
   781  		}
   782  		items = append(items, protocol.ItemDescriptorCount{
   783  			Descriptor: &protocol.DefaultItemDescriptor{
   784  				NetworkID:     int16(rid),
   785  				MetadataValue: meta,
   786  			},
   787  			Count: int32(i.Count()),
   788  		})
   789  	}
   790  	return items
   791  }
   792  
   793  // creativeItems returns all creative inventory items as protocol item stacks.
   794  func creativeItems() []protocol.CreativeItem {
   795  	it := make([]protocol.CreativeItem, 0, len(creative.Items()))
   796  	for index, i := range creative.Items() {
   797  		it = append(it, protocol.CreativeItem{
   798  			CreativeItemNetworkID: uint32(index) + 1,
   799  			Item:                  deleteDamage(stackFromItem(i)),
   800  		})
   801  	}
   802  	return it
   803  }
   804  
   805  // deleteDamage strips the damage from a protocol item.
   806  func deleteDamage(st protocol.ItemStack) protocol.ItemStack {
   807  	delete(st.NBTData, "Damage")
   808  	return st
   809  }
   810  
   811  // protocolToSkin converts protocol.Skin to skin.Skin.
   812  func protocolToSkin(sk protocol.Skin) (s skin.Skin, err error) {
   813  	if sk.SkinID == "" {
   814  		return skin.Skin{}, fmt.Errorf("SkinID must not be an empty string")
   815  	}
   816  
   817  	s = skin.New(int(sk.SkinImageWidth), int(sk.SkinImageHeight))
   818  	s.Persona = sk.PersonaSkin
   819  	s.Pix = sk.SkinData
   820  	s.Model = sk.SkinGeometry
   821  	s.PlayFabID = sk.PlayFabID
   822  
   823  	s.Cape = skin.NewCape(int(sk.CapeImageWidth), int(sk.CapeImageHeight))
   824  	s.Cape.Pix = sk.CapeData
   825  
   826  	m := make(map[string]any)
   827  	if err = json.Unmarshal(sk.SkinGeometry, &m); err != nil {
   828  		return skin.Skin{}, fmt.Errorf("SkinGeometry was not a valid JSON string: %v", err)
   829  	}
   830  
   831  	if s.ModelConfig, err = skin.DecodeModelConfig(sk.SkinResourcePatch); err != nil {
   832  		return skin.Skin{}, fmt.Errorf("SkinResourcePatch was not a valid JSON string: %v", err)
   833  	}
   834  
   835  	for _, anim := range sk.Animations {
   836  		var t skin.AnimationType
   837  		switch anim.AnimationType {
   838  		case protocol.SkinAnimationHead:
   839  			t = skin.AnimationHead
   840  		case protocol.SkinAnimationBody32x32:
   841  			t = skin.AnimationBody32x32
   842  		case protocol.SkinAnimationBody128x128:
   843  			t = skin.AnimationBody128x128
   844  		default:
   845  			return skin.Skin{}, fmt.Errorf("invalid animation type: %v", anim.AnimationType)
   846  		}
   847  
   848  		animation := skin.NewAnimation(int(anim.ImageWidth), int(anim.ImageHeight), int(anim.ExpressionType), t)
   849  		animation.FrameCount = int(anim.FrameCount)
   850  		animation.Pix = anim.ImageData
   851  
   852  		s.Animations = append(s.Animations, animation)
   853  	}
   854  	return
   855  }
   856  
   857  // gameTypeFromMode returns the game type ID from the game mode passed.
   858  func gameTypeFromMode(mode world.GameMode) int32 {
   859  	if mode.AllowsFlying() && mode.CreativeInventory() {
   860  		return packet.GameTypeCreative
   861  	}
   862  	if !mode.Visible() && !mode.HasCollision() {
   863  		return packet.GameTypeSpectator
   864  	}
   865  	return packet.GameTypeSurvival
   866  }
   867  
   868  // The following functions use the go:linkname directive in order to make sure the item.byID and item.toID
   869  // functions do not need to be exported.
   870  
   871  // noinspection ALL
   872  //
   873  //go:linkname item_id github.com/df-mc/dragonfly/server/item.id
   874  func item_id(s item.Stack) int32
   875  
   876  // noinspection ALL
   877  //
   878  //go:linkname world_add github.com/df-mc/dragonfly/server/world.add
   879  func world_add(e world.Entity, w *world.World)