diff --git a/core/entity/chunk.go b/core/entity/chunk.go index c3e3db0..5549494 100644 --- a/core/entity/chunk.go +++ b/core/entity/chunk.go @@ -3,43 +3,27 @@ package entity import ( "log" "sync" - - "github.com/CPunch/gopenfusion/config" ) -type ChunkPosition struct { - X int - Y int -} - -func makeChunkPosition(x, y int) ChunkPosition { - return ChunkPosition{ - X: x / (config.VIEW_DISTANCE / 3), - Y: y / (config.VIEW_DISTANCE / 3), - } -} - type Chunk struct { Position ChunkPosition - entities map[Entity]struct{} + Entities map[Entity]struct{} lock sync.Mutex } func NewChunk(position ChunkPosition) *Chunk { return &Chunk{ Position: position, - entities: make(map[Entity]struct{}), + Entities: make(map[Entity]struct{}), } } func (c *Chunk) AddEntity(entity Entity) { - entity.SetChunk(c) - c.entities[entity] = struct{}{} + c.Entities[entity] = struct{}{} } func (c *Chunk) RemoveEntity(entity Entity) { - entity.SetChunk(nil) - delete(c.entities, entity) + delete(c.Entities, entity) } // send packet to all peers in this chunk and kill each peer if error @@ -47,7 +31,7 @@ func (c *Chunk) SendPacket(typeID uint32, pkt ...interface{}) { c.lock.Lock() defer c.lock.Unlock() - for entity := range c.entities { + for entity := range c.Entities { if entity.GetKind() != ENTITY_KIND_PLAYER { continue } @@ -59,6 +43,7 @@ func (c *Chunk) SendPacket(typeID uint32, pkt ...interface{}) { peer := plr.Peer if err := peer.Send(typeID, pkt...); err != nil { + log.Printf("Error sending packet to peer %p: %v", peer, err) peer.Kill() } } @@ -77,3 +62,20 @@ func (c *Chunk) GetAdjacentPositions() []ChunkPosition { {c.Position.X + 1, c.Position.Y + 1}, } } + +// https://stackoverflow.com/a/45428032 lol +func ChunkSliceDifference(a, b []*Chunk) []*Chunk { + m := make(map[*Chunk]struct{}) + for _, item := range b { + m[item] = struct{}{} + } + + var diff []*Chunk + for _, item := range a { + if _, ok := m[item]; !ok { + diff = append(diff, item) + } + } + + return diff +} diff --git a/core/entity/chunkposition.go b/core/entity/chunkposition.go new file mode 100644 index 0000000..e27016f --- /dev/null +++ b/core/entity/chunkposition.go @@ -0,0 +1,15 @@ +package entity + +import "github.com/CPunch/gopenfusion/config" + +type ChunkPosition struct { + X int + Y int +} + +func MakeChunkPosition(x, y int) ChunkPosition { + return ChunkPosition{ + X: x / (config.VIEW_DISTANCE / 3), + Y: y / (config.VIEW_DISTANCE / 3), + } +} diff --git a/core/entity/entity.go b/core/entity/entity.go index baaeeb3..27ba6a3 100644 --- a/core/entity/entity.go +++ b/core/entity/entity.go @@ -1,5 +1,7 @@ package entity +import "github.com/CPunch/gopenfusion/core/protocol" + type EntityKind int const ( @@ -10,11 +12,14 @@ const ( type Entity interface { GetKind() EntityKind - GetChunk() *Chunk + GetChunk() ChunkPosition GetPosition() (x int, y int, z int) GetAngle() int - SetChunk(chunk *Chunk) + SetChunk(chunk ChunkPosition) SetPosition(x, y, z int) SetAngle(angle int) + + DisappearFromViewOf(peer *protocol.CNPeer) + EnterIntoViewOf(peer *protocol.CNPeer) } diff --git a/core/entity/player.go b/core/entity/player.go index f5af145..95bfbc7 100644 --- a/core/entity/player.go +++ b/core/entity/player.go @@ -7,7 +7,7 @@ import ( type Player struct { Peer *protocol.CNPeer - CurrentChunk *Chunk + Chunk ChunkPosition PlayerID int AccountID int AccountLevel int @@ -35,6 +35,7 @@ type Player struct { ActiveNanoSlotNum int Fatigue int CurrentMissionID int + IPCState int8 } // ==================== Entity interface ==================== @@ -43,8 +44,8 @@ func (plr *Player) GetKind() EntityKind { return ENTITY_KIND_PLAYER } -func (plr *Player) GetChunk() *Chunk { - return plr.CurrentChunk +func (plr *Player) GetChunk() ChunkPosition { + return plr.Chunk } func (plr *Player) GetPosition() (x int, y int, z int) { @@ -55,6 +56,10 @@ func (plr *Player) GetAngle() int { return plr.Angle } +func (plr *Player) SetChunk(chunk ChunkPosition) { + plr.Chunk = chunk +} + func (plr *Player) SetPosition(x, y, z int) { plr.X = x plr.Y = y @@ -65,8 +70,16 @@ func (plr *Player) SetAngle(angle int) { plr.Angle = angle } -func (plr *Player) SetChunk(chunk *Chunk) { - plr.CurrentChunk = chunk +func (plr *Player) DisappearFromViewOf(peer *protocol.CNPeer) { + peer.Send(protocol.P_FE2CL_PC_EXIT, protocol.SP_FE2CL_PC_EXIT{ + IID: int32(plr.PlayerID), + }) +} + +func (plr *Player) EnterIntoViewOf(peer *protocol.CNPeer) { + peer.Send(protocol.P_FE2CL_PC_NEW, protocol.SP_FE2CL_PC_NEW{ + PCAppearanceData: plr.GetAppearanceData(), + }) } func (plr *Player) ToPCLoadData2CL() protocol.SPCLoadData2CL { @@ -96,3 +109,19 @@ func (plr *Player) ToPCLoadData2CL() protocol.SPCLoadData2CL { IFatigue: 50, } } + +func (plr *Player) GetAppearanceData() protocol.SPCAppearanceData { + return protocol.SPCAppearanceData{ + IID: int32(plr.PlayerID), + IHP: int32(plr.HP), + ILv: int16(plr.Level), + IX: int32(plr.X), + IY: int32(plr.Y), + IZ: int32(plr.Z), + IAngle: int32(plr.Angle), + PCStyle: plr.PCStyle, + IPCState: plr.IPCState, + ItemEquip: plr.Equip, + Nano: protocol.SNano{}, //plr.Nanos[plr.ActiveNanoSlotNum], + } +} diff --git a/shard/chunks.go b/shard/chunks.go index 0c5b721..dfa0efb 100644 --- a/shard/chunks.go +++ b/shard/chunks.go @@ -1,6 +1,8 @@ package shard -import "github.com/CPunch/gopenfusion/core/entity" +import ( + "github.com/CPunch/gopenfusion/core/entity" +) func (server *ShardServer) getChunk(pos entity.ChunkPosition) *entity.Chunk { chunk, ok := server.chunks[pos] @@ -12,12 +14,67 @@ func (server *ShardServer) getChunk(pos entity.ChunkPosition) *entity.Chunk { return chunk } -func (server *ShardServer) getViewableChunks(plr *entity.Player) []*entity.Chunk { +func (server *ShardServer) getViewableChunks(pos entity.ChunkPosition) []*entity.Chunk { chunks := make([]*entity.Chunk, 0, 9) - - for _, pos := range plr.GetChunk().GetAdjacentPositions() { + for _, pos := range server.getChunk(pos).GetAdjacentPositions() { chunks = append(chunks, server.getChunk(pos)) } return chunks } + +func (server *ShardServer) sendPacketToViewableChunks(plr *entity.Player, typeID uint32, pkt ...interface{}) error { + for _, chunk := range server.getViewableChunks(plr.Chunk) { + chunk.SendPacket(typeID, pkt...) + } + + return nil +} + +func (server *ShardServer) removeEntityFromChunks(chunks []*entity.Chunk, this entity.Entity) { + for _, chunk := range chunks { + for e, _ := range chunk.Entities { + if e.GetKind() == entity.ENTITY_KIND_PLAYER { + otherPlr := e.(*entity.Player) + this.DisappearFromViewOf(otherPlr.Peer) + } + + if this.GetKind() == entity.ENTITY_KIND_PLAYER { + thisPlr := this.(*entity.Player) + e.DisappearFromViewOf(thisPlr.Peer) + } + } + } +} + +func (server *ShardServer) addEntityToChunks(chunks []*entity.Chunk, this entity.Entity) { + for _, chunk := range chunks { + for e, _ := range chunk.Entities { + if e.GetKind() == entity.ENTITY_KIND_PLAYER { + otherPlr := e.(*entity.Player) + this.EnterIntoViewOf(otherPlr.Peer) + } + + if this.GetKind() == entity.ENTITY_KIND_PLAYER { + thisPlr := this.(*entity.Player) + e.EnterIntoViewOf(thisPlr.Peer) + } + } + } +} + +func (server *ShardServer) updateEntityChunk(e entity.Entity, from entity.ChunkPosition, to entity.ChunkPosition) { + oldViewables := server.getViewableChunks(from) + newViewables := server.getViewableChunks(to) + + // compute differences + toExit := entity.ChunkSliceDifference(oldViewables, newViewables) + toEnter := entity.ChunkSliceDifference(newViewables, oldViewables) + + // update chunks + server.removeEntityFromChunks(toExit, e) + server.addEntityToChunks(toEnter, e) + server.getChunk(from).RemoveEntity(e) + server.getChunk(to).AddEntity(e) + e.SetChunk(to) +} diff --git a/shard/join.go b/shard/join.go index c4b255e..85254ca 100644 --- a/shard/join.go +++ b/shard/join.go @@ -22,9 +22,10 @@ func (server *ShardServer) attachPlayer(peer *protocol.CNPeer, meta redis.LoginM if err != nil { return nil, err } + plr.Peer = peer server.setPlayer(peer, plr) - return plr, err + return plr, nil } func (server *ShardServer) RequestEnter(peer *protocol.CNPeer, pkt protocol.Packet) error { @@ -57,6 +58,8 @@ func (server *ShardServer) RequestEnter(peer *protocol.CNPeer, pkt protocol.Pack peer.SetActiveKey(protocol.USE_FE) log.Printf("Player %d (AccountID %d) entered\n", resp.IID, loginData.AccountID) + + server.updatePlayerPosition(peer, int(plr.X), int(plr.Y), int(plr.Z), int(plr.Angle)) return peer.Send(protocol.P_FE2CL_REP_PC_ENTER_SUCC, resp) } diff --git a/shard/movement.go b/shard/movement.go index 40fed2f..6ea302c 100644 --- a/shard/movement.go +++ b/shard/movement.go @@ -1,6 +1,7 @@ package shard import ( + "github.com/CPunch/gopenfusion/core/entity" "github.com/CPunch/gopenfusion/core/protocol" ) @@ -9,12 +10,16 @@ func (server *ShardServer) updatePlayerPosition(peer *protocol.CNPeer, X, Y, Z, if err != nil { return err } + newPos := entity.MakeChunkPosition(X, Y) + oldPos := plr.Chunk plr.X = X plr.Y = Y plr.Z = Z plr.Angle = Angle - + if newPos != oldPos { + server.updateEntityChunk(plr, oldPos, newPos) + } return nil } diff --git a/shard/shardServer.go b/shard/shardServer.go index 098e5cd..db30c03 100644 --- a/shard/shardServer.go +++ b/shard/shardServer.go @@ -115,6 +115,14 @@ func (server *ShardServer) disconnect(peer *protocol.CNPeer) { server.peerLock.Lock() defer server.peerLock.Unlock() + // remove from chunk(s) + plr, ok := server.peers[peer] + if ok { + log.Printf("Player %d (AccountID %d) disconnected\n", plr.PlayerID, plr.AccountID) + server.removeEntityFromChunks(server.getViewableChunks(plr.Chunk), plr) + server.getChunk(plr.Chunk).RemoveEntity(plr) + } + log.Printf("Peer %p disconnected from SHARD\n", peer) delete(server.peers, peer) }