func ItemLocations(player *Player) (p *net.Packet) {
	changed := 0
	p = net.NewEmptyPacket(99)
	var removed []entity.Entity
	player.LocalItems.Range(func (i entity.Entity) {
		if i, ok := i.(*GroundItem); ok {
			x, y := i.X(), i.Y()
			if !player.Near(i, player.ViewRadius()*3) {
				if !player.Near(i, player.ViewRadius()*9) {
					if chunks, ok := player.Var("distantChunks"); ok {
						player.SetVar("distantChunks", append(chunks.([]entity.Location), i.Location.Clone()))
					} else {
						player.SetVar("distantChunks", []entity.Location{i.Location.Clone()})
					}
				} else {
					p.AddUint8(255)
					p.AddUint8(byte(player.TheirDeltaX(i)))
					p.AddUint8(byte(player.TheirDeltaY(i)))
					changed++
				}
				removed = append(removed, i)
			} else if !i.VisibleTo(player) || !Region(x, y).Items.Contains(i) {
				p.AddUint16(uint16(i.ID | (1<<15)))  
				p.AddUint8(byte(player.TheirDeltaX(i)))
				p.AddUint8(byte(player.TheirDeltaY(i)))
				changed++
				removed = append(removed, i)
			}
		}
	})
	for _, i := range removed {
		player.LocalItems.Remove(i)
	}
	for _, i := range player.NewItems() {
		p.AddUint16(uint16(i.ID))
		p.AddUint8(byte(player.TheirDeltaX(i)))
		p.AddUint8(byte(player.TheirDeltaY(i)))
		player.LocalItems.Add(i)
		changed++
	}
	if changed == 0 {
		return nil
	}
	return
}
ItemLocations Builds a packet with the view-area item positions in it, relative to the player.
If no new items are available and no existing items are removed from area, returns nil.
package world
import (
	"strconv"
	"github.com/spkaeros/rscgo/pkg/definitions"
	"github.com/spkaeros/rscgo/pkg/game/entity"
	"github.com/spkaeros/rscgo/pkg/game/net"
	"github.com/spkaeros/rscgo/pkg/rand"
	"github.com/spkaeros/rscgo/pkg/strutil"
	"github.com/spkaeros/rscgo/pkg/log"
)
func FriendList(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(71)
	p.AddUint8(byte(player.FriendList.Size()))
	for s := range player.FriendList.EntrySet() {
		hash := strutil.Base37.Encode(s)
		p.AddUint64(hash)
		p1, ok := Players.FindHash(hash)
		if p1 != nil && ok && (p1.FriendList.Contains(player.Username()) || !p1.FriendBlocked()) {
			p.AddUint8(0xFF)
		} else {
			p.AddUint8(0)
		}
	}
	return p
}
func PrivateMessage(hash uint64, msg string) (p *net.Packet) {
	p = net.NewEmptyPacket(120)
	p.AddUint64(hash)
	p.AddUint32(rand.Rng.Uint32())  
	for _, c := range []byte(strutil.ChatFilter.Format(msg)) {
		p.AddUint8(c)
	}
	return p
}
func IgnoreList(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(109)
	p.AddUint8(byte(len(player.IgnoreList)))
	for _, hash := range player.IgnoreList {
		p.AddUint64(hash)
	}
	return p
}
func FriendUpdate(hash uint64, online bool) (p *net.Packet) {
	p = net.NewEmptyPacket(149)
	p.AddUint64(hash)
	if online {
		p.AddUint8(0xFF)
	} else {
		p.AddUint8(0)
	}
	return
}
func NpcEvents(player *Player) (p *net.Packet) {
	events, ok := player.VarChecked(npcEvents).([]interface{})
	p = net.NewEmptyPacket(104)
	eventCount := uint16(len(events))
	if eventCount <= 0 {
		return nil
	}
	p.AddUint16(eventCount)
	if ok && eventCount > 0 {
		var newList []interface{}
		for _, e := range events {
			switch e.(type) {
			case HitSplat:
				splat := e.(HitSplat)
				if AsNpc(splat.Owner) != nil {
					p.AddUint16(uint16(splat.Owner.ServerIndex()))
					p.AddUint8(2)
					p.AddUint8(uint8(splat.Damage))
					p.AddUint8(uint8(splat.Owner.Skills().Current(entity.StatHits)))
					p.AddUint8(uint8(splat.Owner.Skills().Maximum(entity.StatHits)))
				}
			case ChatMessage:
				msg := e.(ChatMessage)
				if msg.Owner.IsNpc() {
					p.AddUint16(uint16(msg.Owner.ServerIndex()))
					p.AddUint8(1)
					p.AddUint16(uint16(msg.Target.ServerIndex()))
					p.AddEncryptedString(msg.string)  
				}
			default:
				log.Debugf("Unknown NPC event found in queue: {var event %T = %v\n}", e, e)
				continue
			}
		}
		player.SetVar(npcEvents, newList)
	}
	return
}
var ShopClose = net.NewEmptyPacket(137)
func ShopOpen(shop *Shop) (p *net.Packet) {
	p = net.NewEmptyPacket(101)
	p.AddUint8(uint8(shop.Inventory.Size()))
	p.AddBoolean(shop.BuysUnstocked)
	p.AddUint8(uint8(shop.BasePurchasePercent))
	p.AddUint8(uint8(shop.BaseSalePercent))
	shop.Inventory.Range(func(item *Item) bool {
		p.AddUint16(uint16(item.ID))
		p.AddUint16(uint16(item.Amount))
		p.AddUint8(uint8(shop.DeltaPercentMod(item)))
		return false
	})
	return p
}
func SleepWord(player *Player) (p *net.Packet) {
	return net.NewEmptyPacket(117)
}
func SleepFatigue(player *Player) (p *net.Packet) {
	return net.NewEmptyPacket(244).AddUint16(uint16(player.VarInt("sleepFatigue", 0)))
}
var SleepClose = net.NewEmptyPacket(84)
var SleepWrong = net.NewEmptyPacket(194)
func PrivacySettings(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(51)
	p.AddBoolean(player.ChatBlocked())
	p.AddBoolean(player.FriendBlocked())
	p.AddBoolean(player.TradeBlocked())
	p.AddBoolean(player.DuelBlocked())
	return
}
func OptionMenuOpen(questions ...string) (p *net.Packet) {
	p = net.NewEmptyPacket(245)
	p.AddUint8(uint8(len(questions)))
	for _, question := range questions {
		p.AddFramedString(question)
	}
	return p
}
var OptionMenuClose = net.NewEmptyPacket(252)
func NPCPositions(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(79)
	p.AddBitmask(player.LocalNPCs.Size(), 8)
	var removed = []*NPC{}
	changed := 0
	player.LocalNPCs.RangeNpcs(func(n *NPC) bool {
		changed++
		n.RLock()
		mask := n.SyncMask
		if !player.Near(n, player.ViewRadius()-1) || mask&SyncRemoved == SyncRemoved || n.VarBool("removed", false) {
			p.AddBitmask(0xF, 4)
			removed = append(removed, n)
		} else if mask&SyncMoved == SyncMoved {
			p.AddBitmask(2, 2)
			p.AddBitmask(n.Direction(), 3)
		} else if mask&SyncSprite == SyncSprite {
			p.AddBitmask(3, 2)
			p.AddBitmask(n.Direction(), 4)
		} else {
			p.AddBitmask(0, 1)
			changed--
		}
		n.RUnlock()
		return false
	})
	for _, n := range removed {
		if player.LocalNPCs.Contains(n) {
		player.LocalNPCs.Remove(n)
		}
	}
	newCount := 0
	player.NewNPCs().RangeNpcs(func(n *NPC) bool {
		if player.LocalNPCs.Size() >= 255 {
			return true
		}
		if newCount >= 25 {
			if player.ViewRadius() > 1 {
				player.Dec("viewRadius", 1)
			}
			return true
		}
		if player.ViewRadius() < 15 {
			player.Inc("viewRadius", 1)
		}
		newCount++
		p.AddBitmask(n.ServerIndex(), 12)
		p.AddSignedBits(player.TheirDeltaX(n), 5)
		p.AddSignedBits(player.TheirDeltaY(n), 5)
		p.AddBitmask(n.Direction(), 4)
		p.AddBitmask(n.ID, 10)
		player.LocalNPCs.Add(n)
		return false
	})
	if updates := newCount + changed; updates <= 0 {
		return nil
	}
	return
}
func PrayerStatus(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(206)
	for i := 0; i < len(player.Mob.Prayers); i++ {
		p.AddBoolean(player.PrayerActivated(i))
	}
	return p
}
func QuestStatus(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(5)
	for i := 0; i < 50; i++ {
		p.AddBoolean(player.VarBool("quest"+strconv.Itoa(i), false))
	}
	return p
}
func PlayerPositions(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(191)
	p.AddBitmask(player.X(), 11)
	p.AddBitmask(player.Y(), 13)
	p.AddBitmask(player.Direction(), 4)
	updates := player.LocalPlayers.Size()
	p.AddBitmask(updates, 8)
	changed := 0
	var removed = []entity.MobileEntity{}
	if updates > 0 {
		player.LocalPlayers.RangePlayers(func(p1 *Player) bool {
			changed++
			p1.RLock()
			mask := p1.SyncMask
			if !player.Near(p1, player.ViewRadius()) || mask&SyncRemoved == SyncRemoved {
				p.AddBitmask(0xF, 4)
				removed = append(removed, p1)
			} else if mask&SyncMoved == SyncMoved {
				p.AddBitmask(2, 2)
				p.AddBitmask(p1.Direction(), 3)
			} else if mask&SyncSprite == SyncSprite {
				p.AddBitmask(3, 2)
				p.AddBitmask(p1.Direction(), 4)
			} else {
				p.AddBitmask(0, 1)
				changed--
			}
			p1.RUnlock()
			return false
		})
		for _, p1 := range removed {
			player.LocalPlayers.Remove(p1)
		}
	}
	newPlayerCount := 0
	player.NewPlayers().RangePlayers(func(p1 *Player) bool {
		if player.LocalPlayers.Size() >= 255 {
			return true
		}
		if newPlayerCount >= 50 {
			if player.ViewRadius() > 1 {
				player.Dec("viewRadius", 1)
			}
			return true
		}
		for player.ViewRadius() < 15 {
			player.Inc("viewRadius", 1)
		}
		newPlayerCount++
		player.LocalPlayers.Add(p1)
		p.AddBitmask(p1.ServerIndex(), 11)
		p.AddSignedBits(player.TheirDeltaX(p1), 5)
		p.AddSignedBits(player.TheirDeltaY(p1), 5)
		p.AddBitmask(p1.Direction(), 4)
		if ticket, hasPlayerTicket := player.Var(strconv.Itoa(p1.ServerIndex()) + "=" + strconv.Itoa(p1.AppearanceTicket())); !hasPlayerTicket || ticket != p1.AppearanceTicket() || p1.SyncMask&(SyncRemoved|SyncAppearance) != 0 {
			player.enqueue(playerEvents, map[string]int {"index": int(p1.ServerIndex()), "ticket": int(p1.AppearanceTicket())})
		}
		return false
	})
	return
}
func PlayerAppearances(ourPlayer *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(234)
	list, ok := ourPlayer.VarChecked(playerEvents).([]interface{})
	if !ok || len(list) == 0 {
		return nil
	}
	p.AddUint16(uint16(len(list)))
	if ok {
		for _, e := range list {
			switch e.(type) {
			case ItemBubble:
				bubble := e.(ItemBubble)
				p.AddUint16(uint16(bubble.Owner.ServerIndex()))  
				p.AddUint8(0)                                    
				p.AddUint16(uint16(bubble.Item))                 
			case ChatMessage:
				msg := e.(ChatMessage)
				p.AddUint16(uint16(msg.Owner.ServerIndex()))  
				if msg.Target == nil {
					p.AddUint8(1)
					p.AddUint8(uint8(ourPlayer.Rank()))
					if len(msg.string) > 84 {
						msg.string = msg.string[:84]
					}
					p.AddEncryptedString(msg.string)
				} else {
					p.AddUint8(6)
					p.AddEncryptedString(msg.string)
				}
			case HitSplat:
				splat := e.(HitSplat)
				p.AddUint16(uint16(splat.Owner.ServerIndex()))                    
				p.AddUint8(2)                                                     
				p.AddUint8(uint8(splat.Damage))                                   
				p.AddUint8(uint8(splat.Owner.Skills().Current(entity.StatHits)))  
				p.AddUint8(uint8(splat.Owner.Skills().Maximum(entity.StatHits)))  
			case Projectile:
				shot := e.(Projectile)
				p.AddUint16(uint16(shot.Owner.ServerIndex()))  
				if shot.Target.IsNpc() {
					p.AddUint8(3)
				} else if shot.Target.IsPlayer() {
					p.AddUint8(4)
				} else {
					p.Rewind(2)
					continue
				}
				p.AddUint16(uint16(shot.Kind))                  
				p.AddUint16(uint16(shot.Target.ServerIndex()))  
			case map[string]int:
				ticket := e.(map[string]int)
				idx, ok := ticket["index"]
				if !ok {
					continue
				}
				ticketID, ok := ticket["ticket"]
				if !ok {
					continue
				}
				p1, ok := Players.FindIndex(int(idx))
				if p1.AppearanceTicket() != int(ticketID) || !ok {
					continue
				}
				ourPlayer.SetVar("ticket" + strconv.Itoa(p1.ServerIndex()), ticketID)
				p.AddUint16(uint16(p1.ServerIndex()))  
				p.AddUint8(5)                              
				p.AddUint16(uint16(ticketID))  
				p.AddFramedString(p1.Username())
				p.AddFramedString(p1.Username())
				sprites := p1.Equips()
				p.AddUint8(uint8(len(sprites)))  
				for i := 0; i < len(sprites); i += 1 {
					animID := uint8(sprites[i] & 0xFF)
					if animID < 0 || animID >= 229 {
						p.AddUint8(0)
					} else {
						p.AddUint8(animID)
					}
				}
				p.AddUint8(uint8(p1.Appearance.HeadColor))
				p.AddUint8(uint8(p1.Appearance.BodyColor))
				p.AddUint8(uint8(p1.Appearance.LegsColor))
				p.AddUint8(uint8(p1.Appearance.SkinColor))
				p.AddUint8(uint8(p1.Skills().CombatLevel()))
				p.AddBoolean(p1.Skulled())
			default:
				log.Debugf("Unknown player event found in queue: {var event %T = %v\n}", e, e)
				continue
			}
		}
		ourPlayer.UnsetVar(playerEvents)
	}
	return
}
func ClearDistantChunks(player *Player) (p *net.Packet) {
	p = net.NewEmptyPacket(211)
	ichunks, ok := player.Var("distantChunks")
	if !ok {
		return nil
	}
	chunks := ichunks.([]entity.Location)
	if len(chunks) <= 0 {
		return nil
	}
	for _, chunk := range chunks {
		p.AddUint16(uint16(chunk.X() - player.X()))
		p.AddUint16(uint16(chunk.Y() - player.Y()))
	}
	player.UnsetVar("distantChunks")
	return
}
func ObjectLocations(player *Player) (p *net.Packet) {
	changed := 0
	p = net.NewEmptyPacket(48)
	var removed []entity.Entity
	player.LocalObjects.Range(func(e entity.Entity) {
		if o, ok := e.(*Object); ok {
			if o.Boundary {
				return
			}
			if !player.Near(o, player.ViewRadius()<<1) || GetObject(o.X(), o.Y()) != o {
				if !player.Near(o, player.ViewRadius()*3) {
					if chunks, ok := player.Var("distantChunks"); ok {
						player.SetVar("distantChunks", append(chunks.([]entity.Location), o.Clone()))
					} else {
						player.SetVar("distantChunks", []entity.Location{o.Clone()})
					}
				} else {
					p.AddUint16(60000)
					p.AddUint8(byte(o.X() - player.X()))
					p.AddUint8(byte(o.Y() - player.Y()))
					changed++
				}
				removed = append(removed, o)
			}
		}
		return
	})
	for _, r := range removed {
		player.LocalObjects.Remove(r)
	}
	for _, o := range player.NewObjects() {
		if o.Boundary {
			continue
		}
		p.AddUint16(uint16(o.ID))
		p.AddUint8(byte(o.X() - player.X()))
		p.AddUint8(byte(o.Y() - player.Y()))
		player.LocalObjects.Add(o)
		changed++
	}
	if changed == 0 {
		return nil
	}
	return
}
func BoundaryLocations(player *Player) (p *net.Packet) {
	changed := 0
	p = net.NewEmptyPacket(91)
	var removed []entity.Entity
	player.LocalObjects.Range(func(o entity.Entity) {
		if o, ok := o.(*Object); ok {
			if !o.Boundary {
				return
			}
			if !player.Near(o, player.ViewRadius()+5) {
				removed = append(removed, o)
				if !player.Near(o, (player.ViewRadius()+5)*3) {
					if !player.Near(o, player.ViewRadius()*9) {
						if chunks, ok := player.Var("distantChunks"); ok {
							player.SetVar("distantChunks", append(chunks.([]entity.Location), o.Clone()))
						} else {
							player.SetVar("distantChunks", []entity.Location{o.Clone()})
						}
					} else {
						p.AddUint8(0xFF)
						p.AddUint8(uint8(o.X()))
						p.AddUint8(uint8(o.Y()))
						changed++
					}
				}
			} else if o1 := GetObject(o.X(), o.Y()); o1 != o {
				changed++
				if o1 == nil {
					p.AddUint16(0x10)
					p.AddUint8(uint8(player.TheirDeltaX(o)))
					p.AddUint8(uint8(player.TheirDeltaY(o)))
					p.AddUint8(o.Direction)
					return
				}
				p.AddUint16(uint16(o1.ID))
				p.AddUint8(uint8(player.TheirDeltaX(o1)))
				p.AddUint8(uint8(player.TheirDeltaY(o1)))
				p.AddUint8(o.Direction)
			}
		}
	})
	for _, o1 := range removed {
		player.LocalObjects.Remove(o1)
	}
	for _, o := range player.NewObjects() {
		if !o.Boundary {
			continue
		}
		p.AddUint16(uint16(o.ID))
		p.AddUint8(byte(player.TheirDeltaX(o)))
		p.AddUint8(byte(player.TheirDeltaY(o)))
		p.AddUint8(o.Direction)
		player.LocalObjects.Add(o)
		changed++
	}
	if changed <= 0 {
		return nil
	}
	return
}