mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-02-06 01:57:38 +01:00
Merge remote-tracking branch 'origin/main' into feature/diva
# Conflicts: # server/channelserver/handlers_diva.go
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -270,6 +270,12 @@ func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostBoostTime)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeLimit)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
@@ -1,18 +1,176 @@
|
||||
package channelserver
|
||||
|
||||
import "erupe-ce/network/mhfpacket"
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CampaignEvent struct {
|
||||
ID uint32
|
||||
Unk0 uint32
|
||||
MinHR int16
|
||||
MaxHR int16
|
||||
MinSR int16
|
||||
MaxSR int16
|
||||
MinGR int16
|
||||
MaxGR int16
|
||||
Unk1 uint16
|
||||
Unk2 uint8
|
||||
Unk3 uint8
|
||||
Unk4 uint16
|
||||
Unk5 uint16
|
||||
Start time.Time
|
||||
End time.Time
|
||||
Unk6 uint8
|
||||
String0 string
|
||||
String1 string
|
||||
String2 string
|
||||
String3 string
|
||||
Link string
|
||||
Prefix string
|
||||
Categories []uint16
|
||||
}
|
||||
|
||||
type CampaignCategory struct {
|
||||
ID uint16
|
||||
Type uint8
|
||||
Title string
|
||||
Description string
|
||||
}
|
||||
|
||||
type CampaignLink struct {
|
||||
CategoryID uint16
|
||||
CampaignID uint32
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign)
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
events := []CampaignEvent{}
|
||||
categories := []CampaignCategory{}
|
||||
var campaignLinks []CampaignLink
|
||||
|
||||
if len(events) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(events)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(events)))
|
||||
}
|
||||
for _, event := range events {
|
||||
bf.WriteUint32(event.ID)
|
||||
bf.WriteUint32(event.Unk0)
|
||||
bf.WriteInt16(event.MinHR)
|
||||
bf.WriteInt16(event.MaxHR)
|
||||
bf.WriteInt16(event.MinSR)
|
||||
bf.WriteInt16(event.MaxSR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G3 {
|
||||
bf.WriteInt16(event.MinGR)
|
||||
bf.WriteInt16(event.MaxGR)
|
||||
}
|
||||
bf.WriteUint16(event.Unk1)
|
||||
bf.WriteUint8(event.Unk2)
|
||||
bf.WriteUint8(event.Unk3)
|
||||
bf.WriteUint16(event.Unk4)
|
||||
bf.WriteUint16(event.Unk5)
|
||||
bf.WriteUint32(uint32(event.Start.Unix()))
|
||||
bf.WriteUint32(uint32(event.End.Unix()))
|
||||
bf.WriteUint8(event.Unk6)
|
||||
ps.Uint8(bf, event.String0, true)
|
||||
ps.Uint8(bf, event.String1, true)
|
||||
ps.Uint8(bf, event.String2, true)
|
||||
ps.Uint8(bf, event.String3, true)
|
||||
ps.Uint8(bf, event.Link, true)
|
||||
for i := range event.Categories {
|
||||
campaignLinks = append(campaignLinks, CampaignLink{event.Categories[i], event.ID})
|
||||
}
|
||||
}
|
||||
|
||||
if len(events) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(events)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(events)))
|
||||
}
|
||||
for _, event := range events {
|
||||
bf.WriteUint32(event.ID)
|
||||
bf.WriteUint8(1) // Always 1?
|
||||
bf.WriteBytes([]byte(event.Prefix))
|
||||
}
|
||||
|
||||
if len(categories) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(categories)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(categories)))
|
||||
}
|
||||
for _, category := range categories {
|
||||
bf.WriteUint16(category.ID)
|
||||
bf.WriteUint8(category.Type)
|
||||
xTitle := stringsupport.UTF8ToSJIS(category.Title)
|
||||
xDescription := stringsupport.UTF8ToSJIS(category.Description)
|
||||
bf.WriteUint8(uint8(len(xTitle)))
|
||||
bf.WriteUint8(uint8(len(xDescription)))
|
||||
bf.WriteBytes(xTitle)
|
||||
bf.WriteBytes(xDescription)
|
||||
}
|
||||
|
||||
if len(campaignLinks) > 255 {
|
||||
bf.WriteUint8(255)
|
||||
bf.WriteUint16(uint16(len(campaignLinks)))
|
||||
} else {
|
||||
bf.WriteUint8(uint8(len(campaignLinks)))
|
||||
}
|
||||
for _, link := range campaignLinks {
|
||||
bf.WriteUint16(link.CategoryID)
|
||||
bf.WriteUint32(link.CampaignID)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateCampaign)
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(1)
|
||||
bf.WriteUint16(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfApplyCampaign)
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(1)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateItem)
|
||||
items := []struct {
|
||||
Unk0 uint32
|
||||
Unk1 uint16
|
||||
Unk2 uint16
|
||||
Unk3 uint16
|
||||
Unk4 uint32
|
||||
Unk5 uint32
|
||||
}{}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(len(items)))
|
||||
for _, item := range items {
|
||||
bf.WriteUint32(item.Unk0)
|
||||
bf.WriteUint16(item.Unk1)
|
||||
bf.WriteUint16(item.Unk2)
|
||||
bf.WriteUint16(item.Unk3)
|
||||
bf.WriteUint32(item.Unk4)
|
||||
bf.WriteUint32(item.Unk5)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireItem)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
@@ -1,21 +1,73 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RyoudamaReward struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint8
|
||||
Unk2 uint16
|
||||
Unk3 uint16
|
||||
Unk4 uint16
|
||||
Unk5 uint16
|
||||
}
|
||||
|
||||
type RyoudamaKeyScore struct {
|
||||
Unk0 uint8
|
||||
Unk1 int32
|
||||
}
|
||||
|
||||
type RyoudamaCharInfo struct {
|
||||
CID uint32
|
||||
Unk0 int32
|
||||
Name string
|
||||
}
|
||||
|
||||
type RyoudamaBoostInfo struct {
|
||||
Start time.Time
|
||||
End time.Time
|
||||
}
|
||||
|
||||
type Ryoudama struct {
|
||||
Reward []RyoudamaReward
|
||||
KeyScore []RyoudamaKeyScore
|
||||
CharInfo []RyoudamaCharInfo
|
||||
BoostInfo []RyoudamaBoostInfo
|
||||
Score []int32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRyoudama)
|
||||
// likely guild related
|
||||
// REQ: 00 04 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 01 FE 4E
|
||||
// REQ: 00 06 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00
|
||||
// REQ: 00 05 13 53 8F 18 00
|
||||
// RSP: 0A 21 8E AD 00 00 00 00 00 00 00 00 00 00 00 0E 2A 15 9E CC 00 00 00 01 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 1E 55 B0 2F 00 00 00 01 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 2A 15 9E CC 00 00 00 02 82 79 83 4E 83 8A 81 5B 83 69 00 00 00 00 03 D5 30 56 00 00 00 02 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 3F 57 76 9F 00 00 00 03 93 56 92 6E 96 B3 97 70 00 00 00 00 00 00 38 D9 0E C4 00 00 00 03 87 64 83 78 83 42 00 00 00 00 00 00 00 00 23 F3 B9 77 00 00 00 04 82 B3 82 CC 82 DC 82 E9 81 99 00 00 00 00 3F 1B 17 9C 00 00 00 04 82 B1 82 A4 82 BD 00 00 00 00 00 00 00 00 00 B9 F9 C0 00 00 00 05 82 CD 82 E9 82 A9 00 00 00 00 00 00 00 00 23 9F 9A EA 00 00 00 05 83 70 83 62 83 4C 83 83 83 49 00 00 00 00 38 D9 0E C4 00 00 00 06 87 64 83 78 83 42 00 00 00 00 00 00 00 00 1E 55 B0 2F 00 00 00 06 8D F7 00 00 00 00 00 00 00 00 00 00 00 00 03 D5 30 56 00 00 00 07 95 BD 91 F2 97 42 00 00 00 00 00 00 00 00 02 D3 B8 77 00 00 00 07 6F 77 6C 32 35 32 35 00 00 00 00 00 00 00
|
||||
data, _ := hex.DecodeString("0A218EAD0000000000000000000000010000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
var data []*byteframe.ByteFrame
|
||||
ryoudama := Ryoudama{Score: []int32{0}}
|
||||
switch pkt.Request2 {
|
||||
case 4:
|
||||
for _, score := range ryoudama.Score {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(score)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
for _, info := range ryoudama.CharInfo {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(info.CID)
|
||||
bf.WriteInt32(info.Unk0)
|
||||
bf.WriteBytes(stringsupport.PaddedString(info.Name, 14, true))
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 6:
|
||||
for _, info := range ryoudama.BoostInfo {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(uint32(info.Start.Unix()))
|
||||
bf.WriteUint32(uint32(info.End.Unix()))
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {}
|
||||
@@ -31,8 +83,41 @@ func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCaravanMyScore)
|
||||
var data []*byteframe.ByteFrame
|
||||
/*
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCaravanRanking)
|
||||
var data []*byteframe.ByteFrame
|
||||
/* RYOUDAN
|
||||
bf.WriteInt32(1)
|
||||
bf.WriteUint32(2)
|
||||
bf.WriteBytes(stringsupport.PaddedString("Test", 26, true))
|
||||
*/
|
||||
|
||||
func handleMsgMhfCaravanMyRank(s *Session, p mhfpacket.MHFPacket) {}
|
||||
/* PERSONAL
|
||||
bf.WriteInt32(1)
|
||||
bf.WriteBytes(stringsupport.PaddedString("Test", 14, true))
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfCaravanMyRank(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCaravanMyRank)
|
||||
var data []*byteframe.ByteFrame
|
||||
/*
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"golang.org/x/exp/slices"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -35,16 +36,16 @@ const (
|
||||
BroadcastTypeWorld = 0x0a
|
||||
)
|
||||
|
||||
var commands map[string]config.Command
|
||||
var commands map[string]_config.Command
|
||||
|
||||
func init() {
|
||||
commands = make(map[string]config.Command)
|
||||
commands = make(map[string]_config.Command)
|
||||
zapConfig := zap.NewDevelopmentConfig()
|
||||
zapConfig.DisableCaller = true
|
||||
zapLogger, _ := zapConfig.Build()
|
||||
defer zapLogger.Sync()
|
||||
logger := zapLogger.Named("commands")
|
||||
cmds := config.ErupeConfig.Commands
|
||||
cmds := _config.ErupeConfig.Commands
|
||||
for _, cmd := range cmds {
|
||||
commands[cmd.Name] = cmd
|
||||
if cmd.Enabled {
|
||||
@@ -55,7 +56,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func sendDisabledCommandMessage(s *Session, cmd config.Command) {
|
||||
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
|
||||
}
|
||||
|
||||
@@ -82,23 +83,28 @@ func sendServerChatMessage(s *Session, message string) {
|
||||
}
|
||||
|
||||
func parseChatCommand(s *Session, command string) {
|
||||
if strings.HasPrefix(command, commands["PSN"].Prefix) {
|
||||
args := strings.Split(command[1:], " ")
|
||||
switch args[0] {
|
||||
case commands["PSN"].Prefix:
|
||||
if commands["PSN"].Enabled {
|
||||
var id string
|
||||
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["PSN"].Prefix), &id)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
|
||||
} else {
|
||||
_, err = s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, id, s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], id))
|
||||
if len(args) > 1 {
|
||||
var exists int
|
||||
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists)
|
||||
if exists == 0 {
|
||||
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], args[1]))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandPSNExists"])
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["PSN"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Reload"].Prefix) {
|
||||
// Flush all objects and users and reload
|
||||
case commands["Reload"].Prefix:
|
||||
if commands["Reload"].Enabled {
|
||||
sendServerChatMessage(s, s.server.dict["commandReload"])
|
||||
var temp mhfpacket.MHFPacket
|
||||
@@ -159,64 +165,53 @@ func parseChatCommand(s *Session, command string) {
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Reload"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["KeyQuest"].Prefix) {
|
||||
case commands["KeyQuest"].Prefix:
|
||||
if commands["KeyQuest"].Enabled {
|
||||
if strings.HasPrefix(command, fmt.Sprintf("%s get", commands["KeyQuest"].Prefix)) {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
|
||||
} else if strings.HasPrefix(command, fmt.Sprintf("%s set", commands["KeyQuest"].Prefix)) {
|
||||
var hexs string
|
||||
n, numerr := fmt.Sscanf(command, fmt.Sprintf("%s set %%s", commands["KeyQuest"].Prefix), &hexs)
|
||||
if numerr != nil || n != 1 || len(hexs) != 16 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
|
||||
} else {
|
||||
hexd, _ := hex.DecodeString(hexs)
|
||||
s.kqf = hexd
|
||||
s.kqfOverride = true
|
||||
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
|
||||
if len(args) > 1 {
|
||||
if args[1] == "get" {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
|
||||
} else if args[1] == "set" {
|
||||
if len(args) > 2 && len(args[2]) == 16 {
|
||||
hexd, _ := hex.DecodeString(args[2])
|
||||
s.kqf = hexd
|
||||
s.kqfOverride = true
|
||||
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["KeyQuest"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Rights"].Prefix) {
|
||||
// Set account rights
|
||||
case commands["Rights"].Prefix:
|
||||
if commands["Rights"].Enabled {
|
||||
var v uint32
|
||||
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d", commands["Rights"].Prefix), &v)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
} else {
|
||||
_, err = s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
||||
if len(args) > 1 {
|
||||
v, _ := strconv.Atoi(args[1])
|
||||
_, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
|
||||
if err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Rights"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Course"].Prefix) {
|
||||
case commands["Course"].Prefix:
|
||||
if commands["Course"].Enabled {
|
||||
var name string
|
||||
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["Course"].Prefix), &name)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
|
||||
} else {
|
||||
name = strings.ToLower(name)
|
||||
if len(args) > 1 {
|
||||
for _, course := range mhfcourse.Courses() {
|
||||
for _, alias := range course.Aliases() {
|
||||
if strings.ToLower(name) == strings.ToLower(alias) {
|
||||
if slices.Contains(s.server.erupeConfig.Courses, config.Course{Name: course.Aliases()[0], Enabled: true}) {
|
||||
if strings.ToLower(args[1]) == strings.ToLower(alias) {
|
||||
if slices.Contains(s.server.erupeConfig.Courses, _config.Course{Name: course.Aliases()[0], Enabled: true}) {
|
||||
var delta, rightsInt uint32
|
||||
if mhfcourse.CourseExists(course.ID, s.courses) {
|
||||
ei := slices.IndexFunc(s.courses, func(c mhfcourse.Course) bool {
|
||||
for _, alias := range c.Aliases() {
|
||||
if strings.ToLower(name) == strings.ToLower(alias) {
|
||||
if strings.ToLower(args[1]) == strings.ToLower(alias) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -230,7 +225,7 @@ func parseChatCommand(s *Session, command string) {
|
||||
delta = uint32(math.Pow(2, float64(course.ID)))
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0]))
|
||||
}
|
||||
err = s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
|
||||
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
|
||||
if err == nil {
|
||||
s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", rightsInt+delta, s.charID)
|
||||
}
|
||||
@@ -242,82 +237,82 @@ func parseChatCommand(s *Session, command string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Course"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Raviente"].Prefix) {
|
||||
case commands["Raviente"].Prefix:
|
||||
if commands["Raviente"].Enabled {
|
||||
if getRaviSemaphore(s.server) != nil {
|
||||
s.server.raviente.Lock()
|
||||
if !strings.HasPrefix(command, "!ravi ") {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoCommand"])
|
||||
} else {
|
||||
if strings.HasPrefix(command, "!ravi start") {
|
||||
if s.server.raviente.register.startTime == 0 {
|
||||
s.server.raviente.register.startTime = s.server.raviente.register.postTime
|
||||
if len(args) > 1 {
|
||||
if s.server.getRaviSemaphore() != nil {
|
||||
switch args[1] {
|
||||
case "start":
|
||||
if s.server.raviente.register[1] == 0 {
|
||||
s.server.raviente.register[1] = s.server.raviente.register[3]
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
|
||||
s.notifyRavi()
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
|
||||
}
|
||||
} else if strings.HasPrefix(command, "!ravi cm") || strings.HasPrefix(command, "!ravi checkmultiplier") {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.raviente.GetRaviMultiplier(s.server)))
|
||||
} else if strings.HasPrefix(command, "!ravi sr") || strings.HasPrefix(command, "!ravi sendres") {
|
||||
if s.server.raviente.state.stateData[28] > 0 {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
|
||||
s.server.raviente.state.stateData[28] = 0
|
||||
case "cm", "check", "checkmultiplier", "multiplier":
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier()))
|
||||
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
|
||||
if s.server.erupeConfig.RealClientMode == _config.ZZ {
|
||||
switch args[1] {
|
||||
case "sr", "sendres", "resurrection":
|
||||
if s.server.raviente.state[28] > 0 {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
|
||||
s.server.raviente.state[28] = 0
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
|
||||
}
|
||||
case "ss", "sendsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||
s.server.raviente.support[1] = HP
|
||||
case "rs", "reqsed":
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
|
||||
s.server.raviente.support[1] = HP + 1
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviVersion"])
|
||||
}
|
||||
} else if strings.HasPrefix(command, "!ravi ss") || strings.HasPrefix(command, "!ravi sendsed") {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP
|
||||
} else if strings.HasPrefix(command, "!ravi rs") || strings.HasPrefix(command, "!ravi reqsed") {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
|
||||
// Total BerRavi HP
|
||||
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
|
||||
s.server.raviente.support.supportData[1] = HP + 12
|
||||
} else {
|
||||
default:
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
|
||||
}
|
||||
s.server.raviente.Unlock()
|
||||
} else {
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
|
||||
sendServerChatMessage(s, s.server.dict["commandRaviError"])
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Raviente"])
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(command, commands["Teleport"].Prefix) {
|
||||
case commands["Teleport"].Prefix:
|
||||
if commands["Teleport"].Enabled {
|
||||
var x, y int16
|
||||
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d %%d", commands["Teleport"].Prefix), &x, &y)
|
||||
if err != nil || n != 2 {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
|
||||
|
||||
// Make the inside of the casted binary
|
||||
if len(args) > 2 {
|
||||
x, _ := strconv.Atoi(args[1])
|
||||
y, _ := strconv.Atoi(args[2])
|
||||
payload := byteframe.NewByteFrame()
|
||||
payload.SetLE()
|
||||
payload.WriteUint8(2) // SetState type(position == 2)
|
||||
payload.WriteInt16(x) // X
|
||||
payload.WriteInt16(y) // Y
|
||||
payload.WriteUint8(2) // SetState type(position == 2)
|
||||
payload.WriteInt16(int16(x)) // X
|
||||
payload.WriteInt16(int16(y)) // Y
|
||||
payloadBytes := payload.Data()
|
||||
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
MessageType: BinaryMessageTypeState,
|
||||
RawDataPayload: payloadBytes,
|
||||
})
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
|
||||
}
|
||||
} else {
|
||||
sendDisabledCommandMessage(s, commands["Teleport"])
|
||||
@@ -425,8 +420,9 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
case BroadcastTypeServer:
|
||||
if pkt.MessageType == 1 {
|
||||
if getRaviSemaphore(s.server) != nil {
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
raviSema := s.server.getRaviSemaphore()
|
||||
if raviSema != nil {
|
||||
raviSema.BroadcastMHF(resp, s)
|
||||
}
|
||||
} else {
|
||||
s.server.BroadcastMHF(resp, s)
|
||||
|
||||
@@ -5,33 +5,37 @@ import (
|
||||
"errors"
|
||||
"erupe-ce/common/bfutil"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SavePointer int
|
||||
|
||||
const (
|
||||
pointerGender = 0x51 // +1
|
||||
pointerRP = 0x22D16 // +2
|
||||
pointerHouseTier = 0x1FB6C // +5
|
||||
pointerHouseData = 0x1FE01 // +195
|
||||
pointerBookshelfData = 0x22298 // +5576
|
||||
// Gallery data also exists at 0x21578, is this the contest submission?
|
||||
pointerGalleryData = 0x22320 // +1748
|
||||
pointerToreData = 0x1FCB4 // +240
|
||||
pointerGardenData = 0x22C58 // +68
|
||||
pointerWeaponType = 0x1F715 // +1
|
||||
pointerWeaponID = 0x1F60A // +2
|
||||
pointerHRP = 0x1FDF6 // +2
|
||||
pointerGRP = 0x1FDFC // +4
|
||||
pointerKQF = 0x23D20 // +8
|
||||
pGender = iota // +1
|
||||
pRP // +2
|
||||
pHouseTier // +5
|
||||
pHouseData // +195
|
||||
pBookshelfData // +lBookshelfData
|
||||
pGalleryData // +1748
|
||||
pToreData // +240
|
||||
pGardenData // +68
|
||||
pWeaponType // +1
|
||||
pWeaponID // +2
|
||||
pHRP // +2
|
||||
pGRP // +4
|
||||
pKQF // +8
|
||||
lBookshelfData
|
||||
)
|
||||
|
||||
type CharacterSaveData struct {
|
||||
CharID uint32
|
||||
Name string
|
||||
IsNewCharacter bool
|
||||
Pointers map[SavePointer]int
|
||||
|
||||
Gender bool
|
||||
RP uint16
|
||||
@@ -51,6 +55,55 @@ type CharacterSaveData struct {
|
||||
decompSave []byte
|
||||
}
|
||||
|
||||
func getPointers() map[SavePointer]int {
|
||||
pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576}
|
||||
switch _config.ErupeConfig.RealClientMode {
|
||||
case _config.ZZ:
|
||||
pointers[pWeaponID] = 128522
|
||||
pointers[pWeaponType] = 128789
|
||||
pointers[pHouseTier] = 129900
|
||||
pointers[pToreData] = 130228
|
||||
pointers[pHRP] = 130550
|
||||
pointers[pGRP] = 130556
|
||||
pointers[pHouseData] = 130561
|
||||
pointers[pBookshelfData] = 139928
|
||||
pointers[pGalleryData] = 140064
|
||||
pointers[pGardenData] = 142424
|
||||
pointers[pRP] = 142614
|
||||
pointers[pKQF] = 146720
|
||||
case _config.Z2, _config.Z1, _config.G101, _config.G10:
|
||||
pointers[pWeaponID] = 92522
|
||||
pointers[pWeaponType] = 92789
|
||||
pointers[pHouseTier] = 93900
|
||||
pointers[pToreData] = 94228
|
||||
pointers[pHRP] = 94550
|
||||
pointers[pGRP] = 94556
|
||||
pointers[pHouseData] = 94561
|
||||
pointers[pBookshelfData] = 103928
|
||||
pointers[pGalleryData] = 104064
|
||||
pointers[pGardenData] = 106424
|
||||
pointers[pRP] = 106614
|
||||
pointers[pKQF] = 110720
|
||||
case _config.F5, _config.F4:
|
||||
pointers[pWeaponID] = 60522
|
||||
pointers[pWeaponType] = 60789
|
||||
pointers[pHouseTier] = 61900
|
||||
pointers[pToreData] = 62228
|
||||
pointers[pHRP] = 62550
|
||||
pointers[pHouseData] = 62561
|
||||
pointers[pBookshelfData] = 57118 // This pointer only half works
|
||||
pointers[pGalleryData] = 72064
|
||||
pointers[pGardenData] = 74424
|
||||
pointers[pRP] = 74614
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode == _config.G5 {
|
||||
pointers[lBookshelfData] = 5548
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.GG {
|
||||
pointers[lBookshelfData] = 4520
|
||||
}
|
||||
return pointers
|
||||
}
|
||||
|
||||
func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) {
|
||||
result, err := s.server.db.Query("SELECT id, savedata, is_new_character, name FROM characters WHERE id = $1", charID)
|
||||
if err != nil {
|
||||
@@ -64,7 +117,9 @@ func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveData := &CharacterSaveData{}
|
||||
saveData := &CharacterSaveData{
|
||||
Pointers: getPointers(),
|
||||
}
|
||||
err = result.Scan(&saveData.CharID, &saveData.compSave, &saveData.IsNewCharacter, &saveData.Name)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to scan savedata", zap.Error(err), zap.Uint32("charID", charID))
|
||||
@@ -95,13 +150,18 @@ func (save *CharacterSaveData) Save(s *Session) {
|
||||
|
||||
save.updateSaveDataWithStruct()
|
||||
|
||||
err := save.Compress()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to compress savedata", zap.Error(err))
|
||||
return
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
err := save.Compress()
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to compress savedata", zap.Error(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Saves were not compressed
|
||||
save.compSave = save.decompSave
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hrp=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7
|
||||
_, err := s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hrp=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7
|
||||
`, save.compSave, save.HRP, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to update savedata", zap.Error(err), zap.Uint32("charID", save.CharID))
|
||||
@@ -133,38 +193,47 @@ func (save *CharacterSaveData) Decompress() error {
|
||||
func (save *CharacterSaveData) updateSaveDataWithStruct() {
|
||||
rpBytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(rpBytes, save.RP)
|
||||
copy(save.decompSave[pointerRP:pointerRP+2], rpBytes)
|
||||
copy(save.decompSave[pointerKQF:pointerKQF+8], save.KQF)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes)
|
||||
copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF)
|
||||
} else if _config.ErupeConfig.RealClientMode == _config.F5 || _config.ErupeConfig.RealClientMode == _config.F4 {
|
||||
copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// This will update the save struct with the values stored in the character save
|
||||
func (save *CharacterSaveData) updateStructWithSaveData() {
|
||||
save.Name = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[88:100]))
|
||||
if save.decompSave[pointerGender] == 1 {
|
||||
if save.decompSave[save.Pointers[pGender]] == 1 {
|
||||
save.Gender = true
|
||||
} else {
|
||||
save.Gender = false
|
||||
}
|
||||
if !save.IsNewCharacter {
|
||||
save.RP = binary.LittleEndian.Uint16(save.decompSave[pointerRP : pointerRP+2])
|
||||
save.HouseTier = save.decompSave[pointerHouseTier : pointerHouseTier+5]
|
||||
save.HouseData = save.decompSave[pointerHouseData : pointerHouseData+195]
|
||||
save.BookshelfData = save.decompSave[pointerBookshelfData : pointerBookshelfData+5576]
|
||||
save.GalleryData = save.decompSave[pointerGalleryData : pointerGalleryData+1748]
|
||||
save.ToreData = save.decompSave[pointerToreData : pointerToreData+240]
|
||||
save.GardenData = save.decompSave[pointerGardenData : pointerGardenData+68]
|
||||
save.WeaponType = save.decompSave[pointerWeaponType]
|
||||
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[pointerWeaponID : pointerWeaponID+2])
|
||||
save.HRP = binary.LittleEndian.Uint16(save.decompSave[pointerHRP : pointerHRP+2])
|
||||
if save.HRP == uint16(999) {
|
||||
save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[pointerGRP : pointerGRP+4]))
|
||||
if (_config.ErupeConfig.RealClientMode >= _config.F4 && _config.ErupeConfig.RealClientMode <= _config.F5) || _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2])
|
||||
save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5]
|
||||
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195]
|
||||
save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]]
|
||||
save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748]
|
||||
save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240]
|
||||
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
|
||||
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
|
||||
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
|
||||
save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2])
|
||||
}
|
||||
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8]
|
||||
if save.HRP == uint16(999) {
|
||||
save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))
|
||||
}
|
||||
}
|
||||
save.KQF = save.decompSave[pointerKQF : pointerKQF+8]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func handleMsgMhfSexChanger(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSexChanger)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@ import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -66,10 +67,28 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(distData.MaxSR)
|
||||
bf.WriteUint16(distData.MinGR)
|
||||
bf.WriteUint16(distData.MaxGR)
|
||||
bf.WriteUint32(0) // Unk
|
||||
bf.WriteUint32(0) // Unk
|
||||
ps.Uint16(bf, distData.EventName, true)
|
||||
bf.WriteBytes(make([]byte, 391))
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint8(0)
|
||||
ps.Uint8(bf, distData.EventName, true)
|
||||
for i := 0; i < 6; i++ {
|
||||
for j := 0; j < 13; j++ {
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
}
|
||||
i := uint8(0)
|
||||
bf.WriteUint8(i)
|
||||
if i <= 10 {
|
||||
for j := uint8(0); j < i; j++ {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(distCount))
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
"math/rand"
|
||||
_config "erupe-ce/config"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
@@ -89,7 +90,11 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
var timestamps []uint32
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
|
||||
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
|
||||
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32))
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
|
||||
}
|
||||
return
|
||||
}
|
||||
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
|
||||
@@ -97,9 +102,11 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
timestamps = generateDivaTimestamps(s, start, false)
|
||||
}
|
||||
|
||||
bf.WriteUint32(id)
|
||||
for _, timestamp := range timestamps {
|
||||
bf.WriteUint32(timestamp)
|
||||
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
|
||||
bf.WriteUint32(id)
|
||||
}
|
||||
for i := range timestamps {
|
||||
bf.WriteUint32(timestamps[i])
|
||||
}
|
||||
|
||||
bf.WriteUint16(0x19) // Unk 00011001
|
||||
|
||||
@@ -2,6 +2,7 @@ package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/token"
|
||||
_config "erupe-ce/config"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
@@ -9,47 +10,41 @@ import (
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(pkt.Unk2)
|
||||
bf.WriteUint8(pkt.Unk4)
|
||||
bf.WriteUint16(0x1142)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
|
||||
|
||||
// Do this ack manually because it uses a non-(0|1) error code
|
||||
/*
|
||||
_ACK_SUCCESS = 0
|
||||
_ACK_ERROR = 1
|
||||
|
||||
_ACK_EINPROGRESS = 16
|
||||
_ACK_ENOENT = 17
|
||||
_ACK_ENOSPC = 18
|
||||
_ACK_ETIMEOUT = 19
|
||||
|
||||
_ACK_EINVALID = 64
|
||||
_ACK_EFAILED = 65
|
||||
_ACK_ENOMEM = 66
|
||||
_ACK_ENOTEXIT = 67
|
||||
_ACK_ENOTREADY = 68
|
||||
_ACK_EALREADY = 69
|
||||
_ACK_DISABLE_WORK = 71
|
||||
*/
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
||||
AckHandle: pkt.AckHandle,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0x41,
|
||||
AckData: []byte{0x00, 0x00, 0x00, 0x00},
|
||||
})
|
||||
type Event struct {
|
||||
EventType uint16
|
||||
Unk1 uint16
|
||||
Unk2 uint16
|
||||
Unk3 uint16
|
||||
Unk4 uint16
|
||||
Unk5 uint32
|
||||
Unk6 uint32
|
||||
QuestFileIDs []uint16
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateEvent)
|
||||
stubEnumerateNoResults(s, pkt.AckHandle)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
events := []Event{}
|
||||
|
||||
bf.WriteUint8(uint8(len(events)))
|
||||
for _, event := range events {
|
||||
bf.WriteUint16(event.EventType)
|
||||
bf.WriteUint16(event.Unk1)
|
||||
bf.WriteUint16(event.Unk2)
|
||||
bf.WriteUint16(event.Unk3)
|
||||
bf.WriteUint16(event.Unk4)
|
||||
bf.WriteUint32(event.Unk5)
|
||||
bf.WriteUint32(event.Unk6)
|
||||
if event.EventType == 2 {
|
||||
bf.WriteUint8(uint8(len(event.QuestFileIDs)))
|
||||
for _, qf := range event.QuestFileIDs {
|
||||
bf.WriteUint16(qf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type activeFeature struct {
|
||||
@@ -90,14 +85,24 @@ func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
|
||||
func generateFeatureWeapons(count int) activeFeature {
|
||||
if count > 14 {
|
||||
count = 14
|
||||
_max := 14
|
||||
if _config.ErupeConfig.RealClientMode < _config.ZZ {
|
||||
_max = 13
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode < _config.G10 {
|
||||
_max = 12
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode < _config.GG {
|
||||
_max = 11
|
||||
}
|
||||
if count > _max {
|
||||
count = _max
|
||||
}
|
||||
nums := make([]int, 0)
|
||||
var result int
|
||||
for len(nums) < count {
|
||||
rng := token.RNG()
|
||||
num := rng.Intn(14)
|
||||
num := rng.Intn(_max)
|
||||
exist := false
|
||||
for _, v := range nums {
|
||||
if v == num {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/token"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"sort"
|
||||
"time"
|
||||
@@ -140,13 +140,26 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||
return timestamps
|
||||
}
|
||||
|
||||
type Trial struct {
|
||||
type FestaTrial struct {
|
||||
ID uint32 `db:"id"`
|
||||
Objective uint8 `db:"objective"`
|
||||
Objective uint16 `db:"objective"`
|
||||
GoalID uint32 `db:"goal_id"`
|
||||
TimesReq uint16 `db:"times_req"`
|
||||
Locale uint16 `db:"locale_req"`
|
||||
Reward uint16 `db:"reward"`
|
||||
Monopoly uint16
|
||||
Unk uint16
|
||||
}
|
||||
|
||||
type FestaReward struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint8
|
||||
ItemType uint16
|
||||
Quantity uint16
|
||||
ItemID uint16
|
||||
Unk5 uint16
|
||||
Unk6 uint16
|
||||
Unk7 uint8
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -190,36 +203,82 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint32(blueSouls)
|
||||
bf.WriteUint32(redSouls)
|
||||
|
||||
var trials []FestaTrial
|
||||
var trial FestaTrial
|
||||
rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
|
||||
trialData := byteframe.NewByteFrame()
|
||||
var count uint16
|
||||
for rows.Next() {
|
||||
trial := &Trial{}
|
||||
err := rows.StructScan(&trial)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
trialData.WriteUint32(trial.ID)
|
||||
trialData.WriteUint8(0) // Unk
|
||||
trialData.WriteUint8(trial.Objective)
|
||||
trialData.WriteUint32(trial.GoalID)
|
||||
trialData.WriteUint16(trial.TimesReq)
|
||||
trialData.WriteUint16(trial.Locale)
|
||||
trialData.WriteUint16(trial.Reward)
|
||||
trialData.WriteUint8(0xFF) // Unk
|
||||
trialData.WriteUint8(0xFF) // MonopolyState
|
||||
trialData.WriteUint16(0) // Unk
|
||||
trials = append(trials, trial)
|
||||
}
|
||||
bf.WriteUint16(uint16(len(trials)))
|
||||
for _, trial := range trials {
|
||||
bf.WriteUint32(trial.ID)
|
||||
bf.WriteUint16(trial.Objective)
|
||||
bf.WriteUint32(trial.GoalID)
|
||||
bf.WriteUint16(trial.TimesReq)
|
||||
bf.WriteUint16(trial.Locale)
|
||||
bf.WriteUint16(trial.Reward)
|
||||
trial.Monopoly = 0xFFFF // NYI
|
||||
bf.WriteUint16(trial.Monopoly)
|
||||
bf.WriteUint16(trial.Unk)
|
||||
}
|
||||
bf.WriteUint16(count)
|
||||
bf.WriteBytes(trialData.Data())
|
||||
|
||||
// Static bonus rewards
|
||||
rewards, _ := hex.DecodeString("001901000007015E05F000000000000100000703E81B6300000000010100000C03E8000000000000000100000D0000000000000000000100000100000000000000000002000007015E05F000000000000200000703E81B6300000000010200000C03E8000000000000000200000D0000000000000000000200000400000000000000000003000007015E05F000000000000300000703E81B6300000000010300000C03E8000000000000000300000D0000000000000000000300000100000000000000000004000007015E05F000000000000400000703E81B6300000000010400000C03E8000000000000000400000D0000000000000000000400000400000000000000000005000007015E05F000000000000500000703E81B6300000000010500000C03E8000000000000000500000D00000000000000000005000001000000000000000000")
|
||||
bf.WriteBytes(rewards)
|
||||
// The Winner and Loser Armor IDs are missing
|
||||
// Item 7011 may not exist in older versions, remove to prevent crashes
|
||||
rewards := []FestaReward{
|
||||
{1, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{1, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{1, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{1, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{1, 0, 1, 0, 0, 0, 0, 0},
|
||||
{2, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{2, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{2, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{2, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{2, 0, 4, 0, 0, 0, 0, 0},
|
||||
{3, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{3, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{3, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{3, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{3, 0, 1, 0, 0, 0, 0, 0},
|
||||
{4, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{4, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{4, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{4, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{4, 0, 4, 0, 0, 0, 0, 0},
|
||||
{5, 0, 7, 350, 1520, 0, 0, 0},
|
||||
{5, 0, 7, 1000, 7011, 0, 0, 1},
|
||||
{5, 0, 12, 1000, 0, 0, 0, 0},
|
||||
{5, 0, 13, 0, 0, 0, 0, 0},
|
||||
//{5, 0, 1, 0, 0, 0, 0, 0},
|
||||
}
|
||||
|
||||
bf.WriteUint16(0x0001)
|
||||
bf.WriteUint32(0xD4C001F4)
|
||||
bf.WriteUint16(uint16(len(rewards)))
|
||||
for _, reward := range rewards {
|
||||
bf.WriteUint8(reward.Unk0)
|
||||
bf.WriteUint8(reward.Unk1)
|
||||
bf.WriteUint16(reward.ItemType)
|
||||
bf.WriteUint16(reward.Quantity)
|
||||
bf.WriteUint16(reward.ItemID)
|
||||
// Not confirmed to be G1 but exists in G3
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G1 {
|
||||
bf.WriteUint16(reward.Unk5)
|
||||
bf.WriteUint16(reward.Unk6)
|
||||
bf.WriteUint8(reward.Unk7)
|
||||
}
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G61 {
|
||||
if s.server.erupeConfig.GameplayOptions.MaximumFP > 0xFFFF {
|
||||
s.server.erupeConfig.GameplayOptions.MaximumFP = 0xFFFF
|
||||
}
|
||||
bf.WriteUint16(uint16(s.server.erupeConfig.GameplayOptions.MaximumFP))
|
||||
} else {
|
||||
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP)
|
||||
}
|
||||
bf.WriteUint16(500)
|
||||
|
||||
categoryWinners := uint16(0) // NYI
|
||||
bf.WriteUint16(categoryWinners)
|
||||
@@ -239,8 +298,18 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
ps.Uint8(bf, "", true) // Guild Name
|
||||
}
|
||||
|
||||
d, _ := hex.DecodeString("000000000000000100001388000007D0000003E800000064012C00C8009600640032")
|
||||
bf.WriteBytes(d)
|
||||
// Unknown values
|
||||
bf.WriteUint32(1)
|
||||
bf.WriteUint32(5000)
|
||||
bf.WriteUint32(2000)
|
||||
bf.WriteUint32(1000)
|
||||
bf.WriteUint32(100)
|
||||
bf.WriteUint16(300)
|
||||
bf.WriteUint16(200)
|
||||
bf.WriteUint16(150)
|
||||
bf.WriteUint16(100)
|
||||
bf.WriteUint16(50)
|
||||
|
||||
ps.Uint16(bf, "", false)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
_config "erupe-ce/config"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
@@ -62,7 +62,6 @@ type Guild struct {
|
||||
Recruiting bool `db:"recruiting"`
|
||||
FestivalColour FestivalColour `db:"festival_colour"`
|
||||
Souls uint32 `db:"souls"`
|
||||
Rank uint16 `db:"rank"`
|
||||
AllianceID uint32 `db:"alliance_id"`
|
||||
Icon *GuildIcon `db:"icon"`
|
||||
|
||||
@@ -115,6 +114,39 @@ func (gi *GuildIcon) Value() (valuer driver.Value, err error) {
|
||||
return json.Marshal(gi)
|
||||
}
|
||||
|
||||
func (g *Guild) Rank() uint16 {
|
||||
rpMap := []uint32{
|
||||
24, 48, 96, 144, 192, 240, 288, 360, 432,
|
||||
504, 600, 696, 792, 888, 984, 1080, 1200,
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode <= _config.Z2 {
|
||||
rpMap = []uint32{
|
||||
3500, 6000, 8500, 11000, 13500, 16000, 20000, 24000, 28000,
|
||||
33000, 38000, 43000, 48000, 55000, 70000, 90000, 120000,
|
||||
}
|
||||
}
|
||||
for i, u := range rpMap {
|
||||
if g.RankRP < u {
|
||||
if _config.ErupeConfig.RealClientMode <= _config.S6 && i >= 12 {
|
||||
return 12
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 && i >= 13 {
|
||||
return 13
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G32 && i >= 14 {
|
||||
return 14
|
||||
}
|
||||
return uint16(i)
|
||||
}
|
||||
}
|
||||
if _config.ErupeConfig.RealClientMode <= _config.S6 {
|
||||
return 12
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.F5 {
|
||||
return 13
|
||||
} else if _config.ErupeConfig.RealClientMode <= _config.G32 {
|
||||
return 14
|
||||
}
|
||||
return 17
|
||||
}
|
||||
|
||||
const guildInfoSelectQuery = `
|
||||
SELECT
|
||||
g.id,
|
||||
@@ -137,14 +169,6 @@ SELECT
|
||||
recruiting,
|
||||
COALESCE((SELECT team FROM festa_registrations fr WHERE fr.guild_id = g.id), 'none') AS festival_colour,
|
||||
(SELECT SUM(souls) FROM guild_characters gc WHERE gc.guild_id = g.id) AS souls,
|
||||
CASE
|
||||
WHEN rank_rp <= 48 THEN rank_rp/24
|
||||
WHEN rank_rp <= 288 THEN rank_rp/48+1
|
||||
WHEN rank_rp <= 504 THEN rank_rp/72+3
|
||||
WHEN rank_rp <= 1080 THEN (rank_rp-24)/96+5
|
||||
WHEN rank_rp < 1200 THEN 16
|
||||
ELSE 17
|
||||
END rank,
|
||||
COALESCE((
|
||||
SELECT id FROM guild_alliances ga WHERE
|
||||
ga.parent_id = g.id OR
|
||||
@@ -615,13 +639,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateGuild)
|
||||
|
||||
guild, err := GetGuildInfoByID(s, pkt.GuildID)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
characterGuildInfo, err := GetCharacterGuildData(s, s.charID)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -630,22 +648,19 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
switch pkt.Action {
|
||||
case mhfpacket.OPERATE_GUILD_DISBAND:
|
||||
case mhfpacket.OperateGuildDisband:
|
||||
response := 1
|
||||
if guild.LeaderCharID != s.charID {
|
||||
s.logger.Warn(fmt.Sprintf("character '%d' is attempting to manage guild '%d' without permission", s.charID, guild.ID))
|
||||
return
|
||||
response = 0
|
||||
} else {
|
||||
err = guild.Disband(s)
|
||||
if err != nil {
|
||||
response = 0
|
||||
}
|
||||
}
|
||||
|
||||
err = guild.Disband(s)
|
||||
response := 0x01
|
||||
|
||||
if err != nil {
|
||||
// All successful acks return 0x01, assuming 0x00 is failure
|
||||
response = 0x00
|
||||
}
|
||||
|
||||
bf.WriteUint32(uint32(response))
|
||||
case mhfpacket.OPERATE_GUILD_RESIGN:
|
||||
case mhfpacket.OperateGuildResign:
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
if err == nil {
|
||||
sort.Slice(guildMembers[:], func(i, j int) bool {
|
||||
@@ -664,25 +679,22 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
guild.Save(s)
|
||||
}
|
||||
case mhfpacket.OPERATE_GUILD_APPLY:
|
||||
case mhfpacket.OperateGuildApply:
|
||||
err = guild.CreateApplication(s, s.charID, GuildApplicationTypeApplied, nil)
|
||||
|
||||
if err == nil {
|
||||
bf.WriteUint32(guild.LeaderCharID)
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
case mhfpacket.OPERATE_GUILD_LEAVE:
|
||||
var err error
|
||||
|
||||
case mhfpacket.OperateGuildLeave:
|
||||
if characterGuildInfo.IsApplicant {
|
||||
err = guild.RejectApplication(s, s.charID)
|
||||
} else {
|
||||
err = guild.RemoveCharacter(s, s.charID)
|
||||
}
|
||||
|
||||
response := 0x01
|
||||
response := 1
|
||||
if err != nil {
|
||||
// All successful acks return 0x01, assuming 0x00 is failure
|
||||
response = 0x00
|
||||
response = 0
|
||||
} else {
|
||||
mail := Mail{
|
||||
RecipientID: s.charID,
|
||||
@@ -692,26 +704,25 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
mail.Send(s, nil)
|
||||
}
|
||||
|
||||
bf.WriteUint32(uint32(response))
|
||||
case mhfpacket.OPERATE_GUILD_DONATE_RANK:
|
||||
case mhfpacket.OperateGuildDonateRank:
|
||||
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, false))
|
||||
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_DENY:
|
||||
case mhfpacket.OperateGuildSetApplicationDeny:
|
||||
s.server.db.Exec("UPDATE guilds SET recruiting=false WHERE id=$1", guild.ID)
|
||||
case mhfpacket.OPERATE_GUILD_SET_APPLICATION_ALLOW:
|
||||
case mhfpacket.OperateGuildSetApplicationAllow:
|
||||
s.server.db.Exec("UPDATE guilds SET recruiting=true WHERE id=$1", guild.ID)
|
||||
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_TRUE:
|
||||
case mhfpacket.OperateGuildSetAvoidLeadershipTrue:
|
||||
handleAvoidLeadershipUpdate(s, pkt, true)
|
||||
case mhfpacket.OPERATE_GUILD_SET_AVOID_LEADERSHIP_FALSE:
|
||||
case mhfpacket.OperateGuildSetAvoidLeadershipFalse:
|
||||
handleAvoidLeadershipUpdate(s, pkt, false)
|
||||
case mhfpacket.OPERATE_GUILD_UPDATE_COMMENT:
|
||||
case mhfpacket.OperateGuildUpdateComment:
|
||||
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
guild.Comment = stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())
|
||||
guild.Save(s)
|
||||
case mhfpacket.OPERATE_GUILD_UPDATE_MOTTO:
|
||||
case mhfpacket.OperateGuildUpdateMotto:
|
||||
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -720,27 +731,29 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
guild.SubMotto = pkt.Data1.ReadUint8()
|
||||
guild.MainMotto = pkt.Data1.ReadUint8()
|
||||
guild.Save(s)
|
||||
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_1:
|
||||
case mhfpacket.OperateGuildRenamePugi1:
|
||||
handleRenamePugi(s, pkt.Data2, guild, 1)
|
||||
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_2:
|
||||
case mhfpacket.OperateGuildRenamePugi2:
|
||||
handleRenamePugi(s, pkt.Data2, guild, 2)
|
||||
case mhfpacket.OPERATE_GUILD_RENAME_PUGI_3:
|
||||
case mhfpacket.OperateGuildRenamePugi3:
|
||||
handleRenamePugi(s, pkt.Data2, guild, 3)
|
||||
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_1:
|
||||
case mhfpacket.OperateGuildChangePugi1:
|
||||
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 1)
|
||||
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_2:
|
||||
case mhfpacket.OperateGuildChangePugi2:
|
||||
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 2)
|
||||
case mhfpacket.OPERATE_GUILD_CHANGE_PUGI_3:
|
||||
case mhfpacket.OperateGuildChangePugi3:
|
||||
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 3)
|
||||
case mhfpacket.OPERATE_GUILD_UNLOCK_OUTFIT:
|
||||
case mhfpacket.OperateGuildUnlockOutfit:
|
||||
// TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time
|
||||
s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID)
|
||||
case mhfpacket.OPERATE_GUILD_DONATE_EVENT:
|
||||
case mhfpacket.OperateGuildDonateRoom:
|
||||
// TODO: Where does this go?
|
||||
case mhfpacket.OperateGuildDonateEvent:
|
||||
quantity := uint16(pkt.Data1.ReadUint32())
|
||||
bf.WriteBytes(handleDonateRP(s, quantity, guild, true))
|
||||
// TODO: Move this value onto rp_yesterday and reset to 0... daily?
|
||||
s.server.db.Exec(`UPDATE guild_characters SET rp_today=rp_today+$1 WHERE character_id=$2`, quantity, s.charID)
|
||||
case mhfpacket.OPERATE_GUILD_EVENT_EXCHANGE:
|
||||
case mhfpacket.OperateGuildEventExchange:
|
||||
rp := uint16(pkt.Data1.ReadUint32())
|
||||
var balance uint32
|
||||
s.server.db.QueryRow(`UPDATE guilds SET event_rp=event_rp-$1 WHERE id=$2 RETURNING event_rp`, rp, guild.ID).Scan(&balance)
|
||||
@@ -922,14 +935,19 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
bf.WriteUint32(guild.ID)
|
||||
bf.WriteUint32(guild.LeaderCharID)
|
||||
bf.WriteUint16(guild.Rank)
|
||||
bf.WriteUint16(guild.Rank())
|
||||
bf.WriteUint16(guild.MemberCount)
|
||||
|
||||
bf.WriteUint8(guild.MainMotto)
|
||||
bf.WriteUint8(guild.SubMotto)
|
||||
|
||||
// Unk appears to be static
|
||||
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(0)
|
||||
|
||||
bf.WriteBool(!guild.Recruiting)
|
||||
|
||||
@@ -952,28 +970,39 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(FestivalColourCodes[guild.FestivalColour])
|
||||
bf.WriteUint32(guild.RankRP)
|
||||
bf.WriteBytes(guildLeaderName)
|
||||
bf.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00}) // Unk
|
||||
bf.WriteBool(false) // isReturnGuild
|
||||
bf.WriteBool(false) // earnedSpecialHall
|
||||
bf.WriteBytes([]byte{0x02, 0x02}) // Unk
|
||||
bf.WriteUint32(guild.EventRP)
|
||||
bf.WriteUint32(0) // Unk
|
||||
bf.WriteBool(false) // isReturnGuild
|
||||
bf.WriteBool(false) // earnedSpecialHall
|
||||
bf.WriteUint8(2)
|
||||
bf.WriteUint8(2)
|
||||
bf.WriteUint32(guild.EventRP) // Skipped if last byte is <2?
|
||||
ps.Uint8(bf, guild.PugiName1, true)
|
||||
ps.Uint8(bf, guild.PugiName2, true)
|
||||
ps.Uint8(bf, guild.PugiName3, true)
|
||||
bf.WriteUint8(guild.PugiOutfit1)
|
||||
bf.WriteUint8(guild.PugiOutfit2)
|
||||
bf.WriteUint8(guild.PugiOutfit3)
|
||||
bf.WriteUint8(guild.PugiOutfit1)
|
||||
bf.WriteUint8(guild.PugiOutfit2)
|
||||
bf.WriteUint8(guild.PugiOutfit3)
|
||||
if s.server.erupeConfig.RealClientMode >= _config.Z1 {
|
||||
bf.WriteUint8(guild.PugiOutfit1)
|
||||
bf.WriteUint8(guild.PugiOutfit2)
|
||||
bf.WriteUint8(guild.PugiOutfit3)
|
||||
}
|
||||
bf.WriteUint32(guild.PugiOutfits)
|
||||
|
||||
// Unk flags
|
||||
bf.WriteUint8(0x3C) // also seen as 0x32 on JP and 0x64 on TW
|
||||
if guild.Rank() >= 3 {
|
||||
bf.WriteUint8(40)
|
||||
} else if guild.Rank() >= 7 {
|
||||
bf.WriteUint8(50)
|
||||
} else if guild.Rank() >= 10 {
|
||||
bf.WriteUint8(60)
|
||||
} else {
|
||||
bf.WriteUint8(30)
|
||||
}
|
||||
|
||||
bf.WriteBytes([]byte{
|
||||
0x00, 0x00, 0xD6, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
})
|
||||
bf.WriteUint32(55000)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(0) // Changing Room RP
|
||||
bf.WriteUint16(0)
|
||||
|
||||
if guild.AllianceID > 0 {
|
||||
alliance, err := GetAllianceData(s, guild.AllianceID)
|
||||
@@ -983,7 +1012,8 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint32(alliance.ID)
|
||||
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
|
||||
bf.WriteUint16(alliance.TotalMembers)
|
||||
bf.WriteUint16(0) // Unk0
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(0)
|
||||
ps.Uint16(bf, alliance.Name, true)
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
@@ -1001,7 +1031,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
bf.WriteUint16(alliance.ParentGuild.Rank)
|
||||
bf.WriteUint16(alliance.ParentGuild.Rank())
|
||||
bf.WriteUint16(alliance.ParentGuild.MemberCount)
|
||||
ps.Uint16(bf, alliance.ParentGuild.Name, true)
|
||||
ps.Uint16(bf, alliance.ParentGuild.LeaderName, true)
|
||||
@@ -1013,7 +1043,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
bf.WriteUint16(alliance.SubGuild1.Rank)
|
||||
bf.WriteUint16(alliance.SubGuild1.Rank())
|
||||
bf.WriteUint16(alliance.SubGuild1.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild1.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild1.LeaderName, true)
|
||||
@@ -1026,7 +1056,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
bf.WriteUint16(alliance.SubGuild2.Rank)
|
||||
bf.WriteUint16(alliance.SubGuild2.Rank())
|
||||
bf.WriteUint16(alliance.SubGuild2.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild2.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild2.LeaderName, true)
|
||||
@@ -1043,29 +1073,46 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(uint16(len(applicants)))
|
||||
for _, applicant := range applicants {
|
||||
bf.WriteUint32(applicant.CharID)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(applicant.HRP)
|
||||
bf.WriteUint16(applicant.GR)
|
||||
ps.Uint8(bf, applicant.Name, true)
|
||||
}
|
||||
}
|
||||
|
||||
bf.WriteUint16(0x0000) // lenAllianceApplications
|
||||
type UnkGuildInfo struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint8
|
||||
Unk2 uint8
|
||||
}
|
||||
unkGuildInfo := []UnkGuildInfo{}
|
||||
bf.WriteUint8(uint8(len(unkGuildInfo)))
|
||||
for _, info := range unkGuildInfo {
|
||||
bf.WriteUint8(info.Unk0)
|
||||
bf.WriteUint8(info.Unk1)
|
||||
bf.WriteUint8(info.Unk2)
|
||||
}
|
||||
|
||||
/*
|
||||
alliance application format
|
||||
uint16 numapplicants (above)
|
||||
|
||||
uint32 guild id
|
||||
uint32 guild leader id (for mail)
|
||||
uint32 unk (always null in pcap)
|
||||
uint16 member count
|
||||
uint16 len guild name
|
||||
string nullterm guild name
|
||||
uint16 len guild leader name
|
||||
string nullterm guild leader name
|
||||
*/
|
||||
type AllianceInvite struct {
|
||||
GuildID uint32
|
||||
LeaderID uint32
|
||||
Unk0 uint16
|
||||
Unk1 uint16
|
||||
Members uint16
|
||||
GuildName string
|
||||
LeaderName string
|
||||
}
|
||||
allianceInvites := []AllianceInvite{}
|
||||
bf.WriteUint8(uint8(len(allianceInvites)))
|
||||
for _, invite := range allianceInvites {
|
||||
bf.WriteUint32(invite.GuildID)
|
||||
bf.WriteUint32(invite.LeaderID)
|
||||
bf.WriteUint16(invite.Unk0)
|
||||
bf.WriteUint16(invite.Unk1)
|
||||
bf.WriteUint16(invite.Members)
|
||||
ps.Uint16(bf, invite.GuildName, true)
|
||||
ps.Uint16(bf, invite.LeaderName, true)
|
||||
}
|
||||
|
||||
if guild.Icon != nil {
|
||||
bf.WriteUint8(uint8(len(guild.Icon.Parts)))
|
||||
@@ -1083,7 +1130,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint16(p.PosY)
|
||||
}
|
||||
} else {
|
||||
bf.WriteUint8(0x00)
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint8(0) // Unk
|
||||
|
||||
@@ -1100,91 +1147,87 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
var alliances []*GuildAlliance
|
||||
var rows *sqlx.Rows
|
||||
var err error
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.Data1)
|
||||
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
|
||||
bf.ReadBytes(8)
|
||||
searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()))
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE g.name ILIKE $1 OFFSET $2 LIMIT 11`, guildInfoSelectQuery), searchTerm, pkt.Page*10)
|
||||
if pkt.Type <= 8 {
|
||||
var tempGuilds []*Guild
|
||||
rows, err = s.server.db.Queryx(guildInfoSelectQuery)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
guild, err := buildGuildObjectFromDbResult(rows, err, s)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tempGuilds = append(tempGuilds, guild)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
|
||||
bf.ReadBytes(8)
|
||||
searchTerm := fmt.Sprintf(`%%%s%%`, stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()))
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE lc.name ILIKE $1 OFFSET $2 LIMIT 11`, guildInfoSelectQuery), searchTerm, pkt.Page*10)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.Name, pkt.Data2) {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
|
||||
ID := bf.ReadUint32()
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE leader_id = $1`, guildInfoSelectQuery), ID)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
|
||||
for _, guild := range tempGuilds {
|
||||
if strings.Contains(guild.LeaderName, pkt.Data2) {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
|
||||
if pkt.Sorting {
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count DESC OFFSET $1 LIMIT 11`, guildInfoSelectQuery), pkt.Page*10)
|
||||
} else {
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY member_count ASC OFFSET $1 LIMIT 11`, guildInfoSelectQuery), pkt.Page*10)
|
||||
}
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
|
||||
ID := bf.ReadUint32()
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.LeaderCharID == ID {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
|
||||
if pkt.Sorting {
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id ASC OFFSET $1 LIMIT 11`, guildInfoSelectQuery), pkt.Page*10)
|
||||
} else {
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY id DESC OFFSET $1 LIMIT 11`, guildInfoSelectQuery), pkt.Page*10)
|
||||
}
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_MEMBERS:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].MemberCount > tempGuilds[j].MemberCount
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].MemberCount < tempGuilds[j].MemberCount
|
||||
})
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
|
||||
if pkt.Sorting {
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp DESC OFFSET $1 LIMIT 11`, guildInfoSelectQuery), pkt.Page*10)
|
||||
} else {
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s ORDER BY rank_rp ASC OFFSET $1 LIMIT 11`, guildInfoSelectQuery), pkt.Page*10)
|
||||
}
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_REGISTRATION:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].CreatedAt.Unix() > tempGuilds[j].CreatedAt.Unix()
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].CreatedAt.Unix() < tempGuilds[j].CreatedAt.Unix()
|
||||
})
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
|
||||
mainMotto := bf.ReadUint16()
|
||||
subMotto := bf.ReadUint16()
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE main_motto = $1 AND sub_motto = $2 OFFSET $3 LIMIT 11`, guildInfoSelectQuery), mainMotto, subMotto, pkt.Page*10)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_ORDER_RANK:
|
||||
if pkt.Sorting {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].RankRP > tempGuilds[j].RankRP
|
||||
})
|
||||
} else {
|
||||
sort.Slice(tempGuilds, func(i, j int) bool {
|
||||
return tempGuilds[i].RankRP < tempGuilds[j].RankRP
|
||||
})
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
|
||||
// Assume the player wants the newest guilds with open recruitment
|
||||
rows, err = s.server.db.Queryx(fmt.Sprintf(`%s WHERE recruiting=true ORDER BY id DESC OFFSET $1 LIMIT 11`, guildInfoSelectQuery), pkt.Page*10)
|
||||
if err == nil {
|
||||
for rows.Next() {
|
||||
guild, _ := buildGuildObjectFromDbResult(rows, err, s)
|
||||
guilds = append(guilds, guild)
|
||||
guilds = tempGuilds
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
|
||||
mainMotto := uint8(bf.ReadUint16())
|
||||
subMotto := uint8(bf.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == mainMotto && guild.SubMotto == subMotto {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
|
||||
recruitingMotto := uint8(bf.ReadUint16())
|
||||
for _, guild := range tempGuilds {
|
||||
if guild.MainMotto == recruitingMotto {
|
||||
guilds = append(guilds, guild)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1200,18 +1243,14 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
switch pkt.Type {
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
|
||||
bf.ReadBytes(8)
|
||||
searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
for _, alliance := range tempAlliances {
|
||||
if strings.Contains(alliance.Name, searchTerm) {
|
||||
if strings.Contains(alliance.Name, pkt.Data2) {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
|
||||
bf.ReadBytes(8)
|
||||
searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes())
|
||||
for _, alliance := range tempAlliances {
|
||||
if strings.Contains(alliance.ParentGuild.LeaderName, searchTerm) {
|
||||
if strings.Contains(alliance.ParentGuild.LeaderName, pkt.Data2) {
|
||||
alliances = append(alliances, alliance)
|
||||
}
|
||||
}
|
||||
@@ -1292,8 +1331,8 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint32(guild.ID)
|
||||
bf.WriteUint32(guild.LeaderCharID)
|
||||
bf.WriteUint16(guild.MemberCount)
|
||||
bf.WriteUint16(0x0000) // Unk
|
||||
bf.WriteUint16(guild.Rank) // OR guilds in alliance
|
||||
bf.WriteUint16(0x0000) // Unk
|
||||
bf.WriteUint16(guild.Rank())
|
||||
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
|
||||
ps.Uint8(bf, guild.Name, true)
|
||||
ps.Uint8(bf, guild.LeaderName, true)
|
||||
@@ -1355,7 +1394,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
if guild != nil {
|
||||
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
|
||||
if isApplicant {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1397,14 +1436,21 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
for _, member := range guildMembers {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteUint16(member.HRP)
|
||||
bf.WriteUint16(member.GR)
|
||||
bf.WriteUint16(member.WeaponID)
|
||||
if member.WeaponType == 1 || member.WeaponType == 5 || member.WeaponType == 10 { // If weapon is ranged
|
||||
bf.WriteUint16(0x0700)
|
||||
} else {
|
||||
bf.WriteUint16(0x0600)
|
||||
if s.server.erupeConfig.RealClientMode > _config.G7 {
|
||||
bf.WriteUint16(member.GR)
|
||||
}
|
||||
bf.WriteUint8(member.OrderIndex)
|
||||
if s.server.erupeConfig.RealClientMode < _config.ZZ {
|
||||
// Magnet Spike crash workaround
|
||||
bf.WriteUint16(0)
|
||||
} else {
|
||||
bf.WriteUint16(member.WeaponID)
|
||||
}
|
||||
if member.WeaponType == 1 || member.WeaponType == 5 || member.WeaponType == 10 { // If weapon is ranged
|
||||
bf.WriteUint8(7)
|
||||
} else {
|
||||
bf.WriteUint8(6)
|
||||
}
|
||||
bf.WriteUint16(member.OrderIndex)
|
||||
bf.WriteBool(member.AvoidLeadership)
|
||||
ps.Uint8(bf, member.Name, true)
|
||||
}
|
||||
@@ -1458,7 +1504,6 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight)
|
||||
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
|
||||
if guild == nil && s.prevGuildID != 0 {
|
||||
guild, err = GetGuildInfoByID(s, s.prevGuildID)
|
||||
s.prevGuildID = 0
|
||||
@@ -1468,31 +1513,14 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Warn("failed to respond to manage rights message")
|
||||
return
|
||||
} else if guild == nil {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0x00) // Unk
|
||||
bf.WriteUint16(0x00) // Member count
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
bf.WriteUint16(0x00) // Unk
|
||||
bf.WriteUint16(guild.MemberCount)
|
||||
|
||||
bf.WriteUint32(uint32(guild.MemberCount))
|
||||
members, _ := GetGuildMembers(s, guild.ID, false)
|
||||
|
||||
for _, member := range members {
|
||||
bf.WriteUint32(member.CharID)
|
||||
bf.WriteBool(member.Recruiter)
|
||||
bf.WriteBytes(make([]byte, 3))
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -1689,16 +1717,51 @@ func handleMsgMhfReadGuildcard(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
type GuildMission struct {
|
||||
ID uint32
|
||||
Unk uint32
|
||||
Type uint16
|
||||
Goal uint16
|
||||
Quantity uint16
|
||||
SkipTickets uint16
|
||||
GR bool
|
||||
RewardType uint16
|
||||
RewardLevel uint16
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildMissionList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildMissionList)
|
||||
|
||||
decoded, err := hex.DecodeString("000694610000023E000112990023000100000200015DDD232100069462000002F30000005F000C000200000300025DDD232100069463000002EA0000005F0006000100000100015DDD23210006946400000245000000530010000200000400025DDD232100069465000002B60001129B0019000100000200015DDD232100069466000003DC0000001B0010000100000600015DDD232100069467000002DA000112A00019000100000400015DDD232100069468000002A800010DEF0032000200000200025DDD2321000694690000045500000022003C000200000600025DDD23210006946A00000080000122D90046000200000300025DDD23210006946B000001960000003B000A000100000100015DDD23210006946C0000049200000046005A000300000600035DDD23210006946D000000A4000000260018000200000600025DDD23210006946E0000017A00010DE40096000300000100035DDD23210006946F000001BE0000005E0014000200000400025DDD2355000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
bf := byteframe.NewByteFrame()
|
||||
missions := []GuildMission{
|
||||
{431201, 574, 1, 4761, 35, 1, false, 2, 1},
|
||||
{431202, 755, 0, 95, 12, 2, false, 3, 2},
|
||||
{431203, 746, 0, 95, 6, 1, false, 1, 1},
|
||||
{431204, 581, 0, 83, 16, 2, false, 4, 2},
|
||||
{431205, 694, 1, 4763, 25, 1, false, 2, 1},
|
||||
{431206, 988, 0, 27, 16, 1, false, 6, 1},
|
||||
{431207, 730, 1, 4768, 25, 1, false, 4, 1},
|
||||
{431208, 680, 1, 3567, 50, 2, false, 2, 2},
|
||||
{431209, 1109, 0, 34, 60, 2, false, 6, 2},
|
||||
{431210, 128, 1, 8921, 70, 2, false, 3, 2},
|
||||
{431211, 406, 0, 59, 10, 1, false, 1, 1},
|
||||
{431212, 1170, 0, 70, 90, 3, false, 6, 3},
|
||||
{431213, 164, 0, 38, 24, 2, false, 6, 2},
|
||||
{431214, 378, 1, 3556, 150, 3, false, 1, 3},
|
||||
{431215, 446, 0, 94, 20, 2, false, 4, 2},
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, decoded)
|
||||
for _, mission := range missions {
|
||||
bf.WriteUint32(mission.ID)
|
||||
bf.WriteUint32(mission.Unk)
|
||||
bf.WriteUint16(mission.Type)
|
||||
bf.WriteUint16(mission.Goal)
|
||||
bf.WriteUint16(mission.Quantity)
|
||||
bf.WriteUint16(mission.SkipTickets)
|
||||
bf.WriteBool(mission.GR)
|
||||
bf.WriteUint16(mission.RewardType)
|
||||
bf.WriteUint16(mission.RewardLevel)
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildMissionRecord(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -1783,14 +1846,14 @@ func handleMsgMhfGetGuildWeeklyBonusMaster(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusMaster)
|
||||
|
||||
// Values taken from brand new guild capture
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x28))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 40))
|
||||
}
|
||||
func handleMsgMhfGetGuildWeeklyBonusActiveCount(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusActiveCount)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0x3C) // Active count
|
||||
bf.WriteUint8(0x3C) // Current active count
|
||||
bf.WriteUint8(0x00) // New active count
|
||||
bf.WriteUint8(60) // Active count
|
||||
bf.WriteUint8(60) // Current active count
|
||||
bf.WriteUint8(0) // New active count
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -1798,18 +1861,54 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGuildHuntdata)
|
||||
bf := byteframe.NewByteFrame()
|
||||
switch pkt.Operation {
|
||||
case 0: // Unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
case 1: // Get Huntdata
|
||||
case 0: // Acquire
|
||||
s.server.db.Exec(`UPDATE guild_characters SET box_claimed=$1 WHERE character_id=$2`, TimeAdjusted(), s.charID)
|
||||
case 1: // Enumerate
|
||||
bf.WriteUint8(0) // Entries
|
||||
/* Entry format
|
||||
uint32 UnkID
|
||||
uint32 MonID
|
||||
*/
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
case 2: // Unk, controls glow
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00})
|
||||
rows, err := s.server.db.Query(`SELECT kl.id, kl.monster FROM kill_logs kl
|
||||
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
||||
WHERE gc.guild_id=$1
|
||||
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
||||
`, pkt.GuildID, s.charID)
|
||||
if err == nil {
|
||||
var count uint8
|
||||
var huntID, monID uint32
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&huntID, &monID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
if count > 255 {
|
||||
count = 255
|
||||
rows.Close()
|
||||
break
|
||||
}
|
||||
bf.WriteUint32(huntID)
|
||||
bf.WriteUint32(monID)
|
||||
}
|
||||
bf.Seek(0, 0)
|
||||
bf.WriteUint8(count)
|
||||
}
|
||||
case 2: // Check
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err == nil {
|
||||
var count uint8
|
||||
err = s.server.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl
|
||||
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
|
||||
WHERE gc.guild_id=$1
|
||||
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
|
||||
`, guild.ID, s.charID).Scan(&count)
|
||||
if err == nil && count > 0 {
|
||||
bf.WriteBool(true)
|
||||
} else {
|
||||
bf.WriteBool(false)
|
||||
}
|
||||
} else {
|
||||
bf.WriteBool(false)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type MessageBoardPost struct {
|
||||
|
||||
@@ -209,14 +209,14 @@ func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
bf.WriteUint32(alliance.ParentGuildID)
|
||||
bf.WriteUint32(alliance.ParentGuild.LeaderCharID)
|
||||
bf.WriteUint16(alliance.ParentGuild.Rank)
|
||||
bf.WriteUint16(alliance.ParentGuild.Rank())
|
||||
bf.WriteUint16(alliance.ParentGuild.MemberCount)
|
||||
ps.Uint16(bf, alliance.ParentGuild.Name, true)
|
||||
ps.Uint16(bf, alliance.ParentGuild.LeaderName, true)
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
bf.WriteUint32(alliance.SubGuild1ID)
|
||||
bf.WriteUint32(alliance.SubGuild1.LeaderCharID)
|
||||
bf.WriteUint16(alliance.SubGuild1.Rank)
|
||||
bf.WriteUint16(alliance.SubGuild1.Rank())
|
||||
bf.WriteUint16(alliance.SubGuild1.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild1.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild1.LeaderName, true)
|
||||
@@ -224,7 +224,7 @@ func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
bf.WriteUint32(alliance.SubGuild2ID)
|
||||
bf.WriteUint32(alliance.SubGuild2.LeaderCharID)
|
||||
bf.WriteUint16(alliance.SubGuild2.Rank)
|
||||
bf.WriteUint16(alliance.SubGuild2.Rank())
|
||||
bf.WriteUint16(alliance.SubGuild2.MemberCount)
|
||||
ps.Uint16(bf, alliance.SubGuild2.Name, true)
|
||||
ps.Uint16(bf, alliance.SubGuild2.LeaderName, true)
|
||||
|
||||
@@ -17,7 +17,7 @@ type GuildMember struct {
|
||||
RPYesterday uint16 `db:"rp_yesterday"`
|
||||
Name string `db:"name"`
|
||||
IsApplicant bool `db:"is_applicant"`
|
||||
OrderIndex uint8 `db:"order_index"`
|
||||
OrderIndex uint16 `db:"order_index"`
|
||||
LastLogin uint32 `db:"last_login"`
|
||||
Recruiter bool `db:"recruiter"`
|
||||
AvoidLeadership bool `db:"avoid_leadership"`
|
||||
@@ -25,7 +25,7 @@ type GuildMember struct {
|
||||
HRP uint16 `db:"hrp"`
|
||||
GR uint16 `db:"gr"`
|
||||
WeaponID uint16 `db:"weapon_id"`
|
||||
WeaponType uint16 `db:"weapon_type"`
|
||||
WeaponType uint8 `db:"weapon_type"`
|
||||
}
|
||||
|
||||
func (gm *GuildMember) CanRecruit() bool {
|
||||
|
||||
@@ -4,72 +4,79 @@ import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TreasureHunt struct {
|
||||
HuntID uint32 `db:"id"`
|
||||
HostID uint32 `db:"host_id"`
|
||||
Destination uint32 `db:"destination"`
|
||||
Level uint32 `db:"level"`
|
||||
Return uint32 `db:"return"`
|
||||
Acquired bool `db:"acquired"`
|
||||
Claimed bool `db:"claimed"`
|
||||
Hunters string `db:"hunters"`
|
||||
Treasure string `db:"treasure"`
|
||||
HuntData []byte `db:"hunt_data"`
|
||||
HuntID uint32 `db:"id"`
|
||||
HostID uint32 `db:"host_id"`
|
||||
Destination uint32 `db:"destination"`
|
||||
Level uint32 `db:"level"`
|
||||
Start time.Time `db:"start"`
|
||||
Acquired bool `db:"acquired"`
|
||||
Collected bool `db:"collected"`
|
||||
HuntData []byte `db:"hunt_data"`
|
||||
Hunters uint32 `db:"hunters"`
|
||||
Claimed bool `db:"claimed"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if err != nil || guild == nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
var hunts []TreasureHunt
|
||||
var hunt TreasureHunt
|
||||
|
||||
switch pkt.MaxHunts {
|
||||
case 1:
|
||||
err = s.server.db.QueryRowx(`SELECT id, host_id, destination, level, start, hunt_data FROM guild_hunts WHERE host_id=$1 AND acquired=FALSE`, s.charID).StructScan(&hunt)
|
||||
if err == nil {
|
||||
hunts = append(hunts, hunt)
|
||||
}
|
||||
case 30:
|
||||
rows, err := s.server.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.start, gh.collected, gh.hunt_data,
|
||||
(SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters,
|
||||
CASE
|
||||
WHEN ghc.character_id IS NOT NULL THEN true
|
||||
ELSE false
|
||||
END AS claimed
|
||||
FROM guild_hunts gh
|
||||
LEFT JOIN guild_hunts_claimed ghc ON gh.id = ghc.hunt_id AND ghc.character_id = $1
|
||||
WHERE gh.guild_id=$2 AND gh.level=2 AND gh.acquired=TRUE
|
||||
`, s.charID, guild.ID)
|
||||
if err != nil {
|
||||
rows.Close()
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
} else {
|
||||
for rows.Next() {
|
||||
err = rows.StructScan(&hunt)
|
||||
if err == nil && hunt.Start.Add(time.Second*time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry)).After(TimeAdjusted()) {
|
||||
hunts = append(hunts, hunt)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(hunts) > 30 {
|
||||
hunts = hunts[:30]
|
||||
}
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
hunts := 0
|
||||
rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+604800", guild.ID, TimeAdjusted().Unix())
|
||||
for rows.Next() {
|
||||
hunt := &TreasureHunt{}
|
||||
err = rows.StructScan(&hunt)
|
||||
// Remove self from other hunter count
|
||||
hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if pkt.MaxHunts == 1 {
|
||||
if hunt.HostID != s.charID || hunt.Acquired {
|
||||
continue
|
||||
}
|
||||
hunts++
|
||||
bf.WriteUint32(hunt.HuntID)
|
||||
bf.WriteUint32(hunt.Destination)
|
||||
bf.WriteUint32(hunt.Level)
|
||||
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
|
||||
bf.WriteUint32(hunt.Return)
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBool(false)
|
||||
bf.WriteBytes(hunt.HuntData)
|
||||
break
|
||||
} else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 {
|
||||
if hunts == 30 {
|
||||
break
|
||||
}
|
||||
hunts++
|
||||
bf.WriteUint32(hunt.HuntID)
|
||||
bf.WriteUint32(hunt.Destination)
|
||||
bf.WriteUint32(hunt.Level)
|
||||
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
|
||||
bf.WriteUint32(hunt.Return)
|
||||
bf.WriteBool(hunt.Claimed)
|
||||
bf.WriteBool(stringsupport.CSVContains(hunt.Treasure, int(s.charID)))
|
||||
bf.WriteBytes(hunt.HuntData)
|
||||
}
|
||||
bf.WriteUint16(uint16(len(hunts)))
|
||||
bf.WriteUint16(uint16(len(hunts)))
|
||||
for _, h := range hunts {
|
||||
bf.WriteUint32(h.HuntID)
|
||||
bf.WriteUint32(h.Destination)
|
||||
bf.WriteUint32(h.Level)
|
||||
bf.WriteUint32(h.Hunters)
|
||||
bf.WriteUint32(uint32(h.Start.Unix()))
|
||||
bf.WriteBool(h.Collected)
|
||||
bf.WriteBool(h.Claimed)
|
||||
bf.WriteBytes(h.HuntData)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(hunts))
|
||||
resp.WriteUint16(uint16(hunts))
|
||||
resp.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -77,8 +84,9 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
|
||||
huntData := byteframe.NewByteFrame()
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
if err != nil || guild == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
guildCats := getGuildAirouList(s)
|
||||
destination := bf.ReadUint32()
|
||||
@@ -92,87 +100,55 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
if catID > 0 {
|
||||
catsUsed = stringsupport.CSVAdd(catsUsed, int(catID))
|
||||
for _, cat := range guildCats {
|
||||
if cat.CatID == catID {
|
||||
huntData.WriteBytes(cat.CatName)
|
||||
if cat.ID == catID {
|
||||
huntData.WriteBytes(cat.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
huntData.WriteBytes(bf.ReadBytes(9))
|
||||
}
|
||||
}
|
||||
_, err = s.server.db.Exec("INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
guild.ID, s.charID, destination, level, TimeAdjusted().Unix(), huntData.Data(), catsUsed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.server.db.Exec(`INSERT INTO guild_hunts (guild_id, host_id, destination, level, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6)
|
||||
`, guild.ID, s.charID, destination, level, huntData.Data(), catsUsed)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure)
|
||||
_, err := s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.server.db.Exec(`UPDATE guild_hunts SET acquired=true WHERE id=$1`, pkt.HuntID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func treasureHuntUnregister(s *Session) {
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil || guild == nil {
|
||||
return
|
||||
}
|
||||
var huntID int
|
||||
var hunters string
|
||||
rows, err := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for rows.Next() {
|
||||
rows.Scan(&huntID, &hunters)
|
||||
hunters = stringsupport.CSVRemove(hunters, int(s.charID))
|
||||
s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", hunters, huntID)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport)
|
||||
var csv string
|
||||
if pkt.State == 0 { // Report registration
|
||||
// Unregister from all other hunts
|
||||
treasureHuntUnregister(s)
|
||||
if pkt.HuntID != 0 {
|
||||
// Register to selected hunt
|
||||
err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv = stringsupport.CSVAdd(csv, int(s.charID))
|
||||
_, err = s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
} else if pkt.State == 1 { // Collected by hunter
|
||||
s.server.db.Exec("UPDATE guild_hunts SET hunters='', claimed=true WHERE id=$1", pkt.HuntID)
|
||||
} else if pkt.State == 2 { // Claim treasure
|
||||
err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv = stringsupport.CSVAdd(csv, int(s.charID))
|
||||
_, err = s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch pkt.State {
|
||||
case 0: // Report registration
|
||||
s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=$1 WHERE character_id=$2`, pkt.HuntID, s.charID)
|
||||
case 1: // Collected by hunter
|
||||
s.server.db.Exec(`UPDATE guild_hunts SET collected=true WHERE id=$1`, pkt.HuntID)
|
||||
s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, pkt.HuntID)
|
||||
case 2: // Claim treasure
|
||||
s.server.db.Exec(`INSERT INTO guild_hunts_claimed VALUES ($1, $2)`, pkt.HuntID, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
type TreasureSouvenir struct {
|
||||
Destination uint32
|
||||
Quantity uint32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0)
|
||||
souvenirs := []TreasureSouvenir{}
|
||||
bf.WriteUint16(uint16(len(souvenirs)))
|
||||
for _, souvenir := range souvenirs {
|
||||
bf.WriteUint32(souvenir.Destination)
|
||||
bf.WriteUint32(souvenir.Quantity)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
@@ -118,7 +119,9 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint16(house.HRP)
|
||||
bf.WriteUint16(house.GR)
|
||||
if _config.ErupeConfig.RealClientMode >= _config.G10 {
|
||||
bf.WriteUint16(house.GR)
|
||||
}
|
||||
ps.Uint8(bf, house.Name, true)
|
||||
}
|
||||
bf.Seek(0, 0)
|
||||
@@ -237,8 +240,8 @@ func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo)
|
||||
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Unk0, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Data, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -249,6 +252,9 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.logger.Error("Failed to load decomyset", zap.Error(err))
|
||||
}
|
||||
if len(data) == 0 {
|
||||
if s.server.erupeConfig.RealClientMode < _config.G10 {
|
||||
data = []byte{0x00, 0x00}
|
||||
}
|
||||
data = []byte{0x01, 0x00}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
|
||||
@@ -3,13 +3,12 @@ package channelserver
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/deltacomp"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -56,11 +55,15 @@ func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
|
||||
naviLength := 552
|
||||
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
||||
naviLength = 280
|
||||
}
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load hunternavi", zap.Error(err))
|
||||
data = make([]byte, 0x226)
|
||||
data = make([]byte, naviLength)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
@@ -68,6 +71,10 @@ func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
|
||||
if pkt.IsDataDiff {
|
||||
naviLength := 552
|
||||
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
||||
naviLength = 280
|
||||
}
|
||||
var data []byte
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
@@ -78,7 +85,7 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
// Check if we actually had any hunternavi data, using a blank buffer if not.
|
||||
// This is requried as the client will try to send a diff after character creation without a prior MsgMhfSaveHunterNavi packet.
|
||||
if len(data) == 0 {
|
||||
data = make([]byte, 0x226)
|
||||
data = make([]byte, naviLength)
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
@@ -222,8 +229,8 @@ func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfContractMercenary)
|
||||
switch pkt.Op {
|
||||
case 0:
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=$1 WHERE id=$2", pkt.PactMercID, s.charID)
|
||||
case 0: // Form loan
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=$1 WHERE id=$2", pkt.PactMercID, pkt.CID)
|
||||
case 1: // Cancel lend
|
||||
s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", s.charID)
|
||||
case 2: // Cancel loan
|
||||
@@ -290,18 +297,12 @@ func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateAiroulist)
|
||||
resp := byteframe.NewByteFrame()
|
||||
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")); err == nil {
|
||||
data, _ := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin"))
|
||||
resp.WriteBytes(data)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
return
|
||||
}
|
||||
airouList := getGuildAirouList(s)
|
||||
resp.WriteUint16(uint16(len(airouList)))
|
||||
resp.WriteUint16(uint16(len(airouList)))
|
||||
for _, cat := range airouList {
|
||||
resp.WriteUint32(cat.CatID)
|
||||
resp.WriteBytes(cat.CatName)
|
||||
resp.WriteUint32(cat.ID)
|
||||
resp.WriteBytes(cat.Name)
|
||||
resp.WriteUint32(cat.Experience)
|
||||
resp.WriteUint8(cat.Personality)
|
||||
resp.WriteUint8(cat.Class)
|
||||
@@ -312,11 +313,10 @@ func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
// CatDefinition holds values needed to populate the guild cat list
|
||||
type CatDefinition struct {
|
||||
CatID uint32
|
||||
CatName []byte
|
||||
CurrentTask uint8
|
||||
type Airou struct {
|
||||
ID uint32
|
||||
Name []byte
|
||||
Task uint8
|
||||
Personality uint8
|
||||
Class uint8
|
||||
Experience uint32
|
||||
@@ -324,46 +324,39 @@ type CatDefinition struct {
|
||||
WeaponID uint16
|
||||
}
|
||||
|
||||
func getGuildAirouList(s *Session) []CatDefinition {
|
||||
var guild *Guild
|
||||
var err error
|
||||
var guildCats []CatDefinition
|
||||
|
||||
// returning 0 cats on any guild issues
|
||||
// can probably optimise all of the guild queries pretty heavily
|
||||
guild, err = GetGuildInfoByCharacterId(s, s.charID)
|
||||
func getGuildAirouList(s *Session) []Airou {
|
||||
var guildCats []Airou
|
||||
bannedCats := make(map[uint32]int)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
return guildCats
|
||||
}
|
||||
|
||||
// Get cats used recently
|
||||
// Retail reset at midday, 12 hours is a midpoint
|
||||
tempBanDuration := 43200 - (1800) // Minus hunt time
|
||||
bannedCats := make(map[uint32]int)
|
||||
var csvTemp string
|
||||
rows, err := s.server.db.Query(`SELECT cats_used
|
||||
FROM guild_hunts gh
|
||||
INNER JOIN characters c
|
||||
ON gh.host_id = c.id
|
||||
WHERE c.id=$1 AND gh.return+$2>$3`, s.charID, tempBanDuration, TimeAdjusted().Unix())
|
||||
rows, err := s.server.db.Query(`SELECT cats_used FROM guild_hunts gh
|
||||
INNER JOIN characters c ON gh.host_id = c.id WHERE c.id=$1
|
||||
`, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to get recently used airous", zap.Error(err))
|
||||
return guildCats
|
||||
}
|
||||
|
||||
var csvTemp string
|
||||
var startTemp time.Time
|
||||
for rows.Next() {
|
||||
rows.Scan(&csvTemp)
|
||||
for i, j := range stringsupport.CSVElems(csvTemp) {
|
||||
bannedCats[uint32(j)] = i
|
||||
err = rows.Scan(&csvTemp, &startTemp)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if startTemp.Add(time.Second * time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntPartnyaCooldown)).Before(TimeAdjusted()) {
|
||||
for i, j := range stringsupport.CSVElems(csvTemp) {
|
||||
bannedCats[uint32(j)] = i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ellie's GetGuildMembers didn't seem to pull leader?
|
||||
rows, err = s.server.db.Query(`SELECT c.otomoairou
|
||||
FROM characters c
|
||||
INNER JOIN guild_characters gc
|
||||
ON gc.character_id = c.id
|
||||
rows, err = s.server.db.Query(`SELECT c.otomoairou FROM characters c
|
||||
INNER JOIN guild_characters gc ON gc.character_id = c.id
|
||||
WHERE gc.guild_id = $1 AND c.otomoairou IS NOT NULL
|
||||
ORDER BY c.id ASC
|
||||
LIMIT 60;`, guild.ID)
|
||||
ORDER BY c.id LIMIT 60`, guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Selecting otomoairou based on guild failed", zap.Error(err))
|
||||
return guildCats
|
||||
@@ -372,11 +365,7 @@ func getGuildAirouList(s *Session) []CatDefinition {
|
||||
for rows.Next() {
|
||||
var data []byte
|
||||
err = rows.Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Warn("select failure", zap.Error(err))
|
||||
continue
|
||||
} else if len(data) == 0 {
|
||||
// non extant cats that aren't null in DB
|
||||
if err != nil || len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
// first byte has cat existence in general, can skip if 0
|
||||
@@ -387,10 +376,10 @@ func getGuildAirouList(s *Session) []CatDefinition {
|
||||
continue
|
||||
}
|
||||
bf := byteframe.NewByteFrameFromBytes(decomp)
|
||||
cats := GetCatDetails(bf)
|
||||
cats := GetAirouDetails(bf)
|
||||
for _, cat := range cats {
|
||||
_, exists := bannedCats[cat.CatID]
|
||||
if cat.CurrentTask == 4 && !exists {
|
||||
_, exists := bannedCats[cat.ID]
|
||||
if cat.Task == 4 && !exists {
|
||||
guildCats = append(guildCats, cat)
|
||||
}
|
||||
}
|
||||
@@ -399,20 +388,20 @@ func getGuildAirouList(s *Session) []CatDefinition {
|
||||
return guildCats
|
||||
}
|
||||
|
||||
func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition {
|
||||
func GetAirouDetails(bf *byteframe.ByteFrame) []Airou {
|
||||
catCount := bf.ReadUint8()
|
||||
cats := make([]CatDefinition, catCount)
|
||||
cats := make([]Airou, catCount)
|
||||
for x := 0; x < int(catCount); x++ {
|
||||
var catDef CatDefinition
|
||||
var catDef Airou
|
||||
// cat sometimes has additional bytes for whatever reason, gift items? timestamp?
|
||||
// until actual variance is known we can just seek to end based on start
|
||||
catDefLen := bf.ReadUint32()
|
||||
catStart, _ := bf.Seek(0, io.SeekCurrent)
|
||||
|
||||
catDef.CatID = bf.ReadUint32()
|
||||
bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool
|
||||
catDef.CatName = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest
|
||||
catDef.CurrentTask = bf.ReadUint8()
|
||||
catDef.ID = bf.ReadUint32()
|
||||
bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool
|
||||
catDef.Name = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest
|
||||
catDef.Task = bf.ReadUint8()
|
||||
bf.Seek(16, io.SeekCurrent) // appearance data and what is seemingly null bytes
|
||||
catDef.Personality = bf.ReadUint8()
|
||||
catDef.Class = bf.ReadUint8()
|
||||
|
||||
@@ -10,25 +10,9 @@ import (
|
||||
func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCreateObject)
|
||||
|
||||
// Prevent reusing an object index
|
||||
var nextID uint32
|
||||
for {
|
||||
exists := false
|
||||
nextID = s.stage.NextObjectID()
|
||||
for _, object := range s.stage.objects {
|
||||
if object.id == nextID {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if exists == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.stage.Lock()
|
||||
newObj := &Object{
|
||||
id: nextID,
|
||||
id: s.NextObjectID(),
|
||||
ownerCharID: s.charID,
|
||||
x: pkt.X,
|
||||
y: pkt.Y,
|
||||
@@ -78,7 +62,8 @@ func handleMsgSysRotateObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgSysDuplicateObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysSetObjectBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysSetObjectBinary)
|
||||
_ = p.(*mhfpacket.MsgSysSetObjectBinary)
|
||||
/* This causes issues with PS3 as this actually sends with endiness!
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == s.charID {
|
||||
s.server.userBinaryPartsLock.Lock()
|
||||
@@ -91,6 +76,7 @@ func handleMsgSysSetObjectBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.server.BroadcastMHF(msg, s)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func handleMsgSysGetObjectBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
@@ -42,7 +42,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
data = make([]byte, 0x1AF20)
|
||||
data = make([]byte, 140000)
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
@@ -110,7 +110,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
data = make([]byte, 0x820)
|
||||
data = make([]byte, 4800)
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
@@ -147,7 +147,7 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
s.logger.Error("Failed to load platemyset", zap.Error(err))
|
||||
data = make([]byte, 0x780)
|
||||
data = make([]byte, 1920)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,283 +6,147 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
|
||||
bf := byteframe.NewByteFrame()
|
||||
// Some kind of check if there's already a session
|
||||
if pkt.Unk3 > 0 && s.server.getRaviSemaphore() == nil {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf.WriteUint8(uint8(pkt.WorldID))
|
||||
bf.WriteUint8(uint8(pkt.LandID))
|
||||
bf.WriteUint16(s.server.raviente.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
|
||||
|
||||
// Do this ack manually because it uses a non-(0|1) error code
|
||||
/*
|
||||
_ACK_SUCCESS = 0
|
||||
_ACK_ERROR = 1
|
||||
|
||||
_ACK_EINPROGRESS = 16
|
||||
_ACK_ENOENT = 17
|
||||
_ACK_ENOSPC = 18
|
||||
_ACK_ETIMEOUT = 19
|
||||
|
||||
_ACK_EINVALID = 64
|
||||
_ACK_EFAILED = 65
|
||||
_ACK_ENOMEM = 66
|
||||
_ACK_ENOTEXIT = 67
|
||||
_ACK_ENOTREADY = 68
|
||||
_ACK_EALREADY = 69
|
||||
_ACK_DISABLE_WORK = 71
|
||||
*/
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
||||
AckHandle: pkt.AckHandle,
|
||||
IsBufferResponse: false,
|
||||
ErrorCode: 0x41,
|
||||
AckData: []byte{0x00, 0x00, 0x00, 0x00},
|
||||
})
|
||||
}
|
||||
|
||||
type RaviUpdate struct {
|
||||
Op uint8
|
||||
Dest uint8
|
||||
Data uint32
|
||||
}
|
||||
|
||||
func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysOperateRegister)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
s.server.raviente.Lock()
|
||||
switch pkt.SemaphoreID {
|
||||
case 4:
|
||||
resp := byteframe.NewByteFrame()
|
||||
size := 6
|
||||
for i := 0; i < len(bf.Data())-1; i += size {
|
||||
op := bf.ReadUint8()
|
||||
dest := bf.ReadUint8()
|
||||
data := bf.ReadUint32()
|
||||
resp.WriteUint8(1)
|
||||
resp.WriteUint8(dest)
|
||||
ref := &s.server.raviente.state.stateData[dest]
|
||||
damageMultiplier := s.server.raviente.GetRaviMultiplier(s.server)
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
if dest == 28 { // Berserk resurrection tracker
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
} else if dest == 17 { // Berserk poison tracker
|
||||
if damageMultiplier == 1 {
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
} else {
|
||||
resp.WriteUint32(*ref)
|
||||
}
|
||||
} else {
|
||||
data = uint32(float64(data) * damageMultiplier)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
}
|
||||
case 13:
|
||||
fallthrough
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 5:
|
||||
resp := byteframe.NewByteFrame()
|
||||
size := 6
|
||||
for i := 0; i < len(bf.Data())-1; i += size {
|
||||
op := bf.ReadUint8()
|
||||
dest := bf.ReadUint8()
|
||||
data := bf.ReadUint32()
|
||||
resp.WriteUint8(1)
|
||||
resp.WriteUint8(dest)
|
||||
ref := &s.server.raviente.support.supportData[dest]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
fallthrough
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 6:
|
||||
resp := byteframe.NewByteFrame()
|
||||
size := 6
|
||||
for i := 0; i < len(bf.Data())-1; i += size {
|
||||
op := bf.ReadUint8()
|
||||
dest := bf.ReadUint8()
|
||||
data := bf.ReadUint32()
|
||||
resp.WriteUint8(1)
|
||||
resp.WriteUint8(dest)
|
||||
switch dest {
|
||||
case 0:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.nextTime = data
|
||||
case 1:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.startTime = data
|
||||
case 2:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.killedTime = data
|
||||
case 3:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.postTime = data
|
||||
case 4:
|
||||
ref := &s.server.raviente.register.register[0]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 5:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.carveQuest = data
|
||||
case 6:
|
||||
ref := &s.server.raviente.register.register[1]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 7:
|
||||
ref := &s.server.raviente.register.register[2]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 8:
|
||||
ref := &s.server.raviente.register.register[3]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
case 9:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.maxPlayers = data
|
||||
case 10:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
s.server.raviente.register.ravienteType = data
|
||||
case 11:
|
||||
ref := &s.server.raviente.register.register[4]
|
||||
switch op {
|
||||
case 2:
|
||||
resp.WriteUint32(*ref)
|
||||
resp.WriteUint32(*ref + data)
|
||||
*ref += data
|
||||
case 13:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
}
|
||||
default:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
var raviUpdates []RaviUpdate
|
||||
var raviUpdate RaviUpdate
|
||||
// Strip null terminator
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[:len(pkt.RawDataPayload)-1])
|
||||
for i := len(pkt.RawDataPayload) / 6; i > 0; i-- {
|
||||
raviUpdate.Op = bf.ReadUint8()
|
||||
raviUpdate.Dest = bf.ReadUint8()
|
||||
raviUpdate.Data = bf.ReadUint32()
|
||||
raviUpdates = append(raviUpdates, raviUpdate)
|
||||
}
|
||||
bf = byteframe.NewByteFrame()
|
||||
|
||||
var _old, _new uint32
|
||||
s.server.raviente.Lock()
|
||||
for _, update := range raviUpdates {
|
||||
switch update.Op {
|
||||
case 2:
|
||||
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, true)
|
||||
case 13, 14:
|
||||
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, false)
|
||||
}
|
||||
bf.WriteUint8(1)
|
||||
bf.WriteUint8(update.Dest)
|
||||
bf.WriteUint32(_old)
|
||||
bf.WriteUint32(_new)
|
||||
}
|
||||
s.notifyRavi()
|
||||
s.server.raviente.Unlock()
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
|
||||
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
|
||||
s.notifyRavi()
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysLoadRegister)
|
||||
r := pkt.Unk1
|
||||
switch r {
|
||||
case 12:
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
resp.WriteUint8(12)
|
||||
resp.WriteUint32(s.server.raviente.register.nextTime)
|
||||
resp.WriteUint32(s.server.raviente.register.startTime)
|
||||
resp.WriteUint32(s.server.raviente.register.killedTime)
|
||||
resp.WriteUint32(s.server.raviente.register.postTime)
|
||||
resp.WriteUint32(s.server.raviente.register.register[0])
|
||||
resp.WriteUint32(s.server.raviente.register.carveQuest)
|
||||
resp.WriteUint32(s.server.raviente.register.register[1])
|
||||
resp.WriteUint32(s.server.raviente.register.register[2])
|
||||
resp.WriteUint32(s.server.raviente.register.register[3])
|
||||
resp.WriteUint32(s.server.raviente.register.maxPlayers)
|
||||
resp.WriteUint32(s.server.raviente.register.ravienteType)
|
||||
resp.WriteUint32(s.server.raviente.register.register[4])
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 29:
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
resp.WriteUint8(29)
|
||||
for _, v := range s.server.raviente.state.stateData {
|
||||
resp.WriteUint32(v)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(pkt.Values)
|
||||
for i := uint8(0); i < pkt.Values; i++ {
|
||||
switch pkt.RegisterID {
|
||||
case 0x40000:
|
||||
bf.WriteUint32(s.server.raviente.state[i])
|
||||
case 0x50000:
|
||||
bf.WriteUint32(s.server.raviente.support[i])
|
||||
case 0x60000:
|
||||
bf.WriteUint32(s.server.raviente.register[i])
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
case 25:
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
resp.WriteUint8(25)
|
||||
for _, v := range s.server.raviente.support.supportData {
|
||||
resp.WriteUint32(v)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func (s *Session) notifyRavi() {
|
||||
sema := s.server.getRaviSemaphore()
|
||||
if sema == nil {
|
||||
return
|
||||
}
|
||||
var temp mhfpacket.MHFPacket
|
||||
raviNotif := byteframe.NewByteFrame()
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4}
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x40000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5}
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x50000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 6}
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x60000}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
raviNotif.WriteUint16(0x0010) // End it.
|
||||
sema := getRaviSemaphore(s.server)
|
||||
if sema != nil {
|
||||
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
|
||||
for session := range sema.clients {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
} else {
|
||||
for session := range sema.clients {
|
||||
if session.charID == s.charID {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRaviSemaphore(s *Server) *Semaphore {
|
||||
func (s *Server) getRaviSemaphore() *Semaphore {
|
||||
for _, semaphore := range s.semaphore {
|
||||
if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") {
|
||||
if strings.HasPrefix(semaphore.name, "hs_l0") && strings.HasSuffix(semaphore.name, "3") {
|
||||
return semaphore
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetRavi(s *Session) {
|
||||
s.server.raviente.Lock()
|
||||
s.server.raviente.register.nextTime = 0
|
||||
s.server.raviente.register.startTime = 0
|
||||
s.server.raviente.register.killedTime = 0
|
||||
s.server.raviente.register.postTime = 0
|
||||
s.server.raviente.register.ravienteType = 0
|
||||
s.server.raviente.register.maxPlayers = 0
|
||||
s.server.raviente.register.carveQuest = 0
|
||||
s.server.raviente.register.register = []uint32{0, 0, 0, 0, 0}
|
||||
s.server.raviente.state.stateData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
s.server.raviente.support.supportData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
s.server.raviente.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
106
server/channelserver/handlers_seibattle.go
Normal file
106
server/channelserver/handlers_seibattle.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBreakSeibatuLevelReward)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
type WeeklySeibatuRankingReward struct {
|
||||
Unk0 int32
|
||||
Unk1 int32
|
||||
Unk2 uint32
|
||||
Unk3 int32
|
||||
Unk4 int32
|
||||
Unk5 int32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetWeeklySeibatuRankingReward)
|
||||
var data []*byteframe.ByteFrame
|
||||
weeklySeibatuRankingRewards := []WeeklySeibatuRankingReward{
|
||||
{0, 0, 0, 0, 0, 0},
|
||||
}
|
||||
for _, reward := range weeklySeibatuRankingRewards {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(reward.Unk0)
|
||||
bf.WriteInt32(reward.Unk1)
|
||||
bf.WriteUint32(reward.Unk2)
|
||||
bf.WriteInt32(reward.Unk3)
|
||||
bf.WriteInt32(reward.Unk4)
|
||||
bf.WriteInt32(reward.Unk5)
|
||||
data = append(data, bf)
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetFixedSeibatuRankingTable(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetFixedSeibatuRankingTable)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteBytes(make([]byte, 32))
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadBeatLevel(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadBeatLevel)
|
||||
|
||||
// This response is fixed and will never change on JP,
|
||||
// but I've left it dynamic for possible other client differences.
|
||||
resp := byteframe.NewByteFrame()
|
||||
for i := 0; i < int(pkt.ValidIDCount); i++ {
|
||||
resp.WriteUint32(pkt.IDs[i])
|
||||
resp.WriteUint32(1)
|
||||
resp.WriteUint32(1)
|
||||
resp.WriteUint32(1)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadLastWeekBeatRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadLastWeekBeatRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateBeatLevel(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateBeatLevel)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfReadBeatLevelAllRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadBeatLevelAllRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteBytes(make([]byte, 32))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadBeatLevelMyRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadBeatLevelMyRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -13,9 +12,6 @@ import (
|
||||
func removeSessionFromSemaphore(s *Session) {
|
||||
s.server.semaphoreLock.Lock()
|
||||
for _, semaphore := range s.server.semaphore {
|
||||
if _, exists := semaphore.reservedClientSlots[s.charID]; exists {
|
||||
delete(semaphore.reservedClientSlots, s.charID)
|
||||
}
|
||||
if _, exists := semaphore.clients[s]; exists {
|
||||
delete(semaphore.clients, s)
|
||||
}
|
||||
@@ -31,49 +27,38 @@ func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
func destructEmptySemaphores(s *Session) {
|
||||
s.server.semaphoreLock.Lock()
|
||||
for id, sema := range s.server.semaphore {
|
||||
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
|
||||
s.server.semaphoreLock.Unlock()
|
||||
if len(sema.clients) == 0 {
|
||||
delete(s.server.semaphore, id)
|
||||
s.server.semaphoreLock.Lock()
|
||||
if strings.HasPrefix(id, "hs_l0u3B5") {
|
||||
releaseRaviSemaphore(s, sema)
|
||||
if strings.HasPrefix(id, "hs_l0") {
|
||||
s.server.resetRaviente()
|
||||
}
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func releaseRaviSemaphore(s *Session, sema *Semaphore) {
|
||||
delete(sema.reservedClientSlots, s.charID)
|
||||
delete(sema.clients, s)
|
||||
if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 {
|
||||
s.logger.Debug("Main raviente semaphore is empty, resetting")
|
||||
resetRavi(s)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
|
||||
sem := pkt.AckHandle
|
||||
if s.server.semaphore != nil {
|
||||
destructEmptySemaphores(s)
|
||||
s.server.semaphoreLock.Lock()
|
||||
for id, sema := range s.server.semaphore {
|
||||
if sema.id == sem {
|
||||
if strings.HasPrefix(id, "hs_l0u3B5") {
|
||||
releaseRaviSemaphore(s, sema)
|
||||
s.server.semaphoreLock.Unlock()
|
||||
return
|
||||
destructEmptySemaphores(s)
|
||||
s.server.semaphoreLock.Lock()
|
||||
for id, sema := range s.server.semaphore {
|
||||
if sema.id == pkt.SemaphoreID {
|
||||
for session := range sema.clients {
|
||||
if s == session {
|
||||
delete(sema.clients, s)
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
if len(sema.clients) == 0 {
|
||||
delete(s.server.semaphore, id)
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
|
||||
return
|
||||
if strings.HasPrefix(id, "hs_l0") {
|
||||
s.server.resetRaviente()
|
||||
}
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -81,18 +66,15 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
SemaphoreID := pkt.SemaphoreID
|
||||
|
||||
newSemaphore, exists := s.server.semaphore[SemaphoreID]
|
||||
|
||||
fmt.Printf("Got reserve stage req, StageID: %v\n\n", SemaphoreID)
|
||||
if !exists {
|
||||
s.server.semaphoreLock.Lock()
|
||||
if strings.HasPrefix(SemaphoreID, "hs_l0u3B5") {
|
||||
suffix, _ := strconv.ParseUint(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:], 10, 32)
|
||||
if strings.HasPrefix(SemaphoreID, "hs_l0") {
|
||||
suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
|
||||
s.server.semaphore[SemaphoreID] = &Semaphore{
|
||||
id_semaphore: pkt.SemaphoreID,
|
||||
id: uint32(suffix + 1),
|
||||
clients: make(map[*Session]uint32),
|
||||
reservedClientSlots: make(map[uint32]interface{}),
|
||||
maxPlayers: 32,
|
||||
name: pkt.SemaphoreID,
|
||||
id: uint32((suffix + 1) * 0x10000),
|
||||
clients: make(map[*Session]uint32),
|
||||
maxPlayers: 127,
|
||||
}
|
||||
} else {
|
||||
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1)
|
||||
@@ -103,22 +85,19 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
newSemaphore.Lock()
|
||||
defer newSemaphore.Unlock()
|
||||
if _, exists := newSemaphore.reservedClientSlots[s.charID]; exists {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf := byteframe.NewByteFrame()
|
||||
if _, exists := newSemaphore.clients[s]; exists {
|
||||
bf.WriteUint32(newSemaphore.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else if uint16(len(newSemaphore.reservedClientSlots)) < newSemaphore.maxPlayers {
|
||||
newSemaphore.reservedClientSlots[s.charID] = nil
|
||||
} else if uint16(len(newSemaphore.clients)) < newSemaphore.maxPlayers {
|
||||
newSemaphore.clients[s] = s.charID
|
||||
s.Lock()
|
||||
s.semaphore = newSemaphore
|
||||
s.Unlock()
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(newSemaphore.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
@@ -131,7 +110,6 @@ func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
} else {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package channelserver
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
_config "erupe-ce/config"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"math/rand"
|
||||
)
|
||||
@@ -40,13 +41,14 @@ type GachaEntry struct {
|
||||
EntryType uint8 `db:"entry_type"`
|
||||
ID uint32 `db:"id"`
|
||||
ItemType uint8 `db:"item_type"`
|
||||
ItemNumber uint16 `db:"item_number"`
|
||||
ItemNumber uint32 `db:"item_number"`
|
||||
ItemQuantity uint16 `db:"item_quantity"`
|
||||
Weight float64 `db:"weight"`
|
||||
Rarity uint8 `db:"rarity"`
|
||||
Rolls uint8 `db:"rolls"`
|
||||
FrontierPoints uint16 `db:"frontier_points"`
|
||||
DailyLimit uint8 `db:"daily_limit"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
type GachaItem struct {
|
||||
@@ -108,6 +110,12 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
// 8: special item
|
||||
switch pkt.ShopType {
|
||||
case 1: // Running gachas
|
||||
// Fundamentally, gacha works completely differently, just hide it for now.
|
||||
if _config.ErupeConfig.RealClientMode <= _config.G7 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
var count uint16
|
||||
shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
|
||||
if err != nil {
|
||||
@@ -151,7 +159,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint32(pkt.ShopID)
|
||||
var gachaType int
|
||||
s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType)
|
||||
entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
|
||||
entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
|
||||
if err != nil {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
@@ -168,8 +176,7 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
bf.WriteUint8(gachaEntry.EntryType)
|
||||
bf.WriteUint32(gachaEntry.ID)
|
||||
bf.WriteUint8(gachaEntry.ItemType)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(gachaEntry.ItemNumber)
|
||||
bf.WriteUint32(gachaEntry.ItemNumber)
|
||||
bf.WriteUint16(gachaEntry.ItemQuantity)
|
||||
if gachaType >= 4 { // If box
|
||||
bf.WriteUint16(1)
|
||||
@@ -197,7 +204,11 @@ func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
|
||||
bf.WriteUint16(gachaEntry.FrontierPoints)
|
||||
bf.WriteUint8(gachaEntry.DailyLimit)
|
||||
bf.WriteUint8(0)
|
||||
if gachaEntry.EntryType < 10 {
|
||||
ps.Uint8(bf, gachaEntry.Name, true)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteBytes(temp.Data())
|
||||
}
|
||||
bf.Seek(4, 0)
|
||||
|
||||
@@ -7,54 +7,124 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type TournamentInfo0 struct {
|
||||
ID uint32
|
||||
MaxPlayers uint32
|
||||
CurrentPlayers uint32
|
||||
Unk1 uint16
|
||||
TextColor uint16
|
||||
Unk2 uint32
|
||||
Time1 time.Time
|
||||
Time2 time.Time
|
||||
Time3 time.Time
|
||||
Time4 time.Time
|
||||
Time5 time.Time
|
||||
Time6 time.Time
|
||||
Unk3 uint8
|
||||
Unk4 uint8
|
||||
MinHR uint32
|
||||
MaxHR uint32
|
||||
Unk5 string
|
||||
Unk6 string
|
||||
}
|
||||
|
||||
type TournamentInfo21 struct {
|
||||
Unk0 uint32
|
||||
Unk1 uint32
|
||||
Unk2 uint32
|
||||
Unk3 uint8
|
||||
}
|
||||
|
||||
type TournamentInfo22 struct {
|
||||
Unk0 uint32
|
||||
Unk1 uint32
|
||||
Unk2 uint32
|
||||
Unk3 uint8
|
||||
Unk4 string
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfInfoTournament)
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
tournamentInfo0 := []TournamentInfo0{}
|
||||
tournamentInfo21 := []TournamentInfo21{}
|
||||
tournamentInfo22 := []TournamentInfo22{}
|
||||
|
||||
switch pkt.Unk0 {
|
||||
case 0:
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
bf.WriteUint32(0) // Tied to schedule ID?
|
||||
case 1:
|
||||
|
||||
bf.WriteBytes(make([]byte, 21))
|
||||
ps.Uint8(bf, "", false)
|
||||
break
|
||||
|
||||
bf.WriteUint32(0xACEDCAFE)
|
||||
|
||||
bf.WriteUint32(5) // Active schedule?
|
||||
|
||||
bf.WriteUint32(1) // Schedule ID?
|
||||
|
||||
bf.WriteUint32(32) // Max players
|
||||
bf.WriteUint32(0) // Registered players
|
||||
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(2) // Color code for schedule item
|
||||
bf.WriteUint32(0)
|
||||
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * -10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
bf.WriteUint32(uint32(time.Now().Add(time.Hour * 10).Unix()))
|
||||
|
||||
bf.WriteBool(true) // Unk
|
||||
bf.WriteBool(false) // Cafe-only
|
||||
|
||||
bf.WriteUint32(0) // Min HR
|
||||
bf.WriteUint32(0) // Max HR
|
||||
|
||||
ps.Uint8(bf, "Test", false)
|
||||
|
||||
// ...
|
||||
bf.WriteUint32(uint32(len(tournamentInfo0)))
|
||||
for _, tinfo := range tournamentInfo0 {
|
||||
bf.WriteUint32(tinfo.ID)
|
||||
bf.WriteUint32(tinfo.MaxPlayers)
|
||||
bf.WriteUint32(tinfo.CurrentPlayers)
|
||||
bf.WriteUint16(tinfo.Unk1)
|
||||
bf.WriteUint16(tinfo.TextColor)
|
||||
bf.WriteUint32(tinfo.Unk2)
|
||||
bf.WriteUint32(uint32(tinfo.Time1.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time2.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time3.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time4.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time5.Unix()))
|
||||
bf.WriteUint32(uint32(tinfo.Time6.Unix()))
|
||||
bf.WriteUint8(tinfo.Unk3)
|
||||
bf.WriteUint8(tinfo.Unk4)
|
||||
bf.WriteUint32(tinfo.MinHR)
|
||||
bf.WriteUint32(tinfo.MaxHR)
|
||||
ps.Uint8(bf, tinfo.Unk5, true)
|
||||
ps.Uint16(bf, tinfo.Unk6, true)
|
||||
}
|
||||
case 1:
|
||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||
bf.WriteUint32(0) // Registered ID
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint32(0)
|
||||
ps.Uint8(bf, "", true)
|
||||
case 2:
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(uint32(len(tournamentInfo21)))
|
||||
for _, info := range tournamentInfo21 {
|
||||
bf.WriteUint32(info.Unk0)
|
||||
bf.WriteUint32(info.Unk1)
|
||||
bf.WriteUint32(info.Unk2)
|
||||
bf.WriteUint8(info.Unk3)
|
||||
}
|
||||
bf.WriteUint32(uint32(len(tournamentInfo22)))
|
||||
for _, info := range tournamentInfo22 {
|
||||
bf.WriteUint32(info.Unk0)
|
||||
bf.WriteUint32(info.Unk1)
|
||||
bf.WriteUint32(info.Unk2)
|
||||
bf.WriteUint8(info.Unk3)
|
||||
ps.Uint8(bf, info.Unk4, true)
|
||||
}
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEntryTournament)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) {}
|
||||
type TournamentReward struct {
|
||||
Unk0 uint16
|
||||
Unk1 uint16
|
||||
Unk2 uint16
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireTournament)
|
||||
rewards := []TournamentReward{}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(uint8(len(rewards)))
|
||||
for _, reward := range rewards {
|
||||
bf.WriteUint16(reward.Unk0)
|
||||
bf.WriteUint16(reward.Unk1)
|
||||
bf.WriteUint16(reward.Unk2)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
@@ -1,102 +1,477 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
type TowerInfoTRP struct {
|
||||
TR int32
|
||||
TRP int32
|
||||
}
|
||||
|
||||
type TowerInfoSkill struct {
|
||||
TSP int32
|
||||
Unk1 []int16 // 40
|
||||
}
|
||||
|
||||
type TowerInfoHistory struct {
|
||||
Unk0 []int16 // 5
|
||||
Unk1 []int16 // 5
|
||||
}
|
||||
|
||||
type TowerInfoLevel struct {
|
||||
Floors int32
|
||||
Unk1 int32
|
||||
Unk2 int32
|
||||
Unk3 int32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
|
||||
var data []byte
|
||||
var err error
|
||||
/*
|
||||
type:
|
||||
1 == TOWER_RANK_POINT,
|
||||
2 == GET_OWN_TOWER_SKILL
|
||||
3 == GET_OWN_TOWER_LEVEL_V3
|
||||
4 == TOWER_TOUHA_HISTORY
|
||||
5 = ?
|
||||
|
||||
[] = type
|
||||
req
|
||||
resp
|
||||
|
||||
01 1d 01 fc 00 09 [00 00 00 01] 00 00 00 02 00 00 00 00
|
||||
00 12 01 fc 00 09 01 00 00 18 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00
|
||||
|
||||
01 1d 01 fc 00 0a [00 00 00 02] 00 00 00 00 00 00 00 00
|
||||
00 12 01 fc 00 0a 01 00 00 94 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
|
||||
01 1d 01 ff 00 0f [00 00 00 04] 00 00 00 00 00 00 00 00
|
||||
00 12 01 ff 00 0f 01 00 00 24 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
|
||||
01 1d 01 fc 00 0b [00 00 00 05] 00 00 00 00 00 00 00 00
|
||||
00 12 01 fc 00 0b 01 00 00 10 0a 21 8e ad 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
*/
|
||||
switch pkt.InfoType {
|
||||
case mhfpacket.TowerInfoTypeTowerRankPoint:
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000000000000000")
|
||||
case mhfpacket.TowerInfoTypeGetOwnTowerSkill:
|
||||
//data, err = hex.DecodeString("0A218EAD000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000001C0000000500050000000000020000000000000000000000000000000000030003000000000003000500050000000300030003000300030003000200030001000300020002000300010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||
case mhfpacket.TowerInfoTypeGetOwnTowerLevelV3:
|
||||
panic("No known response values for GetOwnTowerLevelV3")
|
||||
case mhfpacket.TowerInfoTypeTowerTouhaHistory:
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000000000000000000000000000000000000000")
|
||||
case mhfpacket.TowerInfoTypeUnk5:
|
||||
data, err = hex.DecodeString("0A218EAD000000000000000000000000")
|
||||
var data []*byteframe.ByteFrame
|
||||
type TowerInfo struct {
|
||||
TRP []TowerInfoTRP
|
||||
Skill []TowerInfoSkill
|
||||
History []TowerInfoHistory
|
||||
Level []TowerInfoLevel
|
||||
}
|
||||
|
||||
towerInfo := TowerInfo{
|
||||
TRP: []TowerInfoTRP{{0, 0}},
|
||||
Skill: []TowerInfoSkill{{0, make([]int16, 40)}},
|
||||
History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}},
|
||||
Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}},
|
||||
}
|
||||
|
||||
tempSkills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), skills FROM tower WHERE char_id=$1
|
||||
`, s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills)
|
||||
if err != nil {
|
||||
stubGetNoResults(s, pkt.AckHandle)
|
||||
s.server.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, s.charID)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
|
||||
for i, skill := range stringsupport.CSVElems(tempSkills) {
|
||||
towerInfo.Skill[0].Unk1[i] = int16(skill)
|
||||
}
|
||||
|
||||
switch pkt.InfoType {
|
||||
case 1:
|
||||
for _, trp := range towerInfo.TRP {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(trp.TR)
|
||||
bf.WriteInt32(trp.TRP)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 2:
|
||||
for _, skills := range towerInfo.Skill {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(skills.TSP)
|
||||
for i := range skills.Unk1 {
|
||||
bf.WriteInt16(skills.Unk1[i])
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 4:
|
||||
for _, history := range towerInfo.History {
|
||||
bf := byteframe.NewByteFrame()
|
||||
for i := range history.Unk0 {
|
||||
bf.WriteInt16(history.Unk0[i])
|
||||
}
|
||||
for i := range history.Unk1 {
|
||||
bf.WriteInt16(history.Unk1[i])
|
||||
}
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
for _, level := range towerInfo.Level {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(level.Floors)
|
||||
bf.WriteInt32(level.Unk1)
|
||||
bf.WriteInt32(level.Unk2)
|
||||
bf.WriteInt32(level.Unk3)
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("InfoType", pkt.InfoType),
|
||||
zap.Uint32("Unk1", pkt.Unk1),
|
||||
zap.Int32("Skill", pkt.Skill),
|
||||
zap.Int32("TR", pkt.TR),
|
||||
zap.Int32("TRP", pkt.TRP),
|
||||
zap.Int32("Cost", pkt.Cost),
|
||||
zap.Int32("Unk6", pkt.Unk6),
|
||||
zap.Int32("Unk7", pkt.Unk7),
|
||||
zap.Int32("Block1", pkt.Block1),
|
||||
zap.Int64("Unk9", pkt.Unk9),
|
||||
)
|
||||
}
|
||||
|
||||
switch pkt.InfoType {
|
||||
case 2:
|
||||
skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills)
|
||||
s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID)
|
||||
case 7:
|
||||
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=trp+$2, block1=block1+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
// Default missions
|
||||
var tenrouiraiData = []TenrouiraiData{
|
||||
{1, 1, 80, 0, 2, 2, 1, 1, 2, 2},
|
||||
{1, 4, 16, 0, 2, 2, 1, 1, 2, 2},
|
||||
{1, 6, 50, 0, 2, 2, 1, 0, 2, 2},
|
||||
{1, 4, 12, 50, 2, 2, 1, 1, 2, 2},
|
||||
{1, 3, 50, 0, 2, 2, 1, 1, 2, 2},
|
||||
{2, 5, 40000, 0, 2, 2, 1, 0, 2, 2},
|
||||
{1, 5, 50000, 50, 2, 2, 1, 1, 2, 2},
|
||||
{2, 1, 60, 0, 2, 2, 1, 1, 2, 2},
|
||||
{2, 3, 50, 0, 2, 1, 1, 0, 1, 2},
|
||||
{2, 3, 40, 50, 2, 1, 1, 1, 1, 2},
|
||||
{2, 4, 12, 0, 2, 1, 1, 1, 1, 2},
|
||||
{2, 6, 40, 0, 2, 1, 1, 0, 1, 2},
|
||||
{1, 1, 60, 50, 2, 1, 2, 1, 1, 2},
|
||||
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
|
||||
{1, 6, 50, 0, 3, 1, 2, 0, 1, 2},
|
||||
{1, 4, 16, 50, 3, 1, 2, 1, 1, 2},
|
||||
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
|
||||
{2, 3, 40, 0, 3, 1, 2, 0, 1, 2},
|
||||
{1, 3, 50, 50, 3, 1, 2, 1, 1, 2},
|
||||
{2, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
|
||||
{2, 6, 40, 0, 3, 1, 2, 0, 1, 1},
|
||||
{2, 1, 60, 50, 3, 1, 2, 1, 1, 1},
|
||||
{2, 6, 50, 0, 3, 1, 2, 1, 1, 1},
|
||||
{2, 4, 12, 0, 3, 1, 2, 0, 1, 1},
|
||||
{1, 1, 80, 50, 3, 1, 2, 1, 1, 1},
|
||||
{1, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
|
||||
{1, 3, 50, 0, 3, 1, 2, 0, 1, 1},
|
||||
{1, 4, 16, 50, 3, 1, 0, 1, 1, 1},
|
||||
{1, 6, 50, 0, 3, 1, 0, 1, 1, 1},
|
||||
{2, 3, 40, 0, 3, 1, 0, 1, 1, 1},
|
||||
{1, 1, 80, 50, 3, 1, 0, 0, 1, 1},
|
||||
{2, 5, 40000, 0, 3, 1, 0, 0, 1, 1},
|
||||
{2, 6, 40, 0, 3, 1, 0, 0, 1, 1},
|
||||
}
|
||||
|
||||
type TenrouiraiProgress struct {
|
||||
Page uint8
|
||||
Mission1 uint16
|
||||
Mission2 uint16
|
||||
Mission3 uint16
|
||||
}
|
||||
|
||||
type TenrouiraiReward struct {
|
||||
Index uint8
|
||||
Item []uint16 // 5
|
||||
Quantity []uint8 // 5
|
||||
}
|
||||
|
||||
type TenrouiraiKeyScore struct {
|
||||
Unk0 uint8
|
||||
Unk1 int32
|
||||
}
|
||||
|
||||
type TenrouiraiData struct {
|
||||
Block uint8
|
||||
Mission uint8
|
||||
// 1 = Floors climbed
|
||||
// 2 = Collect antiques
|
||||
// 3 = Open chests
|
||||
// 4 = Cats saved
|
||||
// 5 = TRP acquisition
|
||||
// 6 = Monster slays
|
||||
Goal uint16
|
||||
Cost uint16
|
||||
Skill1 uint8 // 80
|
||||
Skill2 uint8 // 40
|
||||
Skill3 uint8 // 40
|
||||
Skill4 uint8 // 20
|
||||
Skill5 uint8 // 40
|
||||
Skill6 uint8 // 50
|
||||
}
|
||||
|
||||
type TenrouiraiCharScore struct {
|
||||
Score int32
|
||||
Name string
|
||||
}
|
||||
|
||||
type TenrouiraiTicket struct {
|
||||
Unk0 uint8
|
||||
RP uint32
|
||||
Unk2 uint32
|
||||
}
|
||||
|
||||
type Tenrouirai struct {
|
||||
Progress []TenrouiraiProgress
|
||||
Reward []TenrouiraiReward
|
||||
KeyScore []TenrouiraiKeyScore
|
||||
Data []TenrouiraiData
|
||||
CharScore []TenrouiraiCharScore
|
||||
Ticket []TenrouiraiTicket
|
||||
}
|
||||
|
||||
func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
// if the game gets bad responses for this it breaks the ability to save
|
||||
pkt := p.(*mhfpacket.MsgMhfGetTenrouirai)
|
||||
var data []byte
|
||||
var err error
|
||||
if pkt.Unk0 == 1 {
|
||||
data, err = hex.DecodeString("0A218EAD000000000000000000000001010000000000060010")
|
||||
} else if pkt.Unk2 == 4 {
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000210101005000000202010102020104001000000202010102020106003200000202010002020104000C003202020101020201030032000002020101020202059C4000000202010002020105C35000320202010102020201003C00000202010102020203003200000201010001020203002800320201010101020204000C00000201010101020206002800000201010001020101003C00320201020101020105C35000000301020101020106003200000301020001020104001000320301020101020105C350000003010201010202030028000003010200010201030032003203010201010202059C4000000301020101010206002800000301020001010201003C00320301020101010206003200000301020101010204000C000003010200010101010050003203010201010101059C40000003010201010101030032000003010200010101040010003203010001010101060032000003010001010102030028000003010001010101010050003203010000010102059C4000000301000001010206002800000301000001010010")
|
||||
} else {
|
||||
data = []byte{0x00, 0x00, 0x00, 0x00}
|
||||
s.logger.Info("GET_TENROUIRAI request for unknown type")
|
||||
var data []*byteframe.ByteFrame
|
||||
|
||||
tenrouirai := Tenrouirai{
|
||||
Progress: []TenrouiraiProgress{{1, 0, 0, 0}},
|
||||
Data: tenrouiraiData,
|
||||
Ticket: []TenrouiraiTicket{{0, 0, 0}},
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
switch pkt.Unk1 {
|
||||
case 1:
|
||||
for _, tdata := range tenrouirai.Data {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(tdata.Block)
|
||||
bf.WriteUint8(tdata.Mission)
|
||||
bf.WriteUint16(tdata.Goal)
|
||||
bf.WriteUint16(tdata.Cost)
|
||||
bf.WriteUint8(tdata.Skill1)
|
||||
bf.WriteUint8(tdata.Skill2)
|
||||
bf.WriteUint8(tdata.Skill3)
|
||||
bf.WriteUint8(tdata.Skill4)
|
||||
bf.WriteUint8(tdata.Skill5)
|
||||
bf.WriteUint8(tdata.Skill6)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 2:
|
||||
for _, reward := range tenrouirai.Reward {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(reward.Index)
|
||||
bf.WriteUint16(reward.Item[0])
|
||||
bf.WriteUint16(reward.Item[1])
|
||||
bf.WriteUint16(reward.Item[2])
|
||||
bf.WriteUint16(reward.Item[3])
|
||||
bf.WriteUint16(reward.Item[4])
|
||||
bf.WriteUint8(reward.Quantity[0])
|
||||
bf.WriteUint8(reward.Quantity[1])
|
||||
bf.WriteUint8(reward.Quantity[2])
|
||||
bf.WriteUint8(reward.Quantity[3])
|
||||
bf.WriteUint8(reward.Quantity[4])
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 4:
|
||||
s.server.db.QueryRow(`SELECT tower_mission_page FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Page)
|
||||
s.server.db.QueryRow(`SELECT SUM(tower_mission_1) AS _, SUM(tower_mission_2) AS _, SUM(tower_mission_3) AS _ FROM guild_characters WHERE guild_id=$1
|
||||
`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Mission1, &tenrouirai.Progress[0].Mission2, &tenrouirai.Progress[0].Mission3)
|
||||
|
||||
if tenrouirai.Progress[0].Mission1 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal {
|
||||
tenrouirai.Progress[0].Mission1 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal
|
||||
}
|
||||
if tenrouirai.Progress[0].Mission2 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-2].Goal {
|
||||
tenrouirai.Progress[0].Mission2 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-2].Goal
|
||||
}
|
||||
if tenrouirai.Progress[0].Mission3 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-1].Goal {
|
||||
tenrouirai.Progress[0].Mission3 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-1].Goal
|
||||
}
|
||||
|
||||
for _, progress := range tenrouirai.Progress {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(progress.Page)
|
||||
bf.WriteUint16(progress.Mission1)
|
||||
bf.WriteUint16(progress.Mission2)
|
||||
bf.WriteUint16(progress.Mission3)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 5:
|
||||
if pkt.Unk3 > 3 {
|
||||
pkt.Unk3 %= 3
|
||||
if pkt.Unk3 == 0 {
|
||||
pkt.Unk3 = 3
|
||||
}
|
||||
}
|
||||
rows, _ := s.server.db.Query(fmt.Sprintf(`SELECT name, tower_mission_%d FROM guild_characters gc INNER JOIN characters c ON gc.character_id = c.id WHERE guild_id=$1 AND tower_mission_%d IS NOT NULL ORDER BY tower_mission_%d DESC`, pkt.Unk3, pkt.Unk3, pkt.Unk3), pkt.GuildID)
|
||||
for rows.Next() {
|
||||
temp := TenrouiraiCharScore{}
|
||||
rows.Scan(&temp.Name, &temp.Score)
|
||||
tenrouirai.CharScore = append(tenrouirai.CharScore, temp)
|
||||
}
|
||||
for _, charScore := range tenrouirai.CharScore {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteInt32(charScore.Score)
|
||||
bf.WriteBytes(stringsupport.PaddedString(charScore.Name, 14, true))
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 6:
|
||||
s.server.db.QueryRow(`SELECT tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Ticket[0].RP)
|
||||
for _, ticket := range tenrouirai.Ticket {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(ticket.Unk0)
|
||||
bf.WriteUint32(ticket.RP)
|
||||
bf.WriteUint32(ticket.Unk2)
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint8("Unk0", pkt.Unk0),
|
||||
zap.Uint8("Op", pkt.Op),
|
||||
zap.Uint32("GuildID", pkt.GuildID),
|
||||
zap.Uint8("Unk1", pkt.Unk1),
|
||||
zap.Uint16("Floors", pkt.Floors),
|
||||
zap.Uint16("Antiques", pkt.Antiques),
|
||||
zap.Uint16("Chests", pkt.Chests),
|
||||
zap.Uint16("Cats", pkt.Cats),
|
||||
zap.Uint16("TRP", pkt.TRP),
|
||||
zap.Uint16("Slays", pkt.Slays),
|
||||
)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetWeeklySeibatuRankingReward)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
if pkt.Op == 2 {
|
||||
var page, requirement, donated int
|
||||
s.server.db.QueryRow(`SELECT tower_mission_page, tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&page, &donated)
|
||||
|
||||
for i := 0; i < (page*3)+1; i++ {
|
||||
requirement += int(tenrouiraiData[i].Cost)
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
sd, err := GetCharacterSaveData(s, s.charID)
|
||||
if err == nil && sd != nil {
|
||||
sd.RP -= pkt.DonatedRP
|
||||
sd.Save(s)
|
||||
if donated+int(pkt.DonatedRP) >= requirement {
|
||||
s.server.db.Exec(`UPDATE guilds SET tower_mission_page=tower_mission_page+1 WHERE id=$1`, pkt.GuildID)
|
||||
s.server.db.Exec(`UPDATE guild_characters SET tower_mission_1=NULL, tower_mission_2=NULL, tower_mission_3=NULL WHERE guild_id=$1`, pkt.GuildID)
|
||||
pkt.DonatedRP = uint16(requirement - donated)
|
||||
}
|
||||
bf.WriteUint32(uint32(pkt.DonatedRP))
|
||||
s.server.db.Exec(`UPDATE guilds SET tower_rp=tower_rp+$1 WHERE id=$2`, pkt.DonatedRP, pkt.GuildID)
|
||||
} else {
|
||||
bf.WriteUint32(0)
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else {
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPresentBox)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
var data []*byteframe.ByteFrame
|
||||
/*
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
bf.WriteInt32(0)
|
||||
*/
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
type GemInfo struct {
|
||||
Gem uint16
|
||||
Quantity uint16
|
||||
}
|
||||
|
||||
type GemHistory struct {
|
||||
Gem uint16
|
||||
Message uint16
|
||||
Timestamp time.Time
|
||||
Sender string
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGemInfo)
|
||||
var data []*byteframe.ByteFrame
|
||||
gemInfo := []GemInfo{}
|
||||
gemHistory := []GemHistory{}
|
||||
|
||||
tempGems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&tempGems)
|
||||
for i, v := range stringsupport.CSVElems(tempGems) {
|
||||
gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)})
|
||||
}
|
||||
|
||||
switch pkt.Unk0 {
|
||||
case 1:
|
||||
for _, info := range gemInfo {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(info.Gem)
|
||||
bf.WriteUint16(info.Quantity)
|
||||
data = append(data, bf)
|
||||
}
|
||||
case 2:
|
||||
for _, history := range gemHistory {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(history.Gem)
|
||||
bf.WriteUint16(history.Message)
|
||||
bf.WriteUint32(uint32(history.Timestamp.Unix()))
|
||||
bf.WriteBytes(stringsupport.PaddedString(history.Sender, 14, true))
|
||||
data = append(data, bf)
|
||||
}
|
||||
}
|
||||
doAckEarthSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
|
||||
|
||||
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
|
||||
s.logger.Debug(
|
||||
p.Opcode().String(),
|
||||
zap.Uint32("Op", pkt.Op),
|
||||
zap.Uint32("Unk1", pkt.Unk1),
|
||||
zap.Int32("Gem", pkt.Gem),
|
||||
zap.Int32("Quantity", pkt.Quantity),
|
||||
zap.Int32("CID", pkt.CID),
|
||||
zap.Int32("Message", pkt.Message),
|
||||
zap.Int32("Unk6", pkt.Unk6),
|
||||
)
|
||||
}
|
||||
|
||||
gems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
|
||||
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&gems)
|
||||
switch pkt.Op {
|
||||
case 1: // Add gem
|
||||
i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5))
|
||||
s.server.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, stringsupport.CSVSetIndex(gems, i, stringsupport.CSVGetIndex(gems, i)+int(pkt.Quantity)), s.charID)
|
||||
case 2: // Transfer gem
|
||||
// no way im doing this for now
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {}
|
||||
func handleMsgMhfGetNotice(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetNotice)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfPostNotice(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostNotice)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package channelserver
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
@@ -22,7 +23,7 @@ type Config struct {
|
||||
Logger *zap.Logger
|
||||
DB *sqlx.DB
|
||||
DiscordBot *discordbot.DiscordBot
|
||||
ErupeConfig *config.Config
|
||||
ErupeConfig *_config.Config
|
||||
Name string
|
||||
Enable bool
|
||||
}
|
||||
@@ -43,10 +44,11 @@ type Server struct {
|
||||
Port uint16
|
||||
logger *zap.Logger
|
||||
db *sqlx.DB
|
||||
erupeConfig *config.Config
|
||||
erupeConfig *_config.Config
|
||||
acceptConns chan net.Conn
|
||||
deleteConns chan net.Conn
|
||||
sessions map[net.Conn]*Session
|
||||
objectIDs map[*Session]uint16
|
||||
listener net.Listener // Listener that is created when Server.Start is called.
|
||||
isShuttingDown bool
|
||||
|
||||
@@ -75,61 +77,30 @@ type Server struct {
|
||||
|
||||
type Raviente struct {
|
||||
sync.Mutex
|
||||
|
||||
register *RavienteRegister
|
||||
state *RavienteState
|
||||
support *RavienteSupport
|
||||
id uint16
|
||||
register []uint32
|
||||
state []uint32
|
||||
support []uint32
|
||||
}
|
||||
|
||||
type RavienteRegister struct {
|
||||
nextTime uint32
|
||||
startTime uint32
|
||||
postTime uint32
|
||||
killedTime uint32
|
||||
ravienteType uint32
|
||||
maxPlayers uint32
|
||||
carveQuest uint32
|
||||
register []uint32
|
||||
}
|
||||
|
||||
type RavienteState struct {
|
||||
stateData []uint32
|
||||
}
|
||||
|
||||
type RavienteSupport struct {
|
||||
supportData []uint32
|
||||
}
|
||||
|
||||
// Set up the Raviente variables for the server
|
||||
func NewRaviente() *Raviente {
|
||||
ravienteRegister := &RavienteRegister{
|
||||
nextTime: 0,
|
||||
startTime: 0,
|
||||
killedTime: 0,
|
||||
postTime: 0,
|
||||
ravienteType: 0,
|
||||
maxPlayers: 0,
|
||||
carveQuest: 0,
|
||||
func (s *Server) resetRaviente() {
|
||||
for _, semaphore := range s.semaphore {
|
||||
if strings.HasPrefix(semaphore.name, "hs_l0") {
|
||||
return
|
||||
}
|
||||
}
|
||||
ravienteState := &RavienteState{}
|
||||
ravienteSupport := &RavienteSupport{}
|
||||
ravienteRegister.register = []uint32{0, 0, 0, 0, 0}
|
||||
ravienteState.stateData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
ravienteSupport.supportData = []uint32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
raviente := &Raviente{
|
||||
register: ravienteRegister,
|
||||
state: ravienteState,
|
||||
support: ravienteSupport,
|
||||
}
|
||||
return raviente
|
||||
s.logger.Debug("All Raviente Semaphores empty, resetting")
|
||||
s.raviente.id = s.raviente.id + 1
|
||||
s.raviente.register = make([]uint32, 30)
|
||||
s.raviente.state = make([]uint32, 30)
|
||||
s.raviente.support = make([]uint32, 30)
|
||||
}
|
||||
|
||||
func (r *Raviente) GetRaviMultiplier(s *Server) float64 {
|
||||
raviSema := getRaviSemaphore(s)
|
||||
func (s *Server) GetRaviMultiplier() float64 {
|
||||
raviSema := s.getRaviSemaphore()
|
||||
if raviSema != nil {
|
||||
var minPlayers int
|
||||
if r.register.maxPlayers > 8 {
|
||||
if s.raviente.register[9] > 8 {
|
||||
minPlayers = 24
|
||||
} else {
|
||||
minPlayers = 4
|
||||
@@ -142,6 +113,33 @@ func (r *Raviente) GetRaviMultiplier(s *Server) float64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) {
|
||||
var prev uint32
|
||||
var dest *[]uint32
|
||||
switch semaID {
|
||||
case 0x40000:
|
||||
switch index {
|
||||
case 17, 28: // Ignore res and poison
|
||||
break
|
||||
default:
|
||||
value = uint32(float64(value) * s.GetRaviMultiplier())
|
||||
}
|
||||
dest = &s.raviente.state
|
||||
case 0x50000:
|
||||
dest = &s.raviente.support
|
||||
case 0x60000:
|
||||
dest = &s.raviente.register
|
||||
default:
|
||||
return 0, 0
|
||||
}
|
||||
if update {
|
||||
(*dest)[index] += value
|
||||
} else {
|
||||
(*dest)[index] = value
|
||||
}
|
||||
return prev, (*dest)[index]
|
||||
}
|
||||
|
||||
// NewServer creates a new Server type.
|
||||
func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
@@ -152,13 +150,19 @@ func NewServer(config *Config) *Server {
|
||||
acceptConns: make(chan net.Conn),
|
||||
deleteConns: make(chan net.Conn),
|
||||
sessions: make(map[net.Conn]*Session),
|
||||
objectIDs: make(map[*Session]uint16),
|
||||
stages: make(map[string]*Stage),
|
||||
userBinaryParts: make(map[userBinaryPartID][]byte),
|
||||
semaphore: make(map[string]*Semaphore),
|
||||
semaphoreIndex: 7,
|
||||
discordBot: config.DiscordBot,
|
||||
name: config.Name,
|
||||
raviente: NewRaviente(),
|
||||
raviente: &Raviente{
|
||||
id: 1,
|
||||
register: make([]uint32, 30),
|
||||
state: make([]uint32, 30),
|
||||
support: make([]uint32, 30),
|
||||
},
|
||||
}
|
||||
|
||||
// Mezeporta
|
||||
@@ -391,17 +395,23 @@ func (s *Server) NextSemaphoreID() uint32 {
|
||||
for {
|
||||
exists := false
|
||||
s.semaphoreIndex = s.semaphoreIndex + 1
|
||||
if s.semaphoreIndex == 0 {
|
||||
s.semaphoreIndex = 7 // Skip reserved indexes
|
||||
if s.semaphoreIndex > 0xFFFF {
|
||||
s.semaphoreIndex = 1
|
||||
}
|
||||
for _, semaphore := range s.semaphore {
|
||||
if semaphore.id == s.semaphoreIndex {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if exists == false {
|
||||
if !exists {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.semaphoreIndex
|
||||
}
|
||||
|
||||
func (s *Server) Season() uint8 {
|
||||
sid := int64(((s.ID & 0xFF00) - 4096) / 256)
|
||||
return uint8(((TimeAdjusted().Unix() / 86400) + sid) % 3)
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ func getLangStrings(s *Server) map[string]string {
|
||||
strings["commandTeleportSuccess"] = "%d %dにテレポート"
|
||||
strings["commandPSNError"] = "PSN連携コマンドエラー 例:%s <psn id>"
|
||||
strings["commandPSNSuccess"] = "PSN「%s」が連携されています"
|
||||
strings["commandPSNExists"] = "PSNは既存のユーザに接続されています"
|
||||
|
||||
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません"
|
||||
strings["commandRaviStartSuccess"] = "大討伐を開始します"
|
||||
@@ -70,6 +71,7 @@ func getLangStrings(s *Server) map[string]string {
|
||||
strings["commandRaviRequest"] = "鎮静支援を要請します"
|
||||
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
|
||||
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
|
||||
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
|
||||
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
|
||||
@@ -146,6 +148,7 @@ func getLangStrings(s *Server) map[string]string {
|
||||
strings["commandTeleportSuccess"] = "Teleporting to %d %d"
|
||||
strings["commandPSNError"] = "Error in command. Format: %s <psn id>"
|
||||
strings["commandPSNSuccess"] = "Connected PSN ID: %s"
|
||||
strings["commandPSNExists"] = "PSN ID is connected to another account!"
|
||||
|
||||
strings["commandRaviNoCommand"] = "No Raviente command specified!"
|
||||
strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment"
|
||||
@@ -157,6 +160,7 @@ func getLangStrings(s *Server) map[string]string {
|
||||
strings["commandRaviRequest"] = "Requesting sedation support!"
|
||||
strings["commandRaviError"] = "Raviente command not recognised!"
|
||||
strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!"
|
||||
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
|
||||
|
||||
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
|
||||
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
|
||||
|
||||
@@ -7,55 +7,35 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Stage holds stage-specific information
|
||||
// Semaphore holds Semaphore-specific information
|
||||
type Semaphore struct {
|
||||
sync.RWMutex
|
||||
|
||||
// Stage ID string
|
||||
id_semaphore string
|
||||
// Semaphore ID string
|
||||
name string
|
||||
|
||||
id uint32
|
||||
|
||||
// Map of session -> charID.
|
||||
// These are clients that are CURRENTLY in the stage
|
||||
// These are clients that are registered to the Semaphore
|
||||
clients map[*Session]uint32
|
||||
|
||||
// Map of charID -> interface{}, only the key is used, value is always nil.
|
||||
reservedClientSlots map[uint32]interface{}
|
||||
|
||||
// Max Players for Semaphore
|
||||
maxPlayers uint16
|
||||
}
|
||||
|
||||
// NewStage creates a new stage with intialized values.
|
||||
// NewSemaphore creates a new Semaphore with intialized values
|
||||
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
|
||||
sema := &Semaphore{
|
||||
id_semaphore: ID,
|
||||
id: s.NextSemaphoreID(),
|
||||
clients: make(map[*Session]uint32),
|
||||
reservedClientSlots: make(map[uint32]interface{}),
|
||||
maxPlayers: MaxPlayers,
|
||||
name: ID,
|
||||
id: s.NextSemaphoreID(),
|
||||
clients: make(map[*Session]uint32),
|
||||
maxPlayers: MaxPlayers,
|
||||
}
|
||||
return sema
|
||||
}
|
||||
|
||||
func (s *Semaphore) BroadcastRavi(pkt mhfpacket.MHFPacket) {
|
||||
// Broadcast the data.
|
||||
for session := range s.clients {
|
||||
|
||||
// Make the header
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(pkt.Opcode()))
|
||||
|
||||
// Build the packet onto the byteframe.
|
||||
pkt.Build(bf, session.clientContext)
|
||||
|
||||
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
|
||||
session.QueueSendNonBlocking(bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the Semaphore
|
||||
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
// Broadcast the data.
|
||||
for session := range s.clients {
|
||||
|
||||
@@ -34,6 +34,7 @@ type Session struct {
|
||||
clientContext *clientctx.ClientContext
|
||||
lastPacket time.Time
|
||||
|
||||
objectIndex uint16
|
||||
userEnteredStage bool // If the user has entered a stage before
|
||||
stageID string
|
||||
stage *Stage
|
||||
@@ -78,6 +79,7 @@ func NewSession(server *Server, conn net.Conn) *Session {
|
||||
sessionStart: TimeAdjusted().Unix(),
|
||||
stageMoveStack: stringstack.New(),
|
||||
}
|
||||
s.SetObjectID()
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -268,3 +270,29 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
|
||||
fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) SetObjectID() {
|
||||
for i := uint16(1); i < 127; i++ {
|
||||
exists := false
|
||||
for _, j := range s.server.objectIDs {
|
||||
if i == j {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
s.server.objectIDs[s] = i
|
||||
return
|
||||
}
|
||||
}
|
||||
s.server.objectIDs[s] = 0
|
||||
}
|
||||
|
||||
func (s *Session) NextObjectID() uint32 {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(s.server.objectIDs[s])
|
||||
s.objectIndex++
|
||||
bf.WriteUint16(s.objectIndex)
|
||||
bf.Seek(0, 0)
|
||||
return bf.ReadUint32()
|
||||
}
|
||||
|
||||
@@ -95,18 +95,3 @@ func (s *Stage) isCharInQuestByID(charID uint32) bool {
|
||||
func (s *Stage) isQuest() bool {
|
||||
return len(s.reservedClientSlots) > 0
|
||||
}
|
||||
|
||||
func (s *Stage) NextObjectID() uint32 {
|
||||
s.objectIndex = s.objectIndex + 1
|
||||
// Objects beyond 127 do not duplicate correctly
|
||||
// Indexes 0 and 127 does not update position correctly
|
||||
if s.objectIndex == 127 {
|
||||
s.objectIndex = 1
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(0)
|
||||
bf.WriteUint8(s.objectIndex)
|
||||
bf.WriteUint16(0)
|
||||
obj := uint32(bf.Data()[3]) | uint32(bf.Data()[2])<<8 | uint32(bf.Data()[1])<<16 | uint32(bf.Data()[0])<<24
|
||||
return obj
|
||||
}
|
||||
|
||||
@@ -23,3 +23,7 @@ func TimeWeekStart() time.Time {
|
||||
func TimeWeekNext() time.Time {
|
||||
return TimeWeekStart().Add(time.Hour * 24 * 7)
|
||||
}
|
||||
|
||||
func TimeGameAbsolute() uint32 {
|
||||
return uint32((TimeAdjusted().Unix() - 2160) % 5760)
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package timeserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var DoOnce_midnight = false
|
||||
var DoOnce_t2 = false
|
||||
var DoOnce_t = false
|
||||
var Fix_midnight = time.Time{}
|
||||
var Fix_t2 = time.Time{}
|
||||
var Fix_t = time.Time{}
|
||||
var Pfixtimer time.Duration
|
||||
var Pnewtime = 0
|
||||
var yearsFixed = -7
|
||||
|
||||
func PFadd_time() time.Duration {
|
||||
Pnewtime = Pnewtime + 24
|
||||
Pfixtimer = time.Duration(Pnewtime)
|
||||
return Pfixtimer
|
||||
}
|
||||
|
||||
func Time_static() time.Time {
|
||||
if !DoOnce_t {
|
||||
DoOnce_t = true
|
||||
// Force to 201x
|
||||
tFix1 := time.Now()
|
||||
tFix2 := tFix1.AddDate(yearsFixed, 0, 0)
|
||||
Fix_t = tFix2.In(time.FixedZone("UTC+1", 1*60*60))
|
||||
}
|
||||
return Fix_t
|
||||
}
|
||||
|
||||
func Tstatic_midnight() time.Time {
|
||||
if !DoOnce_midnight {
|
||||
DoOnce_midnight = true
|
||||
// Force to 201x
|
||||
tFix1 := time.Now()
|
||||
tFix2 := tFix1.AddDate(yearsFixed, 0, 0)
|
||||
var tFix = tFix2.In(time.FixedZone("UTC+1", 1*60*60))
|
||||
yearFix, monthFix, dayFix := tFix2.Date()
|
||||
Fix_midnight = time.Date(yearFix, monthFix, dayFix, 0, 0, 0, 0, tFix.Location()).Add(time.Hour)
|
||||
}
|
||||
return Fix_midnight
|
||||
}
|
||||
|
||||
func Time_midnight() time.Time {
|
||||
// Force to 201x
|
||||
t1 := time.Now()
|
||||
t2 := t1.AddDate(yearsFixed, 0, 0)
|
||||
var t = t2.In(time.FixedZone("UTC+1", 1*60*60))
|
||||
year, month, day := t2.Date()
|
||||
midnight := time.Date(year, month, day, 0, 0, 0, 0, t.Location()).Add(time.Hour)
|
||||
return midnight
|
||||
}
|
||||
|
||||
func TimeCurrent() time.Time {
|
||||
// Force to 201x
|
||||
t1 := time.Now()
|
||||
t2 := t1.AddDate(yearsFixed, 0, 0)
|
||||
var t = t2.In(time.FixedZone("UTC+1", 1*60*60))
|
||||
return t
|
||||
}
|
||||
|
||||
func Time_Current_Week_uint8() uint8 {
|
||||
beginningOfTheMonth := time.Date(TimeCurrent().Year(), TimeCurrent().Month(), 1, 1, 1, 1, 1, time.UTC)
|
||||
_, thisWeek := TimeCurrent().ISOWeek()
|
||||
_, beginningWeek := beginningOfTheMonth.ISOWeek()
|
||||
|
||||
return uint8(1 + thisWeek - beginningWeek)
|
||||
}
|
||||
|
||||
func Time_Current_Week_uint32() uint32 {
|
||||
beginningOfTheMonth := time.Date(TimeCurrent().Year(), TimeCurrent().Month(), 1, 1, 1, 1, 1, time.UTC)
|
||||
_, thisWeek := TimeCurrent().ISOWeek()
|
||||
_, beginningWeek := beginningOfTheMonth.ISOWeek()
|
||||
result := 1 + thisWeek - beginningWeek
|
||||
return uint32(result)
|
||||
}
|
||||
|
||||
func Detect_Day() bool {
|
||||
switch time.Now().Weekday() {
|
||||
case time.Wednesday:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user