mirror of
https://github.com/Mezeporta/Erupe.git
synced 2025-12-16 17:05:03 +01:00
Merge pull request #15 from Ellie42/improve-chat
Improve chat and additional small fixes for party interaction
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
www/tw/
|
||||
www/jp/
|
||||
|
||||
vendor/
|
||||
bin/*.bin
|
||||
bin/quests/*.bin
|
||||
bin/questlists/*.bin
|
||||
|
||||
@@ -14,18 +14,19 @@ type Config struct {
|
||||
DevMode bool
|
||||
|
||||
DevModeOptions DevModeOptions
|
||||
Database Database
|
||||
Launcher Launcher
|
||||
Sign Sign
|
||||
Channel Channel
|
||||
Entrance Entrance
|
||||
Database Database
|
||||
Launcher Launcher
|
||||
Sign Sign
|
||||
Channel Channel
|
||||
Entrance Entrance
|
||||
}
|
||||
|
||||
// DevModeOptions holds various debug/temporary options for use while developing Erupe.
|
||||
type DevModeOptions struct {
|
||||
CleanDB bool // Automatically wipes the DB on server reset.
|
||||
MaxLauncherHR bool // Sets the HR returned in the launcher to HR9 so that you can join non-beginner worlds.
|
||||
FixedStageID bool // Causes all move_stage to use the ID sl1Ns200p0a0u0 to get you into all stages
|
||||
CleanDB bool // Automatically wipes the DB on server reset.
|
||||
MaxLauncherHR bool // Sets the HR returned in the launcher to HR9 so that you can join non-beginner worlds.
|
||||
FixedStageID bool // Causes all move_stage to use the ID sl1Ns200p0a0u0 to get you into all stages
|
||||
LogOutboundMessages bool // Log all messages sent to the clients
|
||||
}
|
||||
|
||||
// Database holds the postgres database config.
|
||||
|
||||
48
network/binpacket/msg_cast_bin_private_message.go
Normal file
48
network/binpacket/msg_cast_bin_private_message.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package binpacket
|
||||
|
||||
import (
|
||||
"github.com/Andoryuuta/Erupe/network"
|
||||
"github.com/Andoryuuta/byteframe"
|
||||
)
|
||||
|
||||
type ChatTargetType uint16
|
||||
|
||||
const (
|
||||
CHAT_TARGET_PRIVATE = 0x05
|
||||
CHAT_TARGET_PARTY = 0x04
|
||||
)
|
||||
|
||||
type MsgBinTargetedChatMessage struct {
|
||||
// I can't see a reason if this is indeed the number of targets, that
|
||||
// it should use 2 bytes
|
||||
TargetCount uint16
|
||||
TargetCharIDs []uint32
|
||||
TargetType uint16
|
||||
RawDataPayload []byte
|
||||
}
|
||||
|
||||
// Opcode returns the ID associated with this packet type.
|
||||
func (m *MsgBinTargetedChatMessage) Opcode() network.PacketID {
|
||||
return network.MSG_SYS_CAST_BINARY
|
||||
}
|
||||
|
||||
func (m *MsgBinTargetedChatMessage) Parse(bf *byteframe.ByteFrame) error {
|
||||
m.TargetCount = bf.ReadUint16()
|
||||
i := uint16(0)
|
||||
|
||||
m.TargetCharIDs = make([]uint32, m.TargetCount)
|
||||
|
||||
for ; i < m.TargetCount; i++ {
|
||||
m.TargetCharIDs[i] = bf.ReadUint32()
|
||||
}
|
||||
|
||||
m.TargetType = bf.ReadUint16()
|
||||
m.RawDataPayload = bf.DataFromCurrent()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Build builds a binary packet from the current data.
|
||||
func (m *MsgBinTargetedChatMessage) Build(bf *byteframe.ByteFrame) error {
|
||||
panic("Not implemented")
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
)
|
||||
|
||||
// MsgSysDeleteUser represents the MSG_SYS_DELETE_USER
|
||||
type MsgSysDeleteUser struct{}
|
||||
type MsgSysDeleteUser struct {
|
||||
CharID uint32
|
||||
}
|
||||
|
||||
// Opcode returns the ID associated with this packet type.
|
||||
func (m *MsgSysDeleteUser) Opcode() network.PacketID {
|
||||
@@ -20,5 +22,7 @@ func (m *MsgSysDeleteUser) Parse(bf *byteframe.ByteFrame) error {
|
||||
|
||||
// Build builds a binary packet from the current data.
|
||||
func (m *MsgSysDeleteUser) Build(bf *byteframe.ByteFrame) error {
|
||||
panic("Not implemented")
|
||||
bf.WriteUint32(m.CharID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -167,3 +167,20 @@ func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session)
|
||||
session.QueueSendNonBlocking(bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) FindSessionByCharID(charID uint32) *Session {
|
||||
s.stagesLock.RLock()
|
||||
defer s.stagesLock.RUnlock()
|
||||
for _, stage := range s.stages {
|
||||
stage.RLock()
|
||||
for client := range stage.clients {
|
||||
if client.charID == charID {
|
||||
stage.RUnlock()
|
||||
return client
|
||||
}
|
||||
}
|
||||
stage.RUnlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/Andoryuuta/Erupe/network/binpacket"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -177,7 +179,9 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.QueueAck(pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) {
|
||||
logoutPlayer(s)
|
||||
}
|
||||
|
||||
func handleMsgSysSetStatus(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
@@ -190,21 +194,71 @@ func handleMsgSysPing(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.QueueAck(pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
const (
|
||||
BINARY_MESSAGE_TYPE_CHAT = 1
|
||||
BINARY_MESSAGE_TYPE_EMOTE = 6
|
||||
)
|
||||
|
||||
const (
|
||||
CHAT_TYPE_WORLD = 0x0a
|
||||
CHAT_TYPE_STAGE = 0x03
|
||||
CHAT_TYPE_TARGETED = 0x01
|
||||
)
|
||||
|
||||
func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCastBinary)
|
||||
|
||||
// Simply forward the packet to all the other clients.
|
||||
// (The client never uses Type0 upon receiving)
|
||||
// TODO(Andoryuuta): Does this broadcast need to be limited? (world, stage, guild, etc).
|
||||
resp := &mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
Type0: pkt.Type0,
|
||||
Type1: pkt.Type1,
|
||||
RawDataPayload: pkt.RawDataPayload,
|
||||
}
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
|
||||
if pkt.Type0 == 3 && pkt.Type1 == 1 {
|
||||
if pkt.Type1 == BINARY_MESSAGE_TYPE_CHAT {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteBytes(pkt.RawDataPayload)
|
||||
bf.Seek(0, io.SeekStart)
|
||||
|
||||
fmt.Println("Got chat message!")
|
||||
|
||||
switch pkt.Type0 {
|
||||
case CHAT_TYPE_WORLD:
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
case CHAT_TYPE_STAGE:
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
case CHAT_TYPE_TARGETED:
|
||||
chatMessage := &binpacket.MsgBinTargetedChatMessage{}
|
||||
err := chatMessage.Parse(bf)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Warn("failed to parse chat message")
|
||||
break
|
||||
}
|
||||
|
||||
chatBf := byteframe.NewByteFrame()
|
||||
|
||||
chatBf.WriteUint16(chatMessage.TargetType)
|
||||
chatBf.WriteBytes(chatMessage.RawDataPayload)
|
||||
|
||||
resp = &mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
Type0: pkt.Type0,
|
||||
Type1: pkt.Type1,
|
||||
RawDataPayload: chatBf.Data(),
|
||||
}
|
||||
|
||||
for _, targetID := range chatMessage.TargetCharIDs {
|
||||
char := s.server.FindSessionByCharID(targetID)
|
||||
|
||||
if char != nil {
|
||||
char.QueueSendMHF(resp)
|
||||
}
|
||||
}
|
||||
default:
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
}
|
||||
|
||||
/*
|
||||
// Made the inside of the casted binary
|
||||
payload := byteframe.NewByteFrame()
|
||||
@@ -237,6 +291,11 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
bfw.WriteUint16(uint16(len(payloadBytes)))
|
||||
bfw.WriteBytes(payloadBytes)
|
||||
*/
|
||||
} else {
|
||||
// Simply forward the packet to all the other clients.
|
||||
// (The client never uses Type0 upon receiving)
|
||||
// TODO(Andoryuuta): Does this broadcast need to be limited? (world, stage, guild, etc).
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +407,7 @@ func handleMsgSysCreateStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
s.server.stagesLock.Lock()
|
||||
stage := NewStage(stripNullTerminator(pkt.StageID))
|
||||
stage.maxPlayers = uint16(pkt.PlayerCount)
|
||||
s.server.stages[stage.id] = stage
|
||||
s.server.stagesLock.Unlock()
|
||||
|
||||
@@ -364,27 +424,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
||||
s.server.stagesLock.Unlock()
|
||||
|
||||
if s.stage != nil {
|
||||
s.stage.Lock()
|
||||
|
||||
// Remove client from old stage.
|
||||
delete(s.stage.clients, s)
|
||||
|
||||
// Delete old stage objects owned by the client.
|
||||
s.logger.Info("Sending MsgSysDeleteObject to old stage clients")
|
||||
for objID, stageObject := range s.stage.objects {
|
||||
if stageObject.ownerCharID == s.charID {
|
||||
// Broadcast the deletion to clients in the stage.
|
||||
s.stage.BroadcastMHF(&mhfpacket.MsgSysDeleteObject{
|
||||
ObjID: stageObject.id,
|
||||
}, s)
|
||||
// TODO(Andoryuuta): Should this be sent to the owner's client as well? it currently isn't.
|
||||
|
||||
// Actually delete it form the objects map.
|
||||
delete(s.stage.objects, objID)
|
||||
}
|
||||
}
|
||||
|
||||
s.stage.Unlock()
|
||||
removeSessionFromStage(s)
|
||||
}
|
||||
|
||||
// Add the new stage.
|
||||
@@ -407,25 +447,28 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
||||
s.QueueAck(ackHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
// Notify existing stage clients that this new client has entered.
|
||||
s.logger.Info("Sending MsgSysInsertUser & MsgSysNotifyUserBinary")
|
||||
s.logger.Info("Sending MsgSysInsertUser")
|
||||
s.stage.BroadcastMHF(&mhfpacket.MsgSysInsertUser{
|
||||
CharID: s.charID,
|
||||
}, s)
|
||||
|
||||
s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
BinaryType: 1,
|
||||
}, s)
|
||||
s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
BinaryType: 2,
|
||||
}, s)
|
||||
s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
BinaryType: 3,
|
||||
}, s)
|
||||
// It seems to be acceptable to recast all MSG_SYS_SET_USER_BINARY messages so far,
|
||||
// players are still notified when a new player has joined the stage.
|
||||
// These extra messages may not be needed
|
||||
//s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{
|
||||
// CharID: s.charID,
|
||||
// BinaryType: 1,
|
||||
//}, s)
|
||||
//s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{
|
||||
// CharID: s.charID,
|
||||
// BinaryType: 2,
|
||||
//}, s)
|
||||
//s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{
|
||||
// CharID: s.charID,
|
||||
// BinaryType: 3,
|
||||
//}, s)
|
||||
|
||||
// Notify the entree client about all of the existing clients in the stage.
|
||||
//Notify the entree client about all of the existing clients in the stage.
|
||||
s.logger.Info("Notifying entree about existing stage clients")
|
||||
s.stage.RLock()
|
||||
clientNotif := byteframe.NewByteFrame()
|
||||
@@ -483,6 +526,54 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
||||
s.QueueSend(clientDupObjNotif.Data())
|
||||
}
|
||||
|
||||
func removeSessionFromStage(s *Session) {
|
||||
s.stage.Lock()
|
||||
defer s.stage.Unlock()
|
||||
|
||||
// Remove client from old stage.
|
||||
delete(s.stage.clients, s)
|
||||
|
||||
// Delete old stage objects owned by the client.
|
||||
s.logger.Info("Sending MsgSysDeleteObject to old stage clients")
|
||||
for objID, stageObject := range s.stage.objects {
|
||||
if stageObject.ownerCharID == s.charID {
|
||||
// Broadcast the deletion to clients in the stage.
|
||||
s.stage.BroadcastMHF(&mhfpacket.MsgSysDeleteObject{
|
||||
ObjID: stageObject.id,
|
||||
}, s)
|
||||
// TODO(Andoryuuta): Should this be sent to the owner's client as well? it currently isn't.
|
||||
|
||||
// Actually delete it form the objects map.
|
||||
delete(s.stage.objects, objID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stageContainsSession(stage *Stage, s *Session) bool {
|
||||
stage.RLock()
|
||||
defer stage.RUnlock()
|
||||
|
||||
for session := range stage.clients {
|
||||
if session == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func logoutPlayer(s *Session) {
|
||||
s.stage.RLock()
|
||||
for client := range s.stage.clients {
|
||||
client.QueueSendMHF(&mhfpacket.MsgSysDeleteUser{
|
||||
CharID: s.charID,
|
||||
})
|
||||
}
|
||||
s.stage.RUnlock()
|
||||
|
||||
removeSessionFromStage(s)
|
||||
}
|
||||
|
||||
func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysEnterStage)
|
||||
|
||||
@@ -540,7 +631,7 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysReserveStage)
|
||||
|
||||
stageID := stripNullTerminator(pkt.StageID)
|
||||
fmt.Printf("Got reserve stage req, Unk0:%v, StageID:%v\n", pkt.Unk0, stageID)
|
||||
fmt.Printf("Got reserve stage req, TargetCount:%v, StageID:%v\n", pkt.Unk0, stageID)
|
||||
|
||||
// Try to get the stage
|
||||
s.server.stagesLock.Lock()
|
||||
@@ -568,6 +659,7 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
s.QueueAck(pkt.AckHandle, []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -691,6 +783,11 @@ func handleMsgSysGetStageBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
if gotBinary {
|
||||
doSizedAckResp(s, pkt.AckHandle, stageBinary)
|
||||
|
||||
} else if pkt.BinaryType1 == 4 {
|
||||
// This particular type seems to be expecting data that isn't set
|
||||
// is it required before the party joining can be completed
|
||||
s.QueueAck(pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x10})
|
||||
} else {
|
||||
s.logger.Warn("Failed to get stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
|
||||
s.logger.Warn("Sending blank stage binary")
|
||||
@@ -846,7 +943,7 @@ func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
// Response to our requesting client.
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // Unk, is this echoed back from pkt.Unk0?
|
||||
resp.WriteUint32(0) // Unk, is this echoed back from pkt.TargetCount?
|
||||
resp.WriteUint32(objID) // New local obj handle.
|
||||
s.QueueAck(pkt.AckHandle, resp.Data())
|
||||
|
||||
@@ -909,6 +1006,13 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.server.userBinaryPartsLock.Lock()
|
||||
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload
|
||||
s.server.userBinaryPartsLock.Unlock()
|
||||
|
||||
msg := &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
BinaryType: pkt.BinaryType,
|
||||
}
|
||||
|
||||
s.stage.BroadcastMHF(msg, s)
|
||||
}
|
||||
|
||||
func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package channelserver
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
@@ -59,6 +60,11 @@ func (s *Session) Start() {
|
||||
|
||||
// QueueSend queues a packet (raw []byte) to be sent.
|
||||
func (s *Session) QueueSend(data []byte) {
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
fmt.Printf("Sending To CharID: '%x'\n", s.charID)
|
||||
fmt.Printf("Sent Data:\n%s\n", hex.Dump(data))
|
||||
}
|
||||
|
||||
s.sendPackets <- data
|
||||
}
|
||||
|
||||
@@ -119,6 +125,13 @@ func (s *Session) sendLoop() {
|
||||
func (s *Session) recvLoop() {
|
||||
for {
|
||||
pkt, err := s.cryptConn.ReadPacket()
|
||||
|
||||
if err == io.EOF {
|
||||
s.logger.Info(fmt.Sprintf("Character(%d) disconnected", s.charID))
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err))
|
||||
return
|
||||
@@ -145,6 +158,7 @@ func (s *Session) handlePacketGroup(pktGroup []byte) {
|
||||
opcode != network.MSG_SYS_NOP &&
|
||||
opcode != network.MSG_SYS_TIME &&
|
||||
opcode != network.MSG_SYS_EXTEND_THRESHOLD {
|
||||
fmt.Printf("CharID: '%x'\n", s.charID)
|
||||
fmt.Printf("Opcode: %s\n", opcode)
|
||||
fmt.Printf("Data:\n%s\n", hex.Dump(pktGroup))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user