mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
repository cleanup
This commit is contained in:
105
server/channelserver/compression/deltacomp/deltacomp.go
Normal file
105
server/channelserver/compression/deltacomp/deltacomp.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package deltacomp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func checkReadUint8(r *bytes.Reader) (uint8, error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func checkReadUint16(r *bytes.Reader) (uint16, error) {
|
||||
data := make([]byte, 2)
|
||||
n, err := r.Read(data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if n != len(data) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
return uint16(data[0])<<8 | uint16(data[1]), nil
|
||||
}
|
||||
|
||||
func readCount(r *bytes.Reader) (int, error) {
|
||||
var count int
|
||||
|
||||
count8, err := checkReadUint8(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count = int(count8)
|
||||
|
||||
if count == 0 {
|
||||
count16, err := checkReadUint16(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count = int(count16)
|
||||
}
|
||||
|
||||
return int(count), nil
|
||||
}
|
||||
|
||||
// ApplyDataDiff applies a delta data diff patch onto given base data.
|
||||
func ApplyDataDiff(diff []byte, baseData []byte) []byte {
|
||||
// Make a copy of the base data to return,
|
||||
// (probably just make this modify the given slice in the future).
|
||||
baseCopy := make([]byte, len(baseData))
|
||||
copy(baseCopy, baseData)
|
||||
|
||||
patch := bytes.NewReader(diff)
|
||||
|
||||
// The very first matchCount is +1 more than it should be, so we start at -1.
|
||||
dataOffset := -1
|
||||
for {
|
||||
// Read the amount of matching bytes.
|
||||
matchCount, err := readCount(patch)
|
||||
if err != nil {
|
||||
// No more data
|
||||
break
|
||||
}
|
||||
|
||||
dataOffset += matchCount
|
||||
|
||||
// Read the amount of differing bytes.
|
||||
differentCount, err := readCount(patch)
|
||||
if err != nil {
|
||||
// No more data
|
||||
break
|
||||
}
|
||||
differentCount--
|
||||
|
||||
// Grow slice if it's required
|
||||
if len(baseCopy) < dataOffset {
|
||||
fmt.Printf("Slice smaller than data offset, growing slice...")
|
||||
baseCopy = append(baseCopy, make([]byte, (dataOffset+differentCount)-len(baseData))...)
|
||||
} else {
|
||||
length := len(baseCopy[dataOffset:])
|
||||
if length < differentCount {
|
||||
length -= differentCount
|
||||
baseCopy = append(baseCopy, make([]byte, length)...)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the patch bytes.
|
||||
for i := 0; i < differentCount; i++ {
|
||||
b, err := checkReadUint8(patch)
|
||||
if err != nil {
|
||||
panic("Invalid or misunderstood patch format!")
|
||||
}
|
||||
|
||||
baseCopy[dataOffset+i] = b
|
||||
}
|
||||
|
||||
dataOffset += differentCount - 1
|
||||
|
||||
}
|
||||
|
||||
return baseCopy
|
||||
}
|
||||
113
server/channelserver/compression/deltacomp/deltacomp_test.go
Normal file
113
server/channelserver/compression/deltacomp/deltacomp_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package deltacomp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
before string
|
||||
patches []string
|
||||
after string
|
||||
}{
|
||||
{
|
||||
"hunternavi_0_before.bin",
|
||||
[]string{
|
||||
"hunternavi_0_patch_0.bin",
|
||||
"hunternavi_0_patch_1.bin",
|
||||
},
|
||||
"hunternavi_0_after.bin",
|
||||
},
|
||||
{
|
||||
// From "Character Progression 1 Creation-NPCs-Tours"
|
||||
"hunternavi_1_before.bin",
|
||||
[]string{
|
||||
"hunternavi_1_patch_0.bin",
|
||||
"hunternavi_1_patch_1.bin",
|
||||
"hunternavi_1_patch_2.bin",
|
||||
"hunternavi_1_patch_3.bin",
|
||||
"hunternavi_1_patch_4.bin",
|
||||
"hunternavi_1_patch_5.bin",
|
||||
"hunternavi_1_patch_6.bin",
|
||||
"hunternavi_1_patch_7.bin",
|
||||
"hunternavi_1_patch_8.bin",
|
||||
"hunternavi_1_patch_9.bin",
|
||||
"hunternavi_1_patch_10.bin",
|
||||
"hunternavi_1_patch_11.bin",
|
||||
"hunternavi_1_patch_12.bin",
|
||||
"hunternavi_1_patch_13.bin",
|
||||
"hunternavi_1_patch_14.bin",
|
||||
"hunternavi_1_patch_15.bin",
|
||||
"hunternavi_1_patch_16.bin",
|
||||
"hunternavi_1_patch_17.bin",
|
||||
"hunternavi_1_patch_18.bin",
|
||||
"hunternavi_1_patch_19.bin",
|
||||
"hunternavi_1_patch_20.bin",
|
||||
"hunternavi_1_patch_21.bin",
|
||||
"hunternavi_1_patch_22.bin",
|
||||
"hunternavi_1_patch_23.bin",
|
||||
"hunternavi_1_patch_24.bin",
|
||||
},
|
||||
"hunternavi_1_after.bin",
|
||||
},
|
||||
{
|
||||
// From "Progress Gogo GRP Grind 9 and Armor Upgrades and Partner Equip and Lost Cat and Manager talk and Pugi Order"
|
||||
// Not really sure this one counts as a valid test as the input and output are exactly the same. The patches cancel each other out.
|
||||
"platedata_0_before.bin",
|
||||
[]string{
|
||||
"platedata_0_patch_0.bin",
|
||||
"platedata_0_patch_1.bin",
|
||||
},
|
||||
"platedata_0_after.bin",
|
||||
},
|
||||
}
|
||||
|
||||
func readTestDataFile(filename string) []byte {
|
||||
data, err := ioutil.ReadFile(fmt.Sprintf("./test_data/%s", filename))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func TestDeltaPatch(t *testing.T) {
|
||||
for k, tt := range tests {
|
||||
testname := fmt.Sprintf("delta_patch_test_%d", k)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
// Load the test binary data.
|
||||
beforeData, err := nullcomp.Decompress(readTestDataFile(tt.before))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
var patches [][]byte
|
||||
for _, patchName := range tt.patches {
|
||||
patchData := readTestDataFile(patchName)
|
||||
patches = append(patches, patchData)
|
||||
}
|
||||
|
||||
afterData, err := nullcomp.Decompress(readTestDataFile(tt.after))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Now actually test calling ApplyDataDiff.
|
||||
data := beforeData
|
||||
|
||||
// Apply the patches in order.
|
||||
for i, patch := range patches {
|
||||
fmt.Println("patch index: ", i)
|
||||
data = ApplyDataDiff(patch, data)
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, afterData) {
|
||||
t.Errorf("got out\n\t%s\nwant\n\t%s", hex.Dump(data), hex.Dump(afterData))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
98
server/channelserver/compression/nullcomp/nullcomp.go
Normal file
98
server/channelserver/compression/nullcomp/nullcomp.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package nullcomp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Decompress decompresses null-compressesed data.
|
||||
func Decompress(compData []byte) ([]byte, error) {
|
||||
r := bytes.NewReader(compData)
|
||||
|
||||
header := make([]byte, 16)
|
||||
n, err := r.Read(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if n != len(header) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Just return the data if it doesn't contain the cmp header.
|
||||
if !bytes.Equal(header, []byte("cmp\x2020110113\x20\x20\x20\x00")) {
|
||||
return compData, nil
|
||||
}
|
||||
|
||||
var output []byte
|
||||
for {
|
||||
b, err := r.ReadByte()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if b == 0 {
|
||||
// If it's a null byte, then the next byte is how many nulls to add.
|
||||
nullCount, err := r.ReadByte()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output = append(output, make([]byte, int(nullCount))...)
|
||||
} else {
|
||||
output = append(output, b)
|
||||
}
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// Compress null compresses give given data.
|
||||
func Compress(rawData []byte) ([]byte, error) {
|
||||
r := bytes.NewReader(rawData)
|
||||
var output []byte
|
||||
output = append(output, []byte("cmp\x2020110113\x20\x20\x20\x00")...)
|
||||
for {
|
||||
b, err := r.ReadByte()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if b == 0 {
|
||||
output = append(output, []byte{0x00}...)
|
||||
// read to get null count
|
||||
nullCount := 1
|
||||
for {
|
||||
i, err := r.ReadByte()
|
||||
if err == io.EOF {
|
||||
output = append(output, []byte{byte(nullCount)}...)
|
||||
break
|
||||
} else if i != 0 && nullCount != 0 {
|
||||
r.UnreadByte()
|
||||
output = append(output, []byte{byte(nullCount)}...)
|
||||
break
|
||||
} else if i != 0 && nullCount == 0 {
|
||||
r.UnreadByte()
|
||||
output = output[:len(output)-2]
|
||||
output = append(output, []byte{byte(0xFF)}...)
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nullCount++
|
||||
|
||||
// Flush the null-count if it gets to 255, start on the next null count.
|
||||
if nullCount == 255 {
|
||||
output = append(output, []byte{0xFF, 0x00}...)
|
||||
nullCount = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output = append(output, b)
|
||||
}
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
1690
server/channelserver/handlers.go
Normal file
1690
server/channelserver/handlers.go
Normal file
File diff suppressed because it is too large
Load Diff
104
server/channelserver/handlers_achievement.go
Normal file
104
server/channelserver/handlers_achievement.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
|
||||
|
||||
achievementStruct := []struct {
|
||||
ID uint8 // Main ID
|
||||
Unk0 uint8 // always FF
|
||||
Unk1 uint16 // 0x05 0x00
|
||||
Unk2 uint32 // 0x01 0x0A 0x05 0x00
|
||||
}{
|
||||
{ID: 0, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 1, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 2, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 3, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 4, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 5, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 6, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 7, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 8, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 9, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 10, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 11, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 12, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 13, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 14, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 15, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 16, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 17, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 18, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 19, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 20, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 21, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 22, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 23, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 24, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 25, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 26, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 27, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 28, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 29, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 30, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 31, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 32, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 33, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 34, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 35, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 36, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 37, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 38, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 39, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 40, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 41, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 42, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 43, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 44, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 45, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 46, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 47, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 48, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 49, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 50, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 51, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 52, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 53, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 54, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 55, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 56, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 57, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 58, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
{ID: 59, Unk0: 0xFF, Unk1: 0, Unk2: 0},
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(len(achievementStruct))) // Entry count
|
||||
for _, entry := range achievementStruct {
|
||||
resp.WriteUint8(entry.ID)
|
||||
resp.WriteUint8(entry.Unk0)
|
||||
resp.WriteUint16(entry.Unk1)
|
||||
resp.WriteUint32(entry.Unk2)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfSetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSetCaAchievementHist)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfSetCaAchievement(s *Session, p mhfpacket.MHFPacket) {}
|
||||
18
server/channelserver/handlers_campaign.go
Normal file
18
server/channelserver/handlers_campaign.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package channelserver
|
||||
|
||||
import "erupe-ce/network/mhfpacket"
|
||||
|
||||
func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign)
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateCampaign)
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfApplyCampaign)
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
11
server/channelserver/handlers_caravan.go
Normal file
11
server/channelserver/handlers_caravan.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfCaravanMyRank(s *Session, p mhfpacket.MHFPacket) {}
|
||||
282
server/channelserver/handlers_cast_binary.go
Normal file
282
server/channelserver/handlers_cast_binary.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
// MSG_SYS_CAST[ED]_BINARY types enum
|
||||
const (
|
||||
BinaryMessageTypeState = 0
|
||||
BinaryMessageTypeChat = 1
|
||||
BinaryMessageTypeMailNotify = 4
|
||||
BinaryMessageTypeEmote = 6
|
||||
)
|
||||
|
||||
// MSG_SYS_CAST[ED]_BINARY broadcast types enum
|
||||
const (
|
||||
BroadcastTypeTargeted = 0x01
|
||||
BroadcastTypeStage = 0x03
|
||||
BroadcastTypeSemaphore = 0x06
|
||||
BroadcastTypeWorld = 0x0a
|
||||
)
|
||||
|
||||
func sendServerChatMessage(s *Session, message string) {
|
||||
// Make the inside of the casted binary
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.SetLE()
|
||||
msgBinChat := &binpacket.MsgBinChat{
|
||||
Unk0: 0,
|
||||
Type: 5,
|
||||
Flags: 0x80,
|
||||
Message: message,
|
||||
SenderName: "Erupe",
|
||||
}
|
||||
msgBinChat.Build(bf)
|
||||
|
||||
castedBin := &mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
MessageType: BinaryMessageTypeChat,
|
||||
RawDataPayload: bf.Data(),
|
||||
}
|
||||
|
||||
s.QueueSendMHF(castedBin)
|
||||
}
|
||||
|
||||
func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCastBinary)
|
||||
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
|
||||
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
|
||||
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
|
||||
_ = tmp.ReadBytes(9)
|
||||
tmp.SetLE()
|
||||
frame := tmp.ReadUint32()
|
||||
sendServerChatMessage(s, fmt.Sprintf("TIME : %d'%d.%03d (%dframe)", frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
|
||||
}
|
||||
}
|
||||
|
||||
// Parse out the real casted binary payload
|
||||
var msgBinTargeted *binpacket.MsgBinTargeted
|
||||
var authorLen, msgLen uint16
|
||||
var msg []byte
|
||||
|
||||
isDiceCommand := false
|
||||
if pkt.MessageType == BinaryMessageTypeChat {
|
||||
tmp.SetLE()
|
||||
tmp.Seek(int64(0), 0)
|
||||
_ = tmp.ReadUint32()
|
||||
authorLen = tmp.ReadUint16()
|
||||
msgLen = tmp.ReadUint16()
|
||||
msg = tmp.ReadNullTerminatedBytes()
|
||||
}
|
||||
|
||||
// Customise payload
|
||||
realPayload := pkt.RawDataPayload
|
||||
if pkt.BroadcastType == BroadcastTypeTargeted {
|
||||
tmp.SetBE()
|
||||
tmp.Seek(int64(0), 0)
|
||||
msgBinTargeted = &binpacket.MsgBinTargeted{}
|
||||
err := msgBinTargeted.Parse(tmp)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to parse targeted cast binary")
|
||||
return
|
||||
}
|
||||
realPayload = msgBinTargeted.RawDataPayload
|
||||
} else if pkt.MessageType == BinaryMessageTypeChat {
|
||||
if msgLen == 6 && string(msg) == "@dice" {
|
||||
isDiceCommand = true
|
||||
roll := byteframe.NewByteFrame()
|
||||
roll.WriteInt16(1) // Unk
|
||||
roll.SetLE()
|
||||
roll.WriteUint16(4) // Unk
|
||||
roll.WriteUint16(authorLen)
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
dice := fmt.Sprintf("%d", rand.Intn(100)+1)
|
||||
roll.WriteUint16(uint16(len(dice) + 1))
|
||||
roll.WriteNullTerminatedBytes([]byte(dice))
|
||||
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
|
||||
realPayload = roll.Data()
|
||||
}
|
||||
}
|
||||
|
||||
// Make the response to forward to the other client(s).
|
||||
resp := &mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
BroadcastType: pkt.BroadcastType, // (The client never uses Type0 upon receiving)
|
||||
MessageType: pkt.MessageType,
|
||||
RawDataPayload: realPayload,
|
||||
}
|
||||
|
||||
// Send to the proper recipients.
|
||||
switch pkt.BroadcastType {
|
||||
case BroadcastTypeWorld:
|
||||
s.server.WorldcastMHF(resp, s)
|
||||
case BroadcastTypeStage:
|
||||
if isDiceCommand {
|
||||
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
|
||||
} else {
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
}
|
||||
case BroadcastTypeSemaphore:
|
||||
if pkt.MessageType == 1 {
|
||||
var session *Semaphore
|
||||
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
|
||||
session = s.server.semaphore["hs_l0u3B51J9k3"]
|
||||
} else if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
|
||||
session = s.server.semaphore["hs_l0u3B5129k3"]
|
||||
} else if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
|
||||
session = s.server.semaphore["hs_l0u3B512Ak3"]
|
||||
}
|
||||
(*session).BroadcastMHF(resp, s)
|
||||
} else {
|
||||
s.Lock()
|
||||
if s.stage != nil {
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
case BroadcastTypeTargeted:
|
||||
for _, targetID := range (*msgBinTargeted).TargetCharIDs {
|
||||
char := s.server.FindSessionByCharID(targetID)
|
||||
|
||||
if char != nil {
|
||||
char.QueueSendMHF(resp)
|
||||
}
|
||||
}
|
||||
default:
|
||||
s.Lock()
|
||||
haveStage := s.stage != nil
|
||||
if haveStage {
|
||||
s.stage.BroadcastMHF(resp, s)
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
// Handle chat
|
||||
if pkt.MessageType == BinaryMessageTypeChat {
|
||||
bf := byteframe.NewByteFrameFromBytes(realPayload)
|
||||
|
||||
// IMPORTANT! Casted binary objects are sent _as they are in memory_,
|
||||
// this means little endian for LE CPUs, might be different for PS3/PS4/PSP/XBOX.
|
||||
bf.SetLE()
|
||||
|
||||
chatMessage := &binpacket.MsgBinChat{}
|
||||
chatMessage.Parse(bf)
|
||||
|
||||
fmt.Printf("Got chat message: %+v\n", chatMessage)
|
||||
|
||||
// Set account rights
|
||||
if strings.HasPrefix(chatMessage.Message, "!rights") {
|
||||
var v uint32
|
||||
n, err := fmt.Sscanf(chatMessage.Message, "!rights %d", &v)
|
||||
if err != nil || n != 1 {
|
||||
sendServerChatMessage(s, "Error in command. Format: !rights n")
|
||||
} 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 err == nil {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Set rights integer: %d", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Discord integration
|
||||
if chatMessage.Type == binpacket.ChatTypeLocal || chatMessage.Type == binpacket.ChatTypeParty {
|
||||
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
|
||||
}
|
||||
|
||||
// RAVI COMMANDS V2
|
||||
if strings.HasPrefix(chatMessage.Message, "!ravi") {
|
||||
if checkRaviSemaphore(s) {
|
||||
s.server.raviente.Lock()
|
||||
if !strings.HasPrefix(chatMessage.Message, "!ravi ") {
|
||||
sendServerChatMessage(s, "No Raviente command specified!")
|
||||
} else {
|
||||
if strings.HasPrefix(chatMessage.Message, "!ravi start") {
|
||||
if s.server.raviente.register.startTime == 0 {
|
||||
s.server.raviente.register.startTime = s.server.raviente.register.postTime
|
||||
sendServerChatMessage(s, "The Great Slaying will begin in a moment")
|
||||
s.notifyall()
|
||||
} else {
|
||||
sendServerChatMessage(s, "The Great Slaying has already begun!")
|
||||
}
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi sm") || strings.HasPrefix(chatMessage.Message, "!ravi setmultiplier") {
|
||||
var num uint16
|
||||
n, numerr := fmt.Sscanf(chatMessage.Message, "!ravi sm %d", &num)
|
||||
if numerr != nil || n != 1 {
|
||||
sendServerChatMessage(s, "Error in command. Format: !ravi sm n")
|
||||
} else if s.server.raviente.state.damageMultiplier == 1 {
|
||||
if num > 65535 {
|
||||
sendServerChatMessage(s, "Raviente multiplier too high, defaulting to 20x")
|
||||
s.server.raviente.state.damageMultiplier = 65535
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier set to %dx", num))
|
||||
s.server.raviente.state.damageMultiplier = uint32(num)
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is already set to %dx!", s.server.raviente.state.damageMultiplier))
|
||||
}
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi cm") || strings.HasPrefix(chatMessage.Message, "!ravi checkmultiplier") {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Raviente multiplier is currently %dx", s.server.raviente.state.damageMultiplier))
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi sr") || strings.HasPrefix(chatMessage.Message, "!ravi sendres") {
|
||||
if s.server.raviente.state.stateData[28] > 0 {
|
||||
sendServerChatMessage(s, "Sending resurrection support!")
|
||||
s.server.raviente.state.stateData[28] = 0
|
||||
} else {
|
||||
sendServerChatMessage(s, "Resurrection support has not been requested!")
|
||||
}
|
||||
} else if strings.HasPrefix(chatMessage.Message, "!ravi ss") || strings.HasPrefix(chatMessage.Message, "!ravi sendsed") {
|
||||
sendServerChatMessage(s, "Sending sedation support if requested!")
|
||||
// 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(chatMessage.Message, "!ravi rs") || strings.HasPrefix(chatMessage.Message, "!ravi reqsed") {
|
||||
sendServerChatMessage(s, "Requesting sedation support!")
|
||||
// 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 {
|
||||
sendServerChatMessage(s, "Raviente command not recognised!")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendServerChatMessage(s, "No one has joined the Great Slaying!")
|
||||
}
|
||||
s.server.raviente.Unlock()
|
||||
}
|
||||
// END RAVI COMMANDS V2
|
||||
|
||||
if strings.HasPrefix(chatMessage.Message, "!tele ") {
|
||||
var x, y int16
|
||||
n, err := fmt.Sscanf(chatMessage.Message, "!tele %d %d", &x, &y)
|
||||
if err != nil || n != 2 {
|
||||
sendServerChatMessage(s, "Invalid command. Usage:\"!tele 500 500\"")
|
||||
} else {
|
||||
sendServerChatMessage(s, fmt.Sprintf("Teleporting to %d %d", x, y))
|
||||
|
||||
// Make the inside of the casted binary
|
||||
payload := byteframe.NewByteFrame()
|
||||
payload.SetLE()
|
||||
payload.WriteUint8(2) // SetState type(position == 2)
|
||||
payload.WriteInt16(x) // X
|
||||
payload.WriteInt16(y) // Y
|
||||
payloadBytes := payload.Data()
|
||||
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: s.charID,
|
||||
MessageType: BinaryMessageTypeState,
|
||||
RawDataPayload: payloadBytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysCastedBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
132
server/channelserver/handlers_character.go
Normal file
132
server/channelserver/handlers_character.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/binary"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
CharacterSaveRPPointer = 0x22D16
|
||||
)
|
||||
|
||||
type CharacterSaveData struct {
|
||||
CharID uint32
|
||||
Name string
|
||||
RP uint16
|
||||
IsNewCharacter bool
|
||||
|
||||
// Use provided setter/getter
|
||||
baseSaveData []byte
|
||||
}
|
||||
|
||||
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 {
|
||||
s.logger.Error("failed to retrieve save data for character", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer result.Close()
|
||||
|
||||
saveData := &CharacterSaveData{}
|
||||
var compressedBaseSave []byte
|
||||
|
||||
if !result.Next() {
|
||||
s.logger.Error("no results found for character save data", zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = result.Scan(&saveData.CharID, &compressedBaseSave, &saveData.IsNewCharacter, &saveData.Name)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve save data for character",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", charID),
|
||||
)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if compressedBaseSave == nil {
|
||||
return saveData, nil
|
||||
}
|
||||
|
||||
decompressedBaseSave, err := nullcomp.Decompress(compressedBaseSave)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to decompress savedata from db", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
saveData.SetBaseSaveData(decompressedBaseSave)
|
||||
|
||||
return saveData, nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) Save(s *Session, transaction *sql.Tx) error {
|
||||
// We need to update the save data byte array before we save it back to the DB
|
||||
save.updateSaveDataWithStruct()
|
||||
|
||||
compressedData, err := save.CompressedBaseData(s)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateSQL := "UPDATE characters SET savedata=$1, is_new_character=$3 WHERE id=$2"
|
||||
|
||||
if transaction != nil {
|
||||
_, err = transaction.Exec(updateSQL, compressedData, save.CharID, save.IsNewCharacter)
|
||||
} else {
|
||||
_, err = s.server.db.Exec(updateSQL, compressedData, save.CharID, save.IsNewCharacter)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Error("failed to save character data", zap.Error(err), zap.Uint32("charID", save.CharID))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) CompressedBaseData(s *Session) ([]byte, error) {
|
||||
compressedData, err := nullcomp.Compress(save.baseSaveData)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to compress saveData", zap.Error(err), zap.Uint32("charID", save.CharID))
|
||||
return nil, err
|
||||
}
|
||||
return compressedData, nil
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) BaseSaveData() []byte {
|
||||
return save.baseSaveData
|
||||
}
|
||||
|
||||
func (save *CharacterSaveData) SetBaseSaveData(data []byte) {
|
||||
save.baseSaveData = data
|
||||
// After setting the new save byte array, we can extract the values to update our struct
|
||||
// This will be useful when we save it back, we use the struct values to overwrite the saveData
|
||||
save.updateStructWithSaveData()
|
||||
}
|
||||
|
||||
// This will update the save struct with the values stored in the raw savedata arrays
|
||||
func (save *CharacterSaveData) updateSaveDataWithStruct() {
|
||||
rpBytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(rpBytes, save.RP)
|
||||
copy(save.baseSaveData[CharacterSaveRPPointer:CharacterSaveRPPointer+2], rpBytes)
|
||||
}
|
||||
|
||||
// This will update the character save struct with the values stored in the raw savedata arrays
|
||||
func (save *CharacterSaveData) updateStructWithSaveData() {
|
||||
save.RP = binary.LittleEndian.Uint16(save.baseSaveData[CharacterSaveRPPointer : CharacterSaveRPPointer+2])
|
||||
}
|
||||
|
||||
func handleMsgMhfSexChanger(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSexChanger)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
110
server/channelserver/handlers_clients.go
Normal file
110
server/channelserver/handlers_clients.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysEnumerateClient)
|
||||
|
||||
s.server.stagesLock.RLock()
|
||||
stage, ok := s.server.stages[pkt.StageID]
|
||||
if !ok {
|
||||
s.server.stagesLock.RUnlock()
|
||||
s.logger.Warn("Can't enumerate clients for stage that doesn't exist!", zap.String("stageID", pkt.StageID))
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
s.server.stagesLock.RUnlock()
|
||||
|
||||
// Read-lock the stage and make the response with all of the charID's in the stage.
|
||||
resp := byteframe.NewByteFrame()
|
||||
stage.RLock()
|
||||
var clients []uint32
|
||||
switch pkt.Unk1 {
|
||||
case 1: // Not ready
|
||||
for cid, ready := range stage.reservedClientSlots {
|
||||
if !ready {
|
||||
clients = append(clients, cid)
|
||||
}
|
||||
}
|
||||
case 2: // Ready
|
||||
for cid, ready := range stage.reservedClientSlots {
|
||||
if ready {
|
||||
clients = append(clients, cid)
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.WriteUint16(uint16(len(clients)))
|
||||
for _, cid := range clients {
|
||||
resp.WriteUint32(cid)
|
||||
}
|
||||
stage.RUnlock()
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
s.logger.Debug("MsgSysEnumerateClient Done!")
|
||||
}
|
||||
|
||||
func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfListMember)
|
||||
|
||||
var csv string
|
||||
var count uint32
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // Blacklist count
|
||||
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cids := stringsupport.CSVElems(csv)
|
||||
for _, cid := range cids {
|
||||
var name string
|
||||
err = s.server.db.QueryRow("SELECT name FROM characters WHERE id=$1", cid).Scan(&name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
resp.WriteUint32(uint32(cid))
|
||||
resp.WriteUint32(16)
|
||||
resp.WriteBytes(stringsupport.PaddedString(name, 16, true))
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint32(count)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOprMember)
|
||||
var csv string
|
||||
if pkt.Blacklist {
|
||||
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if pkt.Operation {
|
||||
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))
|
||||
} else {
|
||||
csv = stringsupport.CSVAdd(csv, int(pkt.CharID))
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET blocked=$1 WHERE id=$2", csv, s.charID)
|
||||
} else { // Friendlist
|
||||
err := s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&csv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if pkt.Operation {
|
||||
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))
|
||||
} else {
|
||||
csv = stringsupport.CSVAdd(csv, int(pkt.CharID))
|
||||
}
|
||||
s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfShutClient(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysHideClient(s *Session, p mhfpacket.MHFPacket) {}
|
||||
384
server/channelserver/handlers_data.go
Normal file
384
server/channelserver/handlers_data.go
Normal file
File diff suppressed because one or more lines are too long
345
server/channelserver/handlers_discord.go
Normal file
345
server/channelserver/handlers_discord.go
Normal file
@@ -0,0 +1,345 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type Character struct {
|
||||
ID uint32 `db:"id"`
|
||||
IsFemale bool `db:"is_female"`
|
||||
IsNewCharacter bool `db:"is_new_character"`
|
||||
Name string `db:"name"`
|
||||
UnkDescString string `db:"unk_desc_string"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
GR uint16 `db:"gr"`
|
||||
WeaponType uint16 `db:"weapon_type"`
|
||||
LastLogin uint32 `db:"last_login"`
|
||||
}
|
||||
|
||||
var weapons = []string{
|
||||
"<:gs:970861408227049477>",
|
||||
"<:hbg:970861408281563206>",
|
||||
"<:hm:970861408239628308>",
|
||||
"<:lc:970861408298352660>",
|
||||
"<:sns:970861408319315988>",
|
||||
"<:lbg:970861408327725137>",
|
||||
"<:ds:970861408277368883>",
|
||||
"<:ls:970861408319311872>",
|
||||
"<:hh:970861408222863400>",
|
||||
"<:gl:970861408327720980>",
|
||||
"<:bw:970861408294174780>",
|
||||
"<:tf:970861408424177744>",
|
||||
"<:sw:970861408340283472>",
|
||||
"<:ms:970861408411594842>",
|
||||
}
|
||||
|
||||
func (s *Server) getCharacterForUser(uid int) (*Character, error) {
|
||||
character := Character{}
|
||||
err := s.db.Get(&character, "SELECT id, is_female, is_new_character, name, unk_desc_string, hrp, gr, weapon_type, last_login FROM characters WHERE id = $1", uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
}
|
||||
|
||||
func CountChars(s *Server) string {
|
||||
count := 0
|
||||
for _, stage := range s.stages {
|
||||
count += len(stage.clients)
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Server [%s]: %d players;", s.name, count)
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
type ListPlayer struct {
|
||||
CharName string
|
||||
InQuest bool
|
||||
WeaponEmoji string
|
||||
QuestEmoji string
|
||||
StageName string
|
||||
}
|
||||
|
||||
func (p *ListPlayer) toString(length int) string {
|
||||
status := ""
|
||||
if p.InQuest {
|
||||
status = "(in quest " + p.QuestEmoji + ")"
|
||||
} else {
|
||||
status = p.StageName
|
||||
}
|
||||
|
||||
missingSpace := length - len(p.CharName)
|
||||
whitespaces := strings.Repeat(" ", missingSpace+5)
|
||||
|
||||
return fmt.Sprintf("%s %s %s %s", p.WeaponEmoji, p.CharName, whitespaces, status)
|
||||
}
|
||||
|
||||
func getPlayerList(s *Server) ([]ListPlayer, int) {
|
||||
list := []ListPlayer{}
|
||||
questEmojis := []string{
|
||||
":white_circle:",
|
||||
":red_circle:",
|
||||
":blue_circle:",
|
||||
":brown_circle:",
|
||||
":green_circle:",
|
||||
":purple_circle:",
|
||||
":yellow_circle:",
|
||||
":orange_circle:",
|
||||
}
|
||||
|
||||
bigNameLen := 0
|
||||
|
||||
for _, stage := range s.stages {
|
||||
if len(stage.clients) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
questEmoji := ":black_circle:"
|
||||
|
||||
if len(questEmojis) > 0 {
|
||||
questEmoji = questEmojis[len(questEmojis)-1]
|
||||
questEmojis = questEmojis[:len(questEmojis)-1]
|
||||
}
|
||||
|
||||
isQuest := stage.isQuest()
|
||||
for client := range stage.clients {
|
||||
char, err := s.getCharacterForUser(int(client.charID))
|
||||
if err == nil {
|
||||
if len(char.Name) > bigNameLen {
|
||||
bigNameLen = len(char.Name)
|
||||
}
|
||||
|
||||
list = append(list, ListPlayer{
|
||||
CharName: char.Name,
|
||||
InQuest: isQuest,
|
||||
QuestEmoji: questEmoji,
|
||||
WeaponEmoji: weapons[char.WeaponType],
|
||||
StageName: stage.GetName(),
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list, bigNameLen
|
||||
}
|
||||
|
||||
func PlayerList(s *Server) string {
|
||||
list := ""
|
||||
count := 0
|
||||
listPlayers, bigNameLen := getPlayerList(s)
|
||||
|
||||
sort.SliceStable(listPlayers, func(i, j int) bool {
|
||||
return listPlayers[i].CharName < listPlayers[j].CharName
|
||||
})
|
||||
|
||||
for _, lp := range listPlayers {
|
||||
list += lp.toString(bigNameLen) + "\n"
|
||||
count++
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("<:SnS:822963937360347148> Frontier Hunters Online: [%s ] <:switcha:822963906401533992> \n============== Total %d =============\n", s.name, count)
|
||||
message += list
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func debug(s *Server) string {
|
||||
list := ""
|
||||
|
||||
for _, stage := range s.stages {
|
||||
if !stage.isQuest() && len(stage.objects) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
list += fmt.Sprintf(" -> Stage: %s StageId: %s\n", stage.GetName(), stage.id)
|
||||
isQuest := "false"
|
||||
hasDeparted := "false"
|
||||
|
||||
if stage.isQuest() {
|
||||
isQuest = "true"
|
||||
}
|
||||
|
||||
list += fmt.Sprintf(" '-> isQuest: %s\n", isQuest)
|
||||
|
||||
if stage.isQuest() {
|
||||
if len(stage.clients) > 0 {
|
||||
hasDeparted = "true"
|
||||
}
|
||||
|
||||
list += fmt.Sprintf(" '-> isDeparted: %s\n", hasDeparted)
|
||||
list += fmt.Sprintf(" '-> reserveSlots (%d/%d)\n", len(stage.reservedClientSlots), stage.maxPlayers)
|
||||
|
||||
for charid, _ := range stage.reservedClientSlots {
|
||||
char, err := s.getCharacterForUser(int(charid))
|
||||
if err == nil {
|
||||
list += fmt.Sprintf(" '-> %s\n", char.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list += " '-> objects: \n"
|
||||
for _, obj := range stage.objects {
|
||||
objInfo := fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
|
||||
list += fmt.Sprintf(" '-> ObjectId: %d - %s\n", obj.id, objInfo)
|
||||
}
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Objects in Server: [%s ]\n", s.name)
|
||||
message += list
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func questlist(s *Server) string {
|
||||
list := ""
|
||||
|
||||
for _, stage := range s.stages {
|
||||
if !stage.isQuest() {
|
||||
continue
|
||||
}
|
||||
|
||||
hasDeparted := ""
|
||||
if len(stage.clients) > 0 {
|
||||
hasDeparted = " - departed"
|
||||
}
|
||||
list += fmt.Sprintf(" '-> StageId: %s (%d/%d) %s - %s\n", stage.id, len(stage.reservedClientSlots), stage.maxPlayers, hasDeparted, stage.createdAt)
|
||||
|
||||
for charid, _ := range stage.reservedClientSlots {
|
||||
char, err := s.getCharacterForUser(int(charid))
|
||||
if err == nil {
|
||||
list += fmt.Sprintf(" '-> %s\n", char.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("Quests in Server: [%s ]\n", s.name)
|
||||
message += list
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
func removeStageById(s *Server, stageId string) string {
|
||||
if s.stages[stageId] != nil {
|
||||
delete(s.stages, stageId)
|
||||
return "Stage deleted!"
|
||||
}
|
||||
|
||||
return "Stage not found!"
|
||||
}
|
||||
|
||||
func cleanStr(str string) string {
|
||||
return strings.ToLower(strings.Trim(str, " "))
|
||||
}
|
||||
|
||||
func getCharInfo(server *Server, charName string) string {
|
||||
var s *Stage
|
||||
var c *Session
|
||||
|
||||
for _, stage := range server.stages {
|
||||
for client := range stage.clients {
|
||||
|
||||
if client.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if cleanStr(client.Name) == cleanStr(charName) {
|
||||
s = stage
|
||||
c = client
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
return "Character not found"
|
||||
}
|
||||
|
||||
objInfo := ""
|
||||
|
||||
obj := server.FindObjectByChar(c.charID)
|
||||
// server.logger.Info("Found object: %+v", zap.Object("obj", obj))
|
||||
|
||||
if obj != nil {
|
||||
objInfo = fmt.Sprintf("X,Y,Z: %f %f %f", obj.x, obj.y, obj.z)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Character: %s\nStage: %s\nStageId: %s\n%s", c.Name, s.GetName(), s.id, objInfo)
|
||||
}
|
||||
|
||||
func (s *Server) isDiscordAdmin(ds *discordgo.Session, m *discordgo.MessageCreate) bool {
|
||||
for _, role := range m.Member.Roles {
|
||||
for _, id := range s.erupeConfig.Discord.DevRoles {
|
||||
if id == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// onDiscordMessage handles receiving messages from discord and forwarding them ingame.
|
||||
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
// Ignore messages from our bot, or ones that are not in the correct channel.
|
||||
if m.Author.ID == ds.State.User.ID || !s.enable {
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore other channels in devMode
|
||||
if s.erupeConfig.Discord.DevMode && m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
|
||||
return
|
||||
}
|
||||
|
||||
args := strings.Split(m.Content, " ")
|
||||
commandName := args[0]
|
||||
|
||||
// Move to slash commadns
|
||||
if commandName == "!players" {
|
||||
ds.ChannelMessageSend(m.ChannelID, PlayerList(s))
|
||||
return
|
||||
}
|
||||
|
||||
if commandName == "-char" {
|
||||
if len(args) < 2 {
|
||||
ds.ChannelMessageSend(m.ChannelID, "Usage: !char <char name>")
|
||||
return
|
||||
}
|
||||
|
||||
charName := strings.Join(args[1:], " ")
|
||||
ds.ChannelMessageSend(m.ChannelID, getCharInfo(s, charName))
|
||||
return
|
||||
}
|
||||
|
||||
if commandName == "!debug" && s.isDiscordAdmin(ds, m) {
|
||||
ds.ChannelMessageSend(m.ChannelID, debug(s))
|
||||
return
|
||||
}
|
||||
|
||||
if commandName == "!questlist" && s.isDiscordAdmin(ds, m) {
|
||||
ds.ChannelMessageSend(m.ChannelID, questlist(s))
|
||||
return
|
||||
}
|
||||
|
||||
if commandName == "!remove-stage" && s.isDiscordAdmin(ds, m) {
|
||||
if len(args) < 2 {
|
||||
ds.ChannelMessageSend(m.ChannelID, "Usage: !remove-stage <stage id>")
|
||||
return
|
||||
}
|
||||
|
||||
stageId := strings.Join(args[1:], " ")
|
||||
ds.ChannelMessageSend(m.ChannelID, removeStageById(s, stageId))
|
||||
return
|
||||
}
|
||||
|
||||
if m.ChannelID == s.erupeConfig.Discord.RealtimeChannelID {
|
||||
message := fmt.Sprintf("[DISCORD] %s: %s", m.Author.Username, m.Content)
|
||||
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
|
||||
}
|
||||
}
|
||||
130
server/channelserver/handlers_distitem.go
Normal file
130
server/channelserver/handlers_distitem.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ItemDist struct {
|
||||
ID uint32 `db:"id"`
|
||||
Deadline uint32 `db:"deadline"`
|
||||
TimesAcceptable uint16 `db:"times_acceptable"`
|
||||
TimesAccepted uint16 `db:"times_accepted"`
|
||||
MinHR uint16 `db:"min_hr"`
|
||||
MaxHR uint16 `db:"max_hr"`
|
||||
MinSR uint16 `db:"min_sr"`
|
||||
MaxSR uint16 `db:"max_sr"`
|
||||
MinGR uint16 `db:"min_gr"`
|
||||
MaxGR uint16 `db:"max_gr"`
|
||||
EventName string `db:"event_name"`
|
||||
Description string `db:"description"`
|
||||
Data []byte `db:"data"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateDistItem)
|
||||
bf := byteframe.NewByteFrame()
|
||||
distCount := 0
|
||||
dists, err := s.server.db.Queryx(`
|
||||
SELECT d.id, event_name, description, times_acceptable,
|
||||
min_hr, max_hr, min_sr, max_sr, min_gr, max_gr,
|
||||
(
|
||||
SELECT count(*)
|
||||
FROM distributions_accepted da
|
||||
WHERE d.id = da.distribution_id
|
||||
AND da.character_id = $1
|
||||
) AS times_accepted,
|
||||
CASE
|
||||
WHEN (EXTRACT(epoch FROM deadline)::int) IS NULL THEN 0
|
||||
ELSE (EXTRACT(epoch FROM deadline)::int)
|
||||
END deadline
|
||||
FROM distribution d
|
||||
WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC;
|
||||
`, s.charID, pkt.Unk0)
|
||||
if err != nil {
|
||||
s.logger.Error("Error getting distribution data from db", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
for dists.Next() {
|
||||
distCount++
|
||||
distData := &ItemDist{}
|
||||
err = dists.StructScan(&distData)
|
||||
if err != nil {
|
||||
s.logger.Error("Error parsing item distribution data", zap.Error(err))
|
||||
}
|
||||
bf.WriteUint32(distData.ID)
|
||||
bf.WriteUint32(distData.Deadline)
|
||||
bf.WriteUint32(0) // Unk
|
||||
bf.WriteUint16(distData.TimesAcceptable)
|
||||
bf.WriteUint16(distData.TimesAccepted)
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(distData.MinHR)
|
||||
bf.WriteUint16(distData.MaxHR)
|
||||
bf.WriteUint16(distData.MinSR)
|
||||
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))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(distCount))
|
||||
resp.WriteBytes(bf.Data())
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfApplyDistItem)
|
||||
|
||||
if pkt.DistributionID == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
|
||||
} else {
|
||||
row := s.server.db.QueryRowx("SELECT data FROM distribution WHERE id = $1", pkt.DistributionID)
|
||||
dist := &ItemDist{}
|
||||
err := row.StructScan(dist)
|
||||
if err != nil {
|
||||
s.logger.Error("Error parsing item distribution data", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
|
||||
return
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(pkt.DistributionID)
|
||||
bf.WriteBytes(dist.Data)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
|
||||
_, err = s.server.db.Exec(`
|
||||
INSERT INTO public.distributions_accepted
|
||||
VALUES ($1, $2)
|
||||
`, pkt.DistributionID, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Error("Error updating accepted dist count", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireDistItem)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfGetDistDescription(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetDistDescription)
|
||||
var desc string
|
||||
err := s.server.db.QueryRow("SELECT description FROM distribution WHERE id = $1", pkt.DistributionID).Scan(&desc)
|
||||
if err != nil {
|
||||
s.logger.Error("Error parsing item distribution description", zap.Error(err))
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
ps.Uint16(bf, desc, true)
|
||||
ps.Uint16(bf, "", false)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
209
server/channelserver/handlers_diva.go
Normal file
209
server/channelserver/handlers_diva.go
Normal file
@@ -0,0 +1,209 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetKijuInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetKijuInfo)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("04965C959782CC8B468EEC00000000000000000000000000000000000000000000815C82A082E782B582DC82A982BA82CC82AB82B682E3815C0A965C959782C682CD96D282E98E7682A281420A95B782AD8ED282C997458B4382F0975E82A682E98142000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001018BAD8C8282CC8B468EEC00000000000000000000000000000000000000000000815C82AB82E582A482B082AB82CC82AB82B682E3815C0A8BAD8C8282C682CD8BAD82A290BA904681420A95B782AD8ED282CC97CD82F08CA482AC909F82DC82B78142200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003138C8B8F5782CC8B468EEC00000000000000000000000000000000000000000000815C82AF82C182B582E382A482CC82AB82B682E3815C0A8C8B8F5782C682CD8A6D8CC582BD82E9904D978A81420A8F5782DF82E982D982C782C98EEB906C82BD82BF82CC90B8905F97CD82C682C882E9814200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041189CC8CEC82CC8B468EEC00000000000000000000000000000000000000000000815C82A482BD82DC82E082E882CC82AB82B682E3815C0A89CC8CEC82C682CD89CC955082CC8CEC82E881420A8F5782DF82E982D982C782C98EEB906C82BD82BF82CC8E7882A682C682C882E9814220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfSetKiju(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSetKiju)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfAddUdPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAddUdPoint)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdMyPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdMyPoint)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("00040000013C000000FA000000000000000000040000007E0000003C02000000000000000000000000000000000000000000000000000002000004CC00000438000000000000000000000000000000000000000000000000000000020000026E00000230000000000000000000020000007D0000007D000000000000000000000000000000000000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdTotalPointInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdTotalPointInfo)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("00000000000007A12000000000000F424000000000001E848000000000002DC6C000000000003D090000000000004C4B4000000000005B8D8000000000006ACFC000000000007A1200000000000089544000000000009896800000000000E4E1C00000000001312D0000000000017D78400000000001C9C3800000000002160EC00000000002625A000000000002AEA5400000000002FAF0800000000003473BC0000000000393870000000000042C1D800000000004C4B40000000000055D4A800000000005F5E10000000000008954400000000001C9C3800000000003473BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001020300000000000000000000000000000000000000000000000000000000000000000000000000000000101F1420")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdSelectedColorInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdSelectedColorInfo)
|
||||
|
||||
// Unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x01, 0x01, 0x01, 0x02, 0x03, 0x02, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdMonsterPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdMonsterPoint)
|
||||
|
||||
monsterPoints := []struct {
|
||||
MID uint8
|
||||
Points uint16
|
||||
}{
|
||||
{MID: 0x01, Points: 0x3C}, // em1 Rathian
|
||||
{MID: 0x02, Points: 0x5A}, // em2 Fatalis
|
||||
{MID: 0x06, Points: 0x14}, // em6 Yian Kut-Ku
|
||||
{MID: 0x07, Points: 0x50}, // em7 Lao-Shan Lung
|
||||
{MID: 0x08, Points: 0x28}, // em8 Cephadrome
|
||||
{MID: 0x0B, Points: 0x3C}, // em11 Rathalos
|
||||
{MID: 0x0E, Points: 0x3C}, // em14 Diablos
|
||||
{MID: 0x0F, Points: 0x46}, // em15 Khezu
|
||||
{MID: 0x11, Points: 0x46}, // em17 Gravios
|
||||
{MID: 0x14, Points: 0x28}, // em20 Gypceros
|
||||
{MID: 0x15, Points: 0x3C}, // em21 Plesioth
|
||||
{MID: 0x16, Points: 0x32}, // em22 Basarios
|
||||
{MID: 0x1A, Points: 0x32}, // em26 Monoblos
|
||||
{MID: 0x1B, Points: 0x0A}, // em27 Velocidrome
|
||||
{MID: 0x1C, Points: 0x0A}, // em28 Gendrome
|
||||
{MID: 0x1F, Points: 0x0A}, // em31 Iodrome
|
||||
{MID: 0x21, Points: 0x50}, // em33 Kirin
|
||||
{MID: 0x24, Points: 0x64}, // em36 Crimson Fatalis
|
||||
{MID: 0x25, Points: 0x3C}, // em37 Pink Rathian
|
||||
{MID: 0x26, Points: 0x1E}, // em38 Blue Yian Kut-Ku
|
||||
{MID: 0x27, Points: 0x28}, // em39 Purple Gypceros
|
||||
{MID: 0x28, Points: 0x50}, // em40 Yian Garuga
|
||||
{MID: 0x29, Points: 0x5A}, // em41 Silver Rathalos
|
||||
{MID: 0x2A, Points: 0x50}, // em42 Gold Rathian
|
||||
{MID: 0x2B, Points: 0x3C}, // em43 Black Diablos
|
||||
{MID: 0x2C, Points: 0x3C}, // em44 White Monoblos
|
||||
{MID: 0x2D, Points: 0x46}, // em45 Red Khezu
|
||||
{MID: 0x2E, Points: 0x3C}, // em46 Green Plesioth
|
||||
{MID: 0x2F, Points: 0x50}, // em47 Black Gravios
|
||||
{MID: 0x30, Points: 0x1E}, // em48 Daimyo Hermitaur
|
||||
{MID: 0x31, Points: 0x3C}, // em49 Azure Rathalos
|
||||
{MID: 0x32, Points: 0x50}, // em50 Ashen Lao-Shan Lung
|
||||
{MID: 0x33, Points: 0x3C}, // em51 Blangonga
|
||||
{MID: 0x34, Points: 0x28}, // em52 Congalala
|
||||
{MID: 0x35, Points: 0x50}, // em53 Rajang
|
||||
{MID: 0x36, Points: 0x6E}, // em54 Kushala Daora
|
||||
{MID: 0x37, Points: 0x50}, // em55 Shen Gaoren
|
||||
{MID: 0x3A, Points: 0x50}, // em58 Yama Tsukami
|
||||
{MID: 0x3B, Points: 0x6E}, // em59 Chameleos
|
||||
{MID: 0x40, Points: 0x64}, // em64 Lunastra
|
||||
{MID: 0x41, Points: 0x6E}, // em65 Teostra
|
||||
{MID: 0x43, Points: 0x28}, // em67 Shogun Ceanataur
|
||||
{MID: 0x44, Points: 0x0A}, // em68 Bulldrome
|
||||
{MID: 0x47, Points: 0x6E}, // em71 White Fatalis
|
||||
{MID: 0x4A, Points: 0xFA}, // em74 Hypnocatrice
|
||||
{MID: 0x4B, Points: 0xFA}, // em75 Lavasioth
|
||||
{MID: 0x4C, Points: 0x46}, // em76 Tigrex
|
||||
{MID: 0x4D, Points: 0x64}, // em77 Akantor
|
||||
{MID: 0x4E, Points: 0xFA}, // em78 Bright Hypnoc
|
||||
{MID: 0x4F, Points: 0xFA}, // em79 Lavasioth Subspecies
|
||||
{MID: 0x50, Points: 0xFA}, // em80 Espinas
|
||||
{MID: 0x51, Points: 0xFA}, // em81 Orange Espinas
|
||||
{MID: 0x52, Points: 0xFA}, // em82 White Hypnoc
|
||||
{MID: 0x53, Points: 0xFA}, // em83 Akura Vashimu
|
||||
{MID: 0x54, Points: 0xFA}, // em84 Akura Jebia
|
||||
{MID: 0x55, Points: 0xFA}, // em85 Berukyurosu
|
||||
{MID: 0x59, Points: 0xFA}, // em89 Pariapuria
|
||||
{MID: 0x5A, Points: 0xFA}, // em90 White Espinas
|
||||
{MID: 0x5B, Points: 0xFA}, // em91 Kamu Orugaron
|
||||
{MID: 0x5C, Points: 0xFA}, // em92 Nono Orugaron
|
||||
{MID: 0x5E, Points: 0xFA}, // em94 Dyuragaua
|
||||
{MID: 0x5F, Points: 0xFA}, // em95 Doragyurosu
|
||||
{MID: 0x60, Points: 0xFA}, // em96 Gurenzeburu
|
||||
{MID: 0x63, Points: 0xFA}, // em99 Rukodiora
|
||||
{MID: 0x65, Points: 0xFA}, // em101 Gogomoa
|
||||
{MID: 0x67, Points: 0xFA}, // em103 Taikun Zamuza
|
||||
{MID: 0x68, Points: 0xFA}, // em104 Abiorugu
|
||||
{MID: 0x69, Points: 0xFA}, // em105 Kuarusepusu
|
||||
{MID: 0x6A, Points: 0xFA}, // em106 Odibatorasu
|
||||
{MID: 0x6B, Points: 0xFA}, // em107 Disufiroa
|
||||
{MID: 0x6C, Points: 0xFA}, // em108 Rebidiora
|
||||
{MID: 0x6D, Points: 0xFA}, // em109 Anorupatisu
|
||||
{MID: 0x6E, Points: 0xFA}, // em110 Hyujikiki
|
||||
{MID: 0x6F, Points: 0xFA}, // em111 Midogaron
|
||||
{MID: 0x70, Points: 0xFA}, // em112 Giaorugu
|
||||
{MID: 0x72, Points: 0xFA}, // em114 Farunokku
|
||||
{MID: 0x73, Points: 0xFA}, // em115 Pokaradon
|
||||
{MID: 0x74, Points: 0xFA}, // em116 Shantien
|
||||
{MID: 0x77, Points: 0xFA}, // em119 Goruganosu
|
||||
{MID: 0x78, Points: 0xFA}, // em120 Aruganosu
|
||||
{MID: 0x79, Points: 0xFA}, // em121 Baruragaru
|
||||
{MID: 0x7A, Points: 0xFA}, // em122 Zerureusu
|
||||
{MID: 0x7B, Points: 0xFA}, // em123 Gougarf
|
||||
{MID: 0x7D, Points: 0xFA}, // em125 Forokururu
|
||||
{MID: 0x7E, Points: 0xFA}, // em126 Meraginasu
|
||||
{MID: 0x7F, Points: 0xFA}, // em127 Diorekkusu
|
||||
{MID: 0x80, Points: 0xFA}, // em128 Garuba Daora
|
||||
{MID: 0x81, Points: 0xFA}, // em129 Inagami
|
||||
{MID: 0x82, Points: 0xFA}, // em130 Varusaburosu
|
||||
{MID: 0x83, Points: 0xFA}, // em131 Poborubarumu
|
||||
{MID: 0x8B, Points: 0xFA}, // em139 Gureadomosu
|
||||
{MID: 0x8C, Points: 0xFA}, // em140 Harudomerugu
|
||||
{MID: 0x8D, Points: 0xFA}, // em141 Toridcless
|
||||
{MID: 0x8E, Points: 0xFA}, // em142 Gasurabazura
|
||||
{MID: 0x90, Points: 0xFA}, // em144 Yama Kurai
|
||||
{MID: 0x92, Points: 0x78}, // em146 Zinogre
|
||||
{MID: 0x93, Points: 0x78}, // em147 Deviljho
|
||||
{MID: 0x94, Points: 0x78}, // em148 Brachydios
|
||||
{MID: 0x96, Points: 0xFA}, // em150 Toa Tesukatora
|
||||
{MID: 0x97, Points: 0x78}, // em151 Barioth
|
||||
{MID: 0x98, Points: 0x78}, // em152 Uragaan
|
||||
{MID: 0x99, Points: 0x78}, // em153 Stygian Zinogre
|
||||
{MID: 0x9A, Points: 0xFA}, // em154 Guanzorumu
|
||||
{MID: 0x9E, Points: 0xFA}, // em158 Voljang
|
||||
{MID: 0x9F, Points: 0x78}, // em159 Nargacuga
|
||||
{MID: 0xA0, Points: 0xFA}, // em160 Keoaruboru
|
||||
{MID: 0xA1, Points: 0xFA}, // em161 Zenaserisu
|
||||
{MID: 0xA2, Points: 0x78}, // em162 Gore Magala
|
||||
{MID: 0xA4, Points: 0x78}, // em164 Shagaru Magala
|
||||
{MID: 0xA5, Points: 0x78}, // em165 Amatsu
|
||||
{MID: 0xA6, Points: 0xFA}, // em166 Elzelion
|
||||
{MID: 0xA9, Points: 0x78}, // em169 Seregios
|
||||
{MID: 0xAA, Points: 0xFA}, // em170 Bogabadorumu
|
||||
}
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(len(monsterPoints)))
|
||||
for _, mp := range monsterPoints {
|
||||
resp.WriteUint8(mp.MID)
|
||||
resp.WriteUint16(mp.Points)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdDailyPresentList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdDailyPresentList)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdNormaPresentList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdNormaPresentList)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireUdItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireUdItem)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdRanking)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdMyRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdMyRanking)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("00000515000005150000CEB4000003CE000003CE0000CEB44D49444E494748542D414E47454C0000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
339
server/channelserver/handlers_event.go
Normal file
339
server/channelserver/handlers_event.go
Normal file
@@ -0,0 +1,339 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
timeServerFix "erupe-ce/server/channelserver/timeserver"
|
||||
)
|
||||
|
||||
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},
|
||||
})
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateEvent)
|
||||
stubEnumerateNoResults(s, pkt.AckHandle)
|
||||
}
|
||||
|
||||
type activeFeature struct {
|
||||
StartTime time.Time
|
||||
ActiveFeatures uint32
|
||||
Unk1 uint16
|
||||
}
|
||||
|
||||
func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetWeeklySchedule)
|
||||
persistentEventSchedule := make([]activeFeature, 8) // generate day after weekly restart
|
||||
for x := -1; x < 7; x++ {
|
||||
feat := generateActiveWeapons(14) // number of active weapons
|
||||
// TODO: only generate this once per restart (server should be restarted weekly)
|
||||
// then load data from db instead of regenerating
|
||||
persistentEventSchedule[x+1] = activeFeature{
|
||||
StartTime: Time_Current_Midnight().Add(time.Duration(24*x) * time.Hour),
|
||||
ActiveFeatures: uint32(feat),
|
||||
Unk1: 0,
|
||||
}
|
||||
}
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(len(persistentEventSchedule))) // Entry count, client only parses the first 7 or 8.
|
||||
resp.WriteUint32(uint32(Time_Current_Adjusted().Add(-5 * time.Minute).Unix())) // 5 minutes ago server time
|
||||
|
||||
for _, es := range persistentEventSchedule {
|
||||
resp.WriteUint32(uint32(es.StartTime.Unix()))
|
||||
resp.WriteUint32(es.ActiveFeatures)
|
||||
resp.WriteUint16(es.Unk1)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func generateActiveWeapons(count int) int {
|
||||
nums := make([]int, 0)
|
||||
var result int
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for len(nums) < count {
|
||||
num := r.Intn(14)
|
||||
exist := false
|
||||
for _, v := range nums {
|
||||
if v == num {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
nums = append(nums, num)
|
||||
}
|
||||
}
|
||||
for _, num := range nums {
|
||||
result += int(math.Pow(2, float64(num)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type loginBoost struct {
|
||||
WeekReq, WeekCount uint8
|
||||
Available bool
|
||||
Expiration uint32
|
||||
}
|
||||
|
||||
func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetKeepLoginBoostStatus)
|
||||
|
||||
var loginBoostStatus []loginBoost
|
||||
insert := false
|
||||
boostState, err := s.server.db.Query("SELECT week_req, week_count, available, end_time FROM login_boost_state WHERE char_id=$1 ORDER BY week_req ASC", s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for boostState.Next() {
|
||||
var boost loginBoost
|
||||
err = boostState.Scan(&boost.WeekReq, &boost.WeekCount, &boost.Available, &boost.Expiration)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loginBoostStatus = append(loginBoostStatus, boost)
|
||||
}
|
||||
if len(loginBoostStatus) == 0 {
|
||||
// create default Entries (should only been week 1 with )
|
||||
insert = true
|
||||
loginBoostStatus = []loginBoost{
|
||||
{
|
||||
WeekReq: 1, // weeks needed
|
||||
WeekCount: 0, // weeks passed
|
||||
Available: true, // available
|
||||
Expiration: 0, //uint32(t.Add(120 * time.Minute).Unix()), // uncomment to enable permanently
|
||||
},
|
||||
{
|
||||
WeekReq: 2,
|
||||
WeekCount: 0,
|
||||
Available: true,
|
||||
Expiration: 0,
|
||||
},
|
||||
{
|
||||
WeekReq: 3,
|
||||
WeekCount: 0,
|
||||
Available: true,
|
||||
Expiration: 0,
|
||||
},
|
||||
{
|
||||
WeekReq: 4,
|
||||
WeekCount: 0,
|
||||
Available: true,
|
||||
Expiration: 0,
|
||||
},
|
||||
{
|
||||
WeekReq: 5,
|
||||
WeekCount: 0,
|
||||
Available: true,
|
||||
Expiration: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
CurrentWeek := Time_Current_Week_uint8()
|
||||
for d := range loginBoostStatus {
|
||||
if CurrentWeek == 1 && loginBoostStatus[d].WeekCount <= 5 {
|
||||
loginBoostStatus[d].WeekCount = 0
|
||||
}
|
||||
if loginBoostStatus[d].WeekReq == CurrentWeek || loginBoostStatus[d].WeekCount != 0 {
|
||||
loginBoostStatus[d].WeekCount = CurrentWeek
|
||||
}
|
||||
if !loginBoostStatus[d].Available && loginBoostStatus[d].WeekCount >= loginBoostStatus[d].WeekReq && uint32(time.Now().In(time.FixedZone("UTC+1", 1*60*60)).Unix()) >= loginBoostStatus[d].Expiration {
|
||||
loginBoostStatus[d].Expiration = 1
|
||||
}
|
||||
if !insert {
|
||||
_, err := s.server.db.Exec(`UPDATE login_boost_state SET week_count=$1, end_time=$2 WHERE char_id=$3 AND week_req=$4`, loginBoostStatus[d].WeekCount, loginBoostStatus[d].Expiration, s.charID, loginBoostStatus[d].WeekReq)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range loginBoostStatus {
|
||||
if insert {
|
||||
_, err := s.server.db.Exec(`INSERT INTO login_boost_state (char_id, week_req, week_count, available, end_time) VALUES ($1,$2,$3,$4,$5)`, s.charID, v.WeekReq, v.WeekCount, v.Available, v.Expiration)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(v.WeekReq)
|
||||
resp.WriteUint8(v.WeekCount)
|
||||
resp.WriteBool(v.Available)
|
||||
resp.WriteUint32(v.Expiration)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
|
||||
// Directly interacts with MhfGetKeepLoginBoostStatus
|
||||
// TODO: make these states persistent on a per character basis
|
||||
pkt := p.(*mhfpacket.MsgMhfUseKeepLoginBoost)
|
||||
var t = time.Now().In(time.FixedZone("UTC+1", 1*60*60))
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
|
||||
// response is end timestamp based on input
|
||||
switch pkt.BoostWeekUsed {
|
||||
case 1:
|
||||
t = t.Add(120 * time.Minute)
|
||||
resp.WriteUint32(uint32(t.Unix()))
|
||||
case 2:
|
||||
t = t.Add(240 * time.Minute)
|
||||
resp.WriteUint32(uint32(t.Unix()))
|
||||
case 3:
|
||||
t = t.Add(120 * time.Minute)
|
||||
resp.WriteUint32(uint32(t.Unix()))
|
||||
case 4:
|
||||
t = t.Add(180 * time.Minute)
|
||||
resp.WriteUint32(uint32(t.Unix()))
|
||||
case 5:
|
||||
t = t.Add(240 * time.Minute)
|
||||
resp.WriteUint32(uint32(t.Unix()))
|
||||
}
|
||||
_, err := s.server.db.Exec(`UPDATE login_boost_state SET available='false', end_time=$1 WHERE char_id=$2 AND week_req=$3`, uint32(t.Unix()), s.charID, pkt.BoostWeekUsed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
|
||||
var t = timeServerFix.Tstatic_midnight()
|
||||
var event int = s.server.erupeConfig.DevModeOptions.DivaEvent
|
||||
|
||||
year, month, day := t.Date()
|
||||
midnight := time.Date(year, month, day, 0, 0, 0, 0, t.Location())
|
||||
// Events with time limits are Festival with Sign up, Soul Week and Winners Weeks
|
||||
// Diva Defense with Prayer, Interception and Song weeks
|
||||
// Mezeporta Festival with simply 'available' being a weekend thing
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0x1d5fda5c) // Unk (1d5fda5c, 0b5397df)
|
||||
|
||||
if event == 1 {
|
||||
resp.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
|
||||
} else {
|
||||
resp.WriteUint32(uint32(midnight.Add(-24 * 21 * time.Hour).Unix())) // Week 1 Timestamp, Festi start?
|
||||
}
|
||||
|
||||
if event == 2 {
|
||||
resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
|
||||
resp.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
|
||||
} else {
|
||||
resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
|
||||
resp.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix())) // Week 2 Timestamp
|
||||
}
|
||||
|
||||
if event == 3 {
|
||||
resp.WriteUint32(uint32(midnight.Add((24) * 7 * time.Hour).Unix())) // Diva Defense Interception
|
||||
resp.WriteUint32(uint32(midnight.Add((24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
|
||||
} else {
|
||||
resp.WriteUint32(uint32(midnight.Add((-24) * 7 * time.Hour).Unix())) // Diva Defense Interception
|
||||
resp.WriteUint32(uint32(midnight.Add((-24) * 14 * time.Hour).Unix())) // Diva Defense Greeting Song
|
||||
}
|
||||
|
||||
resp.WriteUint16(0x19) // Unk 00011001
|
||||
resp.WriteUint16(0x2d) // Unk 00101101
|
||||
resp.WriteUint16(0x02) // Unk 00000010
|
||||
resp.WriteUint16(0x02) // Unk 00000010
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdInfo)
|
||||
// Message that appears on the Diva Defense NPC and triggers the green exclamation mark
|
||||
udInfos := []struct {
|
||||
Text string
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
}{
|
||||
/*{
|
||||
Text: " ~C17【Erupe】 is dead event!\n\n■Features\n~C18 Dont bother walking around!\n~C17 Take down your DB by doing \n~C17 nearly anything!",
|
||||
StartTime: Time_static().Add(time.Duration(-5) * time.Minute), // Event started 5 minutes ago,
|
||||
EndTime: Time_static().Add(time.Duration(24) * time.Hour), // Event ends in 5 minutes,
|
||||
}, */
|
||||
}
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(len(udInfos)))
|
||||
for _, udInfo := range udInfos {
|
||||
resp.WriteBytes(fixedSizeShiftJIS(udInfo.Text, 1024))
|
||||
resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
|
||||
resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
updateRights(s)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSetRestrictionEvent)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
232
server/channelserver/handlers_festa.go
Normal file
232
server/channelserver/handlers_festa.go
Normal file
@@ -0,0 +1,232 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveMezfesData)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadMezfesData)
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // Unk
|
||||
|
||||
resp.WriteUint8(2) // Count of the next 2 uint32s
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
|
||||
resp.WriteUint32(0) // Unk
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateRanking)
|
||||
bf := byteframe.NewByteFrame()
|
||||
state := s.server.erupeConfig.DevModeOptions.TournamentEvent
|
||||
// Unk
|
||||
// Unk
|
||||
// Start?
|
||||
// End?
|
||||
midnight := Time_Current_Midnight()
|
||||
switch state {
|
||||
case 1:
|
||||
bf.WriteUint32(uint32(midnight.Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(12 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(21 * 24 * time.Hour).Unix()))
|
||||
case 2:
|
||||
bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(9 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(16 * 24 * time.Hour).Unix()))
|
||||
case 3:
|
||||
bf.WriteUint32(uint32(midnight.Add(-12 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(-9 * 24 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix()))
|
||||
default:
|
||||
bf.WriteBytes(make([]byte, 16))
|
||||
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
|
||||
bf.WriteUint16(1)
|
||||
bf.WriteUint32(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
return
|
||||
}
|
||||
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
|
||||
d, _ := hex.DecodeString("031491E631353089F18CF68EAE8EEB97C291E589EF00001200000A54001000000000ED130D949A96B697B393A294B081490000000A55001000010000ED130D949A96B697B393A294B081490000000A56001000020000ED130D949A96B697B393A294B081490000000A57001000030000ED130D949A96B697B393A294B081490000000A58001000040000ED130D949A96B697B393A294B081490000000A59001000050000ED130D949A96B697B393A294B081490000000A5A001000060000ED130D949A96B697B393A294B081490000000A5B001000070000ED130D949A96B697B393A294B081490000000A5C001000080000ED130D949A96B697B393A294B081490000000A5D001000090000ED130D949A96B697B393A294B081490000000A5E0010000A0000ED130D949A96B697B393A294B081490000000A5F0010000B0000ED130D949A96B697B393A294B081490000000A600010000C0000ED130D949A96B697B393A294B081490000000A610010000D0000ED130D949A96B697B393A294B081490000000A620011FFFF0000ED121582DD82F182C882C5949A96B697B393A294B081490000000A63000600EA0000000009834C838C834183570000000A64000600ED000000000B836E838A837D834F838D0000000A65000600EF0000000011834A834E8354839383668381834C83930003000002390006000600000E8CC2906C208B9091E58B9B94740001617E43303581798BA38B5A93E09765817A0A7E433030834E83478358836782C592DE82C182BD8B9B82CC83548343835982F08BA382A40A7E433034817991CE8FDB8B9B817A0A7E433030834C838C8341835781410A836E838A837D834F838D8141834A834E8354839383668381834C83930A7E433037817993FC8FDC8FDC9569817A0A7E4330308B9B947482CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C5000000023A0011000700001297C292632082668B89E8E891CA935694740000ED7E43303581798BA38B5A93E09765817A0A7E43303081E182DD82F182C882C5949A96B697B393A294B0814981E282F00A93AF82B697C2926382C98F8A91AE82B782E934906C82DC82C582CC0A97C2926388F582C582A282A982C9918182AD834E838A834182B782E982A90A82F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303091E631343789F18EEB906C8DD582CC8DB02831816032303088CA290A0A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C50A000000023B001000070000128CC2906C2082668B89E8E891CA935694740001497E43303581798BA38B5A93E09765817A0A7E43303081E1949A96B697B393A294B0814981E282F00A82A282A982C9918182AD834E838A834182B782E982A982F08BA382A40A0A7E433037817993FC8FDC8FDC9569817A0A7E43303089A48ED282CC8381835F838B283188CA290A2F8CF68EAE82CC82B582E982B58141835E838B836C835290B68E598C9481410A834F815B834E90B68E598C948141834F815B834E91AB90B68E598C9481410A834F815B834E89F095FA8C94283181603388CA290A2F97C29263837C8343839383672831816031303088CA290A2F8FA08360835083628367817B836E815B8374836083508362836794920A2831816035303088CA290A7E43303381798A4A8DC38AFA8AD4817A0A7E43303032303139944E31318C8E323293FA2031343A303082A982E70A32303139944E31318C8E323593FA2031343A303082DC82C500")
|
||||
bf.WriteBytes(d)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
|
||||
bf := byteframe.NewByteFrame()
|
||||
state := s.server.erupeConfig.DevModeOptions.FestaEvent
|
||||
bf.WriteUint32(0xdeadbeef) // festaID
|
||||
// Registration Week Start
|
||||
// Introductory Week Start
|
||||
// Totalling Time
|
||||
// Reward Festival Start (2.5hrs after totalling)
|
||||
// 2 weeks after RewardFes (next fes?)
|
||||
midnight := Time_Current_Midnight()
|
||||
switch state {
|
||||
case 1:
|
||||
bf.WriteUint32(uint32(midnight.Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24 * 14 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 150*time.Minute).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24*28*time.Hour + 11*time.Hour).Unix()))
|
||||
case 2:
|
||||
bf.WriteUint32(uint32(midnight.Add(-24 * 7 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24 * 7 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24*7*time.Hour + 150*time.Minute).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24 * 21 * time.Hour).Add(11 * time.Hour).Unix()))
|
||||
case 3:
|
||||
bf.WriteUint32(uint32(midnight.Add(-24 * 14 * time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(-24*7*time.Hour + 11*time.Hour).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(150 * time.Minute).Unix()))
|
||||
bf.WriteUint32(uint32(midnight.Add(24*14*time.Hour + 11*time.Hour).Unix()))
|
||||
default:
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf.WriteUint32(uint32(Time_Current_Adjusted().Unix())) // TS Current Time
|
||||
bf.WriteUint8(4)
|
||||
ps.Uint8(bf, "", false)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint32(0) // Blue souls
|
||||
bf.WriteUint32(0) // Red souls
|
||||
|
||||
trials := 0
|
||||
bf.WriteUint16(uint16(trials))
|
||||
for i := 0; i < trials; i++ {
|
||||
bf.WriteUint32(uint32(i + 1)) // trialID
|
||||
bf.WriteUint8(0xFF) // unk
|
||||
bf.WriteUint8(uint8(i)) // objective
|
||||
bf.WriteUint32(0x1B) // monID, itemID if deliver
|
||||
bf.WriteUint16(1) // huntsRemain?
|
||||
bf.WriteUint16(0) // location
|
||||
bf.WriteUint16(1) // numSoulsReward
|
||||
bf.WriteUint8(0xFF) // unk
|
||||
bf.WriteUint8(0xFF) // monopolised
|
||||
bf.WriteUint16(0) // unk
|
||||
}
|
||||
|
||||
unk := 0 // static rewards?
|
||||
bf.WriteUint16(uint16(unk))
|
||||
for i := 0; i < unk; i++ {
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteUint32(0)
|
||||
bf.WriteBool(false)
|
||||
}
|
||||
|
||||
d, _ := hex.DecodeString("0001D4C001F4000411B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10003000109E54BE54E89B38F970029FDCE04000400001381818D84836C8352819993A294B091D1818100000811B6648100010001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100020001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100030001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100040001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100050001152A81F589A497A793C196B18B528E6D926381F52A0011B6648100060001152A81F589A497A793C196B18B528E6D926381F52A000C952CE10007000109E54BE54E89B38F9700000000000008000001000000000100001388000007D0000003E800000064012C00C8009600640032")
|
||||
bf.WriteBytes(d)
|
||||
ps.Uint16(bf, "", false)
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
// state festa (U)ser
|
||||
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(0) // souls
|
||||
bf.WriteUint32(0) // unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
// state festa (G)uild
|
||||
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // souls
|
||||
resp.WriteUint32(1) // unk
|
||||
resp.WriteUint32(1) // unk
|
||||
resp.WriteUint32(1) // unk, rank?
|
||||
resp.WriteUint32(1) // unk
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember)
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(0) // numMembers
|
||||
// uint16 unk
|
||||
// uint32 charID
|
||||
// uint32 souls
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
|
||||
bf := byteframe.NewByteFrame()
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
bf.WriteUint32(uint32(rand.Intn(2)))
|
||||
// Update guild table
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
|
||||
// Update festa state table
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
|
||||
// Mark festa as claimed
|
||||
// Update guild table?
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize)
|
||||
// Set prize as claimed
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize)
|
||||
// Set prize as claimed
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
// uint32 numPrizes
|
||||
// struct festaPrize
|
||||
// uint32 prizeID
|
||||
// uint32 prizeTier (1/2/3, 3 = GR)
|
||||
// uint32 soulsReq
|
||||
// uint32 unk (00 00 00 07)
|
||||
// uint32 itemID
|
||||
// uint32 numItem
|
||||
// bool claimed
|
||||
|
||||
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize)
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize)
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
1909
server/channelserver/handlers_guild.go
Normal file
1909
server/channelserver/handlers_guild.go
Normal file
File diff suppressed because one or more lines are too long
93
server/channelserver/handlers_guild_adventure.go
Normal file
93
server/channelserver/handlers_guild_adventure.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GuildAdventure struct {
|
||||
ID uint32 `db:"id"`
|
||||
Destination uint32 `db:"destination"`
|
||||
Charge uint32 `db:"charge"`
|
||||
Depart uint32 `db:"depart"`
|
||||
Return uint32 `db:"return"`
|
||||
CollectedBy string `db:"collected_by"`
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadGuildAdventure)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
data, err := s.server.db.Queryx("SELECT id, destination, charge, depart, return, collected_by FROM guild_adventures WHERE guild_id = $1", guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild adventures from db", zap.Error(err))
|
||||
}
|
||||
temp := byteframe.NewByteFrame()
|
||||
count := 0
|
||||
for data.Next() {
|
||||
count++
|
||||
adventureData := &GuildAdventure{}
|
||||
err = data.StructScan(&adventureData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to scan adventure data", zap.Error(err))
|
||||
}
|
||||
temp.WriteUint32(adventureData.ID)
|
||||
temp.WriteUint32(adventureData.Destination)
|
||||
temp.WriteUint32(adventureData.Charge)
|
||||
temp.WriteUint32(adventureData.Depart)
|
||||
temp.WriteUint32(adventureData.Return)
|
||||
temp.WriteBool(stringsupport.CSVContains(adventureData.CollectedBy, int(s.charID)))
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint8(uint8(count))
|
||||
bf.WriteBytes(temp.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventure)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, depart, return) VALUES ($1, $2, $3, $4)", guild.ID, pkt.Destination, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(6*time.Hour).Unix())
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to register guild adventure", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireGuildAdventure)
|
||||
var collectedBy string
|
||||
err := s.server.db.QueryRow("SELECT collected_by FROM guild_adventures WHERE id = $1", pkt.ID).Scan(&collectedBy)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Error parsing adventure collected by", zap.Error(err))
|
||||
} else {
|
||||
collectedBy = stringsupport.CSVAdd(collectedBy, int(s.charID))
|
||||
_, err := s.server.db.Exec("UPDATE guild_adventures SET collected_by = $1 WHERE id = $2", collectedBy, pkt.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to collect adventure in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfChargeGuildAdventure)
|
||||
_, err := s.server.db.Exec("UPDATE guild_adventures SET charge = charge + $1 WHERE id = $2", pkt.Amount, pkt.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to charge guild adventure", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventureDiva)
|
||||
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, charge, depart, return) VALUES ($1, $2, $3, $4, $5)", guild.ID, pkt.Destination, pkt.Charge, Time_Current_Adjusted().Unix(), Time_Current_Adjusted().Add(1*time.Hour).Unix())
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to register guild adventure", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
157
server/channelserver/handlers_guild_alliance.go
Normal file
157
server/channelserver/handlers_guild_alliance.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const allianceInfoSelectQuery = `
|
||||
SELECT
|
||||
ga.id,
|
||||
ga.name,
|
||||
created_at,
|
||||
parent_id,
|
||||
CASE
|
||||
WHEN sub1_id IS NULL THEN 0
|
||||
ELSE sub1_id
|
||||
END,
|
||||
CASE
|
||||
WHEN sub2_id IS NULL THEN 0
|
||||
ELSE sub2_id
|
||||
END
|
||||
FROM guild_alliances ga
|
||||
`
|
||||
|
||||
type GuildAlliance struct {
|
||||
ID uint32 `db:"id"`
|
||||
Name string `db:"name"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
TotalMembers uint16
|
||||
|
||||
ParentGuildID uint32 `db:"parent_id"`
|
||||
SubGuild1ID uint32 `db:"sub1_id"`
|
||||
SubGuild2ID uint32 `db:"sub2_id"`
|
||||
|
||||
ParentGuild Guild
|
||||
SubGuild1 Guild
|
||||
SubGuild2 Guild
|
||||
}
|
||||
|
||||
func GetAllianceData(s *Session, AllianceID uint32) (*GuildAlliance, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE ga.id = $1
|
||||
`, allianceInfoSelectQuery), AllianceID)
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to retrieve alliance data from database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
hasRow := rows.Next()
|
||||
if !hasRow {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return buildAllianceObjectFromDbResult(rows, err, s)
|
||||
}
|
||||
|
||||
func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) (*GuildAlliance, error) {
|
||||
alliance := &GuildAlliance{}
|
||||
|
||||
err = result.StructScan(alliance)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve alliance from database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parentGuild, err := GetGuildInfoByID(s, alliance.ParentGuildID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get parent guild info", zap.Error(err))
|
||||
} else {
|
||||
alliance.ParentGuild = *parentGuild
|
||||
alliance.TotalMembers += parentGuild.MemberCount
|
||||
}
|
||||
|
||||
if alliance.SubGuild1ID > 0 {
|
||||
subGuild1, err := GetGuildInfoByID(s, alliance.SubGuild1ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get sub guild 1 info", zap.Error(err))
|
||||
} else {
|
||||
alliance.SubGuild1 = *subGuild1
|
||||
alliance.TotalMembers += subGuild1.MemberCount
|
||||
}
|
||||
}
|
||||
|
||||
if alliance.SubGuild2ID > 0 {
|
||||
subGuild2, err := GetGuildInfoByID(s, alliance.SubGuild2ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get sub guild 2 info", zap.Error(err))
|
||||
} else {
|
||||
alliance.SubGuild2 = *subGuild2
|
||||
alliance.TotalMembers += subGuild2.MemberCount
|
||||
}
|
||||
}
|
||||
|
||||
return alliance, nil
|
||||
}
|
||||
|
||||
func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCreateJoint)
|
||||
_, err := s.server.db.Exec("INSERT INTO guild_alliances (name, parent_id) VALUES ($1, $2)", pkt.Name, pkt.GuildID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to create guild alliance in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01})
|
||||
}
|
||||
|
||||
func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOperateJoint)
|
||||
|
||||
guild, err := GetGuildInfoByID(s, pkt.GuildID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild info", zap.Error(err))
|
||||
}
|
||||
alliance, err := GetAllianceData(s, pkt.AllianceID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get alliance info", zap.Error(err))
|
||||
}
|
||||
|
||||
switch pkt.Action {
|
||||
case mhfpacket.OPERATE_JOINT_DISBAND:
|
||||
if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
|
||||
_, err = s.server.db.Exec("DELETE FROM guild_alliances WHERE id=$1", alliance.ID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to disband alliance", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
s.logger.Warn(
|
||||
"Non-owner of alliance attempted disband",
|
||||
zap.Uint32("CharID", s.charID),
|
||||
zap.Uint32("AllyID", alliance.ID),
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
case mhfpacket.OPERATE_JOINT_LEAVE:
|
||||
if guild.LeaderCharID == s.charID {
|
||||
// delete alliance application
|
||||
// or leave alliance
|
||||
} else {
|
||||
s.logger.Warn(
|
||||
"Non-owner of guild attempted alliance leave",
|
||||
zap.Uint32("CharID", s.charID),
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {}
|
||||
136
server/channelserver/handlers_guild_member.go
Normal file
136
server/channelserver/handlers_guild_member.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type GuildMember struct {
|
||||
GuildID uint32 `db:"guild_id"`
|
||||
CharID uint32 `db:"character_id"`
|
||||
JoinedAt *time.Time `db:"joined_at"`
|
||||
Name string `db:"name"`
|
||||
IsApplicant bool `db:"is_applicant"`
|
||||
OrderIndex uint8 `db:"order_index"`
|
||||
LastLogin uint32 `db:"last_login"`
|
||||
AvoidLeadership bool `db:"avoid_leadership"`
|
||||
IsLeader bool `db:"is_leader"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
GR uint16 `db:"gr"`
|
||||
WeaponID uint16 `db:"weapon_id"`
|
||||
WeaponType uint16 `db:"weapon_type"`
|
||||
}
|
||||
|
||||
func (gm *GuildMember) IsSubLeader() bool {
|
||||
return gm.OrderIndex <= 3 && !gm.AvoidLeadership
|
||||
}
|
||||
|
||||
func (gm *GuildMember) Save(s *Session) error {
|
||||
_, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1 WHERE character_id=$2", gm.AvoidLeadership, gm.CharID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to update guild member data",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", gm.CharID),
|
||||
zap.Uint32("guildID", gm.GuildID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO add the recruiter permission to this check when it exists
|
||||
func (gm *GuildMember) IsRecruiter() bool {
|
||||
return gm.IsLeader || gm.IsSubLeader()
|
||||
}
|
||||
|
||||
const guildMembersSelectSQL = `
|
||||
SELECT g.id as guild_id,
|
||||
joined_at,
|
||||
c.name,
|
||||
character.character_id,
|
||||
coalesce(gc.order_index, 0) as order_index,
|
||||
c.last_login,
|
||||
coalesce(gc.avoid_leadership, false) as avoid_leadership,
|
||||
c.hrp,
|
||||
c.gr,
|
||||
c.weapon_id,
|
||||
c.weapon_type,
|
||||
character.is_applicant,
|
||||
CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader
|
||||
FROM (
|
||||
SELECT character_id, true as is_applicant, guild_id
|
||||
FROM guild_applications ga
|
||||
WHERE ga.application_type = 'applied'
|
||||
UNION
|
||||
SELECT character_id, false as is_applicant, guild_id
|
||||
FROM guild_characters gc
|
||||
) character
|
||||
JOIN characters c on character.character_id = c.id
|
||||
LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
|
||||
JOIN guilds g ON g.id = character.guild_id
|
||||
`
|
||||
|
||||
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf(`
|
||||
%s
|
||||
WHERE character.guild_id = $1 AND is_applicant = $2
|
||||
`, guildMembersSelectSQL), guildID, applicants)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve membership data for guild", zap.Error(err), zap.Uint32("guildID", guildID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
members := make([]*GuildMember, 0)
|
||||
|
||||
for rows.Next() {
|
||||
member, err := buildGuildMemberObjectFromDBResult(rows, err, s)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
members = append(members, member)
|
||||
}
|
||||
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
|
||||
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
hasRow := rows.Next()
|
||||
|
||||
if !hasRow {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return buildGuildMemberObjectFromDBResult(rows, err, s)
|
||||
}
|
||||
|
||||
func buildGuildMemberObjectFromDBResult(rows *sqlx.Rows, err error, s *Session) (*GuildMember, error) {
|
||||
memberData := &GuildMember{}
|
||||
|
||||
err = rows.StructScan(&memberData)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve guild data from database", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberData, nil
|
||||
}
|
||||
310
server/channelserver/handlers_guild_scout.go
Normal file
310
server/channelserver/handlers_guild_scout.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostGuildScout)
|
||||
|
||||
actorCharGuildData, err := GetCharacterGuildData(s, s.charID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if actorCharGuildData == nil || !actorCharGuildData.IsRecruiter() {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
guildInfo, err := GetGuildInfoByID(s, actorCharGuildData.GuildID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hasApplication, err := guildInfo.HasApplicationForCharID(s, pkt.CharID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if hasApplication {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x04})
|
||||
return
|
||||
}
|
||||
|
||||
transaction, err := s.server.db.Begin()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = guildInfo.CreateApplication(s, pkt.CharID, GuildApplicationTypeInvited, transaction)
|
||||
|
||||
if err != nil {
|
||||
rollbackTransaction(s, transaction)
|
||||
doAckBufFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
senderName, err := getCharacterName(s, s.charID)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
mail := &Mail{
|
||||
SenderID: s.charID,
|
||||
RecipientID: pkt.CharID,
|
||||
Subject: "Guild! ヽ(・∀・)ノ",
|
||||
Body: fmt.Sprintf(
|
||||
"%s has invited you to join the wonderful guild %s, do you accept this challenge?",
|
||||
senderName,
|
||||
guildInfo.Name,
|
||||
),
|
||||
IsGuildInvite: true,
|
||||
}
|
||||
|
||||
err = mail.Send(s, transaction)
|
||||
|
||||
if err != nil {
|
||||
rollbackTransaction(s, transaction)
|
||||
doAckBufFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
}
|
||||
|
||||
err = transaction.Commit()
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCancelGuildScout)
|
||||
|
||||
guildCharData, err := GetCharacterGuildData(s, s.charID)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if guildCharData == nil || !guildCharData.IsRecruiter() {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
guild, err := GetGuildInfoByID(s, guildCharData.GuildID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
err = guild.CancelInvitation(s, pkt.InvitationID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout)
|
||||
|
||||
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Warn(
|
||||
"could not retrieve guild invitation",
|
||||
zap.Error(err),
|
||||
zap.Uint32("guildID", guild.ID),
|
||||
zap.Uint32("charID", s.charID),
|
||||
)
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
if pkt.Answer {
|
||||
err = guild.AcceptApplication(s, s.charID)
|
||||
} else {
|
||||
err = guild.RejectApplication(s, s.charID)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
|
||||
senderName, err := getCharacterName(s, pkt.LeaderID)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
successMail := Mail{
|
||||
SenderID: pkt.LeaderID,
|
||||
RecipientID: s.charID,
|
||||
Subject: "Happy days!",
|
||||
Body: fmt.Sprintf("You successfully joined %s and should be proud of all you have accomplished.", guild.Name),
|
||||
IsGuildInvite: false,
|
||||
SenderName: senderName,
|
||||
}
|
||||
|
||||
err = successMail.Send(s, nil)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x0f, 0x42, 0x81, 0x7e})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildScoutList)
|
||||
|
||||
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if guildInfo == nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := s.server.db.Queryx(`
|
||||
SELECT c.id, c.name, ga.actor_id
|
||||
FROM guild_applications ga
|
||||
JOIN characters c ON c.id = ga.character_id
|
||||
WHERE ga.guild_id = $1 AND ga.application_type = 'invited'
|
||||
`, guildInfo.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
bf.SetBE()
|
||||
|
||||
// Result count, we will overwrite this later
|
||||
bf.WriteUint32(0x00)
|
||||
|
||||
count := uint32(0)
|
||||
|
||||
for rows.Next() {
|
||||
var charName string
|
||||
var charID uint32
|
||||
var actorID uint32
|
||||
|
||||
err = rows.Scan(&charID, &charName, &actorID)
|
||||
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// This seems to be used as a unique ID for the invitation sent
|
||||
// we can just use the charID and then filter on guild_id+charID when performing operations
|
||||
// this might be a problem later with mails sent referencing IDs but we'll see.
|
||||
bf.WriteUint32(charID)
|
||||
bf.WriteUint32(actorID)
|
||||
bf.WriteUint32(charID)
|
||||
bf.WriteUint32(uint32(time.Now().Unix()))
|
||||
bf.WriteUint16(0x00) // HR?
|
||||
bf.WriteUint16(0x00) // GR?
|
||||
|
||||
charNameBytes, _ := stringsupport.ConvertUTF8ToShiftJIS(charName)
|
||||
|
||||
bf.WriteBytes(charNameBytes)
|
||||
bf.WriteBytes(make([]byte, 32-len(charNameBytes))) // Fixed length string
|
||||
count++
|
||||
}
|
||||
|
||||
_, err = bf.Seek(0, io.SeekStart)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bf.WriteUint32(count)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRejectGuildScout)
|
||||
|
||||
row := s.server.db.QueryRow("SELECT restrict_guild_scout FROM characters WHERE id=$1", s.charID)
|
||||
|
||||
var currentStatus bool
|
||||
|
||||
err := row.Scan(¤tStatus)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve character guild scout status",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", s.charID),
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
}
|
||||
|
||||
response := uint8(0x00)
|
||||
|
||||
if currentStatus {
|
||||
response = 0x01
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, response})
|
||||
}
|
||||
|
||||
func handleMsgMhfSetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSetRejectGuildScout)
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET restrict_guild_scout=$1 WHERE id=$2", pkt.Reject, s.charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to update character guild scout status",
|
||||
zap.Error(err),
|
||||
zap.Uint32("charID", s.charID),
|
||||
)
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
return
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, nil)
|
||||
}
|
||||
175
server/channelserver/handlers_guild_tresure.go
Normal file
175
server/channelserver/handlers_guild_tresure.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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", guild.ID)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(hunts))
|
||||
resp.WriteUint16(uint16(hunts))
|
||||
resp.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfRegistGuildTresure)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
|
||||
huntData := byteframe.NewByteFrame()
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
guildCats := getGuildAirouList(s)
|
||||
destination := bf.ReadUint32()
|
||||
level := bf.ReadUint32()
|
||||
huntData.WriteUint32(s.charID)
|
||||
huntData.WriteBytes(stringsupport.PaddedString(s.Name, 18, true))
|
||||
catsUsed := ""
|
||||
for i := 0; i < 5; i++ {
|
||||
catID := bf.ReadUint32()
|
||||
huntData.WriteUint32(catID)
|
||||
if catID > 0 {
|
||||
catsUsed = stringsupport.CSVAdd(catsUsed, int(catID))
|
||||
for _, cat := range guildCats {
|
||||
if cat.CatID == catID {
|
||||
huntData.WriteBytes(cat.CatName)
|
||||
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, Time_Current_Adjusted().Unix(), huntData.Data(), catsUsed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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, _ := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID)
|
||||
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)
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresureSouvenir)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
317
server/channelserver/handlers_house.go
Normal file
317
server/channelserver/handlers_house.go
Normal file
@@ -0,0 +1,317 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
|
||||
_, err := s.server.db.Exec("UPDATE characters SET house=$1 WHERE id=$2", pkt.InteriorData, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
type HouseData struct {
|
||||
CharID uint32 `db:"id"`
|
||||
HRP uint16 `db:"hrp"`
|
||||
GR uint16 `db:"gr"`
|
||||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateHouse)
|
||||
bf := byteframe.NewByteFrame()
|
||||
var houses []HouseData
|
||||
switch pkt.Method {
|
||||
case 1:
|
||||
var friendsList string
|
||||
s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&friendsList)
|
||||
cids := stringsupport.CSVElems(friendsList)
|
||||
for _, cid := range cids {
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", cid)
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
guild, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
guildMembers, err := GetGuildMembers(s, guild.ID, false)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
for _, member := range guildMembers {
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", member.CharID)
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE name=$1", pkt.Name)
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
case 4:
|
||||
house := HouseData{}
|
||||
row := s.server.db.QueryRowx("SELECT id, hrp, gr, name FROM characters WHERE id=$1", pkt.CharID)
|
||||
err := row.StructScan(&house)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
houses = append(houses, house)
|
||||
}
|
||||
case 5: // Recent visitors
|
||||
break
|
||||
}
|
||||
var exists int
|
||||
for _, house := range houses {
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == house.CharID {
|
||||
exists++
|
||||
bf.WriteUint32(house.CharID)
|
||||
bf.WriteUint8(session.myseries.state)
|
||||
if len(session.myseries.password) > 0 {
|
||||
bf.WriteUint8(3)
|
||||
} else {
|
||||
bf.WriteUint8(0)
|
||||
}
|
||||
bf.WriteUint16(house.HRP)
|
||||
bf.WriteUint16(house.GR)
|
||||
ps.Uint8(bf, house.Name, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint16(uint16(exists))
|
||||
resp.WriteBytes(bf.Data())
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateHouse)
|
||||
// 01 = closed
|
||||
// 02 = open anyone
|
||||
// 03 = open friends
|
||||
// 04 = open guild
|
||||
// 05 = open friends+guild
|
||||
s.myseries.state = pkt.State
|
||||
s.myseries.password = pkt.Password
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHouse)
|
||||
bf := byteframe.NewByteFrame()
|
||||
if pkt.Destination != 9 && len(pkt.Password) > 0 && pkt.CheckPass {
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID && pkt.Password != session.myseries.password {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var furniture []byte
|
||||
err := s.server.db.QueryRow("SELECT house FROM characters WHERE id=$1", pkt.CharID).Scan(&furniture)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if furniture == nil {
|
||||
furniture = make([]byte, 20)
|
||||
}
|
||||
|
||||
switch pkt.Destination {
|
||||
case 3: // Others house
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.houseTier)
|
||||
bf.WriteBytes(session.myseries.houseData)
|
||||
bf.WriteBytes(make([]byte, 19)) // Padding?
|
||||
bf.WriteBytes(furniture)
|
||||
}
|
||||
}
|
||||
case 4: // Bookshelf
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.bookshelfData)
|
||||
}
|
||||
}
|
||||
case 5: // Gallery
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.galleryData)
|
||||
}
|
||||
}
|
||||
case 8: // Tore
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.toreData)
|
||||
}
|
||||
}
|
||||
case 9: // Own house
|
||||
bf.WriteBytes(furniture)
|
||||
case 10: // Garden
|
||||
for _, session := range s.server.sessions {
|
||||
if session.charID == pkt.CharID {
|
||||
bf.WriteBytes(session.myseries.gardenData)
|
||||
c, d := getGookData(s, pkt.CharID)
|
||||
bf.WriteUint16(c)
|
||||
bf.WriteUint16(0)
|
||||
bf.WriteBytes(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(bf.Data()) == 0 {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetMyhouseInfo)
|
||||
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT trophy FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo)
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET trophy=$1 WHERE id=$2", pkt.Unk0, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadDecoMyset)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get preset decorations savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
//doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
// set first byte to 1 to avoid pop up every time without save
|
||||
body := make([]byte, 0x226)
|
||||
body[0] = 1
|
||||
doAckBufSucceed(s, pkt.AckHandle, body)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset)
|
||||
// https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf
|
||||
var loadData []byte
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[1:]) // skip first unk byte
|
||||
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&loadData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get preset decorations savedata from db", zap.Error(err))
|
||||
} else {
|
||||
numSets := bf.ReadUint8() // sets being written
|
||||
// empty save
|
||||
if len(loadData) == 0 {
|
||||
loadData = []byte{0x01, 0x00}
|
||||
}
|
||||
|
||||
savedSets := loadData[1] // existing saved sets
|
||||
// no sets, new slice with just first 2 bytes for appends later
|
||||
if savedSets == 0 {
|
||||
loadData = []byte{0x01, 0x00}
|
||||
}
|
||||
for i := 0; i < int(numSets); i++ {
|
||||
writeSet := bf.ReadUint16()
|
||||
dataChunk := bf.ReadBytes(76)
|
||||
setBytes := append([]byte{uint8(writeSet >> 8), uint8(writeSet & 0xff)}, dataChunk...)
|
||||
for x := 0; true; x++ {
|
||||
if x == int(savedSets) {
|
||||
// appending set
|
||||
if loadData[len(loadData)-1] == 0x10 {
|
||||
// sanity check for if there was a messy manual import
|
||||
loadData = append(loadData[:len(loadData)-2], setBytes...)
|
||||
} else {
|
||||
loadData = append(loadData, setBytes...)
|
||||
}
|
||||
savedSets++
|
||||
break
|
||||
}
|
||||
currentSet := loadData[3+(x*78)]
|
||||
if int(currentSet) == int(writeSet) {
|
||||
// replacing a set
|
||||
loadData = append(loadData[:2+(x*78)], append(setBytes, loadData[2+((x+1)*78):]...)...)
|
||||
break
|
||||
} else if int(currentSet) > int(writeSet) {
|
||||
// inserting before current set
|
||||
loadData = append(loadData[:2+((x)*78)], append(setBytes, loadData[2+((x)*78):]...)...)
|
||||
savedSets++
|
||||
break
|
||||
}
|
||||
}
|
||||
loadData[1] = savedSets // update set count
|
||||
}
|
||||
_, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update decomyset savedata in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateTitle)
|
||||
bf := byteframe.NewByteFrame()
|
||||
if pkt.CharID == s.charID {
|
||||
titleCount := 114 // all titles unlocked
|
||||
bf.WriteUint16(uint16(titleCount)) // title count
|
||||
bf.WriteUint16(0) // unk
|
||||
for i := 0; i < titleCount; i++ {
|
||||
bf.WriteUint16(uint16(i))
|
||||
bf.WriteUint16(0) // unk
|
||||
bf.WriteUint32(0) // timestamp acquired
|
||||
bf.WriteUint32(0) // timestamp updated
|
||||
}
|
||||
} else {
|
||||
bf.WriteUint16(0)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {}
|
||||
45
server/channelserver/handlers_kouryou.go
Normal file
45
server/channelserver/handlers_kouryou.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgMhfAddKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
// hunting with both ranks maxed gets you these
|
||||
pkt := p.(*mhfpacket.MsgMhfAddKouryouPoint)
|
||||
var points int
|
||||
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=COALESCE(kouryou_point + $1, $1) WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update KouryouPoint in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(uint32(points))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfGetKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetKouryouPoint)
|
||||
var points int
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(kouryou_point, 0) FROM characters WHERE id = $1", s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get kouryou_point savedata from db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(uint32(points))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
// spent at the guildmaster, 10000 a roll
|
||||
var points int
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeKouryouPoint)
|
||||
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=kouryou_point - $1 WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(uint32(points))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
424
server/channelserver/handlers_mail.go
Normal file
424
server/channelserver/handlers_mail.go
Normal file
@@ -0,0 +1,424 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Mail struct {
|
||||
ID int `db:"id"`
|
||||
SenderID uint32 `db:"sender_id"`
|
||||
RecipientID uint32 `db:"recipient_id"`
|
||||
Subject string `db:"subject"`
|
||||
Body string `db:"body"`
|
||||
Read bool `db:"read"`
|
||||
Deleted bool `db:"deleted"`
|
||||
Locked bool `db:"locked"`
|
||||
AttachedItemReceived bool `db:"attached_item_received"`
|
||||
AttachedItemID uint16 `db:"attached_item"`
|
||||
AttachedItemAmount uint16 `db:"attached_item_amount"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
IsGuildInvite bool `db:"is_guild_invite"`
|
||||
SenderName string `db:"sender_name"`
|
||||
}
|
||||
|
||||
func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
|
||||
query := `
|
||||
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
`
|
||||
|
||||
var err error
|
||||
|
||||
if transaction == nil {
|
||||
_, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
|
||||
} else {
|
||||
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to send mail",
|
||||
zap.Error(err),
|
||||
zap.Uint32("senderID", m.SenderID),
|
||||
zap.Uint32("recipientID", m.RecipientID),
|
||||
zap.String("subject", m.Subject),
|
||||
zap.String("body", m.Body),
|
||||
zap.Uint16("itemID", m.AttachedItemID),
|
||||
zap.Uint16("itemAmount", m.AttachedItemAmount),
|
||||
zap.Bool("isGuildInvite", m.IsGuildInvite),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mail) MarkRead(s *Session) error {
|
||||
_, err := s.server.db.Exec(`
|
||||
UPDATE mail SET read = true WHERE id = $1
|
||||
`, m.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to mark mail as read",
|
||||
zap.Error(err),
|
||||
zap.Int("mailID", m.ID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mail) MarkDeleted(s *Session) error {
|
||||
_, err := s.server.db.Exec(`
|
||||
UPDATE mail SET deleted = true WHERE id = $1
|
||||
`, m.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to mark mail as deleted",
|
||||
zap.Error(err),
|
||||
zap.Int("mailID", m.ID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mail) MarkAcquired(s *Session) error {
|
||||
_, err := s.server.db.Exec(`
|
||||
UPDATE mail SET attached_item_received = true WHERE id = $1
|
||||
`, m.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to mark mail item as claimed",
|
||||
zap.Error(err),
|
||||
zap.Int("mailID", m.ID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mail) MarkLocked(s *Session, locked bool) error {
|
||||
_, err := s.server.db.Exec(`
|
||||
UPDATE mail SET locked = $1 WHERE id = $2
|
||||
`, locked, m.ID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to mark mail as locked",
|
||||
zap.Error(err),
|
||||
zap.Int("mailID", m.ID),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
|
||||
rows, err := s.server.db.Queryx(`
|
||||
SELECT
|
||||
m.id,
|
||||
m.sender_id,
|
||||
m.recipient_id,
|
||||
m.subject,
|
||||
m.read,
|
||||
m.attached_item_received,
|
||||
m.attached_item,
|
||||
m.attached_item_amount,
|
||||
m.created_at,
|
||||
m.is_guild_invite,
|
||||
m.deleted,
|
||||
m.locked,
|
||||
c.name as sender_name
|
||||
FROM mail m
|
||||
JOIN characters c ON c.id = m.sender_id
|
||||
WHERE recipient_id = $1 AND m.deleted = false
|
||||
ORDER BY m.created_at DESC, id DESC
|
||||
LIMIT 32
|
||||
`, charID)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error("failed to get mail for character", zap.Error(err), zap.Uint32("charID", charID))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
allMail := make([]Mail, 0)
|
||||
|
||||
for rows.Next() {
|
||||
mail := Mail{}
|
||||
|
||||
err := rows.StructScan(&mail)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allMail = append(allMail, mail)
|
||||
}
|
||||
|
||||
return allMail, nil
|
||||
}
|
||||
|
||||
func GetMailByID(s *Session, ID int) (*Mail, error) {
|
||||
row := s.server.db.QueryRowx(`
|
||||
SELECT
|
||||
m.id,
|
||||
m.sender_id,
|
||||
m.recipient_id,
|
||||
m.subject,
|
||||
m.read,
|
||||
m.body,
|
||||
m.attached_item_received,
|
||||
m.attached_item,
|
||||
m.attached_item_amount,
|
||||
m.created_at,
|
||||
m.is_guild_invite,
|
||||
m.deleted,
|
||||
m.locked,
|
||||
c.name as sender_name
|
||||
FROM mail m
|
||||
JOIN characters c ON c.id = m.sender_id
|
||||
WHERE m.id = $1
|
||||
LIMIT 1
|
||||
`, ID)
|
||||
|
||||
mail := &Mail{}
|
||||
|
||||
err := row.StructScan(mail)
|
||||
|
||||
if err != nil {
|
||||
s.logger.Error(
|
||||
"failed to retrieve mail",
|
||||
zap.Error(err),
|
||||
zap.Int("mailID", ID),
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mail, nil
|
||||
}
|
||||
|
||||
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
|
||||
senderName, err := getCharacterName(s, m.SenderID)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
notification := &binpacket.MsgBinMailNotify{
|
||||
SenderName: senderName,
|
||||
}
|
||||
|
||||
notification.Build(bf)
|
||||
|
||||
castedBinary := &mhfpacket.MsgSysCastedBinary{
|
||||
CharID: m.SenderID,
|
||||
BroadcastType: 0x00,
|
||||
MessageType: BinaryMessageTypeMailNotify,
|
||||
RawDataPayload: bf.Data(),
|
||||
}
|
||||
|
||||
castedBinary.Build(bf, s.clientContext)
|
||||
|
||||
recipient.QueueSendMHF(castedBinary)
|
||||
}
|
||||
|
||||
func getCharacterName(s *Session, charID uint32) (string, error) {
|
||||
row := s.server.db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
|
||||
|
||||
charName := ""
|
||||
|
||||
err := row.Scan(&charName)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return charName, nil
|
||||
}
|
||||
|
||||
func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMail)
|
||||
|
||||
mailId := s.mailList[pkt.AccIndex]
|
||||
|
||||
if mailId == 0 {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic("attempting to read mail that doesn't exist in session map")
|
||||
}
|
||||
|
||||
mail, err := GetMailByID(s, mailId)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_ = mail.MarkRead(s)
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
body := s.clientContext.StrConv.MustEncode(mail.Body)
|
||||
bf.WriteNullTerminatedBytes(body)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfListMail)
|
||||
|
||||
mail, err := GetMailListForCharacter(s, s.charID)
|
||||
|
||||
if err != nil {
|
||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if s.mailList == nil {
|
||||
s.mailList = make([]int, 256)
|
||||
}
|
||||
|
||||
msg := byteframe.NewByteFrame()
|
||||
|
||||
msg.WriteUint32(uint32(len(mail)))
|
||||
|
||||
startIndex := s.mailAccIndex
|
||||
|
||||
for i, m := range mail {
|
||||
accIndex := startIndex + uint8(i)
|
||||
s.mailList[accIndex] = m.ID
|
||||
s.mailAccIndex++
|
||||
|
||||
itemAttached := m.AttachedItemID != 0
|
||||
subject := s.clientContext.StrConv.MustEncode(m.Subject)
|
||||
sender := s.clientContext.StrConv.MustEncode(m.SenderName)
|
||||
|
||||
msg.WriteUint32(m.SenderID)
|
||||
msg.WriteUint32(uint32(m.CreatedAt.Unix()))
|
||||
|
||||
msg.WriteUint8(uint8(accIndex))
|
||||
msg.WriteUint8(uint8(i))
|
||||
|
||||
flags := uint8(0x00)
|
||||
|
||||
if m.Read {
|
||||
flags |= 0x01
|
||||
}
|
||||
|
||||
if m.Locked {
|
||||
flags |= 0x02
|
||||
}
|
||||
|
||||
// System message, hides ID
|
||||
// flags |= 0x04
|
||||
|
||||
if m.AttachedItemReceived {
|
||||
flags |= 0x08
|
||||
}
|
||||
|
||||
if m.IsGuildInvite {
|
||||
flags |= 0x10
|
||||
}
|
||||
|
||||
msg.WriteUint8(flags)
|
||||
msg.WriteBool(itemAttached)
|
||||
msg.WriteUint8(uint8(len(subject) + 1))
|
||||
msg.WriteUint8(uint8(len(sender) + 1))
|
||||
msg.WriteNullTerminatedBytes(subject)
|
||||
msg.WriteNullTerminatedBytes(sender)
|
||||
|
||||
if itemAttached {
|
||||
msg.WriteUint16(m.AttachedItemAmount)
|
||||
msg.WriteUint16(m.AttachedItemID)
|
||||
}
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, msg.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfOprtMail)
|
||||
|
||||
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
switch mhfpacket.OperateMailOperation(pkt.Operation) {
|
||||
case mhfpacket.OPERATE_MAIL_DELETE:
|
||||
err = mail.MarkDeleted(s)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
case mhfpacket.OPERATE_MAIL_LOCK:
|
||||
err = mail.MarkLocked(s, true)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
case mhfpacket.OPERATE_MAIL_UNLOCK:
|
||||
err = mail.MarkLocked(s, false)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM:
|
||||
err = mail.MarkAcquired(s)
|
||||
if err != nil {
|
||||
doAckSimpleFail(s, pkt.AckHandle, nil)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSendMail)
|
||||
query := `
|
||||
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
`
|
||||
|
||||
if pkt.RecipientID == 0 { // Guild mail
|
||||
g, err := GetGuildInfoByCharacterId(s, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild info for mail")
|
||||
}
|
||||
gm, err := GetGuildMembers(s, g.ID, false)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get guild members for mail")
|
||||
}
|
||||
for i := 0; i < len(gm); i++ {
|
||||
_, err := s.server.db.Exec(query, s.charID, gm[i].CharID, pkt.Subject, pkt.Body, 0, 0, false)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to send mail")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err := s.server.db.Exec(query, s.charID, pkt.RecipientID, pkt.Subject, pkt.Body, pkt.ItemID, pkt.Quantity, false)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to send mail")
|
||||
}
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
389
server/channelserver/handlers_mercenary.go
Normal file
389
server/channelserver/handlers_mercenary.go
Normal file
@@ -0,0 +1,389 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/deltacomp"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// THERE ARE [PARTENER] [MERCENARY] [OTOMO AIRU]
|
||||
|
||||
///////////////////////////////////////////
|
||||
/// PARTENER //
|
||||
///////////////////////////////////////////
|
||||
|
||||
func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadPartner)
|
||||
// load partner from database
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get partner savedata from db", zap.Error(err))
|
||||
}
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
// TODO(Andoryuuta): Figure out unusual double ack. One sized, one not.
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePartner)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "_partner")
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update partner savedata in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadLegendDispatch)
|
||||
data := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x8d, 0x40, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x02, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x04, 0x30, 0x40}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get hunter navigation savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
// set first byte to 1 to avoid pop up every time without save
|
||||
body := make([]byte, 0x226)
|
||||
body[0] = 1
|
||||
doAckBufSucceed(s, pkt.AckHandle, body)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "_hunternavi")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get hunternavi savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
// 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[0] = 1 // set first byte to 1 to avoid pop up every time without save
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
s.logger.Info("Diffing...")
|
||||
saveOutput := deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed hunternavi back to DB.")
|
||||
} else {
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////
|
||||
/// MERCENARY //
|
||||
///////////////////////////////////////////
|
||||
|
||||
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata)
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x0A))
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
|
||||
|
||||
bf := byteframe.NewByteFrame()
|
||||
|
||||
bf.WriteUint32(0x00) // Unk
|
||||
bf.WriteUint32(rand.Uint32()) // Partner ID?
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
GCPValue := bf.ReadUint32()
|
||||
_ = bf.ReadUint32() // unk
|
||||
MercDataSize := bf.ReadUint32()
|
||||
MercData := bf.ReadBytes(uint(MercDataSize))
|
||||
_ = bf.ReadUint32() // unk
|
||||
|
||||
if MercDataSize > 0 {
|
||||
// the save packet has an extra null byte after its size
|
||||
_, err := s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
// gcp value is always present regardless
|
||||
_, err := s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCPValue, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
|
||||
var data []byte
|
||||
var gcp uint32
|
||||
// still has issues
|
||||
err := s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get savemercenary data from db", zap.Error(err))
|
||||
}
|
||||
|
||||
err = s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteBytes(data)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint32(gcp)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
|
||||
// accessing actual rasta data of someone else still unsure of the formatting of this
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
///////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////
|
||||
/// OTOMO AIRU //
|
||||
///////////////////////////////////////////
|
||||
|
||||
func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadOtomoAirou)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT otomoairou FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get partnyaa savedata from db", zap.Error(err))
|
||||
}
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou)
|
||||
decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:])
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to decompress airou", zap.Error(err))
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
bf := byteframe.NewByteFrameFromBytes(decomp)
|
||||
cats := bf.ReadUint8()
|
||||
for i := 0; i < int(cats); i++ {
|
||||
dataLen := bf.ReadUint32()
|
||||
catID := bf.ReadUint32()
|
||||
if catID == 0 {
|
||||
var nextID uint32
|
||||
_ = s.server.db.QueryRow("SELECT nextval('airou_id_seq')").Scan(&nextID)
|
||||
bf.Seek(-4, io.SeekCurrent)
|
||||
bf.WriteUint32(nextID)
|
||||
}
|
||||
_ = bf.ReadBytes(uint(dataLen) - 4)
|
||||
}
|
||||
comp, err := nullcomp.Compress(bf.Data())
|
||||
if err != nil {
|
||||
s.logger.Error("Failed to compress airou", zap.Error(err))
|
||||
} else {
|
||||
comp = append([]byte{0x01}, comp...)
|
||||
s.server.db.Exec("UPDATE characters SET otomoairou=$1 WHERE id=$2", comp, s.charID)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
|
||||
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, _ := ioutil.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.Experience)
|
||||
resp.WriteUint8(cat.Personality)
|
||||
resp.WriteUint8(cat.Class)
|
||||
resp.WriteUint8(cat.WeaponType)
|
||||
resp.WriteUint16(cat.WeaponID)
|
||||
resp.WriteUint32(0) // 32 bit unix timestamp, either time at which the cat stops being fatigued or the time at which it started
|
||||
}
|
||||
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
|
||||
Personality uint8
|
||||
Class uint8
|
||||
Experience uint32
|
||||
WeaponType uint8
|
||||
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)
|
||||
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, Time_Current_Adjusted().Unix())
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to get recently used airous", zap.Error(err))
|
||||
}
|
||||
for rows.Next() {
|
||||
rows.Scan(&csvTemp)
|
||||
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
|
||||
WHERE gc.guild_id = $1 AND c.otomoairou IS NOT NULL
|
||||
ORDER BY c.id ASC
|
||||
LIMIT 60;`, guild.ID)
|
||||
if err != nil {
|
||||
s.logger.Warn("Selecting otomoairou based on guild failed", zap.Error(err))
|
||||
return guildCats
|
||||
}
|
||||
|
||||
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
|
||||
continue
|
||||
}
|
||||
// first byte has cat existence in general, can skip if 0
|
||||
if data[0] == 1 {
|
||||
decomp, err := nullcomp.Decompress(data[1:])
|
||||
if err != nil {
|
||||
s.logger.Warn("decomp failure", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
bf := byteframe.NewByteFrameFromBytes(decomp)
|
||||
cats := GetCatDetails(bf)
|
||||
for _, cat := range cats {
|
||||
_, exists := bannedCats[cat.CatID]
|
||||
if cat.CurrentTask == 4 && !exists {
|
||||
guildCats = append(guildCats, cat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return guildCats
|
||||
}
|
||||
|
||||
func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition {
|
||||
catCount := bf.ReadUint8()
|
||||
cats := make([]CatDefinition, catCount)
|
||||
for x := 0; x < int(catCount); x++ {
|
||||
var catDef CatDefinition
|
||||
// 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()
|
||||
bf.Seek(16, io.SeekCurrent) // appearance data and what is seemingly null bytes
|
||||
catDef.Personality = bf.ReadUint8()
|
||||
catDef.Class = bf.ReadUint8()
|
||||
bf.Seek(5, io.SeekCurrent) // affection and colour sliders
|
||||
catDef.Experience = bf.ReadUint32() // raw cat rank points, doesn't have a rank
|
||||
bf.Seek(1, io.SeekCurrent) // bool for weapon being equipped
|
||||
catDef.WeaponType = bf.ReadUint8() // weapon type, presumably always 6 for melee?
|
||||
catDef.WeaponID = bf.ReadUint16() // weapon id
|
||||
bf.Seek(catStart+int64(catDefLen), io.SeekStart)
|
||||
cats[x] = catDef
|
||||
}
|
||||
return cats
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
13
server/channelserver/handlers_mutex.go
Normal file
13
server/channelserver/handlers_mutex.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package channelserver
|
||||
|
||||
import "erupe-ce/network/mhfpacket"
|
||||
|
||||
func handleMsgSysCreateMutex(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysCreateOpenMutex(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysDeleteMutex(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysOpenMutex(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysCloseMutex(s *Session, p mhfpacket.MHFPacket) {}
|
||||
87
server/channelserver/handlers_object.go
Normal file
87
server/channelserver/handlers_object.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCreateObject)
|
||||
|
||||
s.stage.Lock()
|
||||
newObj := &Object{
|
||||
id: s.stage.NextObjectID(),
|
||||
ownerCharID: s.charID,
|
||||
x: pkt.X,
|
||||
y: pkt.Y,
|
||||
z: pkt.Z,
|
||||
}
|
||||
s.stage.objects[s.charID] = newObj
|
||||
s.stage.Unlock()
|
||||
|
||||
// Response to our requesting client.
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(newObj.id) // New local obj handle.
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
|
||||
// Duplicate the object creation to all sessions in the same stage.
|
||||
dupObjUpdate := &mhfpacket.MsgSysDuplicateObject{
|
||||
ObjID: newObj.id,
|
||||
X: newObj.x,
|
||||
Y: newObj.y,
|
||||
Z: newObj.z,
|
||||
OwnerCharID: newObj.ownerCharID,
|
||||
}
|
||||
|
||||
s.logger.Info(fmt.Sprintf("Broadcasting new object: %s (%d)", s.Name, s.charID))
|
||||
s.stage.BroadcastMHF(dupObjUpdate, s)
|
||||
}
|
||||
|
||||
func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysPositionObject)
|
||||
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
fmt.Printf("[%s] with objectID [%d] move to (%f,%f,%f)\n\n", s.Name, pkt.ObjID, pkt.X, pkt.Y, pkt.Z)
|
||||
}
|
||||
s.stage.Lock()
|
||||
object, ok := s.stage.objects[s.charID]
|
||||
if ok {
|
||||
object.x = pkt.X
|
||||
object.y = pkt.Y
|
||||
object.z = pkt.Z
|
||||
}
|
||||
s.stage.Unlock()
|
||||
// One of the few packets we can just re-broadcast directly.
|
||||
s.stage.BroadcastMHF(pkt, s)
|
||||
}
|
||||
|
||||
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)
|
||||
for _, object := range s.stage.objects {
|
||||
if object.id == pkt.ObjID {
|
||||
object.binary = pkt.RawDataPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysGetObjectBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysGetObjectOwner(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysUpdateObjectBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysCleanupObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysAddObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysDelObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysDispObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysHideObject(s *Session, p mhfpacket.MHFPacket) {}
|
||||
165
server/channelserver/handlers_plate.go
Normal file
165
server/channelserver/handlers_plate.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/channelserver/compression/deltacomp"
|
||||
"erupe-ce/server/channelserver/compression/nullcomp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadPlateData)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get plate data savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "_platedata")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get platedata savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
// Decompress
|
||||
s.logger.Info("Decompressing...")
|
||||
data, err = nullcomp.Decompress(data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
data = make([]byte, 0x1AF20)
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
s.logger.Info("Diffing...")
|
||||
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to diff and compress platedata savedata", zap.Error(err))
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed platedata back to DB.")
|
||||
} else {
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadPlateBox)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get sigil box savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
|
||||
|
||||
dumpSaveData(s, pkt.RawDataPayload, "_platebox")
|
||||
|
||||
if pkt.IsDataDiff {
|
||||
var data []byte
|
||||
|
||||
// Load existing save
|
||||
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get sigil box savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
// Decompress
|
||||
if len(data) > 0 {
|
||||
// Decompress
|
||||
s.logger.Info("Decompressing...")
|
||||
data, err = nullcomp.Decompress(data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to decompress savedata from db", zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
// create empty save if absent
|
||||
data = make([]byte, 0x820)
|
||||
}
|
||||
|
||||
// Perform diff and compress it to write back to db
|
||||
s.logger.Info("Diffing...")
|
||||
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to diff and compress savedata", zap.Error(err))
|
||||
}
|
||||
|
||||
_, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platebox savedata in db", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("Wrote recompressed platebox back to DB.")
|
||||
} else {
|
||||
// simply update database, no extra processing
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get presets sigil savedata from db", zap.Error(err))
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
blankData := make([]byte, 0x780)
|
||||
doAckBufSucceed(s, pkt.AckHandle, blankData)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSavePlateMyset)
|
||||
// looks to always return the full thing, simply update database, no extra processing
|
||||
|
||||
_, err := s.server.db.Exec("UPDATE characters SET platemyset=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platemyset savedata in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
101
server/channelserver/handlers_quest.go
Normal file
101
server/channelserver/handlers_quest.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysGetFile)
|
||||
|
||||
// Debug print the request.
|
||||
if pkt.IsScenario {
|
||||
fmt.Printf("%+v\n", pkt.ScenarioIdentifer)
|
||||
filename := fmt.Sprintf("%d_0_0_0_S%d_T%d_C%d", pkt.ScenarioIdentifer.CategoryID, pkt.ScenarioIdentifer.MainID, pkt.ScenarioIdentifer.Flags, pkt.ScenarioIdentifer.ChapterID)
|
||||
// Read the scenario file.
|
||||
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("scenarios/%s.bin", filename)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin")); err == nil {
|
||||
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
// Get quest file.
|
||||
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename)))
|
||||
if err != nil {
|
||||
s.logger.Fatal(fmt.Sprintf("Failed to open quest file: quests/%s.bin", pkt.Filename))
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadFavoriteQuest)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT savefavoritequest FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err == nil && len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveFavoriteQuest)
|
||||
s.server.db.Exec("UPDATE characters SET savefavoritequest=$1 WHERE id=$2", pkt.Data, s.charID)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
|
||||
// local files are easier for now, probably best would be to generate dynamically
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateQuest)
|
||||
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("questlists/list_%d.bin", pkt.QuestList)))
|
||||
if err != nil {
|
||||
fmt.Printf("questlists/list_%d.bin", pkt.QuestList)
|
||||
stubEnumerateNoResults(s, pkt.AckHandle)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdBonusQuestInfo)
|
||||
|
||||
udBonusQuestInfos := []struct {
|
||||
Unk0 uint8
|
||||
Unk1 uint8
|
||||
StartTime uint32 // Unix timestamp (seconds)
|
||||
EndTime uint32 // Unix timestamp (seconds)
|
||||
Unk4 uint32
|
||||
Unk5 uint8
|
||||
Unk6 uint8
|
||||
}{} // Blank stub array.
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(len(udBonusQuestInfos)))
|
||||
for _, q := range udBonusQuestInfos {
|
||||
resp.WriteUint8(q.Unk0)
|
||||
resp.WriteUint8(q.Unk1)
|
||||
resp.WriteUint32(q.StartTime)
|
||||
resp.WriteUint32(q.EndTime)
|
||||
resp.WriteUint32(q.Unk4)
|
||||
resp.WriteUint8(q.Unk5)
|
||||
resp.WriteUint8(q.Unk6)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
327
server/channelserver/handlers_register.go
Normal file
327
server/channelserver/handlers_register.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysOperateRegister)
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
s.server.raviente.Lock()
|
||||
if pkt.SemaphoreID == s.server.raviente.state.semaphoreID {
|
||||
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.state.damageMultiplier
|
||||
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 + data)
|
||||
}
|
||||
} else {
|
||||
resp.WriteUint32(*ref + data*damageMultiplier)
|
||||
*ref += data * damageMultiplier
|
||||
}
|
||||
case 13:
|
||||
fallthrough
|
||||
case 14:
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(data)
|
||||
*ref = data
|
||||
}
|
||||
}
|
||||
resp.WriteUint8(0)
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
} else if pkt.SemaphoreID == s.server.raviente.support.semaphoreID {
|
||||
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())
|
||||
} else if pkt.SemaphoreID == s.server.raviente.register.semaphoreID {
|
||||
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 + uint32(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 + uint32(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 + uint32(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 + uint32(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 + uint32(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())
|
||||
}
|
||||
s.notifyall()
|
||||
s.server.raviente.Unlock()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) notifyall() {
|
||||
var temp mhfpacket.MHFPacket
|
||||
raviNotif := byteframe.NewByteFrame()
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: s.server.raviente.support.semaphoreID}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: s.server.raviente.state.semaphoreID}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: s.server.raviente.register.semaphoreID}
|
||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(raviNotif, s.clientContext)
|
||||
raviNotif.WriteUint16(0x0010) // End it.
|
||||
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
|
||||
for session := range s.server.semaphore["hs_l0u3B51J9k3"].clients {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
} else if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
|
||||
for session := range s.server.semaphore["hs_l0u3B5129k3"].clients {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
} else if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
|
||||
for session := range s.server.semaphore["hs_l0u3B512Ak3"].clients {
|
||||
session.QueueSend(raviNotif.Data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkRaviSemaphore(s *Session) bool {
|
||||
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
|
||||
return true
|
||||
} else if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
|
||||
return true
|
||||
} else if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//func releaseRaviSemaphore(s *Session) {
|
||||
// s.server.raviente.Lock()
|
||||
// if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
|
||||
// if len(s.server.semaphore["hs_l0u3B51J9k3"].reservedClientSlots) == 0 {
|
||||
// resetRavi(s)
|
||||
// }
|
||||
// }
|
||||
// if _, exists := s.server.semaphore["hs_l0u3B5129k3"]; exists {
|
||||
// if len(s.server.semaphore["hs_l0u3B5129k3"].reservedClientSlots) == 0 {
|
||||
// resetRavi(s)
|
||||
// }
|
||||
// }
|
||||
// if _, exists := s.server.semaphore["hs_l0u3B512Ak3"]; exists {
|
||||
// if len(s.server.semaphore["hs_l0u3B512Ak3"].reservedClientSlots) == 0 {
|
||||
// resetRavi(s)
|
||||
// }
|
||||
// }
|
||||
// s.server.raviente.Unlock()
|
||||
//}
|
||||
|
||||
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.state.damageMultiplier = 1
|
||||
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()
|
||||
}
|
||||
|
||||
// Unused
|
||||
func (s *Session) notifyticker() {
|
||||
if _, exists := s.server.semaphore["hs_l0u3B51J9k3"]; exists {
|
||||
s.server.semaphoreLock.Lock()
|
||||
getSemaphore := s.server.semaphore["hs_l0u3B51J9k3"]
|
||||
s.server.semaphoreLock.Unlock()
|
||||
if _, exists := getSemaphore.reservedClientSlots[s.charID]; exists {
|
||||
s.notifyall()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {}
|
||||
96
server/channelserver/handlers_rengoku.go
Normal file
96
server/channelserver/handlers_rengoku.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
// saved every floor on road, holds values such as floors progressed, points etc.
|
||||
// can be safely handled by the client
|
||||
pkt := p.(*mhfpacket.MsgMhfSaveRengokuData)
|
||||
_, err := s.server.db.Exec("UPDATE characters SET rengokudata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update rengokudata savedata in db", zap.Error(err))
|
||||
}
|
||||
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfLoadRengokuData)
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to get rengokudata savedata from db", zap.Error(err))
|
||||
}
|
||||
if len(data) > 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0) // an extra 4 bytes were missing based on pcaps
|
||||
|
||||
resp.WriteUint8(3) // Count of next 3
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint16(0)
|
||||
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
|
||||
resp.WriteUint8(3) // Count of next 3
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
|
||||
resp.WriteUint8(3) // Count of next 3
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
resp.WriteUint32(0)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRengokuBinary)
|
||||
// a (massively out of date) version resides in the game's /dat/ folder or up to date can be pulled from packets
|
||||
data, err := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank)
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteBytes([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
129
server/channelserver/handlers_reserve.go
Normal file
129
server/channelserver/handlers_reserve.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package channelserver
|
||||
|
||||
import "erupe-ce/network/mhfpacket"
|
||||
|
||||
func handleMsgSysReserve188(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysReserve188)
|
||||
|
||||
// Left as raw bytes because I couldn't easily find the request or resp parser function in the binary.
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgSysReserve18B(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysReserve18B)
|
||||
|
||||
// Left as raw bytes because I couldn't easily find the request or resp parser function in the binary.
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x3C})
|
||||
}
|
||||
|
||||
func handleMsgSysReserve55(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve56(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve57(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve01(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve02(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve03(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve04(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve05(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve06(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve07(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve0C(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve0D(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve0E(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve4A(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve4B(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve4C(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve4D(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve4E(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve4F(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve5C(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve5E(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve5F(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve71(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve72(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve73(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve74(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve75(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve76(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve77(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve78(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve79(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve7A(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve7B(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve7C(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve7E(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfReserve10F(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve180(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve18E(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve18F(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve19E(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve19F(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1A4(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1A6(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1A7(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1A8(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1A9(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1AA(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1AB(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1AC(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1AD(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1AE(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve1AF(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve19B(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve192(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve193(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysReserve194(s *Session, p mhfpacket.MHFPacket) {}
|
||||
49
server/channelserver/handlers_reward.go
Normal file
49
server/channelserver/handlers_reward.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgMhfGetAdditionalBeatReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetAdditionalBeatReward)
|
||||
// Actual response in packet captures are all just giant batches of null bytes
|
||||
// I'm assuming this is because it used to be tied to an actual event and
|
||||
// they never bothered killing off the packet when they made it static
|
||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x104))
|
||||
}
|
||||
|
||||
func handleMsgMhfGetUdRankingRewardList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetUdRankingRewardList)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfGetRewardSong(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetRewardSong)
|
||||
// Temporary canned response
|
||||
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfUseRewardSong(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfAddRewardSongCount(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfAcquireMonthlyReward(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireMonthlyReward)
|
||||
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfAcceptReadReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {}
|
||||
142
server/channelserver/handlers_semaphore.go
Normal file
142
server/channelserver/handlers_semaphore.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCreateSemaphore)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x03, 0x00, 0x0d})
|
||||
}
|
||||
|
||||
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()
|
||||
delete(s.server.semaphore, id)
|
||||
s.server.semaphoreLock.Lock()
|
||||
if strings.HasPrefix(id, "hs_l0u3B51") {
|
||||
releaseRaviSemaphore(s, sema)
|
||||
}
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
func releaseRaviSemaphore(s *Session, sema *Semaphore) {
|
||||
if !strings.HasSuffix(sema.id_semaphore, "5") {
|
||||
delete(sema.reservedClientSlots, s.charID)
|
||||
delete(sema.clients, s)
|
||||
}
|
||||
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
|
||||
s.logger.Debug("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_l0u3B51") {
|
||||
releaseRaviSemaphore(s, sema)
|
||||
s.server.semaphoreLock.Unlock()
|
||||
return
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
delete(s.server.semaphore, id)
|
||||
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
|
||||
return
|
||||
}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore)
|
||||
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_l0u3B51") {
|
||||
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 32)
|
||||
if strings.HasSuffix(SemaphoreID, "3") {
|
||||
s.server.raviente.state.semaphoreID = s.server.semaphore[SemaphoreID].id
|
||||
} else if strings.HasSuffix(SemaphoreID, "4") {
|
||||
s.server.raviente.support.semaphoreID = s.server.semaphore[SemaphoreID].id
|
||||
} else if strings.HasSuffix(SemaphoreID, "5") {
|
||||
s.server.raviente.register.semaphoreID = s.server.semaphore[SemaphoreID].id
|
||||
}
|
||||
} else {
|
||||
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1)
|
||||
}
|
||||
newSemaphore = s.server.semaphore[SemaphoreID]
|
||||
s.server.semaphoreLock.Unlock()
|
||||
}
|
||||
|
||||
newSemaphore.Lock()
|
||||
defer newSemaphore.Unlock()
|
||||
if _, exists := newSemaphore.reservedClientSlots[s.charID]; exists {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint32(newSemaphore.id)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||
} else if uint16(len(newSemaphore.reservedClientSlots)) < newSemaphore.maxPlayers {
|
||||
newSemaphore.reservedClientSlots[s.charID] = nil
|
||||
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 {
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
//pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
|
||||
}
|
||||
|
||||
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
//pkt := p.(*mhfpacket.MsgSysReleaseSemaphore)
|
||||
}
|
||||
|
||||
func handleMsgSysCheckSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCheckSemaphore)
|
||||
resp := []byte{0x00, 0x00, 0x00, 0x00}
|
||||
s.server.semaphoreLock.Lock()
|
||||
if _, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
|
||||
resp = []byte{0x00, 0x00, 0x00, 0x01}
|
||||
}
|
||||
s.server.semaphoreLock.Unlock()
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, resp)
|
||||
}
|
||||
738
server/channelserver/handlers_shop_gacha.go
Normal file
738
server/channelserver/handlers_shop_gacha.go
Normal file
@@ -0,0 +1,738 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
//"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"github.com/lib/pq"
|
||||
"github.com/sachaos/lottery"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
|
||||
// SHOP TYPES:
|
||||
// 01 = Running Gachas, 02 = actual gacha, 04 = N Points, 05 = GCP, 07 = Item to GCP, 08 = Diva Defense, 10 = Hunter's Road
|
||||
|
||||
// GACHA FORMAT:
|
||||
// int32: gacha id
|
||||
|
||||
// STORE FORMAT:
|
||||
// Int16: total item count
|
||||
// Int16: total item count
|
||||
|
||||
// ITEM FORMAT:
|
||||
// int32: Unique item hash for tracking purchases
|
||||
// int16: padding?
|
||||
// int16: Item ID
|
||||
// int16: padding?
|
||||
// int16: GCP returns
|
||||
// int16: Number traded at once
|
||||
// int16: HR or SR Requirement
|
||||
// int16: Whichever of the above it isn't
|
||||
// int16: GR Requirement
|
||||
// int16: Store level requirement
|
||||
// int16: Maximum quantity purchasable
|
||||
// int16: Unk
|
||||
// int16: Road floors cleared requirement
|
||||
// int16: Road White Fatalis weekly kills
|
||||
if pkt.ShopType == 2 {
|
||||
shopEntries, err := s.server.db.Query("SELECT entryType, itemhash, currType, currNumber, currQuant, percentage, rarityIcon, rollsCount, itemCount, dailyLimit, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1", pkt.ShopID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var entryType, currType, rarityIcon, rollsCount, itemCount, dailyLimit byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var entryCount int
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(pkt.ShopID)
|
||||
resp.WriteUint16(0) // total defs
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&entryType, &itemhash, &currType, &currNumber, &currQuant, &percentage, &rarityIcon, &rollsCount, &itemCount, &dailyLimit, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp.WriteUint8(entryType)
|
||||
resp.WriteUint32(itemhash)
|
||||
resp.WriteUint8(currType)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(currNumber) // it's either item ID or quantity for gacha coins
|
||||
resp.WriteUint16(currQuant) // only for item ID
|
||||
resp.WriteUint16(percentage)
|
||||
resp.WriteUint8(rarityIcon)
|
||||
resp.WriteUint8(rollsCount)
|
||||
resp.WriteUint8(itemCount)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint8(dailyLimit)
|
||||
resp.WriteUint8(0) // unk, always 0 in existing packets
|
||||
for i := 0; i < int(itemCount); i++ {
|
||||
resp.WriteUint16(uint16(itemType[i])) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(uint16(itemId[i])) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(uint16(quantity[i])) // unk, always 0 in existing packets
|
||||
}
|
||||
entryCount++
|
||||
}
|
||||
if entryCount == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
resp.Seek(4, 0)
|
||||
resp.WriteUint16(uint16(entryCount))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
} else if pkt.ShopType == 1 {
|
||||
gachaCount := 0
|
||||
shopEntries, err := s.server.db.Query("SELECT hash, reqGR, reqHR, gachaName, gachaLink0, gachaLink1, COALESCE(gachaLink2, ''), extraIcon, gachaType, hideFlag FROM gacha_shop")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
var gachaName, gachaLink0, gachaLink1, gachaLink2 string
|
||||
var hash, reqGR, reqHR, extraIcon, gachaType int
|
||||
var hideFlag bool
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&hash, &reqGR, &reqHR, &gachaName, &gachaLink0, &gachaLink1, &gachaLink2, &extraIcon, &gachaType, &hideFlag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp.WriteUint32(uint32(hash))
|
||||
resp.WriteUint32(0) // only 0 in known packets
|
||||
resp.WriteUint32(0) // all of these seem to trigger the 'rank restriction'
|
||||
resp.WriteUint32(0) // message so they are presumably placeholders for a
|
||||
resp.WriteUint32(0) // Z Rank or similar that never turned up?
|
||||
resp.WriteUint32(uint32(reqGR))
|
||||
resp.WriteUint32(uint32(reqHR))
|
||||
resp.WriteUint32(0) // only 0 in known packet
|
||||
stringBytes := append([]byte(gachaName), 0x00)
|
||||
resp.WriteUint8(byte(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
stringBytes = append([]byte(gachaLink0), 0x00)
|
||||
resp.WriteUint8(byte(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
stringBytes = append([]byte(gachaLink1), 0x00)
|
||||
resp.WriteUint8(byte(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
stringBytes = append([]byte(gachaLink2), 0x00)
|
||||
resp.WriteBool(hideFlag)
|
||||
resp.WriteUint8(uint8(len(stringBytes)))
|
||||
resp.WriteBytes(stringBytes)
|
||||
resp.WriteUint16(uint16(extraIcon))
|
||||
resp.WriteUint16(uint16(gachaType))
|
||||
gachaCount++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(uint16(gachaCount))
|
||||
resp.WriteUint16(uint16(gachaCount))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
} else if pkt.ShopType == 7 {
|
||||
// GCP conversion store
|
||||
if pkt.ShopID == 0 {
|
||||
// Items to GCP exchange. Gou Tickets, Shiten Tickets, GP Tickets
|
||||
data, _ := hex.DecodeString("000300033a9186fb000033860000000a000100000000000000000000000000000000097fdb1c0000067e0000000a0001000000000000000000000000000000001374db29000027c300000064000100000000000000000000000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
} else if pkt.ShopType == 8 {
|
||||
// Dive Defense sections
|
||||
// 00 = normal level limited exchange store, 05 = GCP skill store, 07 = limited quantity exchange
|
||||
if pkt.ShopID == 5 {
|
||||
// diva defense skill level limited store
|
||||
data, _ := hex.DecodeString("001f001f2c9365c1000000010000001e000a0000000000000000000a0000000000001979f1c2000000020000003c000a0000000000000000000a0000000000003e5197df000000030000003c000a0000000000000000000a000000000000219337c0000000040000001e000a0000000000000000000a00000000000009b24c9d000000140000001e000a0000000000000000000a0000000000001f1d496e000000150000001e000a0000000000000000000a0000000000003b918fcb000000160000003c000a0000000000000000000a0000000000000b7fd81c000000170000003c000a0000000000000000000a0000000000001374f239000000180000003c000a0000000000000000000a00000000000026950cba0000001c0000003c000a0000000000000000000a0000000000003797eae70000001d0000003c000a012b000000000000000a00000000000015758ad8000000050000003c00000000000000000000000a0000000000003c7035050000000600000050000a0000000000000001000a00000000000024f3b5560000000700000050000a0000000000000001000a00000000000000b600330000000800000050000a0000000000000001000a0000000000002efdce840000001900000050000a0000000000000001000a0000000000002d9365f10000001a00000050000a0000000000000001000a0000000000001979f3420000001f00000050000a012b000000000001000a0000000000003f5397cf0000002000000050000a012b000000000001000a000000000000319337c00000002100000050000a012b000000000001000a00000000000008b04cbd0000000900000064000a0000000000000002000a0000000000000b1d4b6e0000000a00000064000a0000000000000002000a0000000000003b918feb0000000b00000064000a0000000000000002000a0000000000001b7fd81c0000000c00000064000a0000000000000002000a0000000000001276f2290000000d00000064000a0000000000000002000a00000000000022950cba0000000e000000c8000a0000000000000002000a0000000000003697ead70000000f000001f4000a0000000000000003000a00000000000005758a5800000010000003e8000a0000000000000003000a0000000000003c7035250000001b000001f4000a0000000000010003000a00000000000034f3b5d60000001e00000064000a012b000000000003000a00000000000000b600030000002200000064000a0000000000010003000a000000000000")
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
} else {
|
||||
_, week := time.Now().ISOWeek()
|
||||
season := fmt.Sprintf("%d", week%4)
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash,itemID,Points,TradeQuantity,rankReqLow,rankReqHigh,rankReqG,storeLevelReq,maximumQuantity,boughtQuantity,roadFloorsRequired,weeklyFatalisKills, COALESCE(enable_weeks, '') FROM normal_shop_items WHERE shoptype=$1 AND shopid=$2", pkt.ShopType, pkt.ShopID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var ItemHash, entryCount int
|
||||
var itemID, Points, TradeQuantity, rankReqLow, rankReqHigh, rankReqG, storeLevelReq, maximumQuantity, boughtQuantity, roadFloorsRequired, weeklyFatalisKills, charQuantity uint16
|
||||
var itemWeeks string
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0) // total defs
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&ItemHash, &itemID, &Points, &TradeQuantity, &rankReqLow, &rankReqHigh, &rankReqG, &storeLevelReq, &maximumQuantity, &boughtQuantity, &roadFloorsRequired, &weeklyFatalisKills, &itemWeeks)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(itemWeeks) > 0 && !contains(strings.Split(itemWeeks, ","), season) {
|
||||
continue
|
||||
}
|
||||
|
||||
resp.WriteUint32(uint32(ItemHash))
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(itemID)
|
||||
resp.WriteUint16(0) // unk, always 0 in existing packets
|
||||
resp.WriteUint16(Points) // it's either item ID or quantity for gacha coins
|
||||
resp.WriteUint16(TradeQuantity) // only for item ID
|
||||
resp.WriteUint16(rankReqLow)
|
||||
resp.WriteUint16(rankReqHigh)
|
||||
resp.WriteUint16(rankReqG)
|
||||
resp.WriteUint16(storeLevelReq)
|
||||
resp.WriteUint16(maximumQuantity)
|
||||
if maximumQuantity > 0 {
|
||||
var itemWeek int
|
||||
err = s.server.db.QueryRow("SELECT COALESCE(usedquantity,0), COALESCE(week,-1) FROM shop_item_state WHERE itemhash=$1 AND char_id=$2", ItemHash, s.charID).Scan(&charQuantity, &itemWeek)
|
||||
if err != nil {
|
||||
resp.WriteUint16(0)
|
||||
} else if pkt.ShopID == 7 && itemWeek >= 0 && itemWeek != week {
|
||||
clearShopItemState(s, s.charID, uint32(ItemHash))
|
||||
resp.WriteUint16(0)
|
||||
} else {
|
||||
resp.WriteUint16(charQuantity)
|
||||
}
|
||||
} else {
|
||||
resp.WriteUint16(boughtQuantity)
|
||||
}
|
||||
resp.WriteUint16(roadFloorsRequired)
|
||||
resp.WriteUint16(weeklyFatalisKills)
|
||||
entryCount++
|
||||
}
|
||||
if entryCount == 0 {
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(uint16(entryCount))
|
||||
resp.WriteUint16(uint16(entryCount))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
|
||||
_, week := time.Now().ISOWeek()
|
||||
// writing out to an editable shop enumeration
|
||||
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
|
||||
if pkt.DataSize == 10 {
|
||||
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||
_ = bf.ReadUint16() // unk, always 1 in examples
|
||||
itemHash := bf.ReadUint32()
|
||||
buyCount := bf.ReadUint32()
|
||||
_, err := s.server.db.Exec(`INSERT INTO shop_item_state (char_id, itemhash, usedquantity, week)
|
||||
VALUES ($1,$2,$3,$4) ON CONFLICT (char_id, itemhash)
|
||||
DO UPDATE SET usedquantity = shop_item_state.usedquantity + $3
|
||||
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.itemhash=$2`, s.charID, itemHash, buyCount, week)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update shop_item_state in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func clearShopItemState(s *Session, charId uint32, itemHash uint32) {
|
||||
_, err := s.server.db.Exec(`DELETE FROM shop_item_state WHERE char_id=$1 AND itemhash=$2`, charId, itemHash)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to delete shop_item_state in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
|
||||
// returns number of times the gacha was played, will need persistent db stuff
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x0A})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetGachaPoint)
|
||||
var fp, gp, gt uint32
|
||||
_ = s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_prem, 0), COALESCE(gacha_trial,0) FROM characters WHERE id=$1", s.charID).Scan(&fp, &gp, >)
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(gp) // Real Gacha Points?
|
||||
resp.WriteUint32(gt) // Trial Gacha Point?
|
||||
resp.WriteUint32(fp) // Frontier Points?
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
type gachaItem struct {
|
||||
itemhash uint32
|
||||
percentage uint16
|
||||
rarityIcon byte
|
||||
itemCount byte
|
||||
itemType pq.Int64Array
|
||||
itemId pq.Int64Array
|
||||
quantity pq.Int64Array
|
||||
}
|
||||
|
||||
func (i gachaItem) Weight() int {
|
||||
return int(i.percentage)
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
|
||||
// needs to query db for input gacha and return a result or number of results
|
||||
// uint8 number of results
|
||||
// uint8 item type
|
||||
// uint16 item id
|
||||
// uint16 quantity
|
||||
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
results := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
dbUpdate := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0) // results go here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, dbUpdate.Data()...)
|
||||
dbUpdate.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(dbUpdate.Data())
|
||||
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
|
||||
}
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(results))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data[0] = data[0] + results
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end WHERE id=$2", currNumber, s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
// should write to database when that's set up
|
||||
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
|
||||
|
||||
var itemValue, quant int
|
||||
_ = s.server.db.QueryRow("SELECT quant, itemValue FROM fpoint_items WHERE hash=$1", pkt.ItemHash).Scan(&quant, &itemValue)
|
||||
itemCost := (int(pkt.Quantity) * quant) * itemValue
|
||||
|
||||
// also update frontierpoints entry in database
|
||||
_, err := s.server.db.Exec("UPDATE characters SET frontier_points=frontier_points::int - $1 WHERE id=$2", itemCost, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
|
||||
|
||||
var itemValue, quant int
|
||||
_ = s.server.db.QueryRow("SELECT quant, itemValue FROM fpoint_items WHERE hash=$1", pkt.ItemHash).Scan(&quant, &itemValue)
|
||||
itemCost := (int(pkt.Quantity) / quant) * itemValue
|
||||
// also update frontierpoints entry in database
|
||||
_, err := s.server.db.Exec("UPDATE characters SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE id=$2", itemCost, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update minidata in db", zap.Error(err))
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList)
|
||||
//absurd, probably lists every single item to trade to FP?
|
||||
|
||||
var buyables int
|
||||
var sellables int
|
||||
|
||||
buyRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=0")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint32(0)
|
||||
var hash, itemType, itemID, quant, itemValue int
|
||||
for buyRows.Next() {
|
||||
err = buyRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue)
|
||||
if err != nil {
|
||||
panic("Error in fpoint_items")
|
||||
}
|
||||
resp.WriteUint32(uint32(hash))
|
||||
resp.WriteUint32(0) // this and following only 0 in known packets
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint8(byte(itemType))
|
||||
resp.WriteUint16(uint16(itemID))
|
||||
resp.WriteUint16(uint16(quant))
|
||||
resp.WriteUint16(uint16(itemValue))
|
||||
buyables++
|
||||
}
|
||||
|
||||
sellRows, err := s.server.db.Query("SELECT hash,itemType,itemID,quant,itemValue FROM fpoint_items WHERE tradeType=1")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for sellRows.Next() {
|
||||
err = sellRows.Scan(&hash, &itemType, &itemID, &quant, &itemValue)
|
||||
if err != nil {
|
||||
panic("Error in fpoint_items")
|
||||
}
|
||||
resp.WriteUint32(uint32(hash))
|
||||
resp.WriteUint32(0) // this and following only 0 in known packets
|
||||
resp.WriteUint16(0)
|
||||
resp.WriteUint8(byte(itemType))
|
||||
resp.WriteUint16(uint16(itemID))
|
||||
resp.WriteUint16(uint16(quant))
|
||||
resp.WriteUint16(uint16(itemValue))
|
||||
sellables++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint16(uint16(sellables))
|
||||
resp.WriteUint16(uint16(buyables))
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
|
||||
results := byte(0)
|
||||
stepResults := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
rollFrame := byteframe.NewByteFrame()
|
||||
stepFrame := byteframe.NewByteFrame()
|
||||
stepData := []byte{}
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// roll definition includes items with step up gachas that are appended last
|
||||
for x := 0; x < int(itemCount); x++ {
|
||||
stepFrame.WriteUint8(uint8(itemType[x]))
|
||||
stepFrame.WriteUint16(uint16(itemId[x]))
|
||||
stepFrame.WriteUint16(uint16(quantity[x]))
|
||||
stepData = append(stepData, stepFrame.Data()...)
|
||||
stepFrame.WriteUint8(0) // rarity still defined
|
||||
stepResults++
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query("SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity FROM gacha_shop_items WHERE shophash=$1 AND entryType=100", pkt.GachaHash)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
resp.WriteUint16(0) // results count goes here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
rollFrame.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
rollFrame.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, rollFrame.Data()...)
|
||||
rollFrame.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(rollFrame.Data())
|
||||
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
|
||||
}
|
||||
}
|
||||
resp.WriteBytes(stepFrame.Data())
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(results + stepResults))
|
||||
resp.WriteUint8(uint8(results))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data = append(data, stepData...)
|
||||
data[0] = data[0] + results + stepResults
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
// reduce real if trial don't cover cost
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec(`UPDATE characters
|
||||
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end,
|
||||
gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
|
||||
WHERE id=$2`, currNumber, s.charID)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// update step progression
|
||||
_, err = s.server.db.Exec("UPDATE stepup_state SET step_progression = $1 WHERE char_id = $2", pkt.RollType+1, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update step_progression in db", zap.Error(err))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
|
||||
// persistent for claimable items on cat
|
||||
var data []byte
|
||||
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
|
||||
if err != nil {
|
||||
panic("Failed to get gacha_items")
|
||||
}
|
||||
// limit of 36 items are returned
|
||||
if data[0] > 36 {
|
||||
outData := make([]byte, 181)
|
||||
copy(outData, data[0:181])
|
||||
outData[0] = byte(36)
|
||||
saveData := append(data[:1], data[181:]...)
|
||||
saveData[0] = saveData[0] - 36
|
||||
doAckBufSucceed(s, pkt.AckHandle, outData)
|
||||
if pkt.Unk0 != 0x2401 {
|
||||
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = $2 WHERE id = $1", s.charID, saveData)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
if pkt.Unk0 != 0x2401 {
|
||||
_, err := s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
|
||||
// get the reset time from db
|
||||
var step_progression int
|
||||
var step_time time.Time
|
||||
err := s.server.db.QueryRow(`SELECT COALESCE(step_progression, 0), COALESCE(step_time, $1) FROM stepup_state WHERE char_id = $2 AND shophash = $3`, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), s.charID, pkt.GachaHash).Scan(&step_progression, &step_time)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to Select coalesce in db", zap.Error(err))
|
||||
}
|
||||
|
||||
// calculate next midday
|
||||
var t = time.Now().In(time.FixedZone("UTC+9", 9*60*60))
|
||||
year, month, day := t.Date()
|
||||
midday := time.Date(year, month, day, 12, 0, 0, 0, t.Location())
|
||||
if t.After(midday) {
|
||||
midday = midday.Add(24 * time.Hour)
|
||||
}
|
||||
// after midday or not set
|
||||
if t.After(step_time) {
|
||||
step_progression = 0
|
||||
}
|
||||
_, err = s.server.db.Exec(`INSERT INTO stepup_state (shophash, step_progression, step_time, char_id)
|
||||
VALUES ($1,$2,$3,$4) ON CONFLICT (shophash, char_id)
|
||||
DO UPDATE SET step_progression=$2, step_time=$3
|
||||
WHERE EXCLUDED.char_id=$4 AND EXCLUDED.shophash=$1`, pkt.GachaHash, step_progression, midday, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update platedata savedata in db", zap.Error(err))
|
||||
}
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(uint8(step_progression))
|
||||
resp.WriteUint32(uint32(time.Now().In(time.FixedZone("UTC+9", 9*60*60)).Unix()))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
// not sure this is used anywhere, free gachas use the MSG_MHF_PLAY_NORMAL_GACHA method in captures
|
||||
}
|
||||
|
||||
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
|
||||
count := 0
|
||||
var used_itemhash pq.Int64Array
|
||||
// pull array of used values
|
||||
// single sized respone with 0x00 is a valid with no items present
|
||||
_ = s.server.db.QueryRow("SELECT used_itemhash FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID).Scan((*pq.Int64Array)(&used_itemhash))
|
||||
resp := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0)
|
||||
for ind := range used_itemhash {
|
||||
resp.WriteUint32(uint32(used_itemhash[ind]))
|
||||
resp.WriteUint8(1)
|
||||
count++
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(count))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
|
||||
// needs to query db for input gacha and return a result or number of results
|
||||
// uint8 number of results
|
||||
// uint8 item type
|
||||
// uint16 item id
|
||||
// uint16 quantity
|
||||
|
||||
var currType, rarityIcon, rollsCount, itemCount byte
|
||||
var currQuant, currNumber, percentage uint16
|
||||
var itemhash uint32
|
||||
var itemType, itemId, quantity, usedItemHash pq.Int64Array
|
||||
var items []lottery.Weighter
|
||||
// get info for updating data and calculating costs
|
||||
err := s.server.db.QueryRow("SELECT currType, currNumber, currQuant, rollsCount FROM gacha_shop_items WHERE shophash=$1 AND entryType=$2", pkt.GachaHash, pkt.RollType).Scan(&currType, &currNumber, &currQuant, &rollsCount)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// get existing items in storage if any
|
||||
var data []byte
|
||||
_ = s.server.db.QueryRow("SELECT gacha_items FROM characters WHERE id = $1", s.charID).Scan(&data)
|
||||
if len(data) == 0 {
|
||||
data = []byte{0x00}
|
||||
}
|
||||
// get gacha items and iterate through them for gacha roll
|
||||
shopEntries, err := s.server.db.Query(`SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
|
||||
FROM gacha_shop_items
|
||||
WHERE shophash=$1 AND entryType=100
|
||||
EXCEPT ALL SELECT itemhash, percentage, rarityIcon, itemCount, itemType, itemId, quantity
|
||||
FROM gacha_shop_items gsi JOIN lucky_box_state lbs ON gsi.itemhash = ANY(lbs.used_itemhash)
|
||||
WHERE lbs.char_id=$2`, pkt.GachaHash, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for shopEntries.Next() {
|
||||
err = shopEntries.Scan(&itemhash, &percentage, &rarityIcon, &itemCount, (*pq.Int64Array)(&itemType), (*pq.Int64Array)(&itemId), (*pq.Int64Array)(&quantity))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
items = append(items, &gachaItem{itemhash: itemhash, percentage: percentage, rarityIcon: rarityIcon, itemCount: itemCount, itemType: itemType, itemId: itemId, quantity: quantity})
|
||||
}
|
||||
// execute rolls, build response and update database
|
||||
results := byte(0)
|
||||
resp := byteframe.NewByteFrame()
|
||||
dbUpdate := byteframe.NewByteFrame()
|
||||
resp.WriteUint8(0) // results go here later
|
||||
l := lottery.NewDefaultLottery()
|
||||
for x := 0; x < int(rollsCount); x++ {
|
||||
ind := l.Draw(items)
|
||||
results += items[ind].(*gachaItem).itemCount
|
||||
for y := 0; y < int(items[ind].(*gachaItem).itemCount); y++ {
|
||||
// items in storage don't get rarity
|
||||
dbUpdate.WriteUint8(uint8(items[ind].(*gachaItem).itemType[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).itemId[y]))
|
||||
dbUpdate.WriteUint16(uint16(items[ind].(*gachaItem).quantity[y]))
|
||||
data = append(data, dbUpdate.Data()...)
|
||||
dbUpdate.Seek(0, 0)
|
||||
// response needs all item info and the rarity
|
||||
resp.WriteBytes(dbUpdate.Data())
|
||||
resp.WriteUint8(uint8(items[ind].(*gachaItem).rarityIcon))
|
||||
|
||||
usedItemHash = append(usedItemHash, int64(items[ind].(*gachaItem).itemhash))
|
||||
}
|
||||
// remove rolled
|
||||
items = append(items[:ind], items[ind+1:]...)
|
||||
}
|
||||
resp.Seek(0, 0)
|
||||
resp.WriteUint8(uint8(results))
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
|
||||
// add claimables to DB
|
||||
data[0] = data[0] + results
|
||||
_, err = s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", data, s.charID)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_items in db", zap.Error(err))
|
||||
}
|
||||
// update lucky_box_state
|
||||
_, err = s.server.db.Exec(` INSERT INTO lucky_box_state (char_id, shophash, used_itemhash)
|
||||
VALUES ($1,$2,$3) ON CONFLICT (char_id, shophash)
|
||||
DO UPDATE SET used_itemhash = COALESCE(lucky_box_state.used_itemhash::int[] || $3::int[], $3::int[])
|
||||
WHERE EXCLUDED.char_id=$1 AND EXCLUDED.shophash=$2`, s.charID, pkt.GachaHash, usedItemHash)
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update lucky box state in db", zap.Error(err))
|
||||
}
|
||||
// deduct gacha coins if relevant, items are handled fine by the standard savedata packet immediately afterwards
|
||||
if currType == 19 {
|
||||
_, err = s.server.db.Exec(` UPDATE characters
|
||||
SET gacha_trial = CASE WHEN (gacha_trial > $1) then gacha_trial - $1 else gacha_trial end, gacha_prem = CASE WHEN NOT (gacha_trial > $1) then gacha_prem - $1 else gacha_prem end
|
||||
WHERE id=$2`, currNumber, s.charID)
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Fatal("Failed to update gacha_trial in db", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
|
||||
_, err := s.server.db.Exec("DELETE FROM lucky_box_state WHERE shophash=$1 AND char_id=$2", pkt.GachaHash, s.charID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
364
server/channelserver/handlers_stage.go
Normal file
364
server/channelserver/handlers_stage.go
Normal file
@@ -0,0 +1,364 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func handleMsgSysCreateStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysCreateStage)
|
||||
s.server.Lock()
|
||||
defer s.server.Unlock()
|
||||
if _, exists := s.server.stages[pkt.StageID]; exists {
|
||||
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
} else {
|
||||
stage := NewStage(pkt.StageID)
|
||||
stage.maxPlayers = uint16(pkt.PlayerCount)
|
||||
s.server.stages[stage.id] = stage
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysStageDestruct(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
|
||||
s.server.Lock()
|
||||
stage, exists := s.server.stages[stageID]
|
||||
s.server.Unlock()
|
||||
|
||||
if exists {
|
||||
stage.Lock()
|
||||
stage.clients[s] = s.charID
|
||||
stage.Unlock()
|
||||
} else { // Create new stage object
|
||||
s.server.Lock()
|
||||
s.server.stages[stageID] = NewStage(stageID)
|
||||
stage = s.server.stages[stageID]
|
||||
s.server.Unlock()
|
||||
stage.Lock()
|
||||
stage.clients[s] = s.charID
|
||||
stage.Unlock()
|
||||
}
|
||||
|
||||
// Ensure this session no longer belongs to reservations.
|
||||
if s.stage != nil {
|
||||
removeSessionFromStage(s)
|
||||
}
|
||||
|
||||
// Save our new stage ID and pointer to the new stage itself.
|
||||
s.Lock()
|
||||
s.stageID = string(stageID)
|
||||
s.stage = s.server.stages[stageID]
|
||||
s.Unlock()
|
||||
|
||||
// Tell the client to cleanup its current stage objects.
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
|
||||
|
||||
// Confirm the stage entry.
|
||||
doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
|
||||
if s.stage != nil { // avoids lock up when using bed for dream quests
|
||||
// Notify the client to duplicate the existing objects.
|
||||
s.logger.Info("Sending existing stage objects")
|
||||
clientDupObjNotif := byteframe.NewByteFrame()
|
||||
s.stage.RLock()
|
||||
for _, obj := range s.stage.objects {
|
||||
cur := &mhfpacket.MsgSysDuplicateObject{
|
||||
ObjID: obj.id,
|
||||
X: obj.x,
|
||||
Y: obj.y,
|
||||
Z: obj.z,
|
||||
Unk0: 0,
|
||||
OwnerCharID: obj.ownerCharID,
|
||||
}
|
||||
clientDupObjNotif.WriteUint16(uint16(cur.Opcode()))
|
||||
cur.Build(clientDupObjNotif, s.clientContext)
|
||||
}
|
||||
s.stage.RUnlock()
|
||||
clientDupObjNotif.WriteUint16(0x0010) // End it.
|
||||
if len(clientDupObjNotif.Data()) > 2 {
|
||||
s.QueueSend(clientDupObjNotif.Data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func destructEmptyStages(s *Session) {
|
||||
s.server.Lock()
|
||||
defer s.server.Unlock()
|
||||
for _, stage := range s.server.stages {
|
||||
// Destroy empty Quest/My series/Guild stages.
|
||||
if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" {
|
||||
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
|
||||
delete(s.server.stages, stage.id)
|
||||
s.logger.Debug("Destructed stage", zap.String("stage.id", stage.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeSessionFromStage(s *Session) {
|
||||
// Remove client from old stage.
|
||||
s.stage.Lock()
|
||||
delete(s.stage.clients, s)
|
||||
delete(s.stage.reservedClientSlots, s.charID)
|
||||
|
||||
// Delete old stage objects owned by the client.
|
||||
s.logger.Info("Sending notification to old stage clients")
|
||||
for _, object := range s.stage.objects {
|
||||
if object.ownerCharID == s.charID {
|
||||
s.stage.BroadcastMHF(&mhfpacket.MsgSysDeleteObject{ObjID: object.id}, s)
|
||||
delete(s.stage.objects, object.ownerCharID)
|
||||
}
|
||||
}
|
||||
s.stage.Unlock()
|
||||
destructEmptyStages(s)
|
||||
destructEmptySemaphores(s)
|
||||
}
|
||||
|
||||
func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysEnterStage)
|
||||
|
||||
// Push our current stage ID to the movement stack before entering another one.
|
||||
if s.stageID == "" {
|
||||
s.stageMoveStack.Set(pkt.StageID)
|
||||
} else {
|
||||
s.stageMoveStack.Push(s.stageID)
|
||||
s.stageMoveStack.Lock()
|
||||
}
|
||||
|
||||
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
|
||||
if s.reservationStage != nil {
|
||||
s.reservationStage = nil
|
||||
}
|
||||
|
||||
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
|
||||
}
|
||||
|
||||
func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysBackStage)
|
||||
|
||||
// Transfer back to the saved stage ID before the previous move or enter.
|
||||
s.stageMoveStack.Unlock()
|
||||
backStage, err := s.stageMoveStack.Pop()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
doStageTransfer(s, pkt.AckHandle, backStage)
|
||||
}
|
||||
|
||||
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysMoveStage)
|
||||
|
||||
// Set a new move stack from the given stage ID if unlocked
|
||||
if !s.stageMoveStack.Locked {
|
||||
s.stageMoveStack.Set(pkt.StageID)
|
||||
}
|
||||
|
||||
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
|
||||
}
|
||||
|
||||
func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysLockStage)
|
||||
// TODO(Andoryuuta): What does this packet _actually_ do?
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.reservationStage.RLock()
|
||||
defer s.reservationStage.RUnlock()
|
||||
|
||||
for charID := range s.reservationStage.reservedClientSlots {
|
||||
session := s.server.FindSessionByCharID(charID)
|
||||
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
|
||||
}
|
||||
|
||||
s.server.Lock()
|
||||
defer s.server.Unlock()
|
||||
|
||||
delete(s.server.stages, s.reservationStage.id)
|
||||
}
|
||||
|
||||
func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysReserveStage)
|
||||
if stage, exists := s.server.stages[pkt.StageID]; exists {
|
||||
stage.Lock()
|
||||
defer stage.Unlock()
|
||||
if _, exists := stage.reservedClientSlots[s.charID]; exists {
|
||||
switch pkt.Ready {
|
||||
case 1: // 0x01
|
||||
stage.reservedClientSlots[s.charID] = false
|
||||
case 17: // 0x11
|
||||
stage.reservedClientSlots[s.charID] = true
|
||||
}
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers {
|
||||
if len(stage.password) > 0 {
|
||||
if stage.password != s.stagePass {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
return
|
||||
}
|
||||
}
|
||||
stage.reservedClientSlots[s.charID] = false
|
||||
// Save the reservation stage in the session for later use in MsgSysUnreserveStage.
|
||||
s.Lock()
|
||||
s.reservationStage = stage
|
||||
s.Unlock()
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||
} else {
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
} else {
|
||||
s.logger.Error("Failed to get stage", zap.String("StageID", pkt.StageID))
|
||||
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
s.Lock()
|
||||
stage := s.reservationStage
|
||||
s.reservationStage = nil
|
||||
s.Unlock()
|
||||
if stage != nil {
|
||||
stage.Lock()
|
||||
if _, exists := stage.reservedClientSlots[s.charID]; exists {
|
||||
delete(stage.reservedClientSlots, s.charID)
|
||||
}
|
||||
stage.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysSetStagePass(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysSetStagePass)
|
||||
s.Lock()
|
||||
stage := s.reservationStage
|
||||
s.Unlock()
|
||||
if stage != nil {
|
||||
stage.Lock()
|
||||
// Will only exist if host.
|
||||
if _, exists := stage.reservedClientSlots[s.charID]; exists {
|
||||
stage.password = pkt.Password
|
||||
}
|
||||
stage.Unlock()
|
||||
} else {
|
||||
// Store for use on next ReserveStage.
|
||||
s.Lock()
|
||||
s.stagePass = pkt.Password
|
||||
s.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysSetStageBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysSetStageBinary)
|
||||
if stage, exists := s.server.stages[pkt.StageID]; exists {
|
||||
stage.Lock()
|
||||
stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}] = pkt.RawDataPayload
|
||||
stage.Unlock()
|
||||
} else {
|
||||
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysGetStageBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysGetStageBinary)
|
||||
if stage, exists := s.server.stages[pkt.StageID]; exists {
|
||||
stage.Lock()
|
||||
if binaryData, exists := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]; exists {
|
||||
doAckBufSucceed(s, pkt.AckHandle, binaryData)
|
||||
} else if pkt.BinaryType1 == 4 {
|
||||
// Unknown binary type that is supposedly generated server side
|
||||
// Temporary response
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
} else {
|
||||
s.logger.Warn("Failed to get stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
|
||||
s.logger.Warn("Sending blank stage binary")
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{})
|
||||
}
|
||||
stage.Unlock()
|
||||
} else {
|
||||
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
|
||||
}
|
||||
s.logger.Debug("MsgSysGetStageBinary Done!")
|
||||
}
|
||||
|
||||
func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysWaitStageBinary)
|
||||
if stage, exists := s.server.stages[pkt.StageID]; exists {
|
||||
if pkt.BinaryType0 == 1 && pkt.BinaryType1 == 12 {
|
||||
// This might contain the hunter count, or max player count?
|
||||
doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||
return
|
||||
}
|
||||
for {
|
||||
s.logger.Debug("MsgSysWaitStageBinary before lock and get stage")
|
||||
stage.Lock()
|
||||
stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]
|
||||
stage.Unlock()
|
||||
s.logger.Debug("MsgSysWaitStageBinary after lock and get stage")
|
||||
if gotBinary {
|
||||
doAckBufSucceed(s, pkt.AckHandle, stageBinary)
|
||||
break
|
||||
} else {
|
||||
s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
|
||||
}
|
||||
s.logger.Debug("MsgSysWaitStageBinary Done!")
|
||||
}
|
||||
|
||||
func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysEnumerateStage)
|
||||
|
||||
// Read-lock the server stage map.
|
||||
s.server.stagesLock.RLock()
|
||||
defer s.server.stagesLock.RUnlock()
|
||||
|
||||
// Build the response
|
||||
resp := byteframe.NewByteFrame()
|
||||
bf := byteframe.NewByteFrame()
|
||||
var joinable int
|
||||
for sid, stage := range s.server.stages {
|
||||
stage.RLock()
|
||||
defer stage.RUnlock()
|
||||
|
||||
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for valid stage type
|
||||
if sid[3:5] != "Qs" && sid[3:5] != "Ms" {
|
||||
continue
|
||||
}
|
||||
|
||||
joinable++
|
||||
|
||||
resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players.
|
||||
resp.WriteUint16(0) // Unk
|
||||
resp.WriteUint8(0) // Unk
|
||||
resp.WriteBool(len(stage.clients) > 0) // Has departed.
|
||||
resp.WriteUint16(stage.maxPlayers) // Max players.
|
||||
if len(stage.password) > 0 {
|
||||
// This byte has also been seen as 1
|
||||
// The quest is also recognised as locked when this is 2
|
||||
resp.WriteUint8(3)
|
||||
} else {
|
||||
resp.WriteUint8(0)
|
||||
}
|
||||
ps.Uint8(resp, sid, false)
|
||||
}
|
||||
bf.WriteUint16(uint16(joinable))
|
||||
bf.WriteBytes(resp.Data())
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||
}
|
||||
446
server/channelserver/handlers_table.go
Normal file
446
server/channelserver/handlers_table.go
Normal file
@@ -0,0 +1,446 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
type handlerFunc func(s *Session, p mhfpacket.MHFPacket)
|
||||
|
||||
var handlerTable map[network.PacketID]handlerFunc
|
||||
|
||||
func init() {
|
||||
handlerTable = make(map[network.PacketID]handlerFunc)
|
||||
handlerTable[network.MSG_HEAD] = handleMsgHead
|
||||
handlerTable[network.MSG_SYS_reserve01] = handleMsgSysReserve01
|
||||
handlerTable[network.MSG_SYS_reserve02] = handleMsgSysReserve02
|
||||
handlerTable[network.MSG_SYS_reserve03] = handleMsgSysReserve03
|
||||
handlerTable[network.MSG_SYS_reserve04] = handleMsgSysReserve04
|
||||
handlerTable[network.MSG_SYS_reserve05] = handleMsgSysReserve05
|
||||
handlerTable[network.MSG_SYS_reserve06] = handleMsgSysReserve06
|
||||
handlerTable[network.MSG_SYS_reserve07] = handleMsgSysReserve07
|
||||
handlerTable[network.MSG_SYS_ADD_OBJECT] = handleMsgSysAddObject
|
||||
handlerTable[network.MSG_SYS_DEL_OBJECT] = handleMsgSysDelObject
|
||||
handlerTable[network.MSG_SYS_DISP_OBJECT] = handleMsgSysDispObject
|
||||
handlerTable[network.MSG_SYS_HIDE_OBJECT] = handleMsgSysHideObject
|
||||
handlerTable[network.MSG_SYS_reserve0C] = handleMsgSysReserve0C
|
||||
handlerTable[network.MSG_SYS_reserve0D] = handleMsgSysReserve0D
|
||||
handlerTable[network.MSG_SYS_reserve0E] = handleMsgSysReserve0E
|
||||
handlerTable[network.MSG_SYS_EXTEND_THRESHOLD] = handleMsgSysExtendThreshold
|
||||
handlerTable[network.MSG_SYS_END] = handleMsgSysEnd
|
||||
handlerTable[network.MSG_SYS_NOP] = handleMsgSysNop
|
||||
handlerTable[network.MSG_SYS_ACK] = handleMsgSysAck
|
||||
handlerTable[network.MSG_SYS_TERMINAL_LOG] = handleMsgSysTerminalLog
|
||||
handlerTable[network.MSG_SYS_LOGIN] = handleMsgSysLogin
|
||||
handlerTable[network.MSG_SYS_LOGOUT] = handleMsgSysLogout
|
||||
handlerTable[network.MSG_SYS_SET_STATUS] = handleMsgSysSetStatus
|
||||
handlerTable[network.MSG_SYS_PING] = handleMsgSysPing
|
||||
handlerTable[network.MSG_SYS_CAST_BINARY] = handleMsgSysCastBinary
|
||||
handlerTable[network.MSG_SYS_HIDE_CLIENT] = handleMsgSysHideClient
|
||||
handlerTable[network.MSG_SYS_TIME] = handleMsgSysTime
|
||||
handlerTable[network.MSG_SYS_CASTED_BINARY] = handleMsgSysCastedBinary
|
||||
handlerTable[network.MSG_SYS_GET_FILE] = handleMsgSysGetFile
|
||||
handlerTable[network.MSG_SYS_ISSUE_LOGKEY] = handleMsgSysIssueLogkey
|
||||
handlerTable[network.MSG_SYS_RECORD_LOG] = handleMsgSysRecordLog
|
||||
handlerTable[network.MSG_SYS_ECHO] = handleMsgSysEcho
|
||||
handlerTable[network.MSG_SYS_CREATE_STAGE] = handleMsgSysCreateStage
|
||||
handlerTable[network.MSG_SYS_STAGE_DESTRUCT] = handleMsgSysStageDestruct
|
||||
handlerTable[network.MSG_SYS_ENTER_STAGE] = handleMsgSysEnterStage
|
||||
handlerTable[network.MSG_SYS_BACK_STAGE] = handleMsgSysBackStage
|
||||
handlerTable[network.MSG_SYS_MOVE_STAGE] = handleMsgSysMoveStage
|
||||
handlerTable[network.MSG_SYS_LEAVE_STAGE] = handleMsgSysLeaveStage
|
||||
handlerTable[network.MSG_SYS_LOCK_STAGE] = handleMsgSysLockStage
|
||||
handlerTable[network.MSG_SYS_UNLOCK_STAGE] = handleMsgSysUnlockStage
|
||||
handlerTable[network.MSG_SYS_RESERVE_STAGE] = handleMsgSysReserveStage
|
||||
handlerTable[network.MSG_SYS_UNRESERVE_STAGE] = handleMsgSysUnreserveStage
|
||||
handlerTable[network.MSG_SYS_SET_STAGE_PASS] = handleMsgSysSetStagePass
|
||||
handlerTable[network.MSG_SYS_WAIT_STAGE_BINARY] = handleMsgSysWaitStageBinary
|
||||
handlerTable[network.MSG_SYS_SET_STAGE_BINARY] = handleMsgSysSetStageBinary
|
||||
handlerTable[network.MSG_SYS_GET_STAGE_BINARY] = handleMsgSysGetStageBinary
|
||||
handlerTable[network.MSG_SYS_ENUMERATE_CLIENT] = handleMsgSysEnumerateClient
|
||||
handlerTable[network.MSG_SYS_ENUMERATE_STAGE] = handleMsgSysEnumerateStage
|
||||
handlerTable[network.MSG_SYS_CREATE_MUTEX] = handleMsgSysCreateMutex
|
||||
handlerTable[network.MSG_SYS_CREATE_OPEN_MUTEX] = handleMsgSysCreateOpenMutex
|
||||
handlerTable[network.MSG_SYS_DELETE_MUTEX] = handleMsgSysDeleteMutex
|
||||
handlerTable[network.MSG_SYS_OPEN_MUTEX] = handleMsgSysOpenMutex
|
||||
handlerTable[network.MSG_SYS_CLOSE_MUTEX] = handleMsgSysCloseMutex
|
||||
handlerTable[network.MSG_SYS_CREATE_SEMAPHORE] = handleMsgSysCreateSemaphore
|
||||
handlerTable[network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE] = handleMsgSysCreateAcquireSemaphore
|
||||
handlerTable[network.MSG_SYS_DELETE_SEMAPHORE] = handleMsgSysDeleteSemaphore
|
||||
handlerTable[network.MSG_SYS_ACQUIRE_SEMAPHORE] = handleMsgSysAcquireSemaphore
|
||||
handlerTable[network.MSG_SYS_RELEASE_SEMAPHORE] = handleMsgSysReleaseSemaphore
|
||||
handlerTable[network.MSG_SYS_LOCK_GLOBAL_SEMA] = handleMsgSysLockGlobalSema
|
||||
handlerTable[network.MSG_SYS_UNLOCK_GLOBAL_SEMA] = handleMsgSysUnlockGlobalSema
|
||||
handlerTable[network.MSG_SYS_CHECK_SEMAPHORE] = handleMsgSysCheckSemaphore
|
||||
handlerTable[network.MSG_SYS_OPERATE_REGISTER] = handleMsgSysOperateRegister
|
||||
handlerTable[network.MSG_SYS_LOAD_REGISTER] = handleMsgSysLoadRegister
|
||||
handlerTable[network.MSG_SYS_NOTIFY_REGISTER] = handleMsgSysNotifyRegister
|
||||
handlerTable[network.MSG_SYS_CREATE_OBJECT] = handleMsgSysCreateObject
|
||||
handlerTable[network.MSG_SYS_DELETE_OBJECT] = handleMsgSysDeleteObject
|
||||
handlerTable[network.MSG_SYS_POSITION_OBJECT] = handleMsgSysPositionObject
|
||||
handlerTable[network.MSG_SYS_ROTATE_OBJECT] = handleMsgSysRotateObject
|
||||
handlerTable[network.MSG_SYS_DUPLICATE_OBJECT] = handleMsgSysDuplicateObject
|
||||
handlerTable[network.MSG_SYS_SET_OBJECT_BINARY] = handleMsgSysSetObjectBinary
|
||||
handlerTable[network.MSG_SYS_GET_OBJECT_BINARY] = handleMsgSysGetObjectBinary
|
||||
handlerTable[network.MSG_SYS_GET_OBJECT_OWNER] = handleMsgSysGetObjectOwner
|
||||
handlerTable[network.MSG_SYS_UPDATE_OBJECT_BINARY] = handleMsgSysUpdateObjectBinary
|
||||
handlerTable[network.MSG_SYS_CLEANUP_OBJECT] = handleMsgSysCleanupObject
|
||||
handlerTable[network.MSG_SYS_reserve4A] = handleMsgSysReserve4A
|
||||
handlerTable[network.MSG_SYS_reserve4B] = handleMsgSysReserve4B
|
||||
handlerTable[network.MSG_SYS_reserve4C] = handleMsgSysReserve4C
|
||||
handlerTable[network.MSG_SYS_reserve4D] = handleMsgSysReserve4D
|
||||
handlerTable[network.MSG_SYS_reserve4E] = handleMsgSysReserve4E
|
||||
handlerTable[network.MSG_SYS_reserve4F] = handleMsgSysReserve4F
|
||||
handlerTable[network.MSG_SYS_INSERT_USER] = handleMsgSysInsertUser
|
||||
handlerTable[network.MSG_SYS_DELETE_USER] = handleMsgSysDeleteUser
|
||||
handlerTable[network.MSG_SYS_SET_USER_BINARY] = handleMsgSysSetUserBinary
|
||||
handlerTable[network.MSG_SYS_GET_USER_BINARY] = handleMsgSysGetUserBinary
|
||||
handlerTable[network.MSG_SYS_NOTIFY_USER_BINARY] = handleMsgSysNotifyUserBinary
|
||||
handlerTable[network.MSG_SYS_reserve55] = handleMsgSysReserve55
|
||||
handlerTable[network.MSG_SYS_reserve56] = handleMsgSysReserve56
|
||||
handlerTable[network.MSG_SYS_reserve57] = handleMsgSysReserve57
|
||||
handlerTable[network.MSG_SYS_UPDATE_RIGHT] = handleMsgSysUpdateRight
|
||||
handlerTable[network.MSG_SYS_AUTH_QUERY] = handleMsgSysAuthQuery
|
||||
handlerTable[network.MSG_SYS_AUTH_DATA] = handleMsgSysAuthData
|
||||
handlerTable[network.MSG_SYS_AUTH_TERMINAL] = handleMsgSysAuthTerminal
|
||||
handlerTable[network.MSG_SYS_reserve5C] = handleMsgSysReserve5C
|
||||
handlerTable[network.MSG_SYS_RIGHTS_RELOAD] = handleMsgSysRightsReload
|
||||
handlerTable[network.MSG_SYS_reserve5E] = handleMsgSysReserve5E
|
||||
handlerTable[network.MSG_SYS_reserve5F] = handleMsgSysReserve5F
|
||||
handlerTable[network.MSG_MHF_SAVEDATA] = handleMsgMhfSavedata
|
||||
handlerTable[network.MSG_MHF_LOADDATA] = handleMsgMhfLoaddata
|
||||
handlerTable[network.MSG_MHF_LIST_MEMBER] = handleMsgMhfListMember
|
||||
handlerTable[network.MSG_MHF_OPR_MEMBER] = handleMsgMhfOprMember
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_DIST_ITEM] = handleMsgMhfEnumerateDistItem
|
||||
handlerTable[network.MSG_MHF_APPLY_DIST_ITEM] = handleMsgMhfApplyDistItem
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_DIST_ITEM] = handleMsgMhfAcquireDistItem
|
||||
handlerTable[network.MSG_MHF_GET_DIST_DESCRIPTION] = handleMsgMhfGetDistDescription
|
||||
handlerTable[network.MSG_MHF_SEND_MAIL] = handleMsgMhfSendMail
|
||||
handlerTable[network.MSG_MHF_READ_MAIL] = handleMsgMhfReadMail
|
||||
handlerTable[network.MSG_MHF_LIST_MAIL] = handleMsgMhfListMail
|
||||
handlerTable[network.MSG_MHF_OPRT_MAIL] = handleMsgMhfOprtMail
|
||||
handlerTable[network.MSG_MHF_LOAD_FAVORITE_QUEST] = handleMsgMhfLoadFavoriteQuest
|
||||
handlerTable[network.MSG_MHF_SAVE_FAVORITE_QUEST] = handleMsgMhfSaveFavoriteQuest
|
||||
handlerTable[network.MSG_MHF_REGISTER_EVENT] = handleMsgMhfRegisterEvent
|
||||
handlerTable[network.MSG_MHF_RELEASE_EVENT] = handleMsgMhfReleaseEvent
|
||||
handlerTable[network.MSG_MHF_TRANSIT_MESSAGE] = handleMsgMhfTransitMessage
|
||||
handlerTable[network.MSG_SYS_reserve71] = handleMsgSysReserve71
|
||||
handlerTable[network.MSG_SYS_reserve72] = handleMsgSysReserve72
|
||||
handlerTable[network.MSG_SYS_reserve73] = handleMsgSysReserve73
|
||||
handlerTable[network.MSG_SYS_reserve74] = handleMsgSysReserve74
|
||||
handlerTable[network.MSG_SYS_reserve75] = handleMsgSysReserve75
|
||||
handlerTable[network.MSG_SYS_reserve76] = handleMsgSysReserve76
|
||||
handlerTable[network.MSG_SYS_reserve77] = handleMsgSysReserve77
|
||||
handlerTable[network.MSG_SYS_reserve78] = handleMsgSysReserve78
|
||||
handlerTable[network.MSG_SYS_reserve79] = handleMsgSysReserve79
|
||||
handlerTable[network.MSG_SYS_reserve7A] = handleMsgSysReserve7A
|
||||
handlerTable[network.MSG_SYS_reserve7B] = handleMsgSysReserve7B
|
||||
handlerTable[network.MSG_SYS_reserve7C] = handleMsgSysReserve7C
|
||||
handlerTable[network.MSG_CA_EXCHANGE_ITEM] = handleMsgCaExchangeItem
|
||||
handlerTable[network.MSG_SYS_reserve7E] = handleMsgSysReserve7E
|
||||
handlerTable[network.MSG_MHF_PRESENT_BOX] = handleMsgMhfPresentBox
|
||||
handlerTable[network.MSG_MHF_SERVER_COMMAND] = handleMsgMhfServerCommand
|
||||
handlerTable[network.MSG_MHF_SHUT_CLIENT] = handleMsgMhfShutClient
|
||||
handlerTable[network.MSG_MHF_ANNOUNCE] = handleMsgMhfAnnounce
|
||||
handlerTable[network.MSG_MHF_SET_LOGINWINDOW] = handleMsgMhfSetLoginwindow
|
||||
handlerTable[network.MSG_SYS_TRANS_BINARY] = handleMsgSysTransBinary
|
||||
handlerTable[network.MSG_SYS_COLLECT_BINARY] = handleMsgSysCollectBinary
|
||||
handlerTable[network.MSG_SYS_GET_STATE] = handleMsgSysGetState
|
||||
handlerTable[network.MSG_SYS_SERIALIZE] = handleMsgSysSerialize
|
||||
handlerTable[network.MSG_SYS_ENUMLOBBY] = handleMsgSysEnumlobby
|
||||
handlerTable[network.MSG_SYS_ENUMUSER] = handleMsgSysEnumuser
|
||||
handlerTable[network.MSG_SYS_INFOKYSERVER] = handleMsgSysInfokyserver
|
||||
handlerTable[network.MSG_MHF_GET_CA_UNIQUE_ID] = handleMsgMhfGetCaUniqueID
|
||||
handlerTable[network.MSG_MHF_SET_CA_ACHIEVEMENT] = handleMsgMhfSetCaAchievement
|
||||
handlerTable[network.MSG_MHF_CARAVAN_MY_SCORE] = handleMsgMhfCaravanMyScore
|
||||
handlerTable[network.MSG_MHF_CARAVAN_RANKING] = handleMsgMhfCaravanRanking
|
||||
handlerTable[network.MSG_MHF_CARAVAN_MY_RANK] = handleMsgMhfCaravanMyRank
|
||||
handlerTable[network.MSG_MHF_CREATE_GUILD] = handleMsgMhfCreateGuild
|
||||
handlerTable[network.MSG_MHF_OPERATE_GUILD] = handleMsgMhfOperateGuild
|
||||
handlerTable[network.MSG_MHF_OPERATE_GUILD_MEMBER] = handleMsgMhfOperateGuildMember
|
||||
handlerTable[network.MSG_MHF_INFO_GUILD] = handleMsgMhfInfoGuild
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_GUILD] = handleMsgMhfEnumerateGuild
|
||||
handlerTable[network.MSG_MHF_UPDATE_GUILD] = handleMsgMhfUpdateGuild
|
||||
handlerTable[network.MSG_MHF_ARRANGE_GUILD_MEMBER] = handleMsgMhfArrangeGuildMember
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_MEMBER] = handleMsgMhfEnumerateGuildMember
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_CAMPAIGN] = handleMsgMhfEnumerateCampaign
|
||||
handlerTable[network.MSG_MHF_STATE_CAMPAIGN] = handleMsgMhfStateCampaign
|
||||
handlerTable[network.MSG_MHF_APPLY_CAMPAIGN] = handleMsgMhfApplyCampaign
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_ITEM] = handleMsgMhfEnumerateItem
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_ITEM] = handleMsgMhfAcquireItem
|
||||
handlerTable[network.MSG_MHF_TRANSFER_ITEM] = handleMsgMhfTransferItem
|
||||
handlerTable[network.MSG_MHF_MERCENARY_HUNTDATA] = handleMsgMhfMercenaryHuntdata
|
||||
handlerTable[network.MSG_MHF_ENTRY_ROOKIE_GUILD] = handleMsgMhfEntryRookieGuild
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_QUEST] = handleMsgMhfEnumerateQuest
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_EVENT] = handleMsgMhfEnumerateEvent
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_PRICE] = handleMsgMhfEnumeratePrice
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_RANKING] = handleMsgMhfEnumerateRanking
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_ORDER] = handleMsgMhfEnumerateOrder
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_SHOP] = handleMsgMhfEnumerateShop
|
||||
handlerTable[network.MSG_MHF_GET_EXTRA_INFO] = handleMsgMhfGetExtraInfo
|
||||
handlerTable[network.MSG_MHF_UPDATE_INTERIOR] = handleMsgMhfUpdateInterior
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_HOUSE] = handleMsgMhfEnumerateHouse
|
||||
handlerTable[network.MSG_MHF_UPDATE_HOUSE] = handleMsgMhfUpdateHouse
|
||||
handlerTable[network.MSG_MHF_LOAD_HOUSE] = handleMsgMhfLoadHouse
|
||||
handlerTable[network.MSG_MHF_OPERATE_WAREHOUSE] = handleMsgMhfOperateWarehouse
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_WAREHOUSE] = handleMsgMhfEnumerateWarehouse
|
||||
handlerTable[network.MSG_MHF_UPDATE_WAREHOUSE] = handleMsgMhfUpdateWarehouse
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_TITLE] = handleMsgMhfAcquireTitle
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_TITLE] = handleMsgMhfEnumerateTitle
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_ITEM] = handleMsgMhfEnumerateGuildItem
|
||||
handlerTable[network.MSG_MHF_UPDATE_GUILD_ITEM] = handleMsgMhfUpdateGuildItem
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_UNION_ITEM] = handleMsgMhfEnumerateUnionItem
|
||||
handlerTable[network.MSG_MHF_UPDATE_UNION_ITEM] = handleMsgMhfUpdateUnionItem
|
||||
handlerTable[network.MSG_MHF_CREATE_JOINT] = handleMsgMhfCreateJoint
|
||||
handlerTable[network.MSG_MHF_OPERATE_JOINT] = handleMsgMhfOperateJoint
|
||||
handlerTable[network.MSG_MHF_INFO_JOINT] = handleMsgMhfInfoJoint
|
||||
handlerTable[network.MSG_MHF_UPDATE_GUILD_ICON] = handleMsgMhfUpdateGuildIcon
|
||||
handlerTable[network.MSG_MHF_INFO_FESTA] = handleMsgMhfInfoFesta
|
||||
handlerTable[network.MSG_MHF_ENTRY_FESTA] = handleMsgMhfEntryFesta
|
||||
handlerTable[network.MSG_MHF_CHARGE_FESTA] = handleMsgMhfChargeFesta
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_FESTA] = handleMsgMhfAcquireFesta
|
||||
handlerTable[network.MSG_MHF_STATE_FESTA_U] = handleMsgMhfStateFestaU
|
||||
handlerTable[network.MSG_MHF_STATE_FESTA_G] = handleMsgMhfStateFestaG
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_MEMBER] = handleMsgMhfEnumerateFestaMember
|
||||
handlerTable[network.MSG_MHF_VOTE_FESTA] = handleMsgMhfVoteFesta
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_CAFE_ITEM] = handleMsgMhfAcquireCafeItem
|
||||
handlerTable[network.MSG_MHF_UPDATE_CAFEPOINT] = handleMsgMhfUpdateCafepoint
|
||||
handlerTable[network.MSG_MHF_CHECK_DAILY_CAFEPOINT] = handleMsgMhfCheckDailyCafepoint
|
||||
handlerTable[network.MSG_MHF_GET_COG_INFO] = handleMsgMhfGetCogInfo
|
||||
handlerTable[network.MSG_MHF_CHECK_MONTHLY_ITEM] = handleMsgMhfCheckMonthlyItem
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_MONTHLY_ITEM] = handleMsgMhfAcquireMonthlyItem
|
||||
handlerTable[network.MSG_MHF_CHECK_WEEKLY_STAMP] = handleMsgMhfCheckWeeklyStamp
|
||||
handlerTable[network.MSG_MHF_EXCHANGE_WEEKLY_STAMP] = handleMsgMhfExchangeWeeklyStamp
|
||||
handlerTable[network.MSG_MHF_CREATE_MERCENARY] = handleMsgMhfCreateMercenary
|
||||
handlerTable[network.MSG_MHF_SAVE_MERCENARY] = handleMsgMhfSaveMercenary
|
||||
handlerTable[network.MSG_MHF_READ_MERCENARY_W] = handleMsgMhfReadMercenaryW
|
||||
handlerTable[network.MSG_MHF_READ_MERCENARY_M] = handleMsgMhfReadMercenaryM
|
||||
handlerTable[network.MSG_MHF_CONTRACT_MERCENARY] = handleMsgMhfContractMercenary
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_MERCENARY_LOG] = handleMsgMhfEnumerateMercenaryLog
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_GUACOT] = handleMsgMhfEnumerateGuacot
|
||||
handlerTable[network.MSG_MHF_UPDATE_GUACOT] = handleMsgMhfUpdateGuacot
|
||||
handlerTable[network.MSG_MHF_INFO_TOURNAMENT] = handleMsgMhfInfoTournament
|
||||
handlerTable[network.MSG_MHF_ENTRY_TOURNAMENT] = handleMsgMhfEntryTournament
|
||||
handlerTable[network.MSG_MHF_ENTER_TOURNAMENT_QUEST] = handleMsgMhfEnterTournamentQuest
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_TOURNAMENT] = handleMsgMhfAcquireTournament
|
||||
handlerTable[network.MSG_MHF_GET_ACHIEVEMENT] = handleMsgMhfGetAchievement
|
||||
handlerTable[network.MSG_MHF_RESET_ACHIEVEMENT] = handleMsgMhfResetAchievement
|
||||
handlerTable[network.MSG_MHF_ADD_ACHIEVEMENT] = handleMsgMhfAddAchievement
|
||||
handlerTable[network.MSG_MHF_PAYMENT_ACHIEVEMENT] = handleMsgMhfPaymentAchievement
|
||||
handlerTable[network.MSG_MHF_DISPLAYED_ACHIEVEMENT] = handleMsgMhfDisplayedAchievement
|
||||
handlerTable[network.MSG_MHF_INFO_SCENARIO_COUNTER] = handleMsgMhfInfoScenarioCounter
|
||||
handlerTable[network.MSG_MHF_SAVE_SCENARIO_DATA] = handleMsgMhfSaveScenarioData
|
||||
handlerTable[network.MSG_MHF_LOAD_SCENARIO_DATA] = handleMsgMhfLoadScenarioData
|
||||
handlerTable[network.MSG_MHF_GET_BBS_SNS_STATUS] = handleMsgMhfGetBbsSnsStatus
|
||||
handlerTable[network.MSG_MHF_APPLY_BBS_ARTICLE] = handleMsgMhfApplyBbsArticle
|
||||
handlerTable[network.MSG_MHF_GET_ETC_POINTS] = handleMsgMhfGetEtcPoints
|
||||
handlerTable[network.MSG_MHF_UPDATE_ETC_POINT] = handleMsgMhfUpdateEtcPoint
|
||||
handlerTable[network.MSG_MHF_GET_MYHOUSE_INFO] = handleMsgMhfGetMyhouseInfo
|
||||
handlerTable[network.MSG_MHF_UPDATE_MYHOUSE_INFO] = handleMsgMhfUpdateMyhouseInfo
|
||||
handlerTable[network.MSG_MHF_GET_WEEKLY_SCHEDULE] = handleMsgMhfGetWeeklySchedule
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_INV_GUILD] = handleMsgMhfEnumerateInvGuild
|
||||
handlerTable[network.MSG_MHF_OPERATION_INV_GUILD] = handleMsgMhfOperationInvGuild
|
||||
handlerTable[network.MSG_MHF_STAMPCARD_STAMP] = handleMsgMhfStampcardStamp
|
||||
handlerTable[network.MSG_MHF_STAMPCARD_PRIZE] = handleMsgMhfStampcardPrize
|
||||
handlerTable[network.MSG_MHF_UNRESERVE_SRG] = handleMsgMhfUnreserveSrg
|
||||
handlerTable[network.MSG_MHF_LOAD_PLATE_DATA] = handleMsgMhfLoadPlateData
|
||||
handlerTable[network.MSG_MHF_SAVE_PLATE_DATA] = handleMsgMhfSavePlateData
|
||||
handlerTable[network.MSG_MHF_LOAD_PLATE_BOX] = handleMsgMhfLoadPlateBox
|
||||
handlerTable[network.MSG_MHF_SAVE_PLATE_BOX] = handleMsgMhfSavePlateBox
|
||||
handlerTable[network.MSG_MHF_READ_GUILDCARD] = handleMsgMhfReadGuildcard
|
||||
handlerTable[network.MSG_MHF_UPDATE_GUILDCARD] = handleMsgMhfUpdateGuildcard
|
||||
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL] = handleMsgMhfReadBeatLevel
|
||||
handlerTable[network.MSG_MHF_UPDATE_BEAT_LEVEL] = handleMsgMhfUpdateBeatLevel
|
||||
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL_ALL_RANKING] = handleMsgMhfReadBeatLevelAllRanking
|
||||
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL_MY_RANKING] = handleMsgMhfReadBeatLevelMyRanking
|
||||
handlerTable[network.MSG_MHF_READ_LAST_WEEK_BEAT_RANKING] = handleMsgMhfReadLastWeekBeatRanking
|
||||
handlerTable[network.MSG_MHF_ACCEPT_READ_REWARD] = handleMsgMhfAcceptReadReward
|
||||
handlerTable[network.MSG_MHF_GET_ADDITIONAL_BEAT_REWARD] = handleMsgMhfGetAdditionalBeatReward
|
||||
handlerTable[network.MSG_MHF_GET_FIXED_SEIBATU_RANKING_TABLE] = handleMsgMhfGetFixedSeibatuRankingTable
|
||||
handlerTable[network.MSG_MHF_GET_BBS_USER_STATUS] = handleMsgMhfGetBbsUserStatus
|
||||
handlerTable[network.MSG_MHF_KICK_EXPORT_FORCE] = handleMsgMhfKickExportForce
|
||||
handlerTable[network.MSG_MHF_GET_BREAK_SEIBATU_LEVEL_REWARD] = handleMsgMhfGetBreakSeibatuLevelReward
|
||||
handlerTable[network.MSG_MHF_GET_WEEKLY_SEIBATU_RANKING_REWARD] = handleMsgMhfGetWeeklySeibatuRankingReward
|
||||
handlerTable[network.MSG_MHF_GET_EARTH_STATUS] = handleMsgMhfGetEarthStatus
|
||||
handlerTable[network.MSG_MHF_LOAD_PARTNER] = handleMsgMhfLoadPartner
|
||||
handlerTable[network.MSG_MHF_SAVE_PARTNER] = handleMsgMhfSavePartner
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_MISSION_LIST] = handleMsgMhfGetGuildMissionList
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_MISSION_RECORD] = handleMsgMhfGetGuildMissionRecord
|
||||
handlerTable[network.MSG_MHF_ADD_GUILD_MISSION_COUNT] = handleMsgMhfAddGuildMissionCount
|
||||
handlerTable[network.MSG_MHF_SET_GUILD_MISSION_TARGET] = handleMsgMhfSetGuildMissionTarget
|
||||
handlerTable[network.MSG_MHF_CANCEL_GUILD_MISSION_TARGET] = handleMsgMhfCancelGuildMissionTarget
|
||||
handlerTable[network.MSG_MHF_LOAD_OTOMO_AIROU] = handleMsgMhfLoadOtomoAirou
|
||||
handlerTable[network.MSG_MHF_SAVE_OTOMO_AIROU] = handleMsgMhfSaveOtomoAirou
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_TRESURE] = handleMsgMhfEnumerateGuildTresure
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_AIROULIST] = handleMsgMhfEnumerateAiroulist
|
||||
handlerTable[network.MSG_MHF_REGIST_GUILD_TRESURE] = handleMsgMhfRegistGuildTresure
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_TRESURE] = handleMsgMhfAcquireGuildTresure
|
||||
handlerTable[network.MSG_MHF_OPERATE_GUILD_TRESURE_REPORT] = handleMsgMhfOperateGuildTresureReport
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_TRESURE_SOUVENIR] = handleMsgMhfGetGuildTresureSouvenir
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_TRESURE_SOUVENIR] = handleMsgMhfAcquireGuildTresureSouvenir
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_INTERMEDIATE_PRIZE] = handleMsgMhfEnumerateFestaIntermediatePrize
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_FESTA_INTERMEDIATE_PRIZE] = handleMsgMhfAcquireFestaIntermediatePrize
|
||||
handlerTable[network.MSG_MHF_LOAD_DECO_MYSET] = handleMsgMhfLoadDecoMyset
|
||||
handlerTable[network.MSG_MHF_SAVE_DECO_MYSET] = handleMsgMhfSaveDecoMyset
|
||||
handlerTable[network.MSG_MHF_reserve10F] = handleMsgMhfReserve10F
|
||||
handlerTable[network.MSG_MHF_LOAD_GUILD_COOKING] = handleMsgMhfLoadGuildCooking
|
||||
handlerTable[network.MSG_MHF_REGIST_GUILD_COOKING] = handleMsgMhfRegistGuildCooking
|
||||
handlerTable[network.MSG_MHF_LOAD_GUILD_ADVENTURE] = handleMsgMhfLoadGuildAdventure
|
||||
handlerTable[network.MSG_MHF_REGIST_GUILD_ADVENTURE] = handleMsgMhfRegistGuildAdventure
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_ADVENTURE] = handleMsgMhfAcquireGuildAdventure
|
||||
handlerTable[network.MSG_MHF_CHARGE_GUILD_ADVENTURE] = handleMsgMhfChargeGuildAdventure
|
||||
handlerTable[network.MSG_MHF_LOAD_LEGEND_DISPATCH] = handleMsgMhfLoadLegendDispatch
|
||||
handlerTable[network.MSG_MHF_LOAD_HUNTER_NAVI] = handleMsgMhfLoadHunterNavi
|
||||
handlerTable[network.MSG_MHF_SAVE_HUNTER_NAVI] = handleMsgMhfSaveHunterNavi
|
||||
handlerTable[network.MSG_MHF_REGIST_SPABI_TIME] = handleMsgMhfRegistSpabiTime
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_WEEKLY_BONUS_MASTER] = handleMsgMhfGetGuildWeeklyBonusMaster
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_WEEKLY_BONUS_ACTIVE_COUNT] = handleMsgMhfGetGuildWeeklyBonusActiveCount
|
||||
handlerTable[network.MSG_MHF_ADD_GUILD_WEEKLY_BONUS_EXCEPTIONAL_USER] = handleMsgMhfAddGuildWeeklyBonusExceptionalUser
|
||||
handlerTable[network.MSG_MHF_GET_TOWER_INFO] = handleMsgMhfGetTowerInfo
|
||||
handlerTable[network.MSG_MHF_POST_TOWER_INFO] = handleMsgMhfPostTowerInfo
|
||||
handlerTable[network.MSG_MHF_GET_GEM_INFO] = handleMsgMhfGetGemInfo
|
||||
handlerTable[network.MSG_MHF_POST_GEM_INFO] = handleMsgMhfPostGemInfo
|
||||
handlerTable[network.MSG_MHF_GET_EARTH_VALUE] = handleMsgMhfGetEarthValue
|
||||
handlerTable[network.MSG_MHF_DEBUG_POST_VALUE] = handleMsgMhfDebugPostValue
|
||||
handlerTable[network.MSG_MHF_GET_PAPER_DATA] = handleMsgMhfGetPaperData
|
||||
handlerTable[network.MSG_MHF_GET_NOTICE] = handleMsgMhfGetNotice
|
||||
handlerTable[network.MSG_MHF_POST_NOTICE] = handleMsgMhfPostNotice
|
||||
handlerTable[network.MSG_MHF_GET_BOOST_TIME] = handleMsgMhfGetBoostTime
|
||||
handlerTable[network.MSG_MHF_POST_BOOST_TIME] = handleMsgMhfPostBoostTime
|
||||
handlerTable[network.MSG_MHF_GET_BOOST_TIME_LIMIT] = handleMsgMhfGetBoostTimeLimit
|
||||
handlerTable[network.MSG_MHF_POST_BOOST_TIME_LIMIT] = handleMsgMhfPostBoostTimeLimit
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_PERSONAL_PRIZE] = handleMsgMhfEnumerateFestaPersonalPrize
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_FESTA_PERSONAL_PRIZE] = handleMsgMhfAcquireFestaPersonalPrize
|
||||
handlerTable[network.MSG_MHF_GET_RAND_FROM_TABLE] = handleMsgMhfGetRandFromTable
|
||||
handlerTable[network.MSG_MHF_GET_CAFE_DURATION] = handleMsgMhfGetCafeDuration
|
||||
handlerTable[network.MSG_MHF_GET_CAFE_DURATION_BONUS_INFO] = handleMsgMhfGetCafeDurationBonusInfo
|
||||
handlerTable[network.MSG_MHF_RECEIVE_CAFE_DURATION_BONUS] = handleMsgMhfReceiveCafeDurationBonus
|
||||
handlerTable[network.MSG_MHF_POST_CAFE_DURATION_BONUS_RECEIVED] = handleMsgMhfPostCafeDurationBonusReceived
|
||||
handlerTable[network.MSG_MHF_GET_GACHA_POINT] = handleMsgMhfGetGachaPoint
|
||||
handlerTable[network.MSG_MHF_USE_GACHA_POINT] = handleMsgMhfUseGachaPoint
|
||||
handlerTable[network.MSG_MHF_EXCHANGE_FPOINT_2_ITEM] = handleMsgMhfExchangeFpoint2Item
|
||||
handlerTable[network.MSG_MHF_EXCHANGE_ITEM_2_FPOINT] = handleMsgMhfExchangeItem2Fpoint
|
||||
handlerTable[network.MSG_MHF_GET_FPOINT_EXCHANGE_LIST] = handleMsgMhfGetFpointExchangeList
|
||||
handlerTable[network.MSG_MHF_PLAY_STEPUP_GACHA] = handleMsgMhfPlayStepupGacha
|
||||
handlerTable[network.MSG_MHF_RECEIVE_GACHA_ITEM] = handleMsgMhfReceiveGachaItem
|
||||
handlerTable[network.MSG_MHF_GET_STEPUP_STATUS] = handleMsgMhfGetStepupStatus
|
||||
handlerTable[network.MSG_MHF_PLAY_FREE_GACHA] = handleMsgMhfPlayFreeGacha
|
||||
handlerTable[network.MSG_MHF_GET_TINY_BIN] = handleMsgMhfGetTinyBin
|
||||
handlerTable[network.MSG_MHF_POST_TINY_BIN] = handleMsgMhfPostTinyBin
|
||||
handlerTable[network.MSG_MHF_GET_SENYU_DAILY_COUNT] = handleMsgMhfGetSenyuDailyCount
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_TARGET_MEMBER_NUM] = handleMsgMhfGetGuildTargetMemberNum
|
||||
handlerTable[network.MSG_MHF_GET_BOOST_RIGHT] = handleMsgMhfGetBoostRight
|
||||
handlerTable[network.MSG_MHF_START_BOOST_TIME] = handleMsgMhfStartBoostTime
|
||||
handlerTable[network.MSG_MHF_POST_BOOST_TIME_QUEST_RETURN] = handleMsgMhfPostBoostTimeQuestReturn
|
||||
handlerTable[network.MSG_MHF_GET_BOX_GACHA_INFO] = handleMsgMhfGetBoxGachaInfo
|
||||
handlerTable[network.MSG_MHF_PLAY_BOX_GACHA] = handleMsgMhfPlayBoxGacha
|
||||
handlerTable[network.MSG_MHF_RESET_BOX_GACHA_INFO] = handleMsgMhfResetBoxGachaInfo
|
||||
handlerTable[network.MSG_MHF_GET_SEIBATTLE] = handleMsgMhfGetSeibattle
|
||||
handlerTable[network.MSG_MHF_POST_SEIBATTLE] = handleMsgMhfPostSeibattle
|
||||
handlerTable[network.MSG_MHF_GET_RYOUDAMA] = handleMsgMhfGetRyoudama
|
||||
handlerTable[network.MSG_MHF_POST_RYOUDAMA] = handleMsgMhfPostRyoudama
|
||||
handlerTable[network.MSG_MHF_GET_TENROUIRAI] = handleMsgMhfGetTenrouirai
|
||||
handlerTable[network.MSG_MHF_POST_TENROUIRAI] = handleMsgMhfPostTenrouirai
|
||||
handlerTable[network.MSG_MHF_POST_GUILD_SCOUT] = handleMsgMhfPostGuildScout
|
||||
handlerTable[network.MSG_MHF_CANCEL_GUILD_SCOUT] = handleMsgMhfCancelGuildScout
|
||||
handlerTable[network.MSG_MHF_ANSWER_GUILD_SCOUT] = handleMsgMhfAnswerGuildScout
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_SCOUT_LIST] = handleMsgMhfGetGuildScoutList
|
||||
handlerTable[network.MSG_MHF_GET_GUILD_MANAGE_RIGHT] = handleMsgMhfGetGuildManageRight
|
||||
handlerTable[network.MSG_MHF_SET_GUILD_MANAGE_RIGHT] = handleMsgMhfSetGuildManageRight
|
||||
handlerTable[network.MSG_MHF_PLAY_NORMAL_GACHA] = handleMsgMhfPlayNormalGacha
|
||||
handlerTable[network.MSG_MHF_GET_DAILY_MISSION_MASTER] = handleMsgMhfGetDailyMissionMaster
|
||||
handlerTable[network.MSG_MHF_GET_DAILY_MISSION_PERSONAL] = handleMsgMhfGetDailyMissionPersonal
|
||||
handlerTable[network.MSG_MHF_SET_DAILY_MISSION_PERSONAL] = handleMsgMhfSetDailyMissionPersonal
|
||||
handlerTable[network.MSG_MHF_GET_GACHA_PLAY_HISTORY] = handleMsgMhfGetGachaPlayHistory
|
||||
handlerTable[network.MSG_MHF_GET_REJECT_GUILD_SCOUT] = handleMsgMhfGetRejectGuildScout
|
||||
handlerTable[network.MSG_MHF_SET_REJECT_GUILD_SCOUT] = handleMsgMhfSetRejectGuildScout
|
||||
handlerTable[network.MSG_MHF_GET_CA_ACHIEVEMENT_HIST] = handleMsgMhfGetCaAchievementHist
|
||||
handlerTable[network.MSG_MHF_SET_CA_ACHIEVEMENT_HIST] = handleMsgMhfSetCaAchievementHist
|
||||
handlerTable[network.MSG_MHF_GET_KEEP_LOGIN_BOOST_STATUS] = handleMsgMhfGetKeepLoginBoostStatus
|
||||
handlerTable[network.MSG_MHF_USE_KEEP_LOGIN_BOOST] = handleMsgMhfUseKeepLoginBoost
|
||||
handlerTable[network.MSG_MHF_GET_UD_SCHEDULE] = handleMsgMhfGetUdSchedule
|
||||
handlerTable[network.MSG_MHF_GET_UD_INFO] = handleMsgMhfGetUdInfo
|
||||
handlerTable[network.MSG_MHF_GET_KIJU_INFO] = handleMsgMhfGetKijuInfo
|
||||
handlerTable[network.MSG_MHF_SET_KIJU] = handleMsgMhfSetKiju
|
||||
handlerTable[network.MSG_MHF_ADD_UD_POINT] = handleMsgMhfAddUdPoint
|
||||
handlerTable[network.MSG_MHF_GET_UD_MY_POINT] = handleMsgMhfGetUdMyPoint
|
||||
handlerTable[network.MSG_MHF_GET_UD_TOTAL_POINT_INFO] = handleMsgMhfGetUdTotalPointInfo
|
||||
handlerTable[network.MSG_MHF_GET_UD_BONUS_QUEST_INFO] = handleMsgMhfGetUdBonusQuestInfo
|
||||
handlerTable[network.MSG_MHF_GET_UD_SELECTED_COLOR_INFO] = handleMsgMhfGetUdSelectedColorInfo
|
||||
handlerTable[network.MSG_MHF_GET_UD_MONSTER_POINT] = handleMsgMhfGetUdMonsterPoint
|
||||
handlerTable[network.MSG_MHF_GET_UD_DAILY_PRESENT_LIST] = handleMsgMhfGetUdDailyPresentList
|
||||
handlerTable[network.MSG_MHF_GET_UD_NORMA_PRESENT_LIST] = handleMsgMhfGetUdNormaPresentList
|
||||
handlerTable[network.MSG_MHF_GET_UD_RANKING_REWARD_LIST] = handleMsgMhfGetUdRankingRewardList
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_UD_ITEM] = handleMsgMhfAcquireUdItem
|
||||
handlerTable[network.MSG_MHF_GET_REWARD_SONG] = handleMsgMhfGetRewardSong
|
||||
handlerTable[network.MSG_MHF_USE_REWARD_SONG] = handleMsgMhfUseRewardSong
|
||||
handlerTable[network.MSG_MHF_ADD_REWARD_SONG_COUNT] = handleMsgMhfAddRewardSongCount
|
||||
handlerTable[network.MSG_MHF_GET_UD_RANKING] = handleMsgMhfGetUdRanking
|
||||
handlerTable[network.MSG_MHF_GET_UD_MY_RANKING] = handleMsgMhfGetUdMyRanking
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_MONTHLY_REWARD] = handleMsgMhfAcquireMonthlyReward
|
||||
handlerTable[network.MSG_MHF_GET_UD_GUILD_MAP_INFO] = handleMsgMhfGetUdGuildMapInfo
|
||||
handlerTable[network.MSG_MHF_GENERATE_UD_GUILD_MAP] = handleMsgMhfGenerateUdGuildMap
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_POINT] = handleMsgMhfGetUdTacticsPoint
|
||||
handlerTable[network.MSG_MHF_ADD_UD_TACTICS_POINT] = handleMsgMhfAddUdTacticsPoint
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_RANKING] = handleMsgMhfGetUdTacticsRanking
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_REWARD_LIST] = handleMsgMhfGetUdTacticsRewardList
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_LOG] = handleMsgMhfGetUdTacticsLog
|
||||
handlerTable[network.MSG_MHF_GET_EQUIP_SKIN_HIST] = handleMsgMhfGetEquipSkinHist
|
||||
handlerTable[network.MSG_MHF_UPDATE_EQUIP_SKIN_HIST] = handleMsgMhfUpdateEquipSkinHist
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_FOLLOWER] = handleMsgMhfGetUdTacticsFollower
|
||||
handlerTable[network.MSG_MHF_SET_UD_TACTICS_FOLLOWER] = handleMsgMhfSetUdTacticsFollower
|
||||
handlerTable[network.MSG_MHF_GET_UD_SHOP_COIN] = handleMsgMhfGetUdShopCoin
|
||||
handlerTable[network.MSG_MHF_USE_UD_SHOP_COIN] = handleMsgMhfUseUdShopCoin
|
||||
handlerTable[network.MSG_MHF_GET_ENHANCED_MINIDATA] = handleMsgMhfGetEnhancedMinidata
|
||||
handlerTable[network.MSG_MHF_SET_ENHANCED_MINIDATA] = handleMsgMhfSetEnhancedMinidata
|
||||
handlerTable[network.MSG_MHF_SEX_CHANGER] = handleMsgMhfSexChanger
|
||||
handlerTable[network.MSG_MHF_GET_LOBBY_CROWD] = handleMsgMhfGetLobbyCrowd
|
||||
handlerTable[network.MSG_SYS_reserve180] = handleMsgSysReserve180
|
||||
handlerTable[network.MSG_MHF_GUILD_HUNTDATA] = handleMsgMhfGuildHuntdata
|
||||
handlerTable[network.MSG_MHF_ADD_KOURYOU_POINT] = handleMsgMhfAddKouryouPoint
|
||||
handlerTable[network.MSG_MHF_GET_KOURYOU_POINT] = handleMsgMhfGetKouryouPoint
|
||||
handlerTable[network.MSG_MHF_EXCHANGE_KOURYOU_POINT] = handleMsgMhfExchangeKouryouPoint
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_BONUS_QUEST] = handleMsgMhfGetUdTacticsBonusQuest
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_FIRST_QUEST_BONUS] = handleMsgMhfGetUdTacticsFirstQuestBonus
|
||||
handlerTable[network.MSG_MHF_GET_UD_TACTICS_REMAINING_POINT] = handleMsgMhfGetUdTacticsRemainingPoint
|
||||
handlerTable[network.MSG_SYS_reserve188] = handleMsgSysReserve188
|
||||
handlerTable[network.MSG_MHF_LOAD_PLATE_MYSET] = handleMsgMhfLoadPlateMyset
|
||||
handlerTable[network.MSG_MHF_SAVE_PLATE_MYSET] = handleMsgMhfSavePlateMyset
|
||||
handlerTable[network.MSG_SYS_reserve18B] = handleMsgSysReserve18B
|
||||
handlerTable[network.MSG_MHF_GET_RESTRICTION_EVENT] = handleMsgMhfGetRestrictionEvent
|
||||
handlerTable[network.MSG_MHF_SET_RESTRICTION_EVENT] = handleMsgMhfSetRestrictionEvent
|
||||
handlerTable[network.MSG_SYS_reserve18E] = handleMsgSysReserve18E
|
||||
handlerTable[network.MSG_SYS_reserve18F] = handleMsgSysReserve18F
|
||||
handlerTable[network.MSG_MHF_GET_TREND_WEAPON] = handleMsgMhfGetTrendWeapon
|
||||
handlerTable[network.MSG_MHF_UPDATE_USE_TREND_WEAPON_LOG] = handleMsgMhfUpdateUseTrendWeaponLog
|
||||
handlerTable[network.MSG_SYS_reserve192] = handleMsgSysReserve192
|
||||
handlerTable[network.MSG_SYS_reserve193] = handleMsgSysReserve193
|
||||
handlerTable[network.MSG_SYS_reserve194] = handleMsgSysReserve194
|
||||
handlerTable[network.MSG_MHF_SAVE_RENGOKU_DATA] = handleMsgMhfSaveRengokuData
|
||||
handlerTable[network.MSG_MHF_LOAD_RENGOKU_DATA] = handleMsgMhfLoadRengokuData
|
||||
handlerTable[network.MSG_MHF_GET_RENGOKU_BINARY] = handleMsgMhfGetRengokuBinary
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_RENGOKU_RANKING] = handleMsgMhfEnumerateRengokuRanking
|
||||
handlerTable[network.MSG_MHF_GET_RENGOKU_RANKING_RANK] = handleMsgMhfGetRengokuRankingRank
|
||||
handlerTable[network.MSG_MHF_ACQUIRE_EXCHANGE_SHOP] = handleMsgMhfAcquireExchangeShop
|
||||
handlerTable[network.MSG_SYS_reserve19B] = handleMsgSysReserve19B
|
||||
handlerTable[network.MSG_MHF_SAVE_MEZFES_DATA] = handleMsgMhfSaveMezfesData
|
||||
handlerTable[network.MSG_MHF_LOAD_MEZFES_DATA] = handleMsgMhfLoadMezfesData
|
||||
handlerTable[network.MSG_SYS_reserve19E] = handleMsgSysReserve19E
|
||||
handlerTable[network.MSG_SYS_reserve19F] = handleMsgSysReserve19F
|
||||
handlerTable[network.MSG_MHF_UPDATE_FORCE_GUILD_RANK] = handleMsgMhfUpdateForceGuildRank
|
||||
handlerTable[network.MSG_MHF_RESET_TITLE] = handleMsgMhfResetTitle
|
||||
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD] = handleMsgMhfEnumerateGuildMessageBoard
|
||||
handlerTable[network.MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD] = handleMsgMhfUpdateGuildMessageBoard
|
||||
handlerTable[network.MSG_SYS_reserve1A4] = handleMsgSysReserve1A4
|
||||
handlerTable[network.MSG_MHF_REGIST_GUILD_ADVENTURE_DIVA] = handleMsgMhfRegistGuildAdventureDiva
|
||||
handlerTable[network.MSG_SYS_reserve1A6] = handleMsgSysReserve1A6
|
||||
handlerTable[network.MSG_SYS_reserve1A7] = handleMsgSysReserve1A7
|
||||
handlerTable[network.MSG_SYS_reserve1A8] = handleMsgSysReserve1A8
|
||||
handlerTable[network.MSG_SYS_reserve1A9] = handleMsgSysReserve1A9
|
||||
handlerTable[network.MSG_SYS_reserve1AA] = handleMsgSysReserve1AA
|
||||
handlerTable[network.MSG_SYS_reserve1AB] = handleMsgSysReserve1AB
|
||||
handlerTable[network.MSG_SYS_reserve1AC] = handleMsgSysReserve1AC
|
||||
handlerTable[network.MSG_SYS_reserve1AD] = handleMsgSysReserve1AD
|
||||
handlerTable[network.MSG_SYS_reserve1AE] = handleMsgSysReserve1AE
|
||||
handlerTable[network.MSG_SYS_reserve1AF] = handleMsgSysReserve1AF
|
||||
}
|
||||
68
server/channelserver/handlers_tactics.go
Normal file
68
server/channelserver/handlers_tactics.go
Normal file
File diff suppressed because one or more lines are too long
9
server/channelserver/handlers_tournament.go
Normal file
9
server/channelserver/handlers_tournament.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package channelserver
|
||||
|
||||
import "erupe-ce/network/mhfpacket"
|
||||
|
||||
func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) {}
|
||||
63
server/channelserver/handlers_tower.go
Normal file
63
server/channelserver/handlers_tower.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
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 == ?
|
||||
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.TowerInfoTypeUnk3:
|
||||
panic("No known response values for TowerInfoTypeUnk3")
|
||||
case mhfpacket.TowerInfoTypeTowerTouhaHistory:
|
||||
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000000000000000000000000000000000000000")
|
||||
case mhfpacket.TowerInfoTypeUnk5:
|
||||
data, err = hex.DecodeString("0A218EAD000000000000000000000000")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
stubGetNoResults(s, pkt.AckHandle)
|
||||
}
|
||||
doAckBufSucceed(s, pkt.AckHandle, data)
|
||||
}
|
||||
|
||||
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
|
||||
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
||||
}
|
||||
|
||||
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {}
|
||||
122
server/channelserver/handlers_users.go
Normal file
122
server/channelserver/handlers_users.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
func handleMsgSysInsertUser(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgSysDeleteUser(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func broadcastNewUser(s *Session) {
|
||||
s.logger.Debug(fmt.Sprintf("Broadcasting new user: %s (%d)", s.Name, s.charID))
|
||||
|
||||
clientNotif := byteframe.NewByteFrame()
|
||||
var temp mhfpacket.MHFPacket
|
||||
for _, session := range s.server.sessions {
|
||||
if session == s || !session.binariesDone {
|
||||
continue
|
||||
}
|
||||
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
|
||||
clientNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(clientNotif, s.clientContext)
|
||||
for i := 0; i < 3; i++ {
|
||||
temp = &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: session.charID,
|
||||
BinaryType: uint8(i + 1),
|
||||
}
|
||||
clientNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(clientNotif, s.clientContext)
|
||||
}
|
||||
}
|
||||
s.QueueSend(clientNotif.Data())
|
||||
|
||||
serverNotif := byteframe.NewByteFrame()
|
||||
temp = &mhfpacket.MsgSysInsertUser{CharID: s.charID}
|
||||
serverNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(serverNotif, s.clientContext)
|
||||
for i := 0; i < 3; i++ {
|
||||
temp = &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
BinaryType: uint8(i + 1),
|
||||
}
|
||||
serverNotif.WriteUint16(uint16(temp.Opcode()))
|
||||
temp.Build(serverNotif, s.clientContext)
|
||||
}
|
||||
for _, session := range s.server.sessions {
|
||||
if session == s || !session.binariesDone {
|
||||
continue
|
||||
}
|
||||
session.QueueSend(serverNotif.Data())
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysSetUserBinary)
|
||||
s.server.userBinaryPartsLock.Lock()
|
||||
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload
|
||||
s.server.userBinaryPartsLock.Unlock()
|
||||
|
||||
// Insert user once all binary parts exist
|
||||
if !s.binariesDone {
|
||||
for i := 0; i < 3; i++ {
|
||||
_, exists := s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: uint8(i + 1)}]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
}
|
||||
s.binariesDone = true
|
||||
broadcastNewUser(s)
|
||||
return
|
||||
}
|
||||
|
||||
msg := &mhfpacket.MsgSysNotifyUserBinary{
|
||||
CharID: s.charID,
|
||||
BinaryType: pkt.BinaryType,
|
||||
}
|
||||
|
||||
s.server.BroadcastMHF(msg, s)
|
||||
}
|
||||
|
||||
func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
|
||||
pkt := p.(*mhfpacket.MsgSysGetUserBinary)
|
||||
|
||||
// Try to get the data.
|
||||
s.server.userBinaryPartsLock.RLock()
|
||||
defer s.server.userBinaryPartsLock.RUnlock()
|
||||
data, ok := s.server.userBinaryParts[userBinaryPartID{charID: pkt.CharID, index: pkt.BinaryType}]
|
||||
resp := byteframe.NewByteFrame()
|
||||
|
||||
// If we can't get the real data, use a placeholder.
|
||||
if !ok {
|
||||
if pkt.BinaryType == 1 {
|
||||
// Stub name response with character ID
|
||||
resp.WriteBytes([]byte(fmt.Sprintf("CID%d", s.charID)))
|
||||
resp.WriteUint8(0) // NULL terminator.
|
||||
} else if pkt.BinaryType == 2 {
|
||||
data, err := base64.StdEncoding.DecodeString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBn8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAwAAAAAAAAAAAAAABAAAAAAAAAAAAAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp.WriteBytes(data)
|
||||
} else if pkt.BinaryType == 3 {
|
||||
data, err := base64.StdEncoding.DecodeString("AQAAA2ea5P8ATgEA/wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBn8AAAAAAAAAAAABAKAMAAAAAAAAAAAAACgAAAAAAAAAAAABAsQOAAAAAAAAAAABA6UMAAAAAAAAAAABBKAMAAAAAAAAAAABBToNAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resp.WriteBytes(data)
|
||||
}
|
||||
} else {
|
||||
resp.WriteBytes(data)
|
||||
}
|
||||
|
||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||
}
|
||||
|
||||
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}
|
||||
|
||||
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {}
|
||||
413
server/channelserver/sys_channel_server.go
Normal file
413
server/channelserver/sys_channel_server.go
Normal file
@@ -0,0 +1,413 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
ps "erupe-ce/common/pascalstring"
|
||||
"erupe-ce/config"
|
||||
"erupe-ce/network/binpacket"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"erupe-ce/server/discordbot"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type StageIdType = string
|
||||
|
||||
const (
|
||||
// GlobalStage is the stage that is used for all users.
|
||||
MezeportaStageId StageIdType = "sl1Ns200p0a0u0"
|
||||
GuildHallLv1StageId StageIdType = "sl1Ns202p0a0u0"
|
||||
GuildHallLv2StageId StageIdType = "sl1Ns203p0a0u0"
|
||||
GuildHallLv3StageId StageIdType = "sl1Ns204p0a0u0"
|
||||
PugiFarmStageId StageIdType = "sl1Ns205p0a0u0"
|
||||
RastaBarStageId StageIdType = "sl1Ns211p0a0u0"
|
||||
PalloneCaravanStageId StageIdType = "sl1Ns260p0a0u0"
|
||||
GookFarmStageId StageIdType = "sl1Ns265p0a0u0"
|
||||
DivaFountainStageId StageIdType = "sl2Ns379p0a0u0"
|
||||
DivaHallStageId StageIdType = "sl1Ns445p0a0u0"
|
||||
MezFesStageId StageIdType = "sl1Ns462p0a0u0"
|
||||
)
|
||||
|
||||
// Config struct allows configuring the server.
|
||||
type Config struct {
|
||||
ID uint16
|
||||
Logger *zap.Logger
|
||||
DB *sqlx.DB
|
||||
DiscordBot *discordbot.DiscordBot
|
||||
ErupeConfig *config.Config
|
||||
Name string
|
||||
Enable bool
|
||||
}
|
||||
|
||||
// Map key type for a user binary part.
|
||||
type userBinaryPartID struct {
|
||||
charID uint32
|
||||
index uint8
|
||||
}
|
||||
|
||||
// Server is a MHF channel server.
|
||||
type Server struct {
|
||||
sync.Mutex
|
||||
Channels []*Server
|
||||
ID uint16
|
||||
logger *zap.Logger
|
||||
db *sqlx.DB
|
||||
erupeConfig *config.Config
|
||||
acceptConns chan net.Conn
|
||||
deleteConns chan net.Conn
|
||||
sessions map[net.Conn]*Session
|
||||
listener net.Listener // Listener that is created when Server.Start is called.
|
||||
isShuttingDown bool
|
||||
|
||||
stagesLock sync.RWMutex
|
||||
stages map[string]*Stage
|
||||
|
||||
// UserBinary
|
||||
userBinaryPartsLock sync.RWMutex
|
||||
userBinaryParts map[userBinaryPartID][]byte
|
||||
|
||||
// Semaphore
|
||||
semaphoreLock sync.RWMutex
|
||||
semaphore map[string]*Semaphore
|
||||
semaphoreIndex uint32
|
||||
|
||||
// Discord chat integration
|
||||
discordBot *discordbot.DiscordBot
|
||||
|
||||
name string
|
||||
enable bool
|
||||
|
||||
raviente *Raviente
|
||||
}
|
||||
|
||||
type Raviente struct {
|
||||
sync.Mutex
|
||||
|
||||
register *RavienteRegister
|
||||
state *RavienteState
|
||||
support *RavienteSupport
|
||||
}
|
||||
|
||||
type RavienteRegister struct {
|
||||
semaphoreID uint32
|
||||
nextTime uint32
|
||||
startTime uint32
|
||||
postTime uint32
|
||||
killedTime uint32
|
||||
ravienteType uint32
|
||||
maxPlayers uint32
|
||||
carveQuest uint32
|
||||
register []uint32
|
||||
}
|
||||
|
||||
type RavienteState struct {
|
||||
semaphoreID uint32
|
||||
damageMultiplier uint32
|
||||
stateData []uint32
|
||||
}
|
||||
|
||||
type RavienteSupport struct {
|
||||
semaphoreID uint32
|
||||
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,
|
||||
}
|
||||
ravienteState := &RavienteState{
|
||||
damageMultiplier: 1,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// NewServer creates a new Server type.
|
||||
func NewServer(config *Config) *Server {
|
||||
s := &Server{
|
||||
ID: config.ID,
|
||||
logger: config.Logger,
|
||||
db: config.DB,
|
||||
erupeConfig: config.ErupeConfig,
|
||||
acceptConns: make(chan net.Conn),
|
||||
deleteConns: make(chan net.Conn),
|
||||
sessions: make(map[net.Conn]*Session),
|
||||
stages: make(map[string]*Stage),
|
||||
userBinaryParts: make(map[userBinaryPartID][]byte),
|
||||
semaphore: make(map[string]*Semaphore),
|
||||
semaphoreIndex: 0,
|
||||
discordBot: config.DiscordBot,
|
||||
name: config.Name,
|
||||
enable: config.Enable,
|
||||
raviente: NewRaviente(),
|
||||
}
|
||||
|
||||
// Mezeporta
|
||||
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
|
||||
|
||||
// Guild Hall LV1
|
||||
s.stages["sl1Ns202p0a0u0"] = NewStage("sl1Ns202p0a0u0")
|
||||
|
||||
// Guild Hall LV2
|
||||
s.stages["sl1Ns203p0a0u0"] = NewStage("sl1Ns203p0a0u0")
|
||||
|
||||
// Guild Hall LV3
|
||||
s.stages["sl1Ns204p0a0u0"] = NewStage("sl1Ns204p0a0u0")
|
||||
|
||||
// Pugi Farm
|
||||
s.stages["sl1Ns205p0a0u0"] = NewStage("sl1Ns205p0a0u0")
|
||||
|
||||
// Rasta bar stage
|
||||
s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0")
|
||||
|
||||
// Pallone Carvan
|
||||
s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0")
|
||||
|
||||
// Gook Farm
|
||||
s.stages["sl1Ns265p0a0u0"] = NewStage("sl1Ns265p0a0u0")
|
||||
|
||||
// Diva fountain / prayer fountain.
|
||||
s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0")
|
||||
|
||||
// Diva Hall
|
||||
s.stages["sl1Ns445p0a0u0"] = NewStage("sl1Ns445p0a0u0")
|
||||
|
||||
// MezFes
|
||||
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the server in a new goroutine.
|
||||
func (s *Server) Start(port int) error {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.listener = l
|
||||
|
||||
go s.acceptClients()
|
||||
go s.manageSessions()
|
||||
|
||||
// Start the discord bot for chat integration.
|
||||
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
|
||||
s.discordBot.Session.AddHandler(s.onDiscordMessage)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown tries to shut down the server gracefully.
|
||||
func (s *Server) Shutdown() {
|
||||
s.Lock()
|
||||
s.isShuttingDown = true
|
||||
s.Unlock()
|
||||
|
||||
s.listener.Close()
|
||||
|
||||
close(s.acceptConns)
|
||||
}
|
||||
|
||||
func (s *Server) acceptClients() {
|
||||
for {
|
||||
conn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
s.Lock()
|
||||
shutdown := s.isShuttingDown
|
||||
s.Unlock()
|
||||
|
||||
if shutdown {
|
||||
break
|
||||
} else {
|
||||
s.logger.Warn("Error accepting client", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
s.acceptConns <- conn
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) manageSessions() {
|
||||
for {
|
||||
select {
|
||||
case newConn := <-s.acceptConns:
|
||||
// Gracefully handle acceptConns channel closing.
|
||||
if newConn == nil {
|
||||
s.Lock()
|
||||
shutdown := s.isShuttingDown
|
||||
s.Unlock()
|
||||
|
||||
if shutdown {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
session := NewSession(s, newConn)
|
||||
|
||||
s.Lock()
|
||||
s.sessions[newConn] = session
|
||||
s.Unlock()
|
||||
|
||||
session.Start()
|
||||
|
||||
case delConn := <-s.deleteConns:
|
||||
s.Lock()
|
||||
delete(s.sessions, delConn)
|
||||
s.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions.
|
||||
func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
// Broadcast the data.
|
||||
for _, session := range s.sessions {
|
||||
if session == ignoredSession || !session.binariesDone {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
for _, c := range s.Channels {
|
||||
for _, session := range c.sessions {
|
||||
if session == ignoredSession || !session.binariesDone {
|
||||
continue
|
||||
}
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(pkt.Opcode()))
|
||||
pkt.Build(bf, session.clientContext)
|
||||
session.QueueSendNonBlocking(bf.Data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastChatMessage broadcasts a simple chat message to all the sessions.
|
||||
func (s *Server) BroadcastChatMessage(message string) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.SetLE()
|
||||
msgBinChat := &binpacket.MsgBinChat{
|
||||
Unk0: 0,
|
||||
Type: 5,
|
||||
Flags: 0x80,
|
||||
Message: message,
|
||||
SenderName: s.name,
|
||||
}
|
||||
msgBinChat.Build(bf)
|
||||
|
||||
s.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: 0xFFFFFFFF,
|
||||
MessageType: BinaryMessageTypeChat,
|
||||
RawDataPayload: bf.Data(),
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type uint8) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.SetLE()
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteUint16(0x43) // Data len
|
||||
bf.WriteUint16(3) // Unk len
|
||||
var text string
|
||||
switch _type {
|
||||
case 2:
|
||||
text = "<Great Slaying: Berserk> is being held!"
|
||||
case 4:
|
||||
text = "<Great Slaying: Extreme> is being held!"
|
||||
case 5:
|
||||
text = "<Great Slaying: Berserk Practice> is being held!"
|
||||
default:
|
||||
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
|
||||
}
|
||||
ps.Uint16(bf, text, false)
|
||||
bf.WriteBytes([]byte{0x5F, 0x53, 0x00})
|
||||
bf.WriteUint32(ip) // IP address
|
||||
bf.WriteUint16(port) // Port
|
||||
bf.WriteUint16(0) // Unk
|
||||
bf.WriteNullTerminatedBytes(stage)
|
||||
bf.WriteBytes(make([]byte, 17))
|
||||
s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{
|
||||
CharID: 0x00000000,
|
||||
BroadcastType: BroadcastTypeSemaphore,
|
||||
MessageType: BinaryMessageTypeChat,
|
||||
RawDataPayload: bf.Data(),
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (s *Server) DiscordChannelSend(charName string, content string) {
|
||||
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
|
||||
message := fmt.Sprintf("**%s** : %s", charName, content)
|
||||
s.discordBot.RealtimeChannelSend(message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) FindSessionByCharID(charID uint32) *Session {
|
||||
for _, c := range s.Channels {
|
||||
c.stagesLock.RLock()
|
||||
for _, stage := range c.stages {
|
||||
stage.RLock()
|
||||
for client := range stage.clients {
|
||||
if client.charID == charID {
|
||||
stage.RUnlock()
|
||||
return client
|
||||
}
|
||||
}
|
||||
stage.RUnlock()
|
||||
}
|
||||
c.stagesLock.RUnlock()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) FindObjectByChar(charID uint32) *Object {
|
||||
s.stagesLock.RLock()
|
||||
defer s.stagesLock.RUnlock()
|
||||
for _, stage := range s.stages {
|
||||
stage.RLock()
|
||||
for objId := range stage.objects {
|
||||
obj := stage.objects[objId]
|
||||
if obj.ownerCharID == charID {
|
||||
stage.RUnlock()
|
||||
return obj
|
||||
}
|
||||
}
|
||||
stage.RUnlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) NextSemaphoreID() uint32 {
|
||||
s.semaphoreIndex = s.semaphoreIndex + 1
|
||||
return s.semaphoreIndex
|
||||
}
|
||||
76
server/channelserver/sys_semaphore.go
Normal file
76
server/channelserver/sys_semaphore.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Stage holds stage-specific information
|
||||
type Semaphore struct {
|
||||
sync.RWMutex
|
||||
|
||||
// Stage ID string
|
||||
id_semaphore string
|
||||
|
||||
id uint32
|
||||
|
||||
// Map of session -> charID.
|
||||
// These are clients that are CURRENTLY in the stage
|
||||
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.
|
||||
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,
|
||||
}
|
||||
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.
|
||||
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
// Broadcast the data.
|
||||
for session := range s.clients {
|
||||
if session == ignoredSession {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
259
server/channelserver/sys_session.go
Normal file
259
server/channelserver/sys_session.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/common/stringstack"
|
||||
"erupe-ce/common/stringsupport"
|
||||
"erupe-ce/network"
|
||||
"erupe-ce/network/clientctx"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/text/encoding/japanese"
|
||||
)
|
||||
|
||||
// Session holds state for the channel server connection.
|
||||
type Session struct {
|
||||
sync.Mutex
|
||||
logger *zap.Logger
|
||||
server *Server
|
||||
rawConn net.Conn
|
||||
cryptConn *network.CryptConn
|
||||
sendPackets chan []byte
|
||||
clientContext *clientctx.ClientContext
|
||||
|
||||
myseries MySeries
|
||||
stageID string
|
||||
stage *Stage
|
||||
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
|
||||
stagePass string // Temporary storage
|
||||
binariesDone bool
|
||||
charID uint32
|
||||
logKey []byte
|
||||
sessionStart int64
|
||||
rights uint32
|
||||
token string
|
||||
|
||||
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
|
||||
|
||||
// A stack containing the stage movement history (push on enter/move, pop on back)
|
||||
stageMoveStack *stringstack.StringStack
|
||||
|
||||
// Accumulated index used for identifying mail for a client
|
||||
// I'm not certain why this is used, but since the client is sending it
|
||||
// I want to rely on it for now as it might be important later.
|
||||
mailAccIndex uint8
|
||||
// Contains the mail list that maps accumulated indexes to mail IDs
|
||||
mailList []int
|
||||
|
||||
// For Debuging
|
||||
Name string
|
||||
}
|
||||
|
||||
type MySeries struct {
|
||||
houseTier []byte
|
||||
houseData []byte
|
||||
bookshelfData []byte
|
||||
galleryData []byte
|
||||
toreData []byte
|
||||
gardenData []byte
|
||||
state uint8
|
||||
password string
|
||||
}
|
||||
|
||||
// NewSession creates a new Session type.
|
||||
func NewSession(server *Server, conn net.Conn) *Session {
|
||||
s := &Session{
|
||||
logger: server.logger.Named(conn.RemoteAddr().String()),
|
||||
server: server,
|
||||
rawConn: conn,
|
||||
cryptConn: network.NewCryptConn(conn),
|
||||
sendPackets: make(chan []byte, 20),
|
||||
clientContext: &clientctx.ClientContext{
|
||||
StrConv: &stringsupport.StringConverter{
|
||||
Encoding: japanese.ShiftJIS,
|
||||
},
|
||||
},
|
||||
binariesDone: false,
|
||||
sessionStart: Time_Current_Adjusted().Unix(),
|
||||
stageMoveStack: stringstack.New(),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Start starts the session packet send and recv loop(s).
|
||||
func (s *Session) Start() {
|
||||
go func() {
|
||||
s.logger.Info("Channel server got connection!", zap.String("remoteaddr", s.rawConn.RemoteAddr().String()))
|
||||
// Unlike the sign and entrance server,
|
||||
// the client DOES NOT initalize the channel connection with 8 NULL bytes.
|
||||
go s.sendLoop()
|
||||
s.recvLoop()
|
||||
}()
|
||||
}
|
||||
|
||||
// QueueSend queues a packet (raw []byte) to be sent.
|
||||
func (s *Session) QueueSend(data []byte) {
|
||||
bf := byteframe.NewByteFrameFromBytes(data[:2])
|
||||
s.logMessage(bf.ReadUint16(), data, "Server", s.Name)
|
||||
s.sendPackets <- data
|
||||
}
|
||||
|
||||
// QueueSendNonBlocking queues a packet (raw []byte) to be sent, dropping the packet entirely if the queue is full.
|
||||
func (s *Session) QueueSendNonBlocking(data []byte) {
|
||||
select {
|
||||
case s.sendPackets <- data:
|
||||
// Enqueued properly.
|
||||
default:
|
||||
// Couldn't enqueue, likely something wrong with the connection.
|
||||
s.logger.Warn("Dropped packet for session because of full send buffer, something is probably wrong")
|
||||
}
|
||||
}
|
||||
|
||||
// QueueSendMHF queues a MHFPacket to be sent.
|
||||
func (s *Session) QueueSendMHF(pkt mhfpacket.MHFPacket) {
|
||||
// Make the header
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(pkt.Opcode()))
|
||||
|
||||
// Build the packet onto the byteframe.
|
||||
pkt.Build(bf, s.clientContext)
|
||||
|
||||
// Queue it.
|
||||
s.QueueSend(bf.Data())
|
||||
}
|
||||
|
||||
// QueueAck is a helper function to queue an MSG_SYS_ACK with the given ack handle and data.
|
||||
func (s *Session) QueueAck(ackHandle uint32, data []byte) {
|
||||
bf := byteframe.NewByteFrame()
|
||||
bf.WriteUint16(uint16(network.MSG_SYS_ACK))
|
||||
bf.WriteUint32(ackHandle)
|
||||
bf.WriteBytes(data)
|
||||
s.QueueSend(bf.Data())
|
||||
}
|
||||
|
||||
func (s *Session) sendLoop() {
|
||||
for {
|
||||
// TODO(Andoryuuta): Test making this into a buffered channel and grouping the packet together before sending.
|
||||
rawPacket := <-s.sendPackets
|
||||
|
||||
if rawPacket == nil {
|
||||
s.logger.Debug("Got nil from s.SendPackets, exiting send loop")
|
||||
return
|
||||
}
|
||||
|
||||
// Make a copy of the data.
|
||||
terminatedPacket := make([]byte, len(rawPacket))
|
||||
copy(terminatedPacket, rawPacket)
|
||||
|
||||
// Append the MSG_SYS_END tailing opcode.
|
||||
terminatedPacket = append(terminatedPacket, []byte{0x00, 0x10}...)
|
||||
|
||||
s.cryptConn.SendPacket(terminatedPacket)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) recvLoop() {
|
||||
for {
|
||||
pkt, err := s.cryptConn.ReadPacket()
|
||||
|
||||
if err == io.EOF {
|
||||
s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name))
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err))
|
||||
logoutPlayer(s)
|
||||
return
|
||||
}
|
||||
s.handlePacketGroup(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) handlePacketGroup(pktGroup []byte) {
|
||||
bf := byteframe.NewByteFrameFromBytes(pktGroup)
|
||||
opcodeUint16 := bf.ReadUint16()
|
||||
opcode := network.PacketID(opcodeUint16)
|
||||
|
||||
// This shouldn't be needed, but it's better to recover and let the connection die than to panic the server.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf("[%s]", s.Name)
|
||||
fmt.Println("Recovered from panic", r)
|
||||
}
|
||||
}()
|
||||
|
||||
s.logMessage(opcodeUint16, pktGroup, s.Name, "Server")
|
||||
|
||||
if opcode == network.MSG_SYS_LOGOUT {
|
||||
s.rawConn.Close()
|
||||
}
|
||||
// Get the packet parser and handler for this opcode.
|
||||
mhfPkt := mhfpacket.FromOpcode(opcode)
|
||||
if mhfPkt == nil {
|
||||
fmt.Println("Got opcode which we don't know how to parse, can't parse anymore for this group")
|
||||
return
|
||||
}
|
||||
// Parse the packet.
|
||||
err := mhfPkt.Parse(bf, s.clientContext)
|
||||
if err != nil {
|
||||
fmt.Printf("\n!!! [%s] %s NOT IMPLEMENTED !!! \n\n\n", s.Name, opcode)
|
||||
return
|
||||
}
|
||||
// Handle the packet.
|
||||
handlerTable[opcode](s, mhfPkt)
|
||||
// If there is more data on the stream that the .Parse method didn't read, then read another packet off it.
|
||||
remainingData := bf.DataFromCurrent()
|
||||
if len(remainingData) >= 2 {
|
||||
s.handlePacketGroup(remainingData)
|
||||
}
|
||||
}
|
||||
|
||||
func ignored(opcode network.PacketID) bool {
|
||||
ignoreList := []network.PacketID{
|
||||
network.MSG_SYS_END,
|
||||
network.MSG_SYS_PING,
|
||||
network.MSG_SYS_NOP,
|
||||
network.MSG_SYS_TIME,
|
||||
network.MSG_SYS_EXTEND_THRESHOLD,
|
||||
network.MSG_SYS_POSITION_OBJECT,
|
||||
network.MSG_MHF_ENUMERATE_QUEST,
|
||||
network.MSG_MHF_SAVEDATA,
|
||||
}
|
||||
set := make(map[network.PacketID]struct{}, len(ignoreList))
|
||||
for _, s := range ignoreList {
|
||||
set[s] = struct{}{}
|
||||
}
|
||||
_, r := set[opcode]
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipient string) {
|
||||
if !s.server.erupeConfig.DevMode {
|
||||
return
|
||||
}
|
||||
|
||||
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
|
||||
return
|
||||
} else if !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
|
||||
return
|
||||
}
|
||||
|
||||
opcodePID := network.PacketID(opcode)
|
||||
if ignored(opcodePID) {
|
||||
return
|
||||
}
|
||||
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
|
||||
fmt.Printf("Opcode: %s\n", opcodePID)
|
||||
if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength {
|
||||
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
|
||||
} else {
|
||||
fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data))
|
||||
}
|
||||
}
|
||||
134
server/channelserver/sys_stage.go
Normal file
134
server/channelserver/sys_stage.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"time"
|
||||
|
||||
"erupe-ce/common/byteframe"
|
||||
"erupe-ce/network/mhfpacket"
|
||||
)
|
||||
|
||||
// Object holds infomation about a specific object.
|
||||
type Object struct {
|
||||
sync.RWMutex
|
||||
id uint32
|
||||
ownerCharID uint32
|
||||
x, y, z float32
|
||||
binary []byte
|
||||
}
|
||||
|
||||
// stageBinaryKey is a struct used as a map key for identifying a stage binary part.
|
||||
type stageBinaryKey struct {
|
||||
id0 uint8
|
||||
id1 uint8
|
||||
}
|
||||
|
||||
// Stage holds stage-specific information
|
||||
type Stage struct {
|
||||
sync.RWMutex
|
||||
|
||||
// Stage ID string
|
||||
id string
|
||||
|
||||
// Objects
|
||||
objects map[uint32]*Object
|
||||
objectIndex uint32
|
||||
|
||||
// Map of session -> charID.
|
||||
// These are clients that are CURRENTLY in the stage
|
||||
clients map[*Session]uint32
|
||||
|
||||
// Map of charID -> bool, key represents whether they are ready
|
||||
// These are clients that aren't in the stage, but have reserved a slot (for quests, etc).
|
||||
reservedClientSlots map[uint32]bool
|
||||
|
||||
// These are raw binary blobs that the stage owner sets,
|
||||
// other clients expect the server to echo them back in the exact same format.
|
||||
rawBinaryData map[stageBinaryKey][]byte
|
||||
|
||||
maxPlayers uint16
|
||||
password string
|
||||
createdAt string
|
||||
}
|
||||
|
||||
// NewStage creates a new stage with intialized values.
|
||||
func NewStage(ID string) *Stage {
|
||||
s := &Stage{
|
||||
id: ID,
|
||||
clients: make(map[*Session]uint32),
|
||||
reservedClientSlots: make(map[uint32]bool),
|
||||
objects: make(map[uint32]*Object),
|
||||
objectIndex: 0,
|
||||
rawBinaryData: make(map[stageBinaryKey][]byte),
|
||||
maxPlayers: 4,
|
||||
createdAt: time.Now().Format("01-02-2006 15:04:05"),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
|
||||
func (s *Stage) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
||||
// Broadcast the data.
|
||||
for session := range s.clients {
|
||||
if session == ignoredSession || !session.binariesDone {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stage) isCharInQuestByID(charID uint32) bool {
|
||||
if _, exists := s.reservedClientSlots[charID]; exists {
|
||||
return exists
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Stage) isQuest() bool {
|
||||
return len(s.reservedClientSlots) > 0
|
||||
}
|
||||
|
||||
func (s *Stage) GetName() string {
|
||||
switch s.id {
|
||||
case MezeportaStageId:
|
||||
return "Mezeporta"
|
||||
case GuildHallLv1StageId:
|
||||
return "Guild Hall Lv1"
|
||||
case GuildHallLv2StageId:
|
||||
return "Guild Hall Lv2"
|
||||
case GuildHallLv3StageId:
|
||||
return "Guild Hall Lv3"
|
||||
case PugiFarmStageId:
|
||||
return "Pugi Farm"
|
||||
case RastaBarStageId:
|
||||
return "Rasta Bar"
|
||||
case PalloneCaravanStageId:
|
||||
return "Pallone Caravan"
|
||||
case GookFarmStageId:
|
||||
return "Gook Farm"
|
||||
case DivaFountainStageId:
|
||||
return "Diva Fountain"
|
||||
case DivaHallStageId:
|
||||
return "Diva Hall"
|
||||
case MezFesStageId:
|
||||
return "Mez Fes"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stage) NextObjectID() uint32 {
|
||||
s.objectIndex = s.objectIndex + 1
|
||||
return s.objectIndex
|
||||
}
|
||||
48
server/channelserver/sys_timefix.go
Normal file
48
server/channelserver/sys_timefix.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package channelserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
Offset = 9
|
||||
YearAdjust = -7
|
||||
MonthAdjust = 0
|
||||
DayAdjust = 0
|
||||
)
|
||||
|
||||
var (
|
||||
TimeStatic = time.Time{}
|
||||
)
|
||||
|
||||
func Time_Current() time.Time {
|
||||
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60))
|
||||
return baseTime
|
||||
}
|
||||
|
||||
func Time_Current_Adjusted() time.Time {
|
||||
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)
|
||||
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), baseTime.Hour(), baseTime.Minute(), baseTime.Second(), baseTime.Nanosecond(), baseTime.Location())
|
||||
}
|
||||
|
||||
func Time_Current_Midnight() time.Time {
|
||||
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)
|
||||
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location())
|
||||
}
|
||||
|
||||
func Time_Current_Week_uint8() uint8 {
|
||||
baseTime := time.Now().In(time.FixedZone(fmt.Sprintf("UTC+%d", Offset), Offset*60*60)).AddDate(YearAdjust, MonthAdjust, DayAdjust)
|
||||
|
||||
_, thisWeek := baseTime.ISOWeek()
|
||||
_, beginningOfTheMonth := time.Date(baseTime.Year(), baseTime.Month(), 1, 0, 0, 0, 0, baseTime.Location()).ISOWeek()
|
||||
|
||||
return uint8(1 + thisWeek - beginningOfTheMonth)
|
||||
}
|
||||
|
||||
func Time_static() time.Time {
|
||||
if TimeStatic.IsZero() {
|
||||
TimeStatic = Time_Current_Adjusted()
|
||||
}
|
||||
return TimeStatic
|
||||
}
|
||||
87
server/channelserver/timeserver/time_mode.go
Normal file
87
server/channelserver/timeserver/time_mode.go
Normal file
@@ -0,0 +1,87 @@
|
||||
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