diff --git a/network/mhfpacket/msg_sys_cleanup_object.go b/network/mhfpacket/msg_sys_cleanup_object.go index a5548cd4a..0f3881c8d 100644 --- a/network/mhfpacket/msg_sys_cleanup_object.go +++ b/network/mhfpacket/msg_sys_cleanup_object.go @@ -20,5 +20,6 @@ func (m *MsgSysCleanupObject) Parse(bf *byteframe.ByteFrame) error { // Build builds a binary packet from the current data. func (m *MsgSysCleanupObject) Build(bf *byteframe.ByteFrame) error { - panic("Not implemented") -} \ No newline at end of file + // This packet has no data. + return nil +} diff --git a/network/mhfpacket/msg_sys_duplicate_object.go b/network/mhfpacket/msg_sys_duplicate_object.go index 72105acad..3f5e43071 100644 --- a/network/mhfpacket/msg_sys_duplicate_object.go +++ b/network/mhfpacket/msg_sys_duplicate_object.go @@ -6,7 +6,12 @@ import ( ) // MsgSysDuplicateObject represents the MSG_SYS_DUPLICATE_OBJECT -type MsgSysDuplicateObject struct{} +type MsgSysDuplicateObject struct { + ObjID uint32 + X, Y, Z float32 + Unk0 uint32 + OwnerCharID uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgSysDuplicateObject) Opcode() network.PacketID { @@ -20,5 +25,11 @@ func (m *MsgSysDuplicateObject) Parse(bf *byteframe.ByteFrame) error { // Build builds a binary packet from the current data. func (m *MsgSysDuplicateObject) Build(bf *byteframe.ByteFrame) error { - panic("Not implemented") -} \ No newline at end of file + bf.WriteUint32(m.ObjID) + bf.WriteFloat32(m.X) + bf.WriteFloat32(m.Y) + bf.WriteFloat32(m.Z) + bf.WriteUint32(m.Unk0) + bf.WriteUint32(m.OwnerCharID) + return nil +} diff --git a/network/mhfpacket/msg_sys_enter_stage.go b/network/mhfpacket/msg_sys_enter_stage.go index ee8391988..addea7cfa 100644 --- a/network/mhfpacket/msg_sys_enter_stage.go +++ b/network/mhfpacket/msg_sys_enter_stage.go @@ -10,7 +10,7 @@ type MsgSysEnterStage struct { AckHandle uint32 UnkBool uint8 StageIDLength uint8 - StageID []byte + StageID string } // Opcode returns the ID associated with this packet type. @@ -23,7 +23,7 @@ func (m *MsgSysEnterStage) Parse(bf *byteframe.ByteFrame) error { m.AckHandle = bf.ReadUint32() m.UnkBool = bf.ReadUint8() m.StageIDLength = bf.ReadUint8() - m.StageID = bf.ReadBytes(uint(m.StageIDLength)) + m.StageID = string(bf.ReadBytes(uint(m.StageIDLength))) return nil } diff --git a/network/mhfpacket/msg_sys_enumerate_client.go b/network/mhfpacket/msg_sys_enumerate_client.go index 5d3bf1e00..17115db11 100644 --- a/network/mhfpacket/msg_sys_enumerate_client.go +++ b/network/mhfpacket/msg_sys_enumerate_client.go @@ -6,7 +6,13 @@ import ( ) // MsgSysEnumerateClient represents the MSG_SYS_ENUMERATE_CLIENT -type MsgSysEnumerateClient struct{} +type MsgSysEnumerateClient struct { + AckHandle uint32 + Unk0 uint8 // Hardcoded 1 in the client + Unk1 uint8 + StageIDLength uint8 + StageID string +} // Opcode returns the ID associated with this packet type. func (m *MsgSysEnumerateClient) Opcode() network.PacketID { @@ -15,10 +21,15 @@ func (m *MsgSysEnumerateClient) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysEnumerateClient) Parse(bf *byteframe.ByteFrame) error { - panic("Not implemented") + m.AckHandle = bf.ReadUint32() + m.Unk0 = bf.ReadUint8() + m.Unk1 = bf.ReadUint8() + m.StageIDLength = bf.ReadUint8() + m.StageID = string(bf.ReadBytes(uint(m.StageIDLength))) + return nil } // Build builds a binary packet from the current data. func (m *MsgSysEnumerateClient) Build(bf *byteframe.ByteFrame) error { panic("Not implemented") -} \ No newline at end of file +} diff --git a/server/channelserver/channel_server.go b/server/channelserver/channel_server.go index 2a36a06b7..b2c2d9fdd 100644 --- a/server/channelserver/channel_server.go +++ b/server/channelserver/channel_server.go @@ -49,7 +49,8 @@ func NewServer(config *Config) *Server { } // Default town stage that clients try to enter without creating. - s.stages["sl1Ns200p0a0u0"] = &Stage{} + stage := NewStage("sl1Ns200p0a0u0") + s.stages[stage.id] = stage return s } diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 2b9126034..1212dff55 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -256,7 +256,8 @@ func handleMsgSysCreateStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysCreateStage) s.server.stagesLock.Lock() - s.server.stages[stripNullTerminator(s.stageID)] = &Stage{} + stage := NewStage(stripNullTerminator(pkt.StageID)) + s.server.stages[stage.id] = stage s.server.stagesLock.Unlock() resp := make([]byte, 8) // Unk resp. @@ -268,13 +269,40 @@ func handleMsgSysStageDestruct(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) + // Remove this session from old stage clients list and put myself in the new one. + s.server.stagesLock.Lock() + newStage, gotNewStage := s.server.stages[stripNullTerminator(pkt.StageID)] + s.server.stagesLock.Unlock() + + // Remove from old stage. + if s.stage != nil { + s.stage.Lock() + delete(s.stage.clients, s) + s.stage.Unlock() + } + + // Add the new stage. + if gotNewStage { + newStage.Lock() + newStage.clients[s] = s.charID + newStage.Unlock() + } + + // Save our new stage ID and pointer to the new stage itself. s.Lock() - s.stageID = string(pkt.StageID) + s.stageID = string(stripNullTerminator(pkt.StageID)) + s.stage = newStage s.Unlock() - //TODO: Send MSG_SYS_CLEANUP_OBJECT here before the client changes stages. + // Tell the client to cleanup it's current stage objects. + s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{}) + // Confirm the stage entry. s.QueueAck(pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + + // TODO(Andoryuuta): Notify existing stage clients that this new client has entered. + // TODO(Andoryuuta): Notify this client about all of the existing clients in the stage. + } func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {} @@ -292,6 +320,8 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { fmt.Printf("Got reserve stage req, Unk0:%v, StageID:%q\n", pkt.Unk0, pkt.StageID) + // TODO(Andoryuuta): Add proper player-slot reservations for stages. + s.QueueAck(pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) } @@ -305,7 +335,31 @@ func handleMsgSysSetStageBinary(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysGetStageBinary(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgSysEnumerateClient) + + // Read-lock the stages map. + s.server.stagesLock.RLock() + + stage, ok := s.server.stages[stripNullTerminator(pkt.StageID)] + if !ok { + s.logger.Fatal("Can't enumerate clients for stage that doesn't exist!", zap.String("stageID", pkt.StageID)) + } + + // Unlock the stages map. + s.server.stagesLock.RUnlock() + + // Read-lock the stage and make the response with all of the charID's in the stage. + resp := byteframe.NewByteFrame() + stage.RLock() + resp.WriteUint16(uint16(len(stage.clients))) // Client count + for session := range stage.clients { + resp.WriteUint32(session.charID) // Client represented by charID + } + stage.RUnlock() + + doSizedAckResp(s, pkt.AckHandle, resp.Data()) +} func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnumerateStage) @@ -362,27 +416,47 @@ func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysCreateObject) - // Get the current stage. - s.server.stagesLock.RLock() - defer s.server.stagesLock.RUnlock() - stage, ok := s.server.stages[stripNullTerminator(s.stageID)] - if !ok { + // Make sure we have a stage. + if s.stage == nil { s.logger.Fatal("StageID not in the stages map!", zap.String("stageID", s.stageID)) } // Lock the stage. - stage.Lock() - defer stage.Unlock() + s.stage.Lock() - // Make a new object ID. - objID := stage.gameObjectCount - stage.gameObjectCount++ + // Make a new stage object and insert it into the stage. + objID := s.stage.gameObjectCount + s.stage.gameObjectCount++ + newObj := &StageObject{ + id: objID, + ownerCharID: s.charID, + x: pkt.X, + y: pkt.Y, + z: pkt.Z, + } + + s.stage.objects[objID] = newObj + + // Unlock the stage. + s.stage.Unlock() + + // Response to our requesting client. resp := byteframe.NewByteFrame() resp.WriteUint32(0) // Unk, is this echoed back from pkt.Unk0? resp.WriteUint32(objID) // New local obj handle. - s.QueueAck(pkt.AckHandle, resp.Data()) + + // Duplicate the object creation to all sessions in the same stage. + dupObjUpdate := &mhfpacket.MsgSysDuplicateObject{ + ObjID: objID, + X: pkt.X, + Y: pkt.Y, + Z: pkt.Z, + Unk0: 0, + OwnerCharID: s.charID, + } + s.stage.BroadcastMHF(dupObjUpdate, s) } func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {} @@ -391,6 +465,8 @@ func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysPositionObject) fmt.Printf("Moved object %v to (%f,%f,%f)\n", pkt.ObjID, pkt.X, pkt.Y, pkt.Z) + // One of the few packets we can just re-broadcast directly. + s.stage.BroadcastMHF(pkt, s) } func handleMsgSysRotateObject(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/session.go b/server/channelserver/session.go index 1024df74b..e41cb17dc 100644 --- a/server/channelserver/session.go +++ b/server/channelserver/session.go @@ -22,6 +22,7 @@ type Session struct { sendPackets chan []byte stageID string + stage *Stage charID uint32 } diff --git a/server/channelserver/stage.go b/server/channelserver/stage.go index 2056d5af2..33967cf66 100644 --- a/server/channelserver/stage.go +++ b/server/channelserver/stage.go @@ -1,9 +1,54 @@ package channelserver -import "sync" +import ( + "sync" + + "github.com/Andoryuuta/Erupe/network/mhfpacket" + "github.com/Andoryuuta/byteframe" +) + +// StageObject holds infomation about a specific stage object. +type StageObject struct { + sync.RWMutex + id uint32 + ownerCharID uint32 + x, y, z float32 +} // Stage holds stage-specific information type Stage struct { sync.RWMutex - gameObjectCount uint32 + id string // Stage ID string + gameObjectCount uint32 // Total count of objects ever created for this stage. Used for ObjID generation. + objects map[uint32]*StageObject // Map of ObjID -> StageObject + clients map[*Session]uint32 // Map of session -> charID +} + +// NewStage creates a new stage with intialized values. +func NewStage(ID string) *Stage { + s := &Stage{ + objects: make(map[uint32]*StageObject), + clients: make(map[*Session]uint32), + } + + return s +} + +// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage. +func (s *Stage) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) { + // Make the header + bf := byteframe.NewByteFrame() + bf.WriteUint16(uint16(pkt.Opcode())) + + // Build the packet onto the byteframe. + pkt.Build(bf) + + // Broadcast the data. + for session := range s.clients { + if session == ignoredSession { + continue + } + // Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full. + session.QueueSendNonBlocking(bf.Data()) + } }